Skip to content

feat: add bounded DNS-over-TCP fallback - #4

Open
Joshua Tenner (jtenner) wants to merge 5 commits into
agent/tls-full-gridfrom
agent/dns-tcp-fallback
Open

Joshua Tenner (jtenner) wants to merge 5 commits into
agent/tls-full-gridfrom
agent/dns-tcp-fallback

Conversation

@jtenner

@jtenner jtenner commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator

Stack status

Summary

Adds opt-in bounded DNS-over-TCP fallback without changing the six-function wago_net_dns ABI or exposing raw TCP authority to guests.

  • adds dns.EnableTCPFallback(maxResponseBytes, maxServiceAttempts)
  • keeps UDP-only behavior as the default; truncation remains TEMPORARY_FAILURE unless the host opts in
  • quota-reserves the maximum fallback response at query creation but allocates its byte slice only after a correlated truncation, then zeroes and drops it at the terminal transition rather than retaining it until guest close
  • retains only UDP-sized parser scratch in the namespace; larger TCP responses use temporary candidate/name scratch bounded by actual frame bytes and declared answer count
  • detaches quota accounting, the DNS packet, and the eight-record inline result slot from each query object and reuses one cleared/reset slot of each safely, reducing steady query allocation from 1,792 to 288 bytes without stale-handle aliasing
  • caps idle query storage to one accounting slot, one packet slot, one inline record slot, and one overflow slot of at most 256 records; larger overflow buffers are dropped at close instead of becoming an uncharged high-water pool
  • after a valid source/port/checksum/transaction/question-correlated truncated UDP response, retires the UDP source-port lease and starts exactly one private TCP connection to the same configured resolver
  • sends and receives DNS's two-byte TCP length framing through bounded partial I/O
  • retains the existing query handle, poll, cancellation, record iteration, and close ABI
  • enforces exact TCP response-byte, service-attempt, aggregate-retention, port, TCP-buffer, resource, and quota bounds
  • applies raw-TCP deny rules to the private resolver connection without requiring or exposing a raw-TCP allow grant
  • closes the private stream and clears response storage on success, cancellation, timeout, oversized or malformed framing, premature EOF, parser failure, query close, and namespace teardown
  • updates aggregate compatibility TCP-port capacity with checked arithmetic
  • preserves selective composition: DNS-only graphs include the internal lneto TCP adapter, but still exclude the public TCP facade, tcp/register, binding, instance operations, ABI, namespace facet, net.tcp, and wago_net_tcp

Validation

Passed locally and in hosted run 30186935628 on the current head. Run 30184217704 passed the original functional head:

  • go test ./...
  • go test -shuffle=on -count=1 ./...
  • focused race tests for DNS, TCP, aggregate lneto composition, policy, and public DNS registration
  • go vet ./...
  • source-boundary and diff checks
  • TinyGo tests for internal/backend/lneto/dns, public dns, and the root selective-composition matrix; current hosted CI completed all 123 supported packages in 48m04s
  • exact dependency-boundary tests
  • live two-core lneto integration proving UDP truncation followed by a real TCP handshake and length-prefixed A/AAAA/CNAME response
  • hosted test/shuffle/vet/source-boundary, race, standard-Go TLS signoff, and checkptr/386 jobs on the current optimized head
  • allocation benchmarks showing non-truncated fallback-enabled queries no longer allocate the configured 16 KiB response buffer (18,176 B/op, 2 allocations before; 1,792 B/op, 1 allocation after in the documented benchmark configuration)
  • a 65,535-byte TCP response limit with a 512-byte UDP limit now avoids about 554 KiB of always-retained parser scratch per namespace
  • steady resolve/close benchmarks retain one allocation while reducing GC-visible bytes by 83.9%, from 1,792 to 288 B/op, and controlled latency by about 22.1%, from roughly 614 ns to 478 ns
  • deny, cancellation, service-timeout, oversized-length, premature-EOF, aggregate-capacity, cleanup, and unresolved-public-TCP-import tests

Explicitly incomplete

This does not add DNS-over-IPv6, DNS-over-TLS, DNS-over-HTTPS, DNSSEC validation, TCP connection pooling/reuse, raw TCP guest handles, or a portable TinyGo TLS implementation. One private TCP stream is used per fallback query and is closed deterministically.

2026-11-02 restack

Rebased onto PR #3 head 8b9c9445b08eb510788199d40285f9c971014505; current head is ef37e843c8af97d7fc160136000eaf403609dddc. DNS-only composition uses the current Wago plugin loader while preserving private TCP fallback without exposing public TCP imports. Full ordinary, shuffle, vet, source-boundary, race, checkptr, backend-neutral 386, wagodebug, wago_regalloccheck, three-mode benchmark-compilation, and TLS signoff gates passed locally. The hosted plaintext failure was reproduced as a scheduler-sensitive test-harness condition inherited from PR #2; after the bounded-wait correction, the exact test passed 500 checkptr and 200 race repetitions on this head. July benchmark results remain historical; no benchmark timing was executed during this restack. Rewritten commits are unsigned because no desktop signing agent was available.

@jtenner
Joshua Tenner (jtenner) marked this pull request as ready for review November 7, 2026 16:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T16:57:07.947559Z ef37e84 Draft marked ready
🔒 Security Review ✅ Completed 2026-11-07T17:02:15.393278Z ef37e84 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef37e843c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +886 to +887
if errors.Is(err, lneto.ErrTruncatedFrame) && tcpFallbackEnabled(n.config) {
query.beginTCPFallbackLocked()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish the TC flag from malformed-packet truncation errors

When TCP fallback is enabled, a UDP response with an incomplete question or resource record can start a private TCP connection even when its TC bit is clear. parseDNSResponseInto also returns ErrTruncatedFrame for those decoding failures; notably, validateDNSQuestions can return it before validating the question name/type/class. Consequently, this condition bypasses the documented requirement for a question-correlated response with TC set, retires the UDP lease, and consumes TCP resources for malformed input. Return a distinct fallback indication only after question validation and an explicit TC check, rather than treating every parser truncation error as permission to connect.

Useful? React with 👍 / 👎.

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