mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +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)
 | |
| 	}
 | |
| }
 |