mirror of https://github.com/tailscale/tailscale/
net/dns: retrample resolve.conf when another process has trampled it
When using the resolve.conf file for setting DNS, it is possible that some other services will trample the file and overwrite our set DNS server. Experiments has shown this to be a racy error depending on how quickly processes start. Make an attempt to trample back the file a limited number of times if the file is changed. Updates #16635 Signed-off-by: Claus Lensbøl <claus@tailscale.com>cmol/resolveconf_trample_trample_back
parent
b7658a4ad2
commit
8085324449
@ -0,0 +1,109 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
|
||||
"github.com/illarion/gonotify/v3"
|
||||
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/eventbus/eventbustest"
|
||||
)
|
||||
|
||||
func TestDNSTrampleRecovery(t *testing.T) {
|
||||
HookWatchFile.Set(watchFile)
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolvPath := "/etc/resolv.conf"
|
||||
fs := directFS{prefix: tmp}
|
||||
readFile := func(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
b, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
bus := eventbustest.NewBus(t)
|
||||
eventbustest.LogAllEvents(t, bus)
|
||||
m := newDirectManagerOnFS(t.Logf, nil, bus, fs)
|
||||
defer m.Close()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
tw := eventbustest.NewWatcher(t, bus)
|
||||
|
||||
trample := "Hvem er det som tramper på min bro?"
|
||||
if err := fs.WriteFile(resolvPath, []byte(trample), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
synctest.Wait()
|
||||
|
||||
if err := eventbustest.Expect(tw, eventbustest.Type[TrampleDNS]()); err != nil {
|
||||
t.Errorf("did not see trample event: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// watchFile is generally copied from linuxtrample, but cancels the context
|
||||
// after one trample to end the test.
|
||||
func watchFile(ctx context.Context, dir, filename string, cb func()) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
const events = gonotify.IN_ATTRIB |
|
||||
gonotify.IN_CLOSE_WRITE |
|
||||
gonotify.IN_CREATE |
|
||||
gonotify.IN_DELETE |
|
||||
gonotify.IN_MODIFY |
|
||||
gonotify.IN_MOVE
|
||||
|
||||
watcher, err := gonotify.NewDirWatcher(ctx, events, dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NewDirWatcher: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.C:
|
||||
if event.Name == filename {
|
||||
cb()
|
||||
cancel()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue