Skip to content

fix(listener): integrate SERVFAIL, query validation, and UDP truncation (#31 #32 #33) - #34

Merged
olicesx merged 5 commits into
mainfrom
merge/three-pr-fixes
Aug 8, 2026
Merged

fix(listener): integrate SERVFAIL, query validation, and UDP truncation (#31 #32 #33)#34
olicesx merged 5 commits into
mainfrom
merge/three-pr-fixes

Conversation

@olicesx

@olicesx olicesx commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Integrates three related listener-robustness PRs and their review fixes into a single coherent change set:

  • fix(dns): truncate oversized UDP responses #31 — UDP response truncation (RFC 6891 / RFC 1035 §4.2.1): respect the client's advertised EDNS payload size (512-byte classic limit without EDNS), truncate at DNS record boundaries, set the TC bit so the client can retry over TCP. Applied consistently to direct, cached, and asynchronous responses.
  • fix(proto): validate DNS queries before fast-path handling #32 — Request validation before fast paths: only standard single-question QUERY messages reach the cache/static fast paths. Validates the header, QNAME, compression pointers, every declared Additional record, and EDNS OPT records; rejects trailing data. Prevents response packets and malformed packets from being reflected as answers.
  • fix(server): return SERVFAIL instead of dropping valid queries #33 — SERVFAIL instead of silently dropping valid queries: listener processing errors, overall request timeouts, and flow-control permit exhaustion now return SERVFAIL for valid queries. Malformed packets and DNS response packets stay silent (no reflection). Shared wire-format SERVFAIL builder reused by UDP/TCP and DoH.

Review fixes applied on top

  • Truncated responses are trimmed to a record boundary: hickory's bounded encoder rolls back its write offset but not the backing buffer, so truncated output could carry partial bytes from the failed record past the corrected header counts. valid_message_len re-walks the emitted sections and trims trailing garbage. Verified red-green: without the trim, a >512B TXT response emitted 13 bytes of trailing data.
  • Overload path drops parse_quick-rejected packets silently (cheapest overload rejection; the header gate already rejected non-standard packets upstream).
  • TCP framing uses write_all_vectored (zero-allocation, complete-frame guarantee).
  • DRY: the strict SERVFAIL builder reuses is_standard_query_header so listener and fast-path validation cannot drift apart.
  • CI: binary listener tests (cargo test --bin kixdns) are now executed by the CI test job — they previously never ran.
  • Docs: inbound validation / SERVFAIL / truncation behavior documented in README and README.zh-CN.

Verification

  • cargo test --lib — 100 passed
  • cargo test --bin kixdns — 6 passed (real UDP/TCP/DoH integration, incl. overload + malformed silence)
  • cargo test --test doh_integration — 9 passed
  • cargo clippy --all-targets --all-features --locked -- -D warnings — clean
  • cargo fmt --all -- --check — clean

Closes #31, #32, #33.

JohnsonRan and others added 5 commits August 8, 2026 14:27
- #32: is_standard_query_header ingress guard (UDP/TCP) + parse_quick full
  Additional validation (compression pointers, OPT root/unique, EDNS options)
- #31: truncate_udp_response at record boundaries with TC bit, integrated
  into send_udp_response/try_send_udp_response (all 5 UDP send paths)
- #33: strict try_build SERVFAIL for UDP/TCP + DoH reuse, write_all TCP framing
- fix: permit-exhaustion path uses non-blocking send (backpressure drop) with
  fast SERVFAIL from pre-parsed data, never blocks the receive loop
- tests: 99 lib + 6 bin + 9 doh integration, clippy -D warnings, fmt clean
- overload: drop packets that failed parse_quick instead of full re-parse
- TCP: use write_all_vectored for zero-alloc complete frame writes
- DRY: reuse is_standard_query_header in the strict SERVFAIL builder
- document fast-path Additional validation trade-off
- inline the listener_servfail one-line wrapper
… 4.2.1)

hickory's bounded encoder rolls back its write offset but not the backing
buffer when a record does not fit, so truncated output could carry partial
bytes from the failed record past the (corrected) header counts. Re-walk
the emitted sections with valid_message_len and trim to the last complete
record boundary. Verified red-green: without the trim, a >512B TXT response
emitted 13 bytes of trailing garbage.

test(listener): cover overload drop of parse_quick-rejected packets
- trailing-data packet passes the header gate but fails parse_quick, so it
  reaches the overload branch and must stay silent there
ci: run binary listener tests in CI test job (were never executed)
docs: document inbound validation/SERVFAIL/truncation behavior
@olicesx
olicesx merged commit a46e0e6 into main Aug 8, 2026
10 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.

2 participants