From 83675fe607164eab6f9cb16825cd0aeed6d737ba Mon Sep 17 00:00:00 2001 From: wweir Date: Tue, 11 Aug 2026 21:06:18 +0800 Subject: [PATCH] fix(router): reject multi-char ip6.arpa nibbles in reverse-name parse A label like "ff" slipped through ParseUint and silently truncated to a single nibble. Each ip6.arpa label must be exactly one hex digit. Also cover IPv6 upstreams in the internal-upstream tests and add direct isInternalHost cases. --- router/dns.go | 3 +++ router/dns_reverse_test.go | 31 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/router/dns.go b/router/dns.go index f529809..9bc2d4c 100644 --- a/router/dns.go +++ b/router/dns.go @@ -175,6 +175,9 @@ func parseReverseName(domain string) (net.IP, bool) { } ip := make(net.IP, net.IPv6len) for i, h := range nibbles { + if len(h) != 1 { + return nil, false // each label is exactly one hex nibble + } v, err := strconv.ParseUint(h, 16, 8) if err != nil { return nil, false diff --git a/router/dns_reverse_test.go b/router/dns_reverse_test.go index 348e8b4..6c883b0 100644 --- a/router/dns_reverse_test.go +++ b/router/dns_reverse_test.go @@ -38,8 +38,9 @@ func TestParseReverseName(t *testing.T) { "256.1.2.3.in-addr.arpa.", // octet out of range "1.2.3.in-addr.arpa.", // too few octets "1.2.3.4.5.in-addr.arpa.", // too many octets - "g.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", // invalid hex nibble - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", // 31 nibbles + "g.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", // invalid hex nibble + "ff.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", // multi-char label + "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", // 31 nibbles } { if ip, ok := parseReverseName(name); ok { t.Fatalf("expected %q to be rejected, got %v", name, ip) @@ -95,6 +96,11 @@ func TestDNSUpstreamIsInternal(t *testing.T) { t.Fatal("internal upstream not reported") } + r.dns.upstreamAddrs = []string{"[::1]:53"} + if !r.dnsUpstreamIsInternal() { + t.Fatal("ipv6 loopback upstream not reported") + } + r.dns.upstreamAddrs = nil r.dns.upstreamDNS = "192.168.1.1" r.dns.fallbackDNS = "223.5.5.5" @@ -109,6 +115,27 @@ func TestDNSUpstreamIsInternal(t *testing.T) { } } +func TestIsInternalHost(t *testing.T) { + t.Parallel() + + internal := []string{ + "8.8.8.8:53", + "192.168.1.1", "192.168.1.1:53", + "[fd00::1]:53", "[::1]", "[fe80::1]:53", + } + for _, hostport := range internal[1:] { + if !isInternalHost(hostport) { + t.Errorf("expected %q to be internal", hostport) + } + } + if isInternalHost(internal[0]) { + t.Error("expected 8.8.8.8:53 to be public") + } + if isInternalHost("") { + t.Error("expected empty host to be public") + } +} + func TestServeDNSInternalReverseLookup(t *testing.T) { t.Parallel()