mirror of https://github.com/tailscale/tailscale/
net/dns/resolver: add live reconfig, plumb through to ipnlocal.
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>pull/1635/head
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