Luc Ritchie 4 days ago committed by GitHub
commit 366ca1cc4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -39,6 +39,7 @@ import (
"tailscale.com/util/clientmetric"
"tailscale.com/util/cloudenv"
"tailscale.com/util/dnsname"
"tailscale.com/util/rands"
)
const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon."
@ -610,17 +611,17 @@ func stubResolverForOS() (ip netip.Addr, err error) {
return ip, nil
}
// resolveLocal returns an IP for the given domain, if domain is in
// the local hosts map and has an IP corresponding to the requested
// resolveLocal returns all IPs for the given domain, if domain is in
// the local hosts map and has IPs corresponding to the requested
// typ (A, AAAA, ALL).
// Returns dns.RCodeRefused to indicate that the local map is not
// authoritative for domain.
func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, dns.RCode) {
func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) ([]netip.Addr, dns.RCode) {
metricDNSResolveLocal.Add(1)
// Reject .onion domains per RFC 7686.
if dnsname.HasSuffix(domain.WithoutTrailingDot(), ".onion") {
metricDNSResolveLocalErrorOnion.Add(1)
return netip.Addr{}, dns.RCodeNameError
return nil, dns.RCodeNameError
}
// We return a symbolic domain if someone does a reverse lookup on the
@ -629,14 +630,16 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
if domain == dnsSymbolicFQDN {
switch typ {
case dns.TypeA:
return tsaddr.TailscaleServiceIP(), dns.RCodeSuccess
return []netip.Addr{tsaddr.TailscaleServiceIP()}, dns.RCodeSuccess
case dns.TypeAAAA:
return tsaddr.TailscaleServiceIPv6(), dns.RCodeSuccess
return []netip.Addr{tsaddr.TailscaleServiceIPv6()}, dns.RCodeSuccess
case dns.TypeALL:
return []netip.Addr{tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6()}, dns.RCodeSuccess
}
}
// Special-case: 4via6 DNS names.
if ip, ok := r.resolveViaDomain(domain, typ); ok {
return ip, dns.RCodeSuccess
if ips, ok := r.resolveViaDomain(domain, typ); ok {
return ips, dns.RCodeSuccess
}
r.mu.Lock()
@ -650,12 +653,12 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
if suffix.Contains(domain) {
// We are authoritative for the queried domain.
metricDNSResolveLocalErrorMissing.Add(1)
return netip.Addr{}, dns.RCodeNameError
return nil, dns.RCodeNameError
}
}
// Not authoritative, signal that forwarding is advisable.
metricDNSResolveLocalErrorRefused.Add(1)
return netip.Addr{}, dns.RCodeRefused
return nil, dns.RCodeRefused
}
// Refactoring note: this must happen after we check suffixes,
@ -664,42 +667,49 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
// DNS semantics subtlety: when a DNS name exists, but no records
// are available for the requested record type, we must return
// RCodeSuccess with no data, not NXDOMAIN.
var result []netip.Addr
switch typ {
case dns.TypeA:
for _, ip := range addrs {
if ip.Is4() {
metricDNSResolveLocalOKA.Add(1)
return ip, dns.RCodeSuccess
result = append(result, ip)
}
}
metricDNSResolveLocalNoA.Add(1)
return netip.Addr{}, dns.RCodeSuccess
if len(result) > 0 {
metricDNSResolveLocalOKA.Add(1)
} else {
metricDNSResolveLocalNoA.Add(1)
}
return result, dns.RCodeSuccess
case dns.TypeAAAA:
for _, ip := range addrs {
if ip.Is6() {
metricDNSResolveLocalOKAAAA.Add(1)
return ip, dns.RCodeSuccess
result = append(result, ip)
}
}
metricDNSResolveLocalNoAAAA.Add(1)
return netip.Addr{}, dns.RCodeSuccess
if len(result) > 0 {
metricDNSResolveLocalOKAAAA.Add(1)
} else {
metricDNSResolveLocalNoAAAA.Add(1)
}
return result, dns.RCodeSuccess
case dns.TypeALL:
// Answer with whatever we've got.
// It could be IPv4, IPv6, or a zero addr.
// TODO: Return all available resolutions (A and AAAA, if we have them).
if len(addrs) == 0 {
// Answer with all available resolutions (A and AAAA).
result = make([]netip.Addr, len(addrs))
copy(result, addrs)
if len(result) > 0 {
metricDNSResolveLocalOKAll.Add(1)
} else {
metricDNSResolveLocalNoAll.Add(1)
return netip.Addr{}, dns.RCodeSuccess
}
metricDNSResolveLocalOKAll.Add(1)
return addrs[0], dns.RCodeSuccess
return result, dns.RCodeSuccess
// Leave some record types explicitly unimplemented.
// These types relate to recursive resolution or special
// DNS semantics and might be implemented in the future.
case dns.TypeNS, dns.TypeSOA, dns.TypeAXFR, dns.TypeHINFO:
metricDNSResolveNotImplType.Add(1)
return netip.Addr{}, dns.RCodeNotImplemented
return nil, dns.RCodeNotImplemented
// For everything except for the few types above that are explicitly not implemented, return no records.
// This is what other DNS systems do: always return NOERROR
@ -710,7 +720,7 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
default:
metricDNSResolveNoRecordType.Add(1)
// The name exists, but no records exist of the requested type.
return netip.Addr{}, dns.RCodeSuccess
return nil, dns.RCodeSuccess
}
}
@ -722,7 +732,7 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
//
// This exists as a convenient mapping into Tailscales 'Via Range'.
//
// It returns a zero netip.Addr and true to indicate a successful response with
// It returns no IPs and true to indicate a successful response with
// an empty answers section if the specified domain is a valid Tailscale 4via6
// domain, but the request type is neither quad-A nor ALL.
//
@ -730,7 +740,7 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
// (2022-06-02) to work around an issue in Chrome where it would treat
// "http://via-1.1.2.3.4" as a search string instead of a URL. We should rip out
// the old format in early 2023.
func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr, bool) {
func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) ([]netip.Addr, bool) {
fqdn := string(domain.WithoutTrailingDot())
switch typ {
case dns.TypeA, dns.TypeAAAA, dns.TypeALL:
@ -743,10 +753,10 @@ func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Ad
// a zero (invalid) netip.Addr and true to indicate a successful empty response,
// or a zero netip.Addr and false to indicate that it is not a Tailscale 4via6 domain.
default:
return netip.Addr{}, false
return nil, false
}
if len(fqdn) < len("via-X.0.0.0.0") {
return netip.Addr{}, false // too short to be valid
return nil, false // too short to be valid
}
var siteID string
@ -757,29 +767,29 @@ func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Ad
// Third time's a charm. The earlier two formats follow after this block.
firstLabel, domain, _ := strings.Cut(fqdn, ".") // "192-168-1-2-via-7"
if !(domain == "" || dnsname.HasSuffix(domain, "ts.net") || dnsname.HasSuffix(domain, "tailscale.net")) {
return netip.Addr{}, false
return nil, false
}
v4hyphens, suffix, ok := strings.Cut(firstLabel, "-via-")
if !ok {
return netip.Addr{}, false
return nil, false
}
siteID = suffix
ip4Str = strings.ReplaceAll(v4hyphens, "-", ".")
case strings.HasPrefix(fqdn, "via-"):
firstDot := strings.Index(fqdn, ".")
if firstDot < 0 {
return netip.Addr{}, false // missing dot delimiters
return nil, false // missing dot delimiters
}
siteID = fqdn[len("via-"):firstDot]
ip4Str = fqdn[firstDot+1:]
default:
lastDot := strings.LastIndex(fqdn, ".")
if lastDot < 0 {
return netip.Addr{}, false // missing dot delimiters
return nil, false // missing dot delimiters
}
suffix := fqdn[lastDot+1:]
if !strings.HasPrefix(suffix, "via-") {
return netip.Addr{}, false
return nil, false
}
siteID = suffix[len("via-"):]
ip4Str = fqdn[:lastDot]
@ -787,21 +797,21 @@ func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Ad
ip4, err := netip.ParseAddr(ip4Str)
if err != nil {
return netip.Addr{}, false // badly formed, don't respond
return nil, false // badly formed, don't respond
}
prefix, err := strconv.ParseUint(siteID, 0, 32)
if err != nil {
return netip.Addr{}, false // badly formed, don't respond
return nil, false // badly formed, don't respond
}
if typ == dns.TypeA {
return netip.Addr{}, true // the name exists, but cannot be resolved to an IPv4 address
return nil, true // the name exists, but cannot be resolved to an IPv4 address
}
// MapVia will never error when given an IPv4 netip.Prefix.
out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen()))
return out.Addr(), true
return []netip.Addr{out.Addr()}, true
}
// resolveReverse returns the unique domain name that maps to the given address.
@ -1307,14 +1317,17 @@ func (r *Resolver) respond(query []byte) ([]byte, error) {
return r.respondReverse(query, name, parser.response())
}
ip, rcode := r.resolveLocal(name, parser.Question.Type)
ips, rcode := r.resolveLocal(name, parser.Question.Type)
if rcode == dns.RCodeRefused {
return nil, errNotOurName // sentinel error return value: it requests forwarding
}
if len(ips) > 1 {
rands.Shuffle(uint64(time.Now().UnixNano()), ips)
}
resp := parser.response()
resp.Header.RCode = rcode
resp.IP = ip
resp.IPs = ips
metricDNSMagicDNSSuccessName.Add(1)
return marshalResponse(resp)
}

