Skip to content

Commit f78184c

Browse files
rustyrussellcdecker
authored andcommitted
connectd: listen on ports for which we should spawn a proxy.
If the port is set, we spawn it (lightning_websocketd) on any connection to that port. 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 <[email protected]>
1 parent b013b3a commit f78184c

File tree

6 files changed

+225
-27
lines changed

6 files changed

+225
-27
lines changed

connectd/connectd.c

Lines changed: 213 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@
3333
#include <connectd/tor.h>
3434
#include <connectd/tor_autoservice.h>
3535
#include <errno.h>
36+
#include <fcntl.h>
3637
#include <netdb.h>
3738
#include <netinet/in.h>
3839
#include <sodium.h>
40+
#include <sys/types.h>
41+
#include <sys/wait.h>
42+
#include <unistd.h>
3943
#include <wire/wire_sync.h>
4044

4145
/*~ We are passed two file descriptors when exec'ed from `lightningd`: the
@@ -127,6 +131,12 @@ struct daemon {
127131

128132
/* Our features, as lightningd told us */
129133
struct feature_set *our_features;
134+
135+
/* Subdaemon to proxy websocket requests. */
136+
char *websocket_helper;
137+
138+
/* If non-zero, port to listen for websocket connections. */
139+
u16 websocket_port;
130140
};
131141

132142
/* Peers we're trying to reach: we iterate through addrs until we succeed
@@ -529,40 +539,55 @@ static void conn_timeout(struct io_conn *conn)
529539
io_close(conn);
530540
}
531541

532-
/*~ When we get a connection in we set up its network address then call
533-
* handshake.c to set up the crypto state. */
534-
static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon)
542+
/*~ So, where are you from? */
543+
static bool get_remote_address(struct io_conn *conn,
544+
struct wireaddr_internal *addr)
535545
{
536-
struct wireaddr_internal addr;
537546
struct sockaddr_storage s = {};
538547
socklen_t len = sizeof(s);
539548

540549
/* The cast here is a weird Berkeley sockets API feature... */
541550
if (getpeername(io_conn_fd(conn), (struct sockaddr *)&s, &len) != 0) {
542551
status_debug("Failed to get peername for incoming conn: %s",
543552
strerror(errno));
544-
return io_close(conn);
553+
return false;
545554
}
546555

547556
if (s.ss_family == AF_INET6) {
548557
struct sockaddr_in6 *s6 = (void *)&s;
549-
addr.itype = ADDR_INTERNAL_WIREADDR;
550-
wireaddr_from_ipv6(&addr.u.wireaddr,
558+
addr->itype = ADDR_INTERNAL_WIREADDR;
559+
wireaddr_from_ipv6(&addr->u.wireaddr,
551560
&s6->sin6_addr, ntohs(s6->sin6_port));
552561
} else if (s.ss_family == AF_INET) {
553562
struct sockaddr_in *s4 = (void *)&s;
554-
addr.itype = ADDR_INTERNAL_WIREADDR;
555-
wireaddr_from_ipv4(&addr.u.wireaddr,
563+
addr->itype = ADDR_INTERNAL_WIREADDR;
564+
wireaddr_from_ipv4(&addr->u.wireaddr,
556565
&s4->sin_addr, ntohs(s4->sin_port));
557566
} else if (s.ss_family == AF_UNIX) {
558567
struct sockaddr_un *sun = (void *)&s;
559-
addr.itype = ADDR_INTERNAL_SOCKNAME;
560-
memcpy(addr.u.sockname, sun->sun_path, sizeof(sun->sun_path));
568+
addr->itype = ADDR_INTERNAL_SOCKNAME;
569+
memcpy(addr->u.sockname, sun->sun_path, sizeof(sun->sun_path));
561570
} else {
562571
status_broken("Unknown socket type %i for incoming conn",
563572
s.ss_family);
564-
return io_close(conn);
573+
return false;
565574
}
575+
return true;
576+
}
577+
578+
/*~ As so common in C, we need to bundle two args into a callback, so we
579+
* allocate a temporary structure to hold them: */
580+
struct conn_in {
581+
struct wireaddr_internal addr;
582+
struct daemon *daemon;
583+
};
584+
585+
/*~ Once we've got a connection in, we set it up here (whether it's via the
586+
* websocket proxy, or direct). */
587+
static struct io_plan *conn_in(struct io_conn *conn,
588+
struct conn_in *conn_in_arg)
589+
{
590+
struct daemon *daemon = conn_in_arg->daemon;
566591

567592
/* If they don't complete handshake in reasonable time, hang up */
568593
notleak(new_reltimer(&daemon->timers, conn,
@@ -574,10 +599,122 @@ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon
574599
* Note, again, the notleak() to avoid our simplistic leak detection
575600
* code from thinking `conn` (which we don't keep a pointer to) is
576601
* leaked */
577-
return responder_handshake(notleak(conn), &daemon->mykey, &addr,
602+
return responder_handshake(notleak(conn), &daemon->mykey,
603+
&conn_in_arg->addr,
578604
handshake_in_success, daemon);
579605
}
580606

607+
/*~ When we get a direct connection in we set up its network address
608+
* then call handshake.c to set up the crypto state. */
609+
static struct io_plan *connection_in(struct io_conn *conn,
610+
struct daemon *daemon)
611+
{
612+
struct conn_in conn_in_arg;
613+
614+
if (!get_remote_address(conn, &conn_in_arg.addr))
615+
return io_close(conn);
616+
617+
conn_in_arg.daemon = daemon;
618+
return conn_in(conn, &conn_in_arg);
619+
}
620+
621+
/*~ <hello>I speak web socket</hello>.
622+
*
623+
* Actually that's dumb, websocket (aka rfc6455) looks nothing like that. */
624+
static struct io_plan *websocket_connection_in(struct io_conn *conn,
625+
struct daemon *daemon)
626+
{
627+
int childmsg[2], execfail[2];
628+
pid_t childpid;
629+
int err;
630+
struct conn_in conn_in_arg;
631+
632+
if (!get_remote_address(conn, &conn_in_arg.addr))
633+
return io_close(conn);
634+
635+
status_debug("Websocket connection in from %s",
636+
type_to_string(tmpctx, struct wireaddr_internal,
637+
&conn_in_arg.addr));
638+
639+
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, childmsg) != 0)
640+
goto fail;
641+
642+
if (pipe(execfail) != 0)
643+
goto close_msgfd_fail;
644+
645+
if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
646+
| FD_CLOEXEC) < 0)
647+
goto close_execfail_fail;
648+
649+
childpid = fork();
650+
if (childpid < 0)
651+
goto close_execfail_fail;
652+
653+
if (childpid == 0) {
654+
size_t max;
655+
close(childmsg[0]);
656+
close(execfail[0]);
657+
658+
/* Attach remote socket to stdin. */
659+
if (dup2(io_conn_fd(conn), STDIN_FILENO) == -1)
660+
goto child_errno_fail;
661+
662+
/* Attach our socket to stdout. */
663+
if (dup2(childmsg[1], STDOUT_FILENO) == -1)
664+
goto child_errno_fail;
665+
666+
/* Make (fairly!) sure all other fds are closed. */
667+
max = sysconf(_SC_OPEN_MAX);
668+
for (size_t i = STDERR_FILENO + 1; i < max; i++)
669+
close(i);
670+
671+
/* Tell websocket helper what we read so far. */
672+
execlp(daemon->websocket_helper, daemon->websocket_helper,
673+
NULL);
674+
675+
child_errno_fail:
676+
err = errno;
677+
/* Gcc's warn-unused-result fail. */
678+
if (write(execfail[1], &err, sizeof(err))) {
679+
;
680+
}
681+
exit(127);
682+
}
683+
684+
close(childmsg[1]);
685+
close(execfail[1]);
686+
687+
/* Child will close this without writing on successful exec. */
688+
if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
689+
close(execfail[0]);
690+
waitpid(childpid, NULL, 0);
691+
status_broken("Exec of helper %s failed: %s",
692+
daemon->websocket_helper, strerror(err));
693+
errno = err;
694+
return io_close(conn);
695+
}
696+
697+
close(execfail[0]);
698+
699+
/* New connection actually talks to proxy process. */
700+
conn_in_arg.daemon = daemon;
701+
io_new_conn(tal_parent(conn), childmsg[0], conn_in, &conn_in_arg);
702+
703+
/* Abandon original (doesn't close since child has dup'd fd) */
704+
return io_close(conn);
705+
706+
close_execfail_fail:
707+
close_noerr(execfail[0]);
708+
close_noerr(execfail[1]);
709+
close_msgfd_fail:
710+
close_noerr(childmsg[0]);
711+
close_noerr(childmsg[1]);
712+
fail:
713+
status_broken("Preparation of helper failed: %s",
714+
strerror(errno));
715+
return io_close(conn);
716+
}
717+
581718
/*~ These are the mirror functions for the connecting-out case. */
582719
static struct io_plan *handshake_out_success(struct io_conn *conn,
583720
const struct pubkey *key,
@@ -906,16 +1043,22 @@ struct listen_fd {
9061043
* covers IPv4 too. Normally we'd consider failing to listen on a
9071044
* port to be fatal, so we note this when setting up addresses. */
9081045
bool mayfail;
1046+
/* Callback to use for the listening: either connection_in, or for
1047+
* our much-derided WebSocket ability, websocket_connection_in! */
1048+
struct io_plan *(*in_cb)(struct io_conn *conn, struct daemon *daemon);
9091049
};
9101050

911-
static void add_listen_fd(struct daemon *daemon, int fd, bool mayfail)
1051+
static void add_listen_fd(struct daemon *daemon, int fd, bool mayfail,
1052+
struct io_plan *(*in_cb)(struct io_conn *,
1053+
struct daemon *))
9121054
{
9131055
/*~ utils.h contains a convenience macro tal_arr_expand which
9141056
* reallocates a tal_arr to make it one longer, then returns a pointer
9151057
* to the (new) last element. */
9161058
struct listen_fd l;
9171059
l.fd = fd;
9181060
l.mayfail = mayfail;
1061+
l.in_cb = in_cb;
9191062
tal_arr_expand(&daemon->listen_fds, l);
9201063
}
9211064

@@ -970,11 +1113,18 @@ static int make_listen_fd(int domain, void *addr, socklen_t len, bool mayfail)
9701113
/* Return true if it created socket successfully. */
9711114
static bool handle_wireaddr_listen(struct daemon *daemon,
9721115
const struct wireaddr *wireaddr,
973-
bool mayfail)
1116+
bool mayfail,
1117+
bool websocket)
9741118
{
9751119
int fd;
9761120
struct sockaddr_in addr;
9771121
struct sockaddr_in6 addr6;
1122+
struct io_plan *(*in_cb)(struct io_conn *, struct daemon *);
1123+
1124+
if (websocket)
1125+
in_cb = websocket_connection_in;
1126+
else
1127+
in_cb = connection_in;
9781128

9791129
/* Note the use of a switch() over enum here, even though it must be
9801130
* IPv4 or IPv6 here; that will catch future changes. */
@@ -984,19 +1134,21 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
9841134
/* We might fail if IPv6 bound to port first */
9851135
fd = make_listen_fd(AF_INET, &addr, sizeof(addr), mayfail);
9861136
if (fd >= 0) {
987-
status_debug("Created IPv4 listener on port %u",
1137+
status_debug("Created IPv4 %slistener on port %u",
1138+
websocket ? "websocket ": "",
9881139
wireaddr->port);
989-
add_listen_fd(daemon, fd, mayfail);
1140+
add_listen_fd(daemon, fd, mayfail, in_cb);
9901141
return true;
9911142
}
9921143
return false;
9931144
case ADDR_TYPE_IPV6:
9941145
wireaddr_to_ipv6(wireaddr, &addr6);
9951146
fd = make_listen_fd(AF_INET6, &addr6, sizeof(addr6), mayfail);
9961147
if (fd >= 0) {
997-
status_debug("Created IPv6 listener on port %u",
1148+
status_debug("Created IPv6 %slistener on port %u",
1149+
websocket ? "websocket ": "",
9981150
wireaddr->port);
999-
add_listen_fd(daemon, fd, mayfail);
1151+
add_listen_fd(daemon, fd, mayfail, in_cb);
10001152
return true;
10011153
}
10021154
return false;
@@ -1122,7 +1274,7 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
11221274
false);
11231275
status_debug("Created socket listener on file %s",
11241276
addrun.sun_path);
1125-
add_listen_fd(daemon, fd, false);
1277+
add_listen_fd(daemon, fd, false, connection_in);
11261278
/* We don't announce socket names, though we allow
11271279
* them to lazily specify --addr=/socket. */
11281280
add_binding(&binding, &wa);
@@ -1147,7 +1299,7 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
11471299
sizeof(wa.u.wireaddr.addr));
11481300

11491301
ipv6_ok = handle_wireaddr_listen(daemon, &wa.u.wireaddr,
1150-
true);
1302+
true, false);
11511303
if (ipv6_ok) {
11521304
add_binding(&binding, &wa);
11531305
if (announce
@@ -1163,7 +1315,7 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
11631315
sizeof(wa.u.wireaddr.addr));
11641316
/* OK if this fails, as long as one succeeds! */
11651317
if (handle_wireaddr_listen(daemon, &wa.u.wireaddr,
1166-
ipv6_ok)) {
1318+
ipv6_ok, false)) {
11671319
add_binding(&binding, &wa);
11681320
if (announce
11691321
&& public_address(daemon, &wa.u.wireaddr))
@@ -1174,7 +1326,8 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
11741326
}
11751327
/* This is a vanilla wireaddr as per BOLT #7 */
11761328
case ADDR_INTERNAL_WIREADDR:
1177-
handle_wireaddr_listen(daemon, &wa.u.wireaddr, false);
1329+
handle_wireaddr_listen(daemon, &wa.u.wireaddr,
1330+
false, false);
11781331
add_binding(&binding, &wa);
11791332
if (announce && public_address(daemon, &wa.u.wireaddr))
11801333
add_announcable(announcable, &wa.u.wireaddr);
@@ -1188,6 +1341,38 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
11881341
proposed_wireaddr[i].itype);
11891342
}
11901343

1344+
/* If we want websockets to match IPv4/v6, set it up now. */
1345+
if (daemon->websocket_port) {
1346+
bool announced_some = false;
1347+
struct wireaddr addr;
1348+
1349+
for (size_t i = 0; i < tal_count(binding); i++) {
1350+
/* Ignore UNIX sockets */
1351+
if (binding[i].itype != ADDR_INTERNAL_WIREADDR)
1352+
continue;
1353+
1354+
/* Override with websocket port */
1355+
addr = binding[i].u.wireaddr;
1356+
addr.port = daemon->websocket_port;
1357+
handle_wireaddr_listen(daemon, &addr, false, true);
1358+
announced_some = true;
1359+
/* FIXME: We don't report these bindings to
1360+
* lightningd, so they don't appear in
1361+
* getinfo. */
1362+
}
1363+
1364+
1365+
/* We add the websocket port to the announcement if it
1366+
* applies to any */
1367+
if (announced_some) {
1368+
wireaddr_from_websocket(&addr, daemon->websocket_port);
1369+
add_announcable(announcable, &addr);
1370+
}
1371+
}
1372+
1373+
/* FIXME: Websocket over Tor (difficult for autotor, since we need
1374+
* to use the same onion addr!) */
1375+
11911376
/* Now we have bindings, set up any Tor auto addresses: we will point
11921377
* it at the first bound IPv4 or IPv6 address we have. */
11931378
for (size_t i = 0; i < tal_count(proposed_wireaddr); i++) {
@@ -1294,7 +1479,9 @@ static struct io_plan *connect_init(struct io_conn *conn,
12941479
&daemon->dev_allow_localhost, &daemon->use_dns,
12951480
&tor_password,
12961481
&daemon->use_v3_autotor,
1297-
&daemon->timeout_secs)) {
1482+
&daemon->timeout_secs,
1483+
&daemon->websocket_helper,
1484+
&daemon->websocket_port)) {
12981485
/* This is a helper which prints the type expected and the actual
12991486
* message, then exits (it should never be called!). */
13001487
master_badmsg(WIRE_CONNECTD_INIT, msg);
@@ -1367,7 +1554,8 @@ static struct io_plan *connect_activate(struct io_conn *conn,
13671554
}
13681555
notleak(io_new_listener(daemon,
13691556
daemon->listen_fds[i].fd,
1370-
connection_in, daemon));
1557+
daemon->listen_fds[i].in_cb,
1558+
daemon));
13711559
}
13721560
}
13731561
/* Free, with NULL assignment just as an extra sanity check. */

connectd/connectd_wire.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ msgdata,connectd_init,use_dns,bool,
1818
msgdata,connectd_init,tor_password,wirestring,
1919
msgdata,connectd_init,use_v3_autotor,bool,
2020
msgdata,connectd_init,timeout_secs,u32,
21+
msgdata,connectd_init,websocket_helper,wirestring,
22+
msgdata,connectd_init,websocket_port,u16,
2123

2224
# Connectd->master, here are the addresses I bound, can announce.
2325
msgtype,connectd_init_reply,2100

0 commit comments

Comments
 (0)