Fix hang on scoped IPv6 (#184) and non-IP (#179) nameservers - #221
Fix hang on scoped IPv6 (#184) and non-IP (#179) nameservers#221mbroadhead wants to merge 1 commit into
Conversation
…alitz#179) Config.resolve_server could not interpret an IPv6 link-local nameserver with an interface/scope specifier such as "fe80::1%en0" (alexdalitz#184): both IPv4.create and IPv6.create raise on the "%zone" suffix, so it fell through to hostname resolution. That path builds a Resolver/Recursor, which re-parses /etc/resolv.conf, which contains the same unparseable nameserver, calling resolve_server again — an infinite loop (alexdalitz#179). The same loop occurs for any non-IP nameserver in resolv.conf (e.g. a malformed "8.8.8.8," entry). Fixes: - resolve_server accepts a scoped IPv6 literal verbatim instead of resolving it as a hostname. - parse_resolv_conf keeps only IP-literal nameservers (IPv4, IPv6, or scoped IPv6) and warns on/skips anything else, so a non-IP entry can never enter the recursing resolve path. resolv.conf nameservers must be IP literals by definition. - check_ns accepts an explicitly-configured scoped IPv6 nameserver rather than misreading it as a domain name. - PacketSender#check_ipv6 classifies a scoped IPv6 server as IPv6 so it is sent over an IPv6 socket rather than IPv4. Adds offline regression tests (test/tc_res_config_nameserver.rb) covering the scoped-IPv6 predicates, resolve_server, resolv.conf filtering (including the exact alexdalitz#184 and alexdalitz#179 inputs), and the IPv6 socket classification.
|
Thanks for the patch! |
|
Hi @alexdalitz, |
|
Hey thanks for your patch, and the work you've done on it. It's always good to fix issues and make things better - I'm sorry if I came across a little grumpy! |
|
Hi @alexdalitz, |
Fixes #184 and #179, which turn out to be the same failure.
The bug
Config.resolve_servercan't interpret an IPv6 link-local nameserver with an interface/scope specifier such asfe80::1%en0(#184): bothIPv4.createandIPv6.createraise on the%zonesuffix, so it falls through to the "resolve as a hostname" branch. That branch builds aResolver/Recursor, which re-parses/etc/resolv.conf, which contains the same unparseable nameserver, which callsresolve_serveragain — an infinite loop (#179).The same loop happens for any non-IP
nameserverentry inresolv.conf, including the malformed8.8.8.8,from #179's original report.Reproduction (macOS on a hotspot hands out a scoped link-local resolver, so
/etc/resolv.conflooks like the #184 report):The fix
resolve_serveraccepts a scoped IPv6 literal verbatim instead of resolving it as a hostname.parse_resolv_confkeeps only IP-literal nameservers (IPv4, IPv6, or scoped IPv6) and warns-and-skips anything else, so a non-IP entry can never enter the recursing resolve path.resolv.confnameservers are IP literals by definition, so this also fixes the malformed-entry case in never-ending loop when trying to resolve a non-ip nameserver in /etc/resolv.conf #179.check_nsaccepts an explicitly-configured scoped IPv6 nameserver rather than misreading it as a domainName.PacketSender#check_ipv6classifies a scoped IPv6 server as IPv6, so packets go over an IPv6 socket rather than IPv4.Two small predicates (
Config.scoped_ipv6?,Config.ip_nameserver?) centralise the literal validation.Tests
test/tc_res_config_nameserver.rb(added to the offline suite) covers the predicates,resolve_serveron a scoped literal,resolv.conffiltering with the exact #184 and #179 inputs, and the IPv6 socket classification. The full offline suite is green (141 runs, 1419 assertions, 0 failures), including the existing bad-nameserver tests — the legitimate user-supplied-hostname path still works and no longer loops.Caveat
I verified there's no hang, that parsing/filtering is correct, and that a scoped server is classified as IPv6. I could not test actual packet delivery to a live link-local DNS server (none reachable from my environment); that path relies on the OS resolving the
%zoneviagetaddrinfo, which is the standard behaviour. Happy to adjust anything.This patch was generated by Claude Code (Opus 4.8).