From 6193b896504f81dc8459139fb971e107420a8ec2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 26 Aug 2021 12:46:02 +0930 Subject: [PATCH] connectd: allow forking a proxy which handles websockets. If set, we spawn it (lightning_websocketd) on any invalid incoming act1 handshake. That means websocketd is a per-peer daemon, but it means every other daemon uses the connection normally (it's just actually talking to websocketd instead of the client directly). Signed-off-by: Rusty Russell --- connectd/connectd.c | 7 +- connectd/connectd_wire.csv | 1 + connectd/connectd_wiregen.c | 17 +++- connectd/connectd_wiregen.h | 6 +- connectd/handshake.c | 113 +++++++++++++++++++++++++- connectd/handshake.h | 5 +- connectd/test/run-initiator-success.c | 3 + connectd/test/run-responder-success.c | 5 +- lightningd/connect_control.c | 4 +- 9 files changed, 149 insertions(+), 12 deletions(-) diff --git a/connectd/connectd.c b/connectd/connectd.c index 7d45169ce62f..837e4f18cbbc 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -163,6 +163,9 @@ struct daemon { /* Our features, as lightningd told us */ struct feature_set *our_features; + + /* Subdaemon to proxy websocket requests, if any. */ + char *websocket_helper; }; /* Peers we're trying to reach: we iterate through addrs until we succeed @@ -611,6 +614,7 @@ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon * code from thinking `conn` (which we don't keep a pointer to) is * leaked */ return responder_handshake(notleak(conn), &daemon->mykey, &addr, + daemon->websocket_helper, handshake_in_success, daemon); } @@ -1316,7 +1320,8 @@ static struct io_plan *connect_init(struct io_conn *conn, &daemon->dev_allow_localhost, &daemon->use_dns, &tor_password, &daemon->use_v3_autotor, - &daemon->timeout_secs)) { + &daemon->timeout_secs, + &daemon->websocket_helper)) { /* This is a helper which prints the type expected and the actual * message, then exits (it should never be called!). */ master_badmsg(WIRE_CONNECTD_INIT, msg); diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index 093f7d33f2d7..590da48f94f4 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -18,6 +18,7 @@ msgdata,connectd_init,use_dns,bool, msgdata,connectd_init,tor_password,wirestring, msgdata,connectd_init,use_v3_autotor,bool, msgdata,connectd_init,timeout_secs,u32, +msgdata,connectd_init,websocket_helper,?wirestring, # Connectd->master, here are the addresses I bound, can announce. msgtype,connectd_init_reply,2100 diff --git a/connectd/connectd_wiregen.c b/connectd/connectd_wiregen.c index 2426dacaa9c2..66e955cdeef8 100644 --- a/connectd/connectd_wiregen.c +++ b/connectd/connectd_wiregen.c @@ -63,7 +63,7 @@ bool connectd_wire_is_defined(u16 type) /* WIRE: CONNECTD_INIT */ -u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct feature_set *our_features, const struct node_id *id, const struct wireaddr_internal *wireaddrs, const enum addr_listen_announce *listen_announce, const struct wireaddr *tor_proxyaddr, bool use_tor_proxy_always, bool dev_allow_localhost, bool use_dns, const wirestring *tor_password, bool use_v3_autotor, u32 timeout_secs) +u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct feature_set *our_features, const struct node_id *id, const struct wireaddr_internal *wireaddrs, const enum addr_listen_announce *listen_announce, const struct wireaddr *tor_proxyaddr, bool use_tor_proxy_always, bool dev_allow_localhost, bool use_dns, const wirestring *tor_password, bool use_v3_autotor, u32 timeout_secs, const wirestring *websocket_helper) { u16 num_wireaddrs = tal_count(listen_announce); u8 *p = tal_arr(ctx, u8, 0); @@ -89,10 +89,16 @@ u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams towire_wirestring(&p, tor_password); towire_bool(&p, use_v3_autotor); towire_u32(&p, timeout_secs); + if (!websocket_helper) + towire_bool(&p, false); + else { + towire_bool(&p, true); + towire_wirestring(&p, websocket_helper); + } return memcheck(p, tal_count(p)); } -bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct feature_set **our_features, struct node_id *id, struct wireaddr_internal **wireaddrs, enum addr_listen_announce **listen_announce, struct wireaddr **tor_proxyaddr, bool *use_tor_proxy_always, bool *dev_allow_localhost, bool *use_dns, wirestring **tor_password, bool *use_v3_autotor, u32 *timeout_secs) +bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct feature_set **our_features, struct node_id *id, struct wireaddr_internal **wireaddrs, enum addr_listen_announce **listen_announce, struct wireaddr **tor_proxyaddr, bool *use_tor_proxy_always, bool *dev_allow_localhost, bool *use_dns, wirestring **tor_password, bool *use_v3_autotor, u32 *timeout_secs, wirestring **websocket_helper) { u16 num_wireaddrs; @@ -125,6 +131,11 @@ bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainp *tor_password = fromwire_wirestring(ctx, &cursor, &plen); *use_v3_autotor = fromwire_bool(&cursor, &plen); *timeout_secs = fromwire_u32(&cursor, &plen); + if (!fromwire_bool(&cursor, &plen)) + *websocket_helper = NULL; + else { + *websocket_helper = fromwire_wirestring(ctx, &cursor, &plen); + } return cursor != NULL; } @@ -443,4 +454,4 @@ bool fromwire_connectd_dev_memleak_reply(const void *p, bool *leak) *leak = fromwire_bool(&cursor, &plen); return cursor != NULL; } -// SHA256STAMP:685593d3211a88f48474420db7f114a48d0619ee00eb7ec337fe8d78deecf426 +// SHA256STAMP:f30bc0a77c166067f6080c1da06b4f335d6178218bb0a8ed8ac7b0288ccb21be diff --git a/connectd/connectd_wiregen.h b/connectd/connectd_wiregen.h index eb1c87b561e6..b4a1c481ff96 100644 --- a/connectd/connectd_wiregen.h +++ b/connectd/connectd_wiregen.h @@ -51,8 +51,8 @@ bool connectd_wire_is_defined(u16 type); /* WIRE: CONNECTD_INIT */ -u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct feature_set *our_features, const struct node_id *id, const struct wireaddr_internal *wireaddrs, const enum addr_listen_announce *listen_announce, const struct wireaddr *tor_proxyaddr, bool use_tor_proxy_always, bool dev_allow_localhost, bool use_dns, const wirestring *tor_password, bool use_v3_autotor, u32 timeout_secs); -bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct feature_set **our_features, struct node_id *id, struct wireaddr_internal **wireaddrs, enum addr_listen_announce **listen_announce, struct wireaddr **tor_proxyaddr, bool *use_tor_proxy_always, bool *dev_allow_localhost, bool *use_dns, wirestring **tor_password, bool *use_v3_autotor, u32 *timeout_secs); +u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct feature_set *our_features, const struct node_id *id, const struct wireaddr_internal *wireaddrs, const enum addr_listen_announce *listen_announce, const struct wireaddr *tor_proxyaddr, bool use_tor_proxy_always, bool dev_allow_localhost, bool use_dns, const wirestring *tor_password, bool use_v3_autotor, u32 timeout_secs, const wirestring *websocket_helper); +bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct feature_set **our_features, struct node_id *id, struct wireaddr_internal **wireaddrs, enum addr_listen_announce **listen_announce, struct wireaddr **tor_proxyaddr, bool *use_tor_proxy_always, bool *dev_allow_localhost, bool *use_dns, wirestring **tor_password, bool *use_v3_autotor, u32 *timeout_secs, wirestring **websocket_helper); /* WIRE: CONNECTD_INIT_REPLY */ /* Connectd->master */ @@ -110,4 +110,4 @@ bool fromwire_connectd_dev_memleak_reply(const void *p, bool *leak); #endif /* LIGHTNING_CONNECTD_CONNECTD_WIREGEN_H */ -// SHA256STAMP:685593d3211a88f48474420db7f114a48d0619ee00eb7ec337fe8d78deecf426 +// SHA256STAMP:f30bc0a77c166067f6080c1da06b4f335d6178218bb0a8ed8ac7b0288ccb21be diff --git a/connectd/handshake.c b/connectd/handshake.c index 38fca6c4b209..12790f6eb528 100644 --- a/connectd/handshake.c +++ b/connectd/handshake.c @@ -7,19 +7,24 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include @@ -179,6 +184,9 @@ struct handshake { /* Are we initiator or responder. */ enum bolt8_side side; + /* Subdaemon to proxy via if we incorrect handshake */ + const char *websocket_helper; + /* Function to call once handshake complete. */ struct io_plan *(*cb)(struct io_conn *conn, const struct pubkey *their_id, @@ -863,6 +871,107 @@ static struct io_plan *act_two_responder(struct io_conn *conn, return io_write(conn, &h->act2, ACT_TWO_SIZE, act_three_responder, h); } +/* For helper, we don't populate ->websocket_helper so it doesn't loop! */ +static struct io_plan *helper_init(struct io_conn *conn, struct handshake *old_h) +{ + return responder_handshake(conn, &old_h->my_id, &old_h->addr, NULL, + old_h->cb, old_h->cbarg); +} + +static struct io_plan *try_websocket_server(struct io_conn *conn, + struct handshake *h) +{ + int childmsg[2], execfail[2]; + pid_t childpid; + int err; + + if (!h->websocket_helper) { + status_debug("No websocket helper, failing for '%s'", + tal_hexstr(tmpctx, &h->act1, sizeof(h->act1))); + return handshake_failed(conn, h); + } + + status_debug("Websocket helper: trying for '%s'", + tal_hexstr(tmpctx, &h->act1, sizeof(h->act1))); + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, childmsg) != 0) + goto fail; + + if (pipe(execfail) != 0) + goto close_msgfd_fail; + + if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD) + | FD_CLOEXEC) < 0) + goto close_execfail_fail; + + childpid = fork(); + if (childpid < 0) + goto close_execfail_fail; + + if (childpid == 0) { + size_t max; + close(childmsg[0]); + close(execfail[0]); + + /* Attach remote socket to stdin. */ + if (dup2(io_conn_fd(conn), STDIN_FILENO) == -1) + goto child_errno_fail; + + /* Attach our socket to stdout. */ + if (dup2(childmsg[1], STDOUT_FILENO) == -1) + goto child_errno_fail; + + /* Make (fairly!) sure all other fds are closed. */ + max = sysconf(_SC_OPEN_MAX); + for (size_t i = STDERR_FILENO + 1; i < max; i++) + close(i); + + /* Tell websocket helper what we read so far. */ + execlp(h->websocket_helper, h->websocket_helper, + tal_hexstr(tmpctx, &h->act1, sizeof(h->act1)), + NULL); + + child_errno_fail: + err = errno; + /* Gcc's warn-unused-result fail. */ + if (write(execfail[1], &err, sizeof(err))) { + ; + } + exit(127); + } + + close(childmsg[1]); + close(execfail[1]); + + /* Child will close this without writing on successful exec. */ + if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) { + close(execfail[0]); + waitpid(childpid, NULL, 0); + status_broken("Exec of helper %s failed: %s", + h->websocket_helper, strerror(err)); + errno = err; + return io_close(conn); + } + + close(execfail[0]); + + /* Now, we try handshake again, but this time via the helper proxy */ + notleak(io_new_conn(tal_parent(conn), childmsg[0], helper_init, h)); + + /* Abandon original (doesn't close since child has dup'd fd) */ + return io_close(conn); + +close_execfail_fail: + close_noerr(execfail[0]); + close_noerr(execfail[1]); +close_msgfd_fail: + close_noerr(childmsg[0]); + close_noerr(childmsg[1]); +fail: + status_broken("Preparation of helper failed: %s", + strerror(errno)); + return io_close(conn); +} static struct io_plan *act_one_responder2(struct io_conn *conn, struct handshake *h) @@ -873,7 +982,7 @@ static struct io_plan *act_one_responder2(struct io_conn *conn, * MUST abort the connection attempt. */ if (h->act1.v != 0) - return handshake_failed(conn, h); + return try_websocket_server(conn, h); /* BOLT #8: * @@ -963,6 +1072,7 @@ static struct io_plan *act_one_responder(struct io_conn *conn, struct io_plan *responder_handshake_(struct io_conn *conn, const struct pubkey *my_id, const struct wireaddr_internal *addr, + const char *websocket_helper, struct io_plan *(*cb)(struct io_conn *, const struct pubkey *, const struct wireaddr_internal *, @@ -973,6 +1083,7 @@ struct io_plan *responder_handshake_(struct io_conn *conn, struct handshake *h = new_handshake(conn, my_id); h->side = RESPONDER; + h->websocket_helper = websocket_helper; h->my_id = *my_id; h->addr = *addr; h->cbarg = cbarg; diff --git a/connectd/handshake.h b/connectd/handshake.h index 5312ac9c80fa..e26361bda987 100644 --- a/connectd/handshake.h +++ b/connectd/handshake.h @@ -32,8 +32,8 @@ struct io_plan *initiator_handshake_(struct io_conn *conn, void *cbarg); -#define responder_handshake(conn, my_id, addr, cb, cbarg) \ - responder_handshake_((conn), (my_id), (addr), \ +#define responder_handshake(conn, my_id, addr, websocket_helper, cb, cbarg) \ + responder_handshake_((conn), (my_id), (addr), (websocket_helper), \ typesafe_cb_preargs(struct io_plan *, void *, \ (cb), (cbarg), \ struct io_conn *, \ @@ -45,6 +45,7 @@ struct io_plan *initiator_handshake_(struct io_conn *conn, struct io_plan *responder_handshake_(struct io_conn *conn, const struct pubkey *my_id, const struct wireaddr_internal *addr, + const char *websocket_helper, struct io_plan *(*cb)(struct io_conn *, const struct pubkey *, const struct wireaddr_internal *, diff --git a/connectd/test/run-initiator-success.c b/connectd/test/run-initiator-success.c index 627e0e2414dd..926fa7c8a7de 100644 --- a/connectd/test/run-initiator-success.c +++ b/connectd/test/run-initiator-success.c @@ -83,6 +83,9 @@ u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* Generated stub for fromwire_u8_array */ void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for notleak_ */ +void *notleak_(const void *ptr UNNEEDED, bool plus_children UNNEEDED) +{ fprintf(stderr, "notleak_ called!\n"); abort(); } /* Generated stub for towire */ void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) { fprintf(stderr, "towire called!\n"); abort(); } diff --git a/connectd/test/run-responder-success.c b/connectd/test/run-responder-success.c index a531f19ca35a..1d2e594fea4c 100644 --- a/connectd/test/run-responder-success.c +++ b/connectd/test/run-responder-success.c @@ -83,6 +83,9 @@ u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* Generated stub for fromwire_u8_array */ void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for notleak_ */ +void *notleak_(const void *ptr UNNEEDED, bool plus_children UNNEEDED) +{ fprintf(stderr, "notleak_ called!\n"); abort(); } /* Generated stub for towire */ void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) { fprintf(stderr, "towire called!\n"); abort(); } @@ -320,7 +323,7 @@ int main(int argc, char *argv[]) dummy.itype = ADDR_INTERNAL_WIREADDR; dummy.u.wireaddr.addrlen = 0; - responder_handshake((void *)tmpctx, &ls_pub, &dummy, success, NULL); + responder_handshake((void *)tmpctx, &ls_pub, &dummy, NULL, success, NULL); /* Should not exit! */ abort(); } diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index b5981da61c1d..b15e258a9645 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -363,6 +363,7 @@ 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 = NULL; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0) fatal("Could not socketpair for connectd<->gossipd"); @@ -394,7 +395,8 @@ int connectd_init(struct lightningd *ld) IFDEV(ld->dev_allow_localhost, false), ld->config.use_dns, ld->tor_service_password ? ld->tor_service_password : "", ld->config.use_v3_autotor, - ld->config.connection_timeout_secs); + ld->config.connection_timeout_secs, + websocket_helper_path); subd_req(ld->connectd, ld->connectd, take(msg), -1, 0, connect_init_done, NULL);