connectd: gate uniform message padding behind --dev-uniform-padding
What changed, and why it matters
This change makes a message-padding feature optional instead of always-on. The always-on padding was causing Core Lightning nodes to be disconnected by LND-based peers because it sent oversized ping messages. The fix adds a developer flag so operators can turn padding on only when all their peers support it. It is a compatibility/operational fix, not a security patch for an exploitable vulnerability.
No security response required. Node operators experiencing LND disconnects should upgrade to a build containing this commit. Operators wanting the traffic-analysis defense and peering only with CLN can enable --dev-uniform-padding.
Security signals we found
Traffic-analysis defense made opt-in rather than default
Interoperability fix for protocol non-compliance by LND
No memory safety, authentication, or authorization changes
Evidence from the diff
The commit gates uniform message padding (fixed 1460-byte chunks) behind a new –dev-uniform-padding option. Previously, connectd padded every encrypted outgoing message to UNIFORM_MESSAGE_SIZE, which produced a ping(num_pong_bytes=65535) that LND rejects with ‘pong bytes exceeded’ instead of ignoring per BOLT #1. The patch changes write_encrypted_to_peer to write either a full padded chunk or the actual available bytes, and only calls pad_encrypted_queue when dev_uniform_padding is enabled. This restores interoperability with LND while preserving the traffic-analysis defense for CLN-only peerings.
Changed components
connectd/multiplex.cuniform message padding / encrypted peer output queueInspect captured patch +21 / −4
diff --git a/connectd/multiplex.c b/connectd/multiplex.c
index 0c5b0e4f..57a74be4 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);
}
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.