diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 06a2550d2..792a2e522 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -17,6 +17,7 @@ import ( "os" "runtime" "sort" + "strconv" "strings" "sync" "sync/atomic" @@ -633,6 +634,10 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netaddr.IP, return tsaddr.TailscaleServiceIPv6(), dns.RCodeSuccess } } + // Special-case: 'via-.' queries. + if ip, ok := r.parseViaDomain(domain, typ); ok { + return ip, dns.RCodeSuccess + } r.mu.Lock() hosts := r.hostToIP @@ -708,6 +713,46 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netaddr.IP, } } +// parseViaDomain synthesizes an IP address for quad-A DNS requests of +// the form 'via-.', where X is a decimal, or hex-encoded +// number with a '0x' prefix. +// +// This exists as a convenient mapping into Tailscales 'Via Range'. +func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netaddr.IP, bool) { + fqdn := string(domain.WithoutTrailingDot()) + if typ != dns.TypeAAAA { + return netaddr.IP{}, false + } + if len(fqdn) < len("via-X.0.0.0.0") { + return netaddr.IP{}, false // too short to be valid + } + if !strings.HasPrefix(fqdn, "via-") { + return netaddr.IP{}, false + } + + firstDot := strings.Index(fqdn, ".") + if firstDot < 0 { + return netaddr.IP{}, false // missing dot delimiters + } + + siteID := fqdn[len("via-"):firstDot] + ip4Str := fqdn[firstDot+1:] + + ip4, err := netaddr.ParseIP(ip4Str) + if err != nil { + return netaddr.IP{}, false // badly formed, dont respond + } + + prefix, err := strconv.ParseUint(siteID, 0, 32) + if err != nil { + return netaddr.IP{}, false // badly formed, dont respond + } + + // MapVia will never error when given an ipv4 netaddr.IPPrefix. + out, _ := tsaddr.MapVia(uint32(prefix), netaddr.IPPrefixFrom(ip4, ip4.BitLen())) + return out.IP(), true +} + // resolveReverse returns the unique domain name that maps to the given address. func (r *Resolver) resolveLocalReverse(name dnsname.FQDN) (dnsname.FQDN, dns.RCode) { var ip netaddr.IP diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index d304d5c97..cc1931da1 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -348,6 +348,9 @@ func TestResolveLocal(t *testing.T) { {"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netaddr.IP{}, dns.RCodeNameError}, {"onion-domain", "footest.onion.", dns.TypeA, netaddr.IP{}, dns.RCodeNameError}, {"magicdns", dnsSymbolicFQDN, dns.TypeA, netaddr.MustParseIP("100.100.100.100"), dns.RCodeSuccess}, + {"via_hex", dnsname.FQDN("via-0xff.1.2.3.4."), dns.TypeAAAA, netaddr.MustParseIP("fd7a:115c:a1e0:b1a:0:ff:102:304"), dns.RCodeSuccess}, + {"via_dec", dnsname.FQDN("via-1.10.0.0.1."), dns.TypeAAAA, netaddr.MustParseIP("fd7a:115c:a1e0:b1a:0:1:a00:1"), dns.RCodeSuccess}, + {"via_invalid", dnsname.FQDN("via-."), dns.TypeA, netaddr.IP{}, dns.RCodeRefused}, } for _, tt := range tests {