mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 02:01:14 +01:00 
			
		
		
		
	Updates #cleanup Change-Id: I982175e74b0c8c5b3e01a573e5785e6596b7ac39 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			204 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			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)
 | 
						|
	}
 | 
						|
}
 |