Skip to content

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

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

feat: add bounded DNS-over-TCP fallback#4
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.

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