@ -22,6 +22,7 @@ import (
"time"
miekdns "github.com/miekg/dns"
"github.com/stretchr/testify/assert"
dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/health"
"tailscale.com/net/netaddr"
@ -35,8 +36,12 @@ import (
)
var (
testipv4 = netip.MustParseAddr("1.2.3.4")
testipv6 = netip.MustParseAddr("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f")
testipv4 = netip.MustParseAddr("1.2.3.4")
testipv4Second = netip.MustParseAddr("5.6.7.8")
testipv4Third = netip.MustParseAddr("9.10.11.12")
testipv6 = netip.MustParseAddr("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f")
testipv6Second = netip.MustParseAddr("1011:1213:1415:1617:1819:1a1b:1c1d:1e1f")
testipv6Third = netip.MustParseAddr("2021:2223:2425:2627:2829:2a2b:2c2d:2e2f")
testipv4Arpa = dnsname.FQDN("4.3.2.1.in-addr.arpa.")
testipv6Arpa = dnsname.FQDN("f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa.")
@ -46,6 +51,8 @@ var dnsCfg = Config{
Hosts: map[dnsname.FQDN][]netip.Addr{
"test1.ipn.dev.": {testipv4},
"test2.ipn.dev.": {testipv6},
"test3.ipn.dev.": {testipv4Second, testipv6Second},
"test4.ipn.dev.": {testipv4Second, testipv4Third, testipv6Second, testipv6Third},
},
LocalDomains: []dnsname.FQDN{"ipn.dev.", "3.2.1.in-addr.arpa.", "1.0.0.0.ip6.arpa."},
}
@ -245,6 +252,24 @@ func mustIP(str string) netip.Addr {
return ip
}
func parseResponseIPs(t *testing.T, response []byte) (rcode int, a []netip.Addr, aaaa []netip.Addr) {
t.Helper()
var msg miekdns.Msg
if err := msg.Unpack(response); err != nil {
t.Fatalf("failed to unpack response: %v", err)
}
rcode = msg.Rcode
for _, ans := range msg.Answer {
switch rr := ans.(type) {
case *miekdns.A:
a = append(a, netip.AddrFrom4([4]byte(rr.A.To4())))
case *miekdns.AAAA:
aaaa = append(aaaa, netip.AddrFrom16([16]byte(rr.AAAA)))
}
}
return rcode, a, aaaa
}
func TestRoutesRequireNoCustomResolvers(t *testing.T) {
tests := []struct {
name string
@ -374,57 +399,63 @@ func TestResolveLocal(t *testing.T) {
name string
qname dnsname.FQDN
qtype dns.Type
ip netip.Addr
ips []netip.Addr
code dns.RCode
}{
{"ipv4", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess},
{"ipv6", "test2.ipn.dev.", dns.TypeAAAA, testipv6, dns.RCodeSuccess},
{"no-ipv6", "test1.ipn.dev.", dns.TypeAAAA, netip.Addr{}, dns.RCodeSuccess},
{"nxdomain", "test3.ipn.dev.", dns.TypeA, netip.Addr{}, dns.RCodeNameError},
{"foreign domain", "google.com.", dns.TypeA, netip.Addr{}, dns.RCodeRefused},
{"all", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess},
{"mx-ipv4", "test1.ipn.dev.", dns.TypeMX, netip.Addr{}, dns.RCodeSuccess},
{"mx-ipv6", "test2.ipn.dev.", dns.TypeMX, netip.Addr{}, dns.RCodeSuccess},
{"mx-nxdomain", "test3.ipn.dev.", dns.TypeMX, netip.Addr{}, dns.RCodeNameError},
{"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netip.Addr{}, dns.RCodeNameError},
{"onion-domain", "footest.onion.", dns.TypeA, netip.Addr{}, dns.RCodeNameError},
{"magicdns", dnsSymbolicFQDN, dns.TypeA, netip.MustParseAddr("100.100.100.100"), dns.RCodeSuccess},
{"via_hex", dnsname.FQDN("via-0xff.1.2.3.4."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:1.2.3.4"), dns.RCodeSuccess},
{"via_dec", dnsname.FQDN("via-1.10.0.0.1."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:10.0.0.1"), dns.RCodeSuccess},
{"x_via_hex", dnsname.FQDN("4.3.2.1.via-0xff."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:4.3.2.1"), dns.RCodeSuccess},
{"x_via_dec", dnsname.FQDN("1.0.0.10.via-1."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.0.0.10"), dns.RCodeSuccess},
{"via_invalid", dnsname.FQDN("via-."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
{"via_invalid_2", dnsname.FQDN("2.3.4.5.via-."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
{"a-ipv4", "test1.ipn.dev.", dns.TypeA, []netip.Addr{testipv4}, dns.RCodeSuccess},
{"aaaa-ipv4", "test1.ipn.dev.", dns.TypeAAAA, nil, dns.RCodeSuccess},
{"all-ipv4", "test1.ipn.dev.", dns.TypeALL, []netip.Addr{testipv4}, dns.RCodeSuccess},
{"a-ipv6", "test2.ipn.dev.", dns.TypeA, nil, dns.RCodeSuccess},
{"aaaa-ipv6", "test2.ipn.dev.", dns.TypeAAAA, []netip.Addr{testipv6}, dns.RCodeSuccess},
{"all-ipv6", "test2.ipn.dev.", dns.TypeAAAA, []netip.Addr{testipv6}, dns.RCodeSuccess},
{"a-both", "test3.ipn.dev.", dns.TypeA, []netip.Addr{testipv4Second}, dns.RCodeSuccess},
{"aaaa-both", "test3.ipn.dev.", dns.TypeAAAA, []netip.Addr{testipv6Second}, dns.RCodeSuccess},
{"all-both", "test3.ipn.dev.", dns.TypeALL, []netip.Addr{testipv4Second, testipv6Second}, dns.RCodeSuccess},
{"a-multi", "test4.ipn.dev.", dns.TypeA, []netip.Addr{testipv4Second, testipv4Third}, dns.RCodeSuccess},
{"aaaa-multi", "test4.ipn.dev.", dns.TypeAAAA, []netip.Addr{testipv6Second, testipv6Third}, dns.RCodeSuccess},
{"all-multi", "test4.ipn.dev.", dns.TypeALL, []netip.Addr{testipv4Second, testipv4Third, testipv6Second, testipv6Third}, dns.RCodeSuccess},
{"nxdomain", "footest.ipn.dev.", dns.TypeA, nil, dns.RCodeNameError},
{"foreign domain", "google.com.", dns.TypeA, nil, dns.RCodeRefused},
{"mx-ipv4", "test1.ipn.dev.", dns.TypeMX, nil, dns.RCodeSuccess},
{"mx-ipv6", "test2.ipn.dev.", dns.TypeMX, nil, dns.RCodeSuccess},
{"mx-nxdomain", "footest.ipn.dev.", dns.TypeMX, nil, dns.RCodeNameError},
{"ns-nxdomain", "footest.ipn.dev.", dns.TypeNS, nil, dns.RCodeNameError},
{"onion-domain", "footest.onion.", dns.TypeA, nil, dns.RCodeNameError},
{"magicdns", dnsSymbolicFQDN, dns.TypeA, []netip.Addr{netip.MustParseAddr("100.100.100.100")}, dns.RCodeSuccess},
{"via_hex", dnsname.FQDN("via-0xff.1.2.3.4."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:1.2.3.4")}, dns.RCodeSuccess},
{"via_dec", dnsname.FQDN("via-1.10.0.0.1."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:10.0.0.1")}, dns.RCodeSuccess},
{"x_via_hex", dnsname.FQDN("4.3.2.1.via-0xff."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:4.3.2.1")}, dns.RCodeSuccess},
{"x_via_dec", dnsname.FQDN("1.0.0.10.via-1."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.0.0.10")}, dns.RCodeSuccess},
{"via_invalid", dnsname.FQDN("via-."), dns.TypeAAAA, nil, dns.RCodeRefused},
{"via_invalid_2", dnsname.FQDN("2.3.4.5.via-."), dns.TypeAAAA, nil, dns.RCodeRefused},
// Hyphenated 4via6 format.
// Without any suffix domain:
{"via_form3_hex_bare", dnsname.FQDN("1-2-3-4-via-0xff."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:1.2.3.4"), dns.RCodeSuccess},
{"via_form3_dec_bare", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
{"via_form3_hex_bare", dnsname.FQDN("1-2-3-4-via-0xff."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:1.2.3.4")}, dns.RCodeSuccess},
{"via_form3_dec_bare", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4")}, dns.RCodeSuccess},
// With a Tailscale domain:
{"via_form3_dec_ts.net", dnsname.FQDN("1-2-3-4-via-1.foo.ts.net."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
{"via_form3_dec_tailscale.net", dnsname.FQDN("1-2-3-4-via-1.foo.tailscale.net."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
{"via_form3_dec_ts.net", dnsname.FQDN("1-2-3-4-via-1.foo.ts.net."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4")}, dns.RCodeSuccess},
{"via_form3_dec_tailscale.net", dnsname.FQDN("1-2-3-4-via-1.foo.tailscale.net."), dns.TypeAAAA, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4")}, dns.RCodeSuccess},
// Non-Tailscale domain suffixes aren't allowed for now: (the allowed
// suffixes are currently hard-coded and not plumbed via the netmap)
{"via_form3_dec_example.com", dnsname.FQDN("1-2-3-4-via-1.example.com."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
{"via_form3_dec_examplets.net", dnsname.FQDN("1-2-3-4-via-1.examplets.net."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
{"via_form3_dec_example.com", dnsname.FQDN("1-2-3-4-via-1.example.com."), dns.TypeAAAA, nil, dns.RCodeRefused},
{"via_form3_dec_examplets.net", dnsname.FQDN("1-2-3-4-via-1.examplets.net."), dns.TypeAAAA, nil, dns.RCodeRefused},
// Resolve A and ALL types of resource records.
{"via_type_a", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeA, netip.Addr{}, dns.RCodeSuccess},
{"via_invalid_type_a", dnsname.FQDN("1-2-3-4-via-."), dns.TypeA, netip.Addr{}, dns.RCodeRefused},
{"via_type_all", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeALL, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
{"via_invalid_type_all", dnsname.FQDN("1-2-3-4-via-."), dns.TypeALL, netip.Addr{}, dns.RCodeRefused},
{"via_type_a", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeA, nil, dns.RCodeSuccess},
{"via_invalid_type_a", dnsname.FQDN("1-2-3-4-via-."), dns.TypeA, nil, dns.RCodeRefused},
{"via_type_all", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeALL, []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4")}, dns.RCodeSuccess},
{"via_invalid_type_all", dnsname.FQDN("1-2-3-4-via-."), dns.TypeALL, nil, dns.RCodeRefused},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ip, code := r.resolveLocal(tt.qname, tt.qtype)
ips, code := r.resolveLocal(tt.qname, tt.qtype)
if code != tt.code {
t.Errorf("code = %v; want %v", code, tt.code)
}
// Only check ip for non-err
if ip != tt.ip {
t.Errorf("ip = %v; want %v", ip, tt.ip)
}
// Only check ips for non-err
assert.ElementsMatch(t, tt.ips, ips)
})
}
}
@ -902,7 +933,7 @@ var nxdomainResponse = []byte{
0x00, 0x00, // no answers
0x00, 0x00, 0x00, 0x00, // no authority or additional RRs
// Question:
0x05, 0x74, 0x65, 0x73, 0x74, 0x33, 0x03, 0x69, 0x70, 0x6e, 0x03, 0x64, 0x65, 0x76, 0x00, // name
0x07, 0x66, 0x6f, 0x6f, 0x74, 0x65, 0x73, 0x74, 0x03, 0x69, 0x70, 0x6e, 0x03, 0x64, 0x65, 0x76, 0x00, // name: footest.ipn.dev.
0x00, 0x01, 0x00, 0x01, // type A, class IN
}
@ -937,7 +968,7 @@ func TestFull(t *testing.T) {
{"ptr4", dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR, noEdns), ptrResponse},
{"ptr6", dnspacket("f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa.",
dns.TypePTR, noEdns), ptrResponse6},
{"nxdomain", dnspacket("test3.ipn.dev.", dns.TypeA, noEdns), nxdomainResponse},
{"nxdomain", dnspacket("footest.ipn.dev.", dns.TypeA, noEdns), nxdomainResponse},
}
for _, tt := range tests {
@ -953,6 +984,43 @@ func TestFull(t *testing.T) {
}
}
// Test addresses parsed from the response, to tolerate different record ordering
func TestFullParsed(t *testing.T) {
r := newResolver(t)
defer r.Close()
r.SetConfig(dnsCfg)
tests := []struct {
name string
qname dnsname.FQDN
qtype dns.Type
wantA []netip.Addr
wantAAAA []netip.Addr
}{
{"single-a", "test1.ipn.dev.", dns.TypeA, []netip.Addr{testipv4}, nil},
{"single-aaaa", "test2.ipn.dev.", dns.TypeAAAA, nil, []netip.Addr{testipv6}},
{"multi-a", "test4.ipn.dev.", dns.TypeA, []netip.Addr{testipv4Second, testipv4Third}, nil},
{"multi-aaaa", "test4.ipn.dev.", dns.TypeAAAA, nil, []netip.Addr{testipv6Second, testipv6Third}},
{"multi-all", "test4.ipn.dev.", dns.TypeALL, []netip.Addr{testipv4Second, testipv4Third}, []netip.Addr{testipv6Second, testipv6Third}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
response, err := syncRespond(r, dnspacket(tt.qname, tt.qtype, noEdns))
if err != nil {
t.Fatalf("err = %v; want nil", err)
}
rcode, a, aaaa := parseResponseIPs(t, response)
if rcode != int(dns.RCodeSuccess) {
t.Errorf("rcode = %v; want %v", rcode, dns.RCodeSuccess)
}
assert.ElementsMatch(t, tt.wantA, a, "A records mismatch")
assert.ElementsMatch(t, tt.wantAAAA, aaaa, "AAAA records mismatch")
})
}
}
func TestAllocs(t *testing.T) {
r := newResolver(t)
defer r.Close()

Loading…
Cancel
Save