diff --git a/connectd/connectd.c b/connectd/connectd.c index 14dbb36bdba6..eb488ec2d727 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -1689,8 +1689,9 @@ static void connect_init(struct daemon *daemon, const u8 *msg) &dev_throttle_gossip, &daemon->dev_no_reconnect, &daemon->dev_fast_reconnect, - &dev_limit_connections_inflight, - &daemon->dev_keep_nagle)) { + &dev_limit_connections_inflight, + &daemon->dev_keep_nagle, + &daemon->dev_uniform_padding)) { /* 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); @@ -2565,6 +2566,7 @@ int main(int argc, char *argv[]) daemon->dev_exhausted_fds = false; daemon->dev_lightningd_is_slow = false; daemon->dev_keep_nagle = false; + daemon->dev_uniform_padding = false; /* We generally allow 1MB per second per peer, except for dev testing */ daemon->gossip_stream_limit = 1000000; daemon->scid_htable = new_htable(daemon, scid_htable); diff --git a/connectd/connectd.h b/connectd/connectd.h index ea012f6a2824..7616a1852635 100644 --- a/connectd/connectd.h +++ b/connectd/connectd.h @@ -309,6 +309,9 @@ struct daemon { /* Allow localhost to be considered "public", only with --developer */ bool dev_allow_localhost; + /* Pad outgoing messages to uniform 1460-byte segments (traffic analysis defence) */ + bool dev_uniform_padding; + /* How much to gossip allow a peer every 60 seconds (bytes) */ size_t gossip_stream_limit; diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index d5f6aee33b01..700e68296e23 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -29,6 +29,8 @@ msgdata,connectd_init,dev_no_reconnect,bool, msgdata,connectd_init,dev_fast_reconnect,bool, msgdata,connectd_init,dev_limit_connections_inflight,bool, msgdata,connectd_init,dev_keep_nagle,bool, +# Pad outgoing messages to uniform 1460-byte segments (traffic analysis defence) +msgdata,connectd_init,dev_uniform_padding,bool, # Connectd->master, here are the addresses I bound, can announce. msgtype,connectd_init_reply,2100 diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 0c5b0e4f0222..57a74be4d37d 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -485,10 +485,16 @@ static bool have_empty_encrypted_queue(const struct peer *peer) /* (Continue) writing the encrypted_peer_out array */ static struct io_plan *write_encrypted_to_peer(struct peer *peer) { - assert(membuf_num_elems(&peer->encrypted_peer_out) >= UNIFORM_MESSAGE_SIZE); + size_t avail = membuf_num_elems(&peer->encrypted_peer_out); + /* With padding: always a full uniform-size chunk. + * Without: flush whatever we have (caller ensures non-zero). */ + size_t write_size = peer->daemon->dev_uniform_padding + ? UNIFORM_MESSAGE_SIZE : avail; + + assert(avail >= write_size && write_size > 0); return io_write_partial(peer->to_peer, membuf_elems(&peer->encrypted_peer_out), - UNIFORM_MESSAGE_SIZE, + write_size, &peer->encrypted_peer_out_sent, write_to_peer, peer); } @@ -1245,8 +1251,11 @@ static struct io_plan *write_to_peer(struct io_conn *peer_conn, /* Wait for them to wake us */ return msg_queue_wait(peer_conn, peer->peer_outq, write_to_peer, peer); } - /* OK, add padding. */ - pad_encrypted_queue(peer); + /* OK, add padding (only if --dev-uniform-padding enabled). */ + if (peer->daemon->dev_uniform_padding) + pad_encrypted_queue(peer); + else + break; } else { if (peer->draining_state == WRITING_TO_PEER) status_peer_debug(&peer->id, "draining, but sending %s.", @@ -1264,6 +1273,14 @@ static struct io_plan *write_to_peer(struct io_conn *peer_conn, } peer->nonurgent_flush_timer = tal_free(peer->nonurgent_flush_timer); + + /* With uniform padding the buffer is always a full UNIFORM_MESSAGE_SIZE. + * Without it, write whatever we have; if nothing, go back to waiting. */ + if (have_empty_encrypted_queue(peer)) { + io_wake(&peer->subds); + io_wake(&peer->peer_in); + return msg_queue_wait(peer_conn, peer->peer_outq, write_to_peer, peer); + } return write_encrypted_to_peer(peer); } diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index e95a04db9167..f13ac32b8075 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -726,8 +726,9 @@ int connectd_init(struct lightningd *ld) ld->dev_throttle_gossip, !ld->reconnect, ld->dev_fast_reconnect, - ld->dev_limit_connections_inflight, - ld->dev_keep_nagle); + ld->dev_limit_connections_inflight, + ld->dev_keep_nagle, + ld->dev_uniform_padding); subd_req(ld->connectd, ld->connectd, take(msg), -1, 0, connect_init_done, NULL); diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 0241b992da20..3a669e1b3102 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -150,6 +150,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->dev_strict_forwarding = false; ld->dev_limit_connections_inflight = false; ld->dev_keep_nagle = false; + ld->dev_uniform_padding = 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 diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 9edba38d09fa..3a00f9f3e4be 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -369,6 +369,9 @@ struct lightningd { /* Tell connectd we don't want TCP_NODELAY */ bool dev_keep_nagle; + /* Pad outgoing messages to uniform 1460-byte segments (traffic analysis defence) */ + bool dev_uniform_padding; + /* tor support */ struct wireaddr *proxyaddr; bool always_use_proxy; diff --git a/lightningd/options.c b/lightningd/options.c index d9ef8e445a0e..88148f3e6d12 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -952,6 +952,10 @@ static void dev_register_opts(struct lightningd *ld) opt_set_bool, &ld->dev_keep_nagle, "Tell connectd not to set TCP_NODELAY."); + clnopt_noarg("--dev-uniform-padding", OPT_DEV, + opt_set_bool, + &ld->dev_uniform_padding, + "Pad all outgoing peer messages to uniform 1460-byte segments"); /* This is handled directly in daemon_developer_mode(), so we ignore it here */ clnopt_noarg("--dev-debug-self", OPT_DEV, opt_ignore, diff --git a/tests/test_connection.py b/tests/test_connection.py index dece3e1a87d2..87ee2b66d8b0 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -4818,7 +4818,7 @@ def test_constant_packet_size(node_factory, tcp_capture): Test that TCP packets between nodes are constant size. This will be skipped unless you can run `dumpcap` (usually means you have to be in the `wireshark` group). """ - l1, l2, l3, l4 = node_factory.get_nodes(4) + l1, l2, l3, l4 = node_factory.get_nodes(4, opts={'dev-uniform-padding': None}) # Encrypted setup BOLT 8 has some short packets. l1.connect(l2)