mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 20:26:47 +02:00
Test fixes for consistent pass rates under repeated runs (-count=N): Global state isolation: - appc: check metric deltas instead of absolute values - cmd/derper: initialize DNS caches before test - tsweb/varz: use unpublished expvar to avoid global registration - wgengine/netstack: check metric deltas instead of absolute values - net/dns: use SetForTest() with deferred restore for hooks Timeout and concurrency: - cmd/containerboot: increase wait loop timeouts for parallel load - tsconsensus: add deadline to waitFor(), use sync.Once for netns - tstest/integration: add tstest.Shard/Parallel, fix IPN bus watchers - net/netcheck: set testCaptivePortalDelay to prevent hangs - wgengine/magicsock: use Port:0, add timeout to callback wait - drive/driveimpl: use http.Server for proper shutdown Race conditions: - tstest/archtest: remove !race from build constraint - util/deephash: use local sink variable instead of package-level - net/art: switch to math/rand/v2 for thread-safe globals - tstest/integration: use Status() instead of MustStatus() from goroutines Test optimization: - net/udprelay: rewrite VNI test to avoid iterating all 16M values - ipn/ipnlocal: reset env vars between subtests - cmd/containerboot/serve: use SetWaitDurationForTest - tsnet: wait for service VIP in AllowedIPs before dialing Change-Id: Id6186fb8a45031920550a208ded77382e57cc016 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
111 lines
2.6 KiB
Go
111 lines
2.6 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// 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) {
|
|
restore := HookWatchFile.SetForTest(watchFile)
|
|
defer restore()
|
|
synctest.Test(t, func(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const 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.Errorf("Reading DNS config: %v", 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)
|
|
}
|
|
|
|
const 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)
|
|
|
|
const 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 the first call to cb() after the first 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()
|
|
}
|
|
}
|
|
}
|