You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/net/dns/direct_test.go

204 lines
4.9 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"context"
"errors"
"fmt"
"io/fs"
"net/netip"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
qt "github.com/frankban/quicktest"
"tailscale.com/util/dnsname"
)
func TestDirectManager(t *testing.T) {
tmp := t.TempDir()
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
testDirect(t, directFS{prefix: tmp})
}
type boundResolvConfFS struct {
directFS
}
func (fs boundResolvConfFS) Rename(old, new string) error {
if old == "/etc/resolv.conf" || new == "/etc/resolv.conf" {
return errors.New("cannot move to/from /etc/resolv.conf")
}
return fs.directFS.Rename(old, new)
}
func (fs boundResolvConfFS) Remove(name string) error {
if name == "/etc/resolv.conf" {
return errors.New("cannot remove /etc/resolv.conf")
}
return fs.directFS.Remove(name)
}
func TestDirectBrokenRename(t *testing.T) {
tmp := t.TempDir()
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
testDirect(t, boundResolvConfFS{directFS{prefix: tmp}})
}
func testDirect(t *testing.T, fs wholeFileFS) {
const orig = "nameserver 9.9.9.9 # orig"
resolvPath := "/etc/resolv.conf"
backupPath := "/etc/resolv.pre-tailscale-backup.conf"
if err := fs.WriteFile(resolvPath, []byte(orig), 0644); err != nil {
t.Fatal(err)
}
readFile := func(t *testing.T, path string) string {
t.Helper()
b, err := fs.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(b)
}
assertBaseState := func(t *testing.T) {
if got := readFile(t, resolvPath); got != orig {
t.Fatalf("resolv.conf:\n%s, want:\n%s", got, orig)
}
if _, err := fs.Stat(backupPath); !os.IsNotExist(err) {
t.Fatalf("resolv.conf backup: want it to be gone but: %v", err)
}
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m := directManager{logf: t.Logf, fs: fs, ctx: ctx, ctxClose: cancel}
if err := m.SetDNS(OSConfig{
Nameservers: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("8.8.4.4")},
SearchDomains: []dnsname.FQDN{"ts.net.", "ts-dns.test."},
MatchDomains: []dnsname.FQDN{"ignored."},
}); err != nil {
t.Fatal(err)
}
want := `# resolv.conf(5) file generated by tailscale
# For more info, see https://tailscale.com/s/resolvconf-overwrite
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN
nameserver 8.8.8.8
nameserver 8.8.4.4
search ts.net ts-dns.test
`
if got := readFile(t, resolvPath); got != want {
t.Fatalf("resolv.conf:\n%s, want:\n%s", got, want)
}
if got := readFile(t, backupPath); got != orig {
t.Fatalf("resolv.conf backup:\n%s, want:\n%s", got, orig)
}
// Test that a nil OSConfig cleans up resolv.conf.
if err := m.SetDNS(OSConfig{}); err != nil {
t.Fatal(err)
}
assertBaseState(t)
// Test that Close cleans up resolv.conf.
if err := m.SetDNS(OSConfig{Nameservers: []netip.Addr{netip.MustParseAddr("8.8.8.8")}}); err != nil {
t.Fatal(err)
}
if err := m.Close(); err != nil {
t.Fatal(err)
}
assertBaseState(t)
}
type brokenRemoveFS struct {
directFS
}
func (b brokenRemoveFS) Rename(old, new string) error {
return errors.New("nyaaah I'm a silly container!")
}
func (b brokenRemoveFS) Remove(name string) error {
if strings.Contains(name, "/etc/resolv.conf") {
return fmt.Errorf("Faking remove failure: %q", &fs.PathError{Err: syscall.EBUSY})
}
return b.directFS.Remove(name)
}
func TestDirectBrokenRemove(t *testing.T) {
tmp := t.TempDir()
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
testDirect(t, brokenRemoveFS{directFS{prefix: tmp}})
}
func TestReadResolve(t *testing.T) {
c := qt.New(t)
tests := []struct {
in string
want OSConfig
wantErr bool
}{
{in: `nameserver 192.168.0.100`,
want: OSConfig{
Nameservers: []netip.Addr{
netip.MustParseAddr("192.168.0.100"),
},
},
},
{in: `nameserver 192.168.0.100 # comment`,
want: OSConfig{
Nameservers: []netip.Addr{
netip.MustParseAddr("192.168.0.100"),
},
},
},
{in: `nameserver 192.168.0.100#`,
want: OSConfig{
Nameservers: []netip.Addr{
netip.MustParseAddr("192.168.0.100"),
},
},
},
{in: `nameserver #192.168.0.100`, wantErr: true},
{in: `nameserver`, wantErr: true},
{in: `# nameserver 192.168.0.100`, want: OSConfig{}},
{in: `nameserver192.168.0.100`, wantErr: true},
{in: `search tailscale.com`,
want: OSConfig{
SearchDomains: []dnsname.FQDN{"tailscale.com."},
},
},
{in: `search tailscale.com # comment`,
want: OSConfig{
SearchDomains: []dnsname.FQDN{"tailscale.com."},
},
},
{in: `searchtailscale.com`, wantErr: true},
{in: `search`, wantErr: true},
}
for _, test := range tests {
cfg, err := readResolv(strings.NewReader(test.in))
if test.wantErr {
c.Assert(err, qt.IsNotNil)
} else {
c.Assert(err, qt.IsNil)
}
c.Assert(cfg, qt.DeepEquals, test.want)
}
}