Skip to content

feat(resolver): support udp:///tcp:///tls:// schemes in RESOLVER_ADDRESS - #1212

Open
yangyaofei wants to merge 1 commit into
qdm12:masterfrom
yangyaofei:fix/resolver-scheme-support
Open

feat(resolver): support udp:///tcp:///tls:// schemes in RESOLVER_ADDRESS#1212
yangyaofei wants to merge 1 commit into
qdm12:masterfrom
yangyaofei:fix/resolver-scheme-support

Conversation

@yangyaofei

Copy link
Copy Markdown

Fixes #1211

Root cause

RESOLVER_ADDRESS values carrying a scheme prefix (tls://223.5.5.5 in my case, to dodge fake-IP DNS hijacking on my LAN) were silently mis-parsed:

  • Settings.validate() ran net.SplitHostPort() before any scheme handling — and Go splits at the last colon, so tls://223.5.5.5 "validated" as host=tls, port=//223.5.5.5
  • New() then installed a custom Dial closure hardcoding protocol = "udp" and dialing the raw string including the scheme → every lookup failed with lookup udp///223.5.5.5: unknown port
  • consequence A: the built-in healthcheck reported permanently unhealthy (its IP-comparison lookup always errored)
  • consequence B: shouldUpdateRecordWithLookup treats lookup failure as "update anyway" → blind unconditional record writes every period

Changes

Transport Syntax Default port when omitted
plaintext UDP (unchanged default) 1.1.1.1, udp://… 53
DNS-over-TCP tcp://… 53
DNS-over-TLS (RFC 7858) tls://… 853
  • optional scheme prefixes parsed in one place (splitTransport); unknown schemes now fail fast with an actionable error instead of garbage dial targets
  • transport-aware default ports applied after parsing
  • DoT path: TCP dial + tls.Client with ServerName from the address host; handshake honors the resolver context deadline; a tls.Conn satisfies net.Conn so it plugs straight into net.Resolver.Dial (PreferGo already set)

Testing

  • new unit tests: scheme splitting, default-port application (incl. IPv6 bracketing), validation accept/reject matrix, empty-host rejection
  • exercised the patch in a scratch module against a live DoT endpoint:
address := "tls://223.5.5.5"
r.LookupIP(ctx, "ip4", "<my-ddns-host>")
→ resolved via DoT: [203.0.113.7]   // real public A record, no more hijacked answer

With this patch both consequences above disappear: lookups succeed behind DNS-hijacking middleboxes, so the update-loop comparison is meaningful again and the built-in healthcheck goes green.

Scheme-prefixed addresses were silently mis-parsed: validation passed
(net.SplitHostPort splits at the last colon, so "tls://223.5.5.5"
yielded host=tls, port=//223.5.5.5) while every lookup failed at dial
time with a cryptic "unknown port" error, leaving the built-in
healthcheck permanently unhealthy and making record updates blind
writes.

- parse optional scheme prefixes and select the transport accordingly;
  defaults to plaintext UDP when no scheme is present (unchanged)
- apply transport default ports when omitted: udp/tcp -> :53,
  tls -> :853
- DNS-over-TLS dials TCP then wraps the conn in tls.Client with
  ServerName set from the address host; the handshake honors the
  resolver context deadline. A tls.Conn satisfies net.Conn so it plugs
  directly into net.Resolver.Dial (PreferGo is already set)
- fail fast on unknown schemes (e.g. https://) instead of passing them
  through as garbage dial targets

Fixes qdm12#1211
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant