Skip to content

Name a COMPRESS mismatch instead of reporting an opaque decode failure - #10

Merged
andylemin merged 3 commits into
masterfrom
server-rust
Sep 3, 2026
Merged

Name a COMPRESS mismatch instead of reporting an opaque decode failure#10
andylemin merged 3 commits into
masterfrom
server-rust

Conversation

@andylemin

Copy link
Copy Markdown
Owner

A COMPRESS mismatch between a resolver and a firewall reported only
Failed to decode, the same answer as a corrupt payload or malformed JSON, with
nothing to say which of the three it was or which end was wrong. Compression is
configuration at both ends and is deliberately not signalled on the wire, so
that is the only way a mismatch can ever surface.

The daemon names the direction

The lz4 frame magic settles it in four bytes. Both directions, each proven
against a running daemon:

Daemon Sender Logged
COMPRESS: True plain JSON payload is not an lz4 frame but COMPRESS is on: the sender is not compressing. COMPRESS must match at both ends
COMPRESS: False lz4 payload is an lz4 frame but COMPRESS is off: the sender is compressing. COMPRESS must match at both ends

The reply stays Failed to decode, because PROTOCOL.md's reply set is fixed and
a test pins it. A frame whose magic is intact but whose body is damaged is still
reported as corruption, so the new message cannot mislead the other way; that
has its own test.

The resolver names it too

An operator reads the resolver's log first, and the resolver knows what it sent,
which fixes the direction without seeing the firewall at all:

did not confirm the update: b'Failed to decode' (this resolver sent
uncompressed data (COMPRESS: False), so the firewall is set the other way;
they must match)

Every refusal now carries what it usually means — framing skew for Bad length,
release skew for Missing kind, no routable address for No records. A test
asserts each hint corresponds to a reply string PROTOCOL.md documents, so advice
cannot outlive the refusal it explains.

A transport error is no longer called a decode failure

Any read error that was not a timeout was answered Failed to decode, so a
broken connection looked to the sender like a malformed message — and would have
muddied exactly this diagnosis. The protocol has no reply for a transport fault
and the connection has already broken, so it is logged as itself and closed.

Known limit

Over UDP a refused datagram gets no reply, by design, so a mismatch there is
named only in the firewall's log; the resolver sees the circuit breaker open.
Answering an unverified sender would make the daemon a reflector.

Testing

  • 126 Rust tests, 238 Python tests, cargo fmt --check and clippy -D warnings clean
  • Both mismatch directions and a matching control exercised against a running
    release daemon, with the messages above captured from its log

Andy Lemin added 3 commits September 3, 2026 23:15
Any read error that was not a timeout was answered "Failed to decode", so a
broken connection looked to the sender like a malformed message. The protocol
has no reply for a transport fault and the connection has already broken, so it
is logged as itself and closed.
Compression is configuration at both ends and is deliberately not signalled on
the wire, so a mismatch could only ever appear as "Failed to decode" — the same
answer as a corrupt payload or malformed JSON, with nothing to say which.

The lz4 frame magic settles it in four bytes, so the daemon now says which end
is wrong:

    payload is not an lz4 frame but COMPRESS is on: the sender is not
    compressing. COMPRESS must match at both ends

The reply stays "Failed to decode", since the protocol's reply set is fixed. A
frame whose magic is intact but whose body is damaged is still reported as
corruption, not as a configuration error.

The resolver reads these refusals too, and is where an operator sees them
first, so it now logs what each one usually means. A test pins those hints to
the reply strings PROTOCOL.md documents, so advice cannot outlive its refusal.
The firewall names the direction in its own log, but an operator reads the
resolver's first. The resolver knows what it sent, which settles the direction
from this end alone:

    did not confirm the update: b'Failed to decode' (this resolver sent
    uncompressed data (COMPRESS: False), so the firewall is set the other way;
    they must match)
@andylemin
andylemin merged commit a05101d into master Sep 3, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant