tailscale/net/dnsfallback/dnsfallback_test.go

204 lines
5.1 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dnsfallback
import (
"context"
"encoding/json"
"flag"
"os"
"path/filepath"
"reflect"
"testing"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
)
func TestGetDERPMap(t *testing.T) {
dm := GetDERPMap()
if dm == nil {
t.Fatal("nil")
}
if len(dm.Regions) == 0 {
t.Fatal("no regions")
}
}
func TestCache(t *testing.T) {
cacheFile := filepath.Join(t.TempDir(), "cache.json")
// Write initial cache value
initialCache := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
99: {
RegionID: 99,
RegionCode: "test",
RegionName: "Testville",
Nodes: []*tailcfg.DERPNode{{
Name: "99a",
RegionID: 99,
HostName: "derp99a.tailscale.com",
IPv4: "1.2.3.4",
}},
},
// Intentionally attempt to "overwrite" something
1: {
RegionID: 1,
RegionCode: "r1",
RegionName: "r1",
Nodes: []*tailcfg.DERPNode{{
Name: "1c",
RegionID: 1,
HostName: "derp1c.tailscale.com",
IPv4: "127.0.0.1",
IPv6: "::1",
}},
},
},
}
d, err := json.Marshal(initialCache)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(cacheFile, d, 0666); err != nil {
t.Fatal(err)
}
// Clear any existing cached DERP map(s)
cachedDERPMap.Store(nil)
// Load the cache
SetCachePath(cacheFile, t.Logf)
if cm := cachedDERPMap.Load(); !reflect.DeepEqual(initialCache, cm) {
t.Fatalf("cached map was %+v; want %+v", cm, initialCache)
}
// Verify that our DERP map is merged with the cache.
dm := GetDERPMap()
region, ok := dm.Regions[99]
if !ok {
t.Fatal("expected region 99")
}
if !reflect.DeepEqual(region, initialCache.Regions[99]) {
t.Fatalf("region 99: got %+v; want %+v", region, initialCache.Regions[99])
}
// Verify that our cache can't override a statically-baked-in DERP server.
n0 := dm.Regions[1].Nodes[0]
if n0.IPv4 == "127.0.0.1" || n0.IPv6 == "::1" {
t.Errorf("got %+v; expected no overwrite for node", n0)
}
// Also, make sure that the static DERP map still has the same first
// node as when this test was last written/updated; this ensures that
// we don't accidentally start allowing overwrites due to some of the
// test's assumptions changing out from underneath us as we update the
// JSON file of fallback servers.
if getStaticDERPMap().Regions[1].Nodes[0].HostName != "derp1c.tailscale.com" {
t.Errorf("DERP server has a different name; please update this test")
}
}
func TestCacheUnchanged(t *testing.T) {
cacheFile := filepath.Join(t.TempDir(), "cache.json")
// Write initial cache value
initialCache := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
99: {
RegionID: 99,
RegionCode: "test",
RegionName: "Testville",
Nodes: []*tailcfg.DERPNode{{
Name: "99a",
RegionID: 99,
HostName: "derp99a.tailscale.com",
IPv4: "1.2.3.4",
}},
},
},
}
d, err := json.Marshal(initialCache)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(cacheFile, d, 0666); err != nil {
t.Fatal(err)
}
// Clear any existing cached DERP map(s)
cachedDERPMap.Store(nil)
// Load the cache
SetCachePath(cacheFile, t.Logf)
if cm := cachedDERPMap.Load(); !reflect.DeepEqual(initialCache, cm) {
t.Fatalf("cached map was %+v; want %+v", cm, initialCache)
}
// Remove the cache file on-disk, then re-set to the current value. If
// our equality comparison is working, we won't rewrite the file
// on-disk since the cached value won't have changed.
if err := os.Remove(cacheFile); err != nil {
t.Fatal(err)
}
UpdateCache(initialCache, t.Logf)
if _, err := os.Stat(cacheFile); !os.IsNotExist(err) {
t.Fatalf("got err=%v; expected to not find cache file", err)
}
// Now, update the cache with something slightly different and verify
// that we did re-write the file on-disk.
updatedCache := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
99: {
RegionID: 99,
RegionCode: "test",
RegionName: "Testville",
Nodes: []*tailcfg.DERPNode{ /* set below */ },
},
},
}
clonedNode := *initialCache.Regions[99].Nodes[0]
clonedNode.IPv4 = "1.2.3.5"
updatedCache.Regions[99].Nodes = append(updatedCache.Regions[99].Nodes, &clonedNode)
UpdateCache(updatedCache, t.Logf)
if st, err := os.Stat(cacheFile); err != nil {
t.Fatalf("could not stat cache file; err=%v", err)
} else if !st.Mode().IsRegular() || st.Size() == 0 {
t.Fatalf("didn't find non-empty regular file; mode=%v size=%d", st.Mode(), st.Size())
}
}
var extNetwork = flag.Bool("use-external-network", false, "use the external network in tests")
func TestLookup(t *testing.T) {
if !*extNetwork {
t.Skip("skipping test without --use-external-network")
}
logf, closeLogf := logger.LogfCloser(t.Logf)
defer closeLogf()
netMon, err := netmon.New(logf)
if err != nil {
t.Fatal(err)
}
resolver := &fallbackResolver{
logf: logf,
netMon: netMon,
waitForCompare: true,
}
addrs, err := resolver.Lookup(context.Background(), "controlplane.tailscale.com")
if err != nil {
t.Fatal(err)
}
t.Logf("addrs: %+v", addrs)
}