fix(proto): tolerate undecodable peer datagrams instead of disconnecting

The DTLS recv loops in src/listen.rs and src/connect.rs each read
one full datagram per call. A failed `try_into::<ProtoEvent>()`
means the datagram's leading EventType byte didn't match any
known variant — a misalignment is impossible because DTLS is
message-framed, not stream-framed.

Previously, src/listen.rs would `break` out of the loop on parse
failure (tearing down the connection) and src/connect.rs would
silently swallow the error with no log. Both are wrong as
forward-compat behavior: any future protocol addition (e.g. a
new event variant) would force every existing peer to disconnect
rather than gracefully ignoring the unknown event.

Skip-and-continue on both sides, with a debug-level log so the
behavior is observable. Pre-requisite for any future ProtoEvent
variant to land without forcing a coordinated upgrade across
every peer in a deployment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Kinney
2026-04-29 13:21:42 -05:00
committed by Ferdinand Schober
parent dbeaea03ad
commit 82766cdc87
2 changed files with 25 additions and 10 deletions

View File

@@ -255,16 +255,23 @@ async fn receive_loop(
) {
let mut buf = [0u8; MAX_EVENT_SIZE];
while conn.recv(&mut buf).await.is_ok() {
if let Ok(event) = buf.try_into() {
log::trace!("{addr} <==<==<== {event}");
match event {
ProtoEvent::Pong(b) => {
client_manager.set_active_addr(handle, Some(addr));
client_manager.set_alive(handle, b);
ping_response.borrow_mut().insert(addr);
match buf.try_into() {
Ok(event) => {
log::trace!("{addr} <==<==<== {event}");
match event {
ProtoEvent::Pong(b) => {
client_manager.set_active_addr(handle, Some(addr));
client_manager.set_alive(handle, b);
ping_response.borrow_mut().insert(addr);
}
event => tx.send((handle, event)).expect("channel closed"),
}
event => tx.send((handle, event)).expect("channel closed"),
}
// Skip undecodable datagrams without dropping the
// connection. Each DTLS recv is one framed message, so
// skipping is safe and keeps us forward-compatible with
// peers that send event types we don't yet know about.
Err(e) => log::debug!("ignoring undecodable event from {addr}: {e}"),
}
}
log::warn!("recv error");

View File

@@ -259,8 +259,16 @@ async fn read_loop(
.send(ListenEvent::Msg { event, addr })
.expect("channel closed"),
Err(e) => {
log::warn!("error receiving event: {e}");
break;
// Skip the malformed/unknown datagram and keep
// listening. Each DTLS recv returns one full
// datagram, so a parse error here can't desync a
// stream; the next call gets a fresh, framed
// message. This makes the protocol forward-
// compatible: a peer running a newer Lan Mouse
// version can introduce additional event types
// and old peers will simply ignore them rather
// than dropping the connection.
log::debug!("ignoring undecodable event from {addr}: {e}");
}
}
}