The resolver still only supports a single upstream config, and ipn/wgengine still have to split up the DNS config, but this moves closer to unifying the DNS configs. As a handy side-effect of the refactor, IPv6 MagicDNS records exist now. Signed-off-by: David Anderson <danderson@tailscale.com>main
parent
caeafc4a32
commit
90f82b6946
@ -1,160 +0,0 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package resolver |
||||
|
||||
import ( |
||||
"sort" |
||||
"strings" |
||||
|
||||
"inet.af/netaddr" |
||||
) |
||||
|
||||
// Map is all the data Resolver needs to resolve DNS queries within the Tailscale network.
|
||||
type Map struct { |
||||
// nameToIP is a mapping of Tailscale domain names to their IP addresses.
|
||||
// For example, monitoring.tailscale.us -> 100.64.0.1.
|
||||
nameToIP map[string]netaddr.IP |
||||
// ipToName is the inverse of nameToIP.
|
||||
ipToName map[netaddr.IP]string |
||||
// names are the keys of nameToIP in sorted order.
|
||||
names []string |
||||
// rootDomains are the domains whose subdomains should always
|
||||
// be resolved locally to prevent leakage of sensitive names.
|
||||
rootDomains []string // e.g. "user.provider.beta.tailscale.net."
|
||||
} |
||||
|
||||
// NewMap returns a new Map with name to address mapping given by nameToIP.
|
||||
//
|
||||
// rootDomains are the domains whose subdomains should always be
|
||||
// resolved locally to prevent leakage of sensitive names. They should
|
||||
// end in a period ("user-foo.tailscale.net.").
|
||||
func NewMap(initNameToIP map[string]netaddr.IP, rootDomains []string) *Map { |
||||
// TODO(dmytro): we have to allocate names and ipToName, but nameToIP can be avoided.
|
||||
// It is here because control sends us names not in canonical form. Change this.
|
||||
names := make([]string, 0, len(initNameToIP)) |
||||
nameToIP := make(map[string]netaddr.IP, len(initNameToIP)) |
||||
ipToName := make(map[netaddr.IP]string, len(initNameToIP)) |
||||
|
||||
for name, ip := range initNameToIP { |
||||
if len(name) == 0 { |
||||
// Nothing useful can be done with empty names.
|
||||
continue |
||||
} |
||||
if name[len(name)-1] != '.' { |
||||
name += "." |
||||
} |
||||
names = append(names, name) |
||||
nameToIP[name] = ip |
||||
ipToName[ip] = name |
||||
} |
||||
sort.Strings(names) |
||||
|
||||
return &Map{ |
||||
nameToIP: nameToIP, |
||||
ipToName: ipToName, |
||||
names: names, |
||||
|
||||
rootDomains: rootDomains, |
||||
} |
||||
} |
||||
|
||||
func printSingleNameIP(buf *strings.Builder, name string, ip netaddr.IP) { |
||||
buf.WriteString(name) |
||||
buf.WriteByte('\t') |
||||
buf.WriteString(ip.String()) |
||||
buf.WriteByte('\n') |
||||
} |
||||
|
||||
func (m *Map) Pretty() string { |
||||
buf := new(strings.Builder) |
||||
for _, name := range m.names { |
||||
printSingleNameIP(buf, name, m.nameToIP[name]) |
||||
} |
||||
return buf.String() |
||||
} |
||||
|
||||
func (m *Map) PrettyDiffFrom(old *Map) string { |
||||
var ( |
||||
oldNameToIP map[string]netaddr.IP |
||||
newNameToIP map[string]netaddr.IP |
||||
oldNames []string |
||||
newNames []string |
||||
) |
||||
if old != nil { |
||||
oldNameToIP = old.nameToIP |
||||
oldNames = old.names |
||||
} |
||||
if m != nil { |
||||
newNameToIP = m.nameToIP |
||||
newNames = m.names |
||||
} |
||||
|
||||
buf := new(strings.Builder) |
||||
space := func() bool { |
||||
return buf.Len() < (1 << 10) |
||||
} |
||||
|
||||
for len(oldNames) > 0 && len(newNames) > 0 { |
||||
var name string |
||||
|
||||
newName, oldName := newNames[0], oldNames[0] |
||||
switch { |
||||
case oldName < newName: |
||||
name = oldName |
||||
oldNames = oldNames[1:] |
||||
case oldName > newName: |
||||
name = newName |
||||
newNames = newNames[1:] |
||||
case oldNames[0] == newNames[0]: |
||||
name = oldNames[0] |
||||
oldNames = oldNames[1:] |
||||
newNames = newNames[1:] |
||||
} |
||||
if !space() { |
||||
continue |
||||
} |
||||
|
||||
ipOld, inOld := oldNameToIP[name] |
||||
ipNew, inNew := newNameToIP[name] |
||||
switch { |
||||
case !inOld: |
||||
buf.WriteByte('+') |
||||
printSingleNameIP(buf, name, ipNew) |
||||
case !inNew: |
||||
buf.WriteByte('-') |
||||
printSingleNameIP(buf, name, ipOld) |
||||
case ipOld != ipNew: |
||||
buf.WriteByte('-') |
||||
printSingleNameIP(buf, name, ipOld) |
||||
buf.WriteByte('+') |
||||
printSingleNameIP(buf, name, ipNew) |
||||
} |
||||
} |
||||
|
||||
for _, name := range oldNames { |
||||
if !space() { |
||||
break |
||||
} |
||||
if _, ok := newNameToIP[name]; !ok { |
||||
buf.WriteByte('-') |
||||
printSingleNameIP(buf, name, oldNameToIP[name]) |
||||
} |
||||
} |
||||
|
||||
for _, name := range newNames { |
||||
if !space() { |
||||
break |
||||
} |
||||
if _, ok := oldNameToIP[name]; !ok { |
||||
buf.WriteByte('+') |
||||
printSingleNameIP(buf, name, newNameToIP[name]) |
||||
} |
||||
} |
||||
if !space() { |
||||
buf.WriteString("... [truncated]\n") |
||||
} |
||||
|
||||
return buf.String() |
||||
} |
||||
@ -1,156 +0,0 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package resolver |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"inet.af/netaddr" |
||||
) |
||||
|
||||
func TestPretty(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
dmap *Map |
||||
want string |
||||
}{ |
||||
{"empty", NewMap(nil, nil), ""}, |
||||
{ |
||||
"single", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"hello.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
}, nil), |
||||
"hello.ipn.dev.\t100.101.102.103\n", |
||||
}, |
||||
{ |
||||
"multiple", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.domain.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.sub.domain.": netaddr.IPv4(100, 99, 9, 1), |
||||
}, nil), |
||||
"test1.domain.\t100.101.102.103\ntest2.sub.domain.\t100.99.9.1\n", |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
got := tt.dmap.Pretty() |
||||
if tt.want != got { |
||||
t.Errorf("want %v; got %v", tt.want, got) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestPrettyDiffFrom(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
map1 *Map |
||||
map2 *Map |
||||
want string |
||||
}{ |
||||
{ |
||||
"from_empty", |
||||
nil, |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
"+test1.ipn.dev.\t100.101.102.103\n+test2.ipn.dev.\t100.103.102.101\n", |
||||
}, |
||||
{ |
||||
"equal", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
}, nil), |
||||
"", |
||||
}, |
||||
{ |
||||
"changed_ip", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 104, 102, 101), |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
}, nil), |
||||
"-test2.ipn.dev.\t100.103.102.101\n+test2.ipn.dev.\t100.104.102.101\n", |
||||
}, |
||||
{ |
||||
"new_domain", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test3.ipn.dev.": netaddr.IPv4(100, 105, 106, 107), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
}, nil), |
||||
"+test3.ipn.dev.\t100.105.106.107\n", |
||||
}, |
||||
{ |
||||
"gone_domain", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
}, nil), |
||||
"-test2.ipn.dev.\t100.103.102.101\n", |
||||
}, |
||||
{ |
||||
"mixed", |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), |
||||
"test4.ipn.dev.": netaddr.IPv4(100, 107, 106, 105), |
||||
"test5.ipn.dev.": netaddr.IPv4(100, 64, 1, 1), |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), |
||||
}, nil), |
||||
NewMap(map[string]netaddr.IP{ |
||||
"test2.ipn.dev.": netaddr.IPv4(100, 104, 102, 101), |
||||
"test1.ipn.dev.": netaddr.IPv4(100, 100, 101, 102), |
||||
"test3.ipn.dev.": netaddr.IPv4(100, 64, 1, 1), |
||||
}, nil), |
||||
"-test1.ipn.dev.\t100.101.102.103\n+test1.ipn.dev.\t100.100.101.102\n" + |
||||
"-test2.ipn.dev.\t100.103.102.101\n+test2.ipn.dev.\t100.104.102.101\n" + |
||||
"+test3.ipn.dev.\t100.64.1.1\n-test4.ipn.dev.\t100.107.106.105\n-test5.ipn.dev.\t100.64.1.1\n", |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
got := tt.map2.PrettyDiffFrom(tt.map1) |
||||
if tt.want != got { |
||||
t.Errorf("want %v; got %v", tt.want, got) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
t.Run("truncated", func(t *testing.T) { |
||||
small := NewMap(nil, nil) |
||||
m := map[string]netaddr.IP{} |
||||
for i := 0; i < 5000; i++ { |
||||
m[fmt.Sprintf("host%d.ipn.dev.", i)] = netaddr.IPv4(100, 64, 1, 1) |
||||
} |
||||
veryBig := NewMap(m, nil) |
||||
diff := veryBig.PrettyDiffFrom(small) |
||||
if len(diff) > 3<<10 { |
||||
t.Errorf("pretty diff too large: %d bytes", len(diff)) |
||||
} |
||||
if !strings.Contains(diff, "truncated") { |
||||
t.Errorf("big diff not truncated") |
||||
} |
||||
}) |
||||
} |
||||
Loading…
Reference in new issue