Skip to content

Commit ea6de62

Browse files
committed
experimental-websocket: option to enable autodetection of WebSocket transport.
Signed-off-by: Rusty Russell <[email protected]> Changelog-EXPERIMENTAL: Protocol: `experimental-websocket` allows Websocket connections to the Lightning port.
1 parent 2ef3263 commit ea6de62

11 files changed

+111
-9
lines changed

common/features.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ static const struct feature_style feature_styles[] = {
9090
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
9191
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
9292
[CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } },
93+
{ OPT_WEBSOCKET,
94+
.copy_style = { [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT } },
9395
};
9496

9597
struct dependency {
@@ -399,7 +401,7 @@ static const char *feature_name(const tal_t *ctx, size_t f)
399401
NULL,
400402
"option_onion_messages", /* 38/39 */
401403
NULL, /* 40/41 */
402-
NULL,
404+
"option_websocket",
403405
NULL,
404406
NULL,
405407
NULL,

common/features.h

+6
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@ const char *fmt_featurebits(const tal_t *ctx, const u8 *featurebits);
135135

136136
#define OPT_SHUTDOWN_WRONG_FUNDING 104
137137

138+
/* BOLT-websocket #9:
139+
*
140+
* | 42/43 | `option_websocket` |... N ...
141+
*/
142+
#define OPT_WEBSOCKET 42
143+
138144
#endif /* LIGHTNING_COMMON_FEATURES_H */

doc/lightning-listconfigs.7

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/lightning-listconfigs.7.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ On success, an object is returned, containing:
5656
- **experimental-onion-messages** (boolean, optional): `experimental-onion-messages` field from config or cmdline, or default
5757
- **experimental-offers** (boolean, optional): `experimental-offers` field from config or cmdline, or default
5858
- **experimental-shutdown-wrong-funding** (boolean, optional): `experimental-shutdown-wrong-funding` field from config or cmdline, or default
59+
- **experimental-websocket** (boolean, optional): `experimental-websocket` field from config or cmdline, or default
5960
- **rgb** (hex, optional): `rgb` field from config or cmdline, or default (always 6 characters)
6061
- **alias** (string, optional): `alias` field from config or cmdline, or default
6162
- **pid-file** (string, optional): `pid-file` field from config or cmdline, or default
@@ -84,7 +85,7 @@ On success, an object is returned, containing:
8485
- **log-prefix** (string, optional): `log-prefix` field from config or cmdline, or default
8586
- **log-file** (string, optional): `log-file` field from config or cmdline, or default
8687
- **log-timestamps** (boolean, optional): `log-timestamps` field from config or cmdline, or default
87-
- **force-feerates** (string, optional): `force-feerates` field from config or cmdline, if any
88+
- **force-feerates** (string, optional): force-feerate configuration setting, if any
8889
- **subdaemon** (string, optional): `subdaemon` fields from config or cmdline if any (can be more than one)
8990
- **tor-service-password** (string, optional): `tor-service-password` field from config or cmdline, if any
9091
[comment]: # (GENERATE-FROM-SCHEMA-END)
@@ -204,4 +205,4 @@ RESOURCES
204205
---------
205206

206207
Main web site: <https://github.com/ElementsProject/lightning>
207-
[comment]: # ( SHA256STAMP:ad98179a7b6254a936d4fde179918b6a975e186adcbc396917a0c2ed2888519e)
208+
[comment]: # ( SHA256STAMP:6083722fadd8b5724326fc2685f05b5d056c0a6ab0752af64b10294173d1f9a2)

doc/lightningd-config.5

+9-3
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ What log level to print out: options are io, debug, info, unusual,
145145
broken\. If \fISUBSYSTEM\fR is supplied, this sets the logging level
146146
for any subsystem containing that string\. Subsystems include:
147147

148-
149148
.RS
150149
.IP \[bu]
151150
\fIlightningd\fR: The main lightning daemon
@@ -171,7 +170,6 @@ for any subsystem containing that string\. Subsystems include:
171170
The following subsystems exist for each channel, where N is an incrementing
172171
internal integer id assigned for the lifetime of the channel:
173172

174-
175173
.RS
176174
.IP \[bu]
177175
\fIopeningd-chan#N\fR: Each opening / idling daemon
@@ -627,6 +625,14 @@ about whether to add funds or not to a proposed channel is handled
627625
automatically by a plugin that implements the appropriate logic for
628626
your needs\. The default behavior is to not contribute funds\.
629627

628+
629+
\fBexperimental-websocket\fR
630+
631+
632+
Specifying this enables support for accepting incoming WebSocket
633+
connections: the normal protocol is expected to be sent over
634+
WebSocket binary frames once the connection is upgraded\.
635+
630636
.SH BUGS
631637

632638
You should report bugs on our github issues page, and maybe submit a fix
@@ -652,4 +658,4 @@ Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
652658
Note: the modules in the ccan/ directory have their own licenses, but
653659
the rest of the code is covered by the BSD-style MIT license\.
654660

655-
\" SHA256STAMP:1c392f3fee66dc6c1fc2c34200204a9be1d79e53fd5fb1720ad169fc671f71c0
661+
\" SHA256STAMP:114b4e458af6112500f2f01210760f1577f9e82fc74e03b6d821f669287c93b2

doc/lightningd-config.5.md

+6
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ about whether to add funds or not to a proposed channel is handled
517517
automatically by a plugin that implements the appropriate logic for
518518
your needs. The default behavior is to not contribute funds.
519519

520+
**experimental-websocket**
521+
522+
Specifying this enables support for accepting incoming WebSocket
523+
connections: the normal protocol is expected to be sent over
524+
WebSocket binary frames once the connection is upgraded.
525+
520526
BUGS
521527
----
522528

doc/schemas/listconfigs.schema.json

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@
121121
"type": "boolean",
122122
"description": "`experimental-shutdown-wrong-funding` field from config or cmdline, or default"
123123
},
124+
"experimental-websocket": {
125+
"type": "boolean",
126+
"description": "`experimental-websocket` field from config or cmdline, or default"
127+
},
124128
"rgb": {
125129
"type": "hex",
126130
"description": "`rgb` field from config or cmdline, or default",

lightningd/connect_control.c

+8-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,14 @@ int connectd_init(struct lightningd *ld)
363363
int hsmfd;
364364
struct wireaddr_internal *wireaddrs = ld->proposed_wireaddr;
365365
enum addr_listen_announce *listen_announce = ld->proposed_listen_announce;
366-
const char *websocket_helper_path = NULL;
366+
const char *websocket_helper_path;
367+
368+
if (feature_offered(ld->our_features->bits[NODE_ANNOUNCE_FEATURE],
369+
OPT_WEBSOCKET))
370+
websocket_helper_path = subdaemon_path(tmpctx, ld,
371+
"lightning_websocketd");
372+
else
373+
websocket_helper_path = NULL;
367374

368375
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0)
369376
fatal("Could not socketpair for connectd<->gossipd");

lightningd/options.c

+17
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,14 @@ static char *opt_set_wumbo(struct lightningd *ld)
861861
return NULL;
862862
}
863863

864+
static char *opt_set_websocket(struct lightningd *ld)
865+
{
866+
feature_set_or(ld->our_features,
867+
take(feature_set_for_feature(NULL,
868+
OPTIONAL_FEATURE(OPT_WEBSOCKET))));
869+
return NULL;
870+
}
871+
864872
static char *opt_set_dual_fund(struct lightningd *ld)
865873
{
866874
/* Dual funding implies anchor outputs */
@@ -942,6 +950,10 @@ static void register_opts(struct lightningd *ld)
942950
" and allow peers to establish channels"
943951
" via v2 channel open protocol.");
944952

953+
opt_register_early_noarg("--experimental-websocket",
954+
opt_set_websocket, ld,
955+
"experimental: allow peers to connect using"
956+
" WebSockets (RFC6455)");
945957
/* This affects our features, so set early. */
946958
opt_register_early_noarg("--experimental-onion-messages",
947959
opt_set_onion_messages, ld,
@@ -1386,6 +1398,11 @@ static void add_config(struct lightningd *ld,
13861398
feature_offered(ld->our_features
13871399
->bits[INIT_FEATURE],
13881400
OPT_DUAL_FUND));
1401+
} else if (opt->cb == (void *)opt_set_websocket) {
1402+
json_add_bool(response, name0,
1403+
feature_offered(ld->our_features
1404+
->bits[NODE_ANNOUNCE_FEATURE],
1405+
OPT_WEBSOCKET));
13891406
} else if (opt->cb == (void *)opt_set_onion_messages) {
13901407
json_add_bool(response, name0,
13911408
feature_offered(ld->our_features

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mrkd ~= 0.1.6
33
Mako ~= 1.1.3
44
flake8 ~= 3.7.8
5+
websocket
56

67
./contrib/pyln-client
78
./contrib/pyln-proto

tests/test_connection.py

+50
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fixtures import TEST_NETWORK
44
from flaky import flaky # noqa: F401
55
from pyln.client import RpcError, Millisatoshi
6+
import pyln.proto.wire as wire
67
from utils import (
78
only_one, wait_for, sync_blockheight, TIMEOUT,
89
expected_peer_features, expected_node_features,
@@ -20,6 +21,7 @@
2021
import shutil
2122
import time
2223
import unittest
24+
import websocket
2325

2426

2527
def test_connect(node_factory):
@@ -3734,3 +3736,51 @@ def test_old_feerate(node_factory):
37343736

37353737
# This will timeout if l2 didn't accept fee.
37363738
l1.pay(l2, 1000)
3739+
3740+
3741+
def test_websocket(node_factory):
3742+
l1 = node_factory.get_node(options={'experimental-websocket': None, 'log-level': 'io'})
3743+
assert l1.rpc.listconfigs()['experimental-websocket']
3744+
3745+
# Adapter to turn websocket into a stream "connection"
3746+
class BinWebSocket(object):
3747+
def __init__(self, hostname, port):
3748+
self.ws = websocket.WebSocket()
3749+
self.ws.connect("ws://" + hostname + ":" + str(port))
3750+
self.recvbuf = bytes()
3751+
3752+
def send(self, data):
3753+
self.ws.send(data, websocket.ABNF.OPCODE_BINARY)
3754+
3755+
def recv(self, maxlen):
3756+
while len(self.recvbuf) < maxlen:
3757+
self.recvbuf += self.ws.recv()
3758+
3759+
ret = self.recvbuf[:maxlen]
3760+
self.recvbuf = self.recvbuf[maxlen:]
3761+
return ret
3762+
3763+
ws = BinWebSocket('localhost', l1.port)
3764+
lconn = wire.LightningConnection(ws,
3765+
wire.PublicKey(bytes.fromhex(l1.info['id'])),
3766+
wire.PrivateKey(bytes([1] * 32)),
3767+
is_initiator=True)
3768+
# Perform handshake.
3769+
lconn.shake()
3770+
3771+
# Expect to receive init msg.
3772+
msg = lconn.read_message()
3773+
assert int.from_bytes(msg[0:2], 'big') == 16
3774+
3775+
# Echo same message back.
3776+
lconn.send_message(msg)
3777+
3778+
# Now try sending a ping, ask for 50 bytes
3779+
msg = bytes((0, 18, 0, 50, 0, 0))
3780+
lconn.send_message(msg)
3781+
3782+
# Could actually reply with some gossip msg!
3783+
while True:
3784+
msg = lconn.read_message()
3785+
if int.from_bytes(msg[0:2], 'big') == 19:
3786+
break

0 commit comments

Comments
 (0)