mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-04 23:45:34 +00:00
0d991249e1
And convert all callers over to the methods that check SelfNode. Now we don't have multiple ways to express things in tests (setting fields on SelfNode vs NetworkMap, sometimes inconsistently) and don't have multiple ways to check those two fields (often only checking one or the other). Updates #9443 Change-Id: I2d7ba1cf6556142d219fae2be6f484f528756e3c Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
403 lines
11 KiB
Go
403 lines
11 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package ipnlocal
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/net/dns"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/tstest"
|
|
"tailscale.com/types/dnstype"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/util/cloudenv"
|
|
"tailscale.com/util/cmpx"
|
|
"tailscale.com/util/dnsname"
|
|
)
|
|
|
|
func ipps(ippStrs ...string) (ipps []netip.Prefix) {
|
|
for _, s := range ippStrs {
|
|
if ip, err := netip.ParseAddr(s); err == nil {
|
|
ipps = append(ipps, netip.PrefixFrom(ip, ip.BitLen()))
|
|
continue
|
|
}
|
|
ipps = append(ipps, netip.MustParsePrefix(s))
|
|
}
|
|
return
|
|
}
|
|
|
|
func ips(ss ...string) (ips []netip.Addr) {
|
|
for _, s := range ss {
|
|
ips = append(ips, netip.MustParseAddr(s))
|
|
}
|
|
return
|
|
}
|
|
|
|
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
|
|
nv := make([]tailcfg.NodeView, len(v))
|
|
for i, n := range v {
|
|
nv[i] = n.View()
|
|
}
|
|
return nv
|
|
}
|
|
|
|
func TestDNSConfigForNetmap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
nm *netmap.NetworkMap
|
|
peers []tailcfg.NodeView
|
|
os string // version.OS value; empty means linux
|
|
cloud cloudenv.Cloud
|
|
prefs *ipn.Prefs
|
|
want *dns.Config
|
|
wantLog string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
nm: &netmap.NetworkMap{},
|
|
prefs: &ipn.Prefs{},
|
|
want: &dns.Config{
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{},
|
|
},
|
|
},
|
|
{
|
|
name: "self_name_and_peers",
|
|
nm: &netmap.NetworkMap{
|
|
Name: "myname.net",
|
|
SelfNode: (&tailcfg.Node{
|
|
Addresses: ipps("100.101.101.101"),
|
|
}).View(),
|
|
},
|
|
peers: nodeViews([]*tailcfg.Node{
|
|
{
|
|
ID: 1,
|
|
Name: "peera.net",
|
|
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"),
|
|
},
|
|
{
|
|
ID: 2,
|
|
Name: "b.net",
|
|
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::2"),
|
|
},
|
|
{
|
|
ID: 3,
|
|
Name: "v6-only.net",
|
|
Addresses: ipps("fe75::3"), // no IPv4, so we don't ignore IPv6
|
|
},
|
|
}),
|
|
prefs: &ipn.Prefs{},
|
|
want: &dns.Config{
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{
|
|
"b.net.": ips("100.102.0.1", "100.102.0.2"),
|
|
"myname.net.": ips("100.101.101.101"),
|
|
"peera.net.": ips("100.102.0.1", "100.102.0.2"),
|
|
"v6-only.net.": ips("fe75::3"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// An ephemeral node with only an IPv6 address
|
|
// should get IPv6 records for all its peers,
|
|
// even if they have IPv4.
|
|
name: "v6_only_self",
|
|
nm: &netmap.NetworkMap{
|
|
Name: "myname.net",
|
|
SelfNode: (&tailcfg.Node{
|
|
Addresses: ipps("fe75::1"),
|
|
}).View(),
|
|
},
|
|
peers: nodeViews([]*tailcfg.Node{
|
|
{
|
|
ID: 1,
|
|
Name: "peera.net",
|
|
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001"),
|
|
},
|
|
{
|
|
ID: 2,
|
|
Name: "b.net",
|
|
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::2"),
|
|
},
|
|
{
|
|
ID: 3,
|
|
Name: "v6-only.net",
|
|
Addresses: ipps("fe75::3"), // no IPv4, so we don't ignore IPv6
|
|
},
|
|
}),
|
|
prefs: &ipn.Prefs{},
|
|
want: &dns.Config{
|
|
OnlyIPv6: true,
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{
|
|
"b.net.": ips("fe75::2"),
|
|
"myname.net.": ips("fe75::1"),
|
|
"peera.net.": ips("fe75::1001"),
|
|
"v6-only.net.": ips("fe75::3"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "extra_records",
|
|
nm: &netmap.NetworkMap{
|
|
Name: "myname.net",
|
|
SelfNode: (&tailcfg.Node{
|
|
Addresses: ipps("100.101.101.101"),
|
|
}).View(),
|
|
DNS: tailcfg.DNSConfig{
|
|
ExtraRecords: []tailcfg.DNSRecord{
|
|
{Name: "foo.com", Value: "1.2.3.4"},
|
|
{Name: "bar.com", Value: "1::6"},
|
|
{Name: "sdlfkjsdklfj", Type: "IGNORE"},
|
|
},
|
|
},
|
|
},
|
|
prefs: &ipn.Prefs{},
|
|
want: &dns.Config{
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{
|
|
"myname.net.": ips("100.101.101.101"),
|
|
"foo.com.": ips("1.2.3.4"),
|
|
"bar.com.": ips("1::6"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "corp_dns_misc",
|
|
nm: &netmap.NetworkMap{
|
|
Name: "host.some.domain.net.",
|
|
DNS: tailcfg.DNSConfig{
|
|
Proxied: true,
|
|
Domains: []string{"foo.com", "bar.com"},
|
|
},
|
|
},
|
|
prefs: &ipn.Prefs{
|
|
CorpDNS: true,
|
|
},
|
|
want: &dns.Config{
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{},
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
|
|
"0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.": nil,
|
|
"100.100.in-addr.arpa.": nil,
|
|
"101.100.in-addr.arpa.": nil,
|
|
"102.100.in-addr.arpa.": nil,
|
|
"103.100.in-addr.arpa.": nil,
|
|
"104.100.in-addr.arpa.": nil,
|
|
"105.100.in-addr.arpa.": nil,
|
|
"106.100.in-addr.arpa.": nil,
|
|
"107.100.in-addr.arpa.": nil,
|
|
"108.100.in-addr.arpa.": nil,
|
|
"109.100.in-addr.arpa.": nil,
|
|
"110.100.in-addr.arpa.": nil,
|
|
"111.100.in-addr.arpa.": nil,
|
|
"112.100.in-addr.arpa.": nil,
|
|
"113.100.in-addr.arpa.": nil,
|
|
"114.100.in-addr.arpa.": nil,
|
|
"115.100.in-addr.arpa.": nil,
|
|
"116.100.in-addr.arpa.": nil,
|
|
"117.100.in-addr.arpa.": nil,
|
|
"118.100.in-addr.arpa.": nil,
|
|
"119.100.in-addr.arpa.": nil,
|
|
"120.100.in-addr.arpa.": nil,
|
|
"121.100.in-addr.arpa.": nil,
|
|
"122.100.in-addr.arpa.": nil,
|
|
"123.100.in-addr.arpa.": nil,
|
|
"124.100.in-addr.arpa.": nil,
|
|
"125.100.in-addr.arpa.": nil,
|
|
"126.100.in-addr.arpa.": nil,
|
|
"127.100.in-addr.arpa.": nil,
|
|
"64.100.in-addr.arpa.": nil,
|
|
"65.100.in-addr.arpa.": nil,
|
|
"66.100.in-addr.arpa.": nil,
|
|
"67.100.in-addr.arpa.": nil,
|
|
"68.100.in-addr.arpa.": nil,
|
|
"69.100.in-addr.arpa.": nil,
|
|
"70.100.in-addr.arpa.": nil,
|
|
"71.100.in-addr.arpa.": nil,
|
|
"72.100.in-addr.arpa.": nil,
|
|
"73.100.in-addr.arpa.": nil,
|
|
"74.100.in-addr.arpa.": nil,
|
|
"75.100.in-addr.arpa.": nil,
|
|
"76.100.in-addr.arpa.": nil,
|
|
"77.100.in-addr.arpa.": nil,
|
|
"78.100.in-addr.arpa.": nil,
|
|
"79.100.in-addr.arpa.": nil,
|
|
"80.100.in-addr.arpa.": nil,
|
|
"81.100.in-addr.arpa.": nil,
|
|
"82.100.in-addr.arpa.": nil,
|
|
"83.100.in-addr.arpa.": nil,
|
|
"84.100.in-addr.arpa.": nil,
|
|
"85.100.in-addr.arpa.": nil,
|
|
"86.100.in-addr.arpa.": nil,
|
|
"87.100.in-addr.arpa.": nil,
|
|
"88.100.in-addr.arpa.": nil,
|
|
"89.100.in-addr.arpa.": nil,
|
|
"90.100.in-addr.arpa.": nil,
|
|
"91.100.in-addr.arpa.": nil,
|
|
"92.100.in-addr.arpa.": nil,
|
|
"93.100.in-addr.arpa.": nil,
|
|
"94.100.in-addr.arpa.": nil,
|
|
"95.100.in-addr.arpa.": nil,
|
|
"96.100.in-addr.arpa.": nil,
|
|
"97.100.in-addr.arpa.": nil,
|
|
"98.100.in-addr.arpa.": nil,
|
|
"99.100.in-addr.arpa.": nil,
|
|
"some.domain.net.": nil,
|
|
},
|
|
SearchDomains: []dnsname.FQDN{
|
|
"foo.com.",
|
|
"bar.com.",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Prior to fixing https://github.com/tailscale/tailscale/issues/2116,
|
|
// Android had cases where it needed FallbackResolvers. This was the
|
|
// negative test for the case where Override-local-DNS was set, so the
|
|
// fallback resolvers did not need to be used. This test is still valid
|
|
// so we keep it, but the fallback test has been removed.
|
|
name: "android_does_NOT_need_fallbacks",
|
|
os: "android",
|
|
nm: &netmap.NetworkMap{
|
|
DNS: tailcfg.DNSConfig{
|
|
Resolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.8.8"},
|
|
},
|
|
FallbackResolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.4.4"},
|
|
},
|
|
Routes: map[string][]*dnstype.Resolver{
|
|
"foo.com.": {{Addr: "1.2.3.4"}},
|
|
},
|
|
},
|
|
},
|
|
prefs: &ipn.Prefs{
|
|
CorpDNS: true,
|
|
},
|
|
want: &dns.Config{
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{},
|
|
DefaultResolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.8.8"},
|
|
},
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
|
|
"foo.com.": {{Addr: "1.2.3.4"}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "exit_nodes_need_fallbacks",
|
|
nm: &netmap.NetworkMap{
|
|
DNS: tailcfg.DNSConfig{
|
|
FallbackResolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.4.4"},
|
|
},
|
|
},
|
|
},
|
|
prefs: &ipn.Prefs{
|
|
CorpDNS: true,
|
|
ExitNodeID: "some-id",
|
|
},
|
|
want: &dns.Config{
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{},
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
DefaultResolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.4.4"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "not_exit_node_NOT_need_fallbacks",
|
|
nm: &netmap.NetworkMap{
|
|
DNS: tailcfg.DNSConfig{
|
|
FallbackResolvers: []*dnstype.Resolver{
|
|
{Addr: "8.8.4.4"},
|
|
},
|
|
},
|
|
},
|
|
prefs: &ipn.Prefs{
|
|
CorpDNS: true,
|
|
},
|
|
want: &dns.Config{
|
|
Hosts: map[dnsname.FQDN][]netip.Addr{},
|
|
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
verOS := cmpx.Or(tt.os, "linux")
|
|
var log tstest.MemLogger
|
|
got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), log.Logf, verOS)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
gotj, _ := json.MarshalIndent(got, "", "\t")
|
|
wantj, _ := json.MarshalIndent(tt.want, "", "\t")
|
|
t.Errorf("wrong\n got: %s\n\nwant: %s\n", gotj, wantj)
|
|
}
|
|
if got := log.String(); got != tt.wantLog {
|
|
t.Errorf("log output wrong\n got: %q\nwant: %q\n", got, tt.wantLog)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func peersMap(s []tailcfg.NodeView) map[tailcfg.NodeID]tailcfg.NodeView {
|
|
m := make(map[tailcfg.NodeID]tailcfg.NodeView)
|
|
for _, n := range s {
|
|
if n.ID() == 0 {
|
|
panic("zero Node.ID")
|
|
}
|
|
m[n.ID()] = n
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestAllowExitNodeDNSProxyToServeName(t *testing.T) {
|
|
b := &LocalBackend{}
|
|
if b.allowExitNodeDNSProxyToServeName("google.com") {
|
|
t.Fatal("unexpected true on backend with nil NetMap")
|
|
}
|
|
|
|
b.netMap = &netmap.NetworkMap{
|
|
DNS: tailcfg.DNSConfig{
|
|
ExitNodeFilteredSet: []string{
|
|
".ts.net",
|
|
"some.exact.bad",
|
|
},
|
|
},
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
want bool
|
|
}{
|
|
// Allow by default:
|
|
{"google.com", true},
|
|
{"GOOGLE.com", true},
|
|
|
|
// Rejected by suffix:
|
|
{"foo.TS.NET", false},
|
|
{"foo.ts.net", false},
|
|
|
|
// Suffix doesn't match
|
|
{"ts.net", true},
|
|
|
|
// Rejected by exact match:
|
|
{"some.exact.bad", false},
|
|
{"SOME.EXACT.BAD", false},
|
|
|
|
// But a prefix is okay.
|
|
{"prefix-okay.some.exact.bad", true},
|
|
}
|
|
for _, tt := range tests {
|
|
got := b.allowExitNodeDNSProxyToServeName(tt.name)
|
|
if got != tt.want {
|
|
t.Errorf("for %q = %v; want %v", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
|
|
}
|