Skip to content

Commit

Permalink
experimental-websocket-port: option to create a WebSocket port.
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Oct 15, 2021
1 parent 4a0a759 commit 3a4a277
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 3 deletions.
3 changes: 2 additions & 1 deletion doc/lightning-listconfigs.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ On success, an object is returned, containing:
- **experimental-onion-messages** (boolean, optional): `experimental-onion-messages` field from config or cmdline, or default
- **experimental-offers** (boolean, optional): `experimental-offers` field from config or cmdline, or default
- **experimental-shutdown-wrong-funding** (boolean, optional): `experimental-shutdown-wrong-funding` field from config or cmdline, or default
- **experimental-websocket-port** (u16, optional): `experimental-websocket-port` field from config or cmdline, or default
- **rgb** (hex, optional): `rgb` field from config or cmdline, or default (always 6 characters)
- **alias** (string, optional): `alias` field from config or cmdline, or default
- **pid-file** (string, optional): `pid-file` field from config or cmdline, or default
Expand Down Expand Up @@ -205,4 +206,4 @@ RESOURCES
---------

Main web site: <https://github.com/ElementsProject/lightning>
[comment]: # ( SHA256STAMP:7bb40fc8fac201b32d9701b02596d0fa59eb14a3baf606439cbf96dc11548ed4)
[comment]: # ( SHA256STAMP:47c067588120e0f9a71206313685cebb2a8c515e9b04b688b202d2772c8f8146)
7 changes: 7 additions & 0 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,13 @@ about whether to add funds or not to a proposed channel is handled
automatically by a plugin that implements the appropriate logic for
your needs. The default behavior is to not contribute funds.

**experimental-websocket-port**

Specifying this enables support for accepting incoming WebSocket
connections on that port, on any IPv4 and IPv6 addresses you listen
to. The normal protocol is expected to be sent over WebSocket binary
frames once the connection is upgraded.

BUGS
----

Expand Down
4 changes: 4 additions & 0 deletions doc/schemas/listconfigs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
"type": "boolean",
"description": "`experimental-shutdown-wrong-funding` field from config or cmdline, or default"
},
"experimental-websocket-port": {
"type": "u16",
"description": "`experimental-websocket-port` field from config or cmdline, or default"
},
"rgb": {
"type": "hex",
"description": "`rgb` field from config or cmdline, or default",
Expand Down
7 changes: 5 additions & 2 deletions lightningd/connect_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,10 @@ int connectd_init(struct lightningd *ld)
int hsmfd;
struct wireaddr_internal *wireaddrs = ld->proposed_wireaddr;
enum addr_listen_announce *listen_announce = ld->proposed_listen_announce;
const char *websocket_helper_path = "";
const char *websocket_helper_path;

websocket_helper_path = subdaemon_path(tmpctx, ld,
"lightning_websocketd");

if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0)
fatal("Could not socketpair for connectd<->gossipd");
Expand Down Expand Up @@ -384,7 +387,7 @@ int connectd_init(struct lightningd *ld)
ld->config.use_v3_autotor,
ld->config.connection_timeout_secs,
websocket_helper_path,
0);
ld->websocket_port);

subd_req(ld->connectd, ld->connectd, take(msg), -1, 0,
connect_init_done, NULL);
Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->always_use_proxy = false;
ld->pure_tor_setup = false;
ld->tor_service_password = NULL;
ld->websocket_port = 0;

/*~ This is initialized later, but the plugin loop examines this,
* so set it to NULL explicitly now. */
Expand Down
3 changes: 3 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ struct lightningd {
/* Array of (even) TLV types that we should allow. This is required
* since we otherwise would outright reject them. */
u64 *accept_extra_tlv_types;

/* EXPERIMENTAL: websocket port if non-zero */
u16 websocket_port;
};

/* Turning this on allows a tal allocation to return NULL, rather than aborting.
Expand Down
25 changes: 25 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,21 @@ static char *opt_set_wumbo(struct lightningd *ld)
return NULL;
}

static char *opt_set_websocket_port(const char *arg, struct lightningd *ld)
{
u32 port;
char *err;

err = opt_set_u32(arg, &port);
if (err)
return err;

ld->websocket_port = port;
if (ld->websocket_port != port)
return tal_fmt(NULL, "'%s' is out of range", arg);
return NULL;
}

static char *opt_set_dual_fund(struct lightningd *ld)
{
/* Dual funding implies anchor outputs */
Expand Down Expand Up @@ -1051,6 +1066,11 @@ static void register_opts(struct lightningd *ld)
"--subdaemon=hsmd:remote_signer "
"would use a hypothetical remote signing subdaemon.");

opt_register_arg("--experimental-websocket-port",
opt_set_websocket_port, NULL,
ld,
"experimental: alternate port for peers to connect"
" using WebSockets (RFC6455)");
opt_register_logging(ld);
opt_register_version();

Expand Down Expand Up @@ -1463,6 +1483,11 @@ static void add_config(struct lightningd *ld,
json_add_opt_disable_plugins(response, ld->plugins);
} else if (opt->cb_arg == (void *)opt_force_feerates) {
answer = fmt_force_feerates(name0, ld->force_feerates);
} else if (opt->cb_arg == (void *)opt_set_websocket_port) {
if (ld->websocket_port)
json_add_u32(response, name0,
ld->websocket_port);
return;
} else if (opt->cb_arg == (void *)opt_important_plugin) {
/* Do nothing, this is already handled by
* opt_add_plugin. */
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mrkd ~= 0.1.6
Mako ~= 1.1.3
flake8 ~= 3.7.8
websocket-client

./contrib/pyln-client
./contrib/pyln-proto
Expand Down
55 changes: 55 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from fixtures import * # noqa: F401,F403
from fixtures import TEST_NETWORK
from flaky import flaky # noqa: F401
from ephemeral_port_reserve import reserve # type: ignore
from pyln.client import RpcError, Millisatoshi
import pyln.proto.wire as wire
from utils import (
only_one, wait_for, sync_blockheight, TIMEOUT,
expected_peer_features, expected_node_features,
Expand All @@ -20,6 +22,7 @@
import shutil
import time
import unittest
import websocket


def test_connect(node_factory):
Expand Down Expand Up @@ -3740,6 +3743,58 @@ def test_old_feerate(node_factory):
l1.pay(l2, 1000)


def test_websocket(node_factory):
ws_port = reserve()
l1 = node_factory.get_node(options={'experimental-websocket-port': ws_port, 'log-level': 'io'})
assert l1.rpc.listconfigs()['experimental-websocket-port'] == ws_port

# Adapter to turn websocket into a stream "connection"
class BinWebSocket(object):
def __init__(self, hostname, port):
self.ws = websocket.WebSocket()
self.ws.connect("ws://" + hostname + ":" + str(port))
self.recvbuf = bytes()

def send(self, data):
self.ws.send(data, websocket.ABNF.OPCODE_BINARY)

def recv(self, maxlen):
while len(self.recvbuf) < maxlen:
self.recvbuf += self.ws.recv()

ret = self.recvbuf[:maxlen]
self.recvbuf = self.recvbuf[maxlen:]
return ret

ws = BinWebSocket('localhost', ws_port)
lconn = wire.LightningConnection(ws,
wire.PublicKey(bytes.fromhex(l1.info['id'])),
wire.PrivateKey(bytes([1] * 32)),
is_initiator=True)

l1.daemon.wait_for_log('Websocket connection in from')

# Perform handshake.
lconn.shake()

# Expect to receive init msg.
msg = lconn.read_message()
assert int.from_bytes(msg[0:2], 'big') == 16

# Echo same message back.
lconn.send_message(msg)

# Now try sending a ping, ask for 50 bytes
msg = bytes((0, 18, 0, 50, 0, 0))
lconn.send_message(msg)

# Could actually reply with some gossip msg!
while True:
msg = lconn.read_message()
if int.from_bytes(msg[0:2], 'big') == 19:
break


@pytest.mark.developer("dev-disconnect required")
def test_ping_timeout(node_factory):
# Disconnects after this, but doesn't know it.
Expand Down

0 comments on commit 3a4a277

Please sign in to comment.