diff --git a/connectd/connectd.c b/connectd/connectd.c index 069a73436797..15798373324e 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -121,6 +123,10 @@ struct daemon { /* pubkey equivalent. */ struct pubkey mykey; + /* Base for timeout timers, and how long to wait for init msg */ + struct timers timers; + u32 timeout_secs; + /* Peers that we've handed to `lightningd`, which it hasn't told us * have disconnected. */ struct node_set peers; @@ -509,6 +515,14 @@ static struct io_plan *handshake_in_success(struct io_conn *conn, cs, &id, addr); } +/*~ If the timer goes off, we simply free everything, which hangs up. */ +static void conn_timeout(struct io_conn *conn) +{ + status_debug("conn timed out"); + errno = ETIMEDOUT; + io_close(conn); +} + /*~ When we get a connection in we set up its network address then call * handshake.c to set up the crypto state. */ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon) @@ -544,7 +558,11 @@ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon return io_close(conn); } - /* FIXME: Timeout */ + /* If they don't complete handshake in reasonable time, hang up */ + notleak(new_reltimer(&daemon->timers, conn, + time_from_sec(daemon->timeout_secs), + conn_timeout, conn)); + /*~ The crypto handshake differs depending on whether you received or * initiated the socket connection, so there are two entry points. * Note, again, the notleak() to avoid our simplistic leak detection @@ -583,7 +601,10 @@ struct io_plan *connection_out(struct io_conn *conn, struct connecting *connect) return io_close(conn); } - /* FIXME: Timeout */ + /* If they don't complete handshake in reasonable time, hang up */ + notleak(new_reltimer(&connect->daemon->timers, conn, + time_from_sec(connect->daemon->timeout_secs), + conn_timeout, conn)); status_peer_debug(&connect->id, "Connected out, starting crypto"); connect->connstate = "Cryptographic handshake"; @@ -1243,7 +1264,8 @@ static struct io_plan *connect_init(struct io_conn *conn, &proxyaddr, &daemon->use_proxy_always, &daemon->dev_allow_localhost, &daemon->use_dns, &tor_password, - &daemon->use_v3_autotor)) { + &daemon->use_v3_autotor, + &daemon->timeout_secs)) { /* 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); @@ -1638,6 +1660,7 @@ int main(int argc, char *argv[]) memleak_add_helper(daemon, memleak_daemon_cb); list_head_init(&daemon->connecting); daemon->listen_fds = tal_arr(daemon, struct listen_fd, 0); + timers_init(&daemon->timers, time_mono()); /* stdin == control */ daemon->master = daemon_conn_new(daemon, STDIN_FILENO, recv_req, NULL, daemon); @@ -1651,9 +1674,11 @@ int main(int argc, char *argv[]) * status_failed on error. */ ecdh_hsmd_setup(HSM_FD, status_failed); - /* Should never exit. */ - io_loop(NULL, NULL); - abort(); + for (;;) { + struct timer *expired; + io_loop(&daemon->timers, &expired); + timer_expired(daemon, expired); + } } /*~ Getting bored? This was a pretty simple daemon! diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index 98cd38f5059a..859e695f1ab7 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -17,6 +17,7 @@ msgdata,connectd_init,dev_allow_localhost,bool, 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, # 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 b7988d927669..4de846b01e90 100644 --- a/connectd/connectd_wiregen.c +++ b/connectd/connectd_wiregen.c @@ -61,7 +61,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) +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) { u16 num_wireaddrs = tal_count(listen_announce); u8 *p = tal_arr(ctx, u8, 0); @@ -86,10 +86,11 @@ u8 *towire_connectd_init(const tal_t *ctx, const struct chainparams *chainparams towire_bool(&p, use_dns); towire_wirestring(&p, tor_password); towire_bool(&p, use_v3_autotor); + towire_u32(&p, timeout_secs); 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) +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) { u16 num_wireaddrs; @@ -121,6 +122,7 @@ bool fromwire_connectd_init(const tal_t *ctx, const void *p, const struct chainp *use_dns = fromwire_bool(&cursor, &plen); *tor_password = fromwire_wirestring(ctx, &cursor, &plen); *use_v3_autotor = fromwire_bool(&cursor, &plen); + *timeout_secs = fromwire_u32(&cursor, &plen); return cursor != NULL; } @@ -404,4 +406,4 @@ bool fromwire_connectd_dev_memleak_reply(const void *p, bool *leak) *leak = fromwire_bool(&cursor, &plen); return cursor != NULL; } -// SHA256STAMP:ae6ae82d79526abc039896e90f677549a6acb73c0a2cd171f6658849025318b5 +// SHA256STAMP:48e50860685655b8ae8e12dd3f07d1c1cceea48934fe3846c7d276d93c70b211 diff --git a/connectd/connectd_wiregen.h b/connectd/connectd_wiregen.h index 4ab89fda8f39..d71335d0a3b6 100644 --- a/connectd/connectd_wiregen.h +++ b/connectd/connectd_wiregen.h @@ -49,8 +49,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); -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); +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); /* WIRE: CONNECTD_INIT_REPLY */ /* Connectd->master */ @@ -103,4 +103,4 @@ bool fromwire_connectd_dev_memleak_reply(const void *p, bool *leak); #endif /* LIGHTNING_CONNECTD_CONNECTD_WIREGEN_H */ -// SHA256STAMP:ae6ae82d79526abc039896e90f677549a6acb73c0a2cd171f6658849025318b5 +// SHA256STAMP:48e50860685655b8ae8e12dd3f07d1c1cceea48934fe3846c7d276d93c70b211 diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index 3eba919683e1..08879267b969 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -371,7 +371,8 @@ int connectd_init(struct lightningd *ld) ld->proxyaddr, ld->use_proxy_always || ld->pure_tor_setup, 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.use_v3_autotor, + ld->config.connection_timeout_secs); subd_req(ld->connectd, ld->connectd, take(msg), -1, 0, connect_init_done, NULL); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 11c4dc6aac4a..c8395c1f5e8d 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -69,6 +69,9 @@ struct config { /* This is the key we use to encrypt `hsm_secret`. */ struct secret *keypass; + + /* How long before we give up waiting for INIT msg */ + u32 connection_timeout_secs; }; typedef STRMAP(const char *) alt_subdaemon_map; diff --git a/lightningd/options.c b/lightningd/options.c index f795ac845bae..6854bd8ec35b 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -593,6 +593,9 @@ static void dev_register_opts(struct lightningd *ld) "Make builtin plugins unimportant so you can plugin stop them."); opt_register_arg("--dev-force-features", opt_force_featureset, NULL, ld, "Force the init/globalinit/node_announce/channel/bolt11 features, each comma-separated bitnumbers"); + opt_register_arg("--dev-timeout-secs", opt_set_u32, opt_show_u32, + &ld->config.connection_timeout_secs, + "Seconds to timeout if we don't receive INIT from peer"); } #endif /* DEVELOPER */ @@ -638,6 +641,9 @@ static const struct config testnet_config = { .min_capacity_sat = 10000, .use_v3_autotor = true, + + /* 1 minute should be enough for anyone! */ + .connection_timeout_secs = 60, }; /* aka. "Dude, where's my coins?" */ @@ -694,6 +700,9 @@ static const struct config mainnet_config = { /* Allow to define the default behavior of tor services calls*/ .use_v3_autotor = true, + + /* 1 minute should be enough for anyone! */ + .connection_timeout_secs = 60, }; static void check_config(struct lightningd *ld) diff --git a/tests/test_connection.py b/tests/test_connection.py index eeed39b302e8..02780a2333a9 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2634,3 +2634,20 @@ def test_nonstatic_channel(node_factory, bitcoind): l1.pay(l2, 1000) l1.rpc.close(l2.info['id']) + + +@unittest.skipIf(not DEVELOPER, "needs --dev-timeout-secs") +def test_connection_timeout(node_factory): + # l1 hears nothing back after sending INIT, should time out. + l1, l2 = node_factory.get_nodes(2, + opts=[{'dev-timeout-secs': 1, + 'disconnect': ['0WIRE_INIT', '0WIRE_INIT']}, + {}]) + + with pytest.raises(RpcError, match='timed out'): + l1.rpc.connect(l2.info['id'], 'localhost', port=l2.port) + l1.daemon.wait_for_log('conn timed out') + + with pytest.raises(RpcError, match='reset by peer'): + l2.rpc.connect(l1.info['id'], 'localhost', port=l1.port) + l1.daemon.wait_for_log('conn timed out')