Skip to content

Commit

Permalink
pytest: add test for connection ratelimiting.
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Aug 31, 2024
1 parent 09c9242 commit 153bbe1
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 4 deletions.
12 changes: 9 additions & 3 deletions connectd/connectd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,7 @@ static void connect_init(struct daemon *daemon, const u8 *msg)
enum addr_listen_announce *proposed_listen_announce;
struct wireaddr *announceable;
char *tor_password;
bool dev_disconnect, dev_throttle_gossip;
bool dev_disconnect, dev_throttle_gossip, dev_limit_connections_inflight;
char *errstr;

/* Fields which require allocation are allocated off daemon */
Expand All @@ -1631,7 +1631,8 @@ static void connect_init(struct daemon *daemon, const u8 *msg)
&daemon->dev_handshake_no_reply,
&dev_throttle_gossip,
&daemon->dev_no_reconnect,
&daemon->dev_fast_reconnect)) {
&daemon->dev_fast_reconnect,
&dev_limit_connections_inflight)) {
/* 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);
Expand Down Expand Up @@ -1702,6 +1703,9 @@ static void connect_init(struct daemon *daemon, const u8 *msg)
if (dev_throttle_gossip)
daemon->gossip_stream_limit = 500;

if (dev_limit_connections_inflight)
daemon->max_connect_in_flight = 1;

/* Does nothing (no peers yet!) but arms timer */
release_one_connection_from_timer(daemon);
}
Expand Down Expand Up @@ -1912,7 +1916,8 @@ static void try_connect_peer(struct daemon *daemon,

/* We wait for another to be destroyed if too many are in
* progress (useful for startup of large nodes) */
connect->waiting = (connecting_htable_count(daemon->connecting) > 10);
connect->waiting = (connecting_htable_count(daemon->connecting)
> daemon->max_connect_in_flight);
if (connect->waiting)
status_peer_debug(id, "Too many connections, waiting...");
else
Expand Down Expand Up @@ -2516,6 +2521,7 @@ int main(int argc, char *argv[])
timers_init(&daemon->timers, time_mono());
daemon->gossmap_raw = NULL;
daemon->shutting_down = false;
daemon->max_connect_in_flight = 10;
daemon->dev_suppress_gossip = false;
daemon->custom_msgs = NULL;
daemon->dev_exhausted_fds = false;
Expand Down
4 changes: 4 additions & 0 deletions connectd/connectd.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ struct daemon {
/* Timer which releases one pending connection per second. */
struct oneshot *connect_release_timer;

/* How many connection attempts do we allow at once
* (--dev-limit-connectsion-inflight sets this to 1 for testing). */
size_t max_connect_in_flight;

/* Hack to speed up gossip timer */
bool dev_fast_gossip;
/* Hack to avoid ping timeouts */
Expand Down
1 change: 1 addition & 0 deletions connectd/connectd_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ msgdata,connectd_init,dev_noreply,bool,
msgdata,connectd_init,dev_throttle_gossip,bool,
msgdata,connectd_init,dev_no_reconnect,bool,
msgdata,connectd_init,dev_fast_reconnect,bool,
msgdata,connectd_init,dev_limit_connections_inflight,bool,

# Connectd->master, here are the addresses I bound, can announce.
msgtype,connectd_init_reply,2100
Expand Down
3 changes: 2 additions & 1 deletion lightningd/connect_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,8 @@ int connectd_init(struct lightningd *ld)
ld->dev_handshake_no_reply,
ld->dev_throttle_gossip,
!ld->reconnect,
ld->dev_fast_reconnect);
ld->dev_fast_reconnect,
ld->dev_limit_connections_inflight);

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 @@ -151,6 +151,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->dev_hsmd_no_preapprove_check = false;
ld->dev_hsmd_fail_preapprove = false;
ld->dev_handshake_no_reply = false;
ld->dev_limit_connections_inflight = false;

/*~ We try to ensure enough fds for twice the number of channels
* we start with. We have a developer option to change that factor
Expand Down
3 changes: 3 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,9 @@ struct lightningd {
/* Tell connectd not to talk after handshake */
bool dev_handshake_no_reply;

/* Tell connectd to block more than 1 simultanous connection attempt */
bool dev_limit_connections_inflight;

/* tor support */
struct wireaddr *proxyaddr;
bool always_use_proxy;
Expand Down
4 changes: 4 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,10 @@ static void dev_register_opts(struct lightningd *ld)
opt_set_bool,
&ld->dev_throttle_gossip,
"Throttle gossip right down, for testing");
clnopt_noarg("--dev-limit-connections-inflight", OPT_DEV,
opt_set_bool,
&ld->dev_limit_connections_inflight,
"Throttle connection limiting down for testing.");
/* This is handled directly in daemon_developer_mode(), so we ignore it here */
clnopt_noarg("--dev-debug-self", OPT_DEV,
opt_ignore,
Expand Down
35 changes: 35 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4655,3 +4655,38 @@ def test_connect_transient_pending(node_factory, bitcoind, executor):
fut1.result(TIMEOUT)
else:
fut2.result(TIMEOUT)


def test_connect_ratelimit(node_factory, bitcoind):
"""l1 has 5 peers, restarts, make sure we limit"""
nodes = node_factory.get_nodes(6,
opts=[{'dev-limit-connections-inflight': None, 'may_reconnect': True}] + [{'may_reconnect': True}] * 5)

l1 = nodes[0]
nodes = nodes[1:]

addr = l1.rpc.newaddr()['bech32']
for n in nodes:
bitcoind.rpc.sendtoaddress(addr, (FUNDAMOUNT + 1000000) / 10**8)
bitcoind.generate_block(1, wait_for_mempool=len(nodes))
sync_blockheight(bitcoind, [l1])

for n in nodes:
l1.rpc.connect(n.info['id'], 'localhost', n.port)
l1.rpc.fundchannel(n.info['id'], FUNDAMOUNT)

# Make sure all channels are established and announced.
bitcoind.generate_block(6, wait_for_mempool=len(nodes))
wait_for(lambda: len(l1.rpc.listchannels()['channels']) == len(nodes) * 2)

assert not l1.daemon.is_in_log('Unblocking for')

l1.restart()

# The first will be ok, but others should block and be unblocked.
l1.daemon.wait_for_logs((['Unblocking for ']
+ ['Too many connections, waiting'])
* (len(nodes) - 1))

# And now they're all connected
wait_for(lambda: [p['connected'] for p in l1.rpc.listpeers()['peers']] == [True] * len(nodes))

0 comments on commit 153bbe1

Please sign in to comment.