tsnet apps in particular never use the Linux DNS OSManagers, so they don't need DBus, etc. I started to pull that all out into separate features so tsnet doesn't need to bring in DBus, but hit this first. Here you can see that tsnet (and the k8s-operator) no longer pulls in inotify. Updates #17206 Change-Id: I7af0f391f60c5e7dbeed7a080346f83262346591 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
f9c699812a
commit
798fddbe5c
@ -0,0 +1,13 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_linuxdnsfight
|
||||
|
||||
package buildfeatures |
||||
|
||||
// HasLinuxDNSFight is whether the binary was built with support for modular feature "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_linuxdnsfight" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasLinuxDNSFight = false |
||||
@ -0,0 +1,13 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_linuxdnsfight
|
||||
|
||||
package buildfeatures |
||||
|
||||
// HasLinuxDNSFight is whether the binary was built with support for modular feature "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_linuxdnsfight" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasLinuxDNSFight = true |
||||
@ -0,0 +1,8 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android && !ts_omit_linuxdnsfight
|
||||
|
||||
package condregister |
||||
|
||||
import _ "tailscale.com/feature/linuxdnsfight" |
||||
@ -0,0 +1,51 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
// Package linuxdnsfight provides Linux support for detecting DNS fights
|
||||
// (inotify watching of /etc/resolv.conf).
|
||||
package linuxdnsfight |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/illarion/gonotify/v3" |
||||
"tailscale.com/net/dns" |
||||
) |
||||
|
||||
func init() { |
||||
dns.HookWatchFile.Set(watchFile) |
||||
} |
||||
|
||||
// watchFile sets up an inotify watch for a given directory and
|
||||
// calls the callback function every time a particular file is changed.
|
||||
// The filename should be located in the provided directory.
|
||||
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() |
||||
} |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
} |
||||
} |
||||
@ -1,7 +1,9 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package dns |
||||
//go:build linux && !android
|
||||
|
||||
package linuxdnsfight |
||||
|
||||
import ( |
||||
"context" |
||||
@ -1,104 +0,0 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
package dns |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/illarion/gonotify/v3" |
||||
"tailscale.com/health" |
||||
) |
||||
|
||||
func (m *directManager) runFileWatcher() { |
||||
if err := watchFile(m.ctx, "/etc/", resolvConf, m.checkForFileTrample); err != nil { |
||||
// This is all best effort for now, so surface warnings to users.
|
||||
m.logf("dns: inotify: %s", err) |
||||
} |
||||
} |
||||
|
||||
// watchFile sets up an inotify watch for a given directory and
|
||||
// calls the callback function every time a particular file is changed.
|
||||
// The filename should be located in the provided directory.
|
||||
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() |
||||
} |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
} |
||||
} |
||||
|
||||
var resolvTrampleWarnable = health.Register(&health.Warnable{ |
||||
Code: "resolv-conf-overwritten", |
||||
Severity: health.SeverityMedium, |
||||
Title: "Linux DNS configuration issue", |
||||
Text: health.StaticMessage("Linux DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight"), |
||||
}) |
||||
|
||||
// checkForFileTrample checks whether /etc/resolv.conf has been trampled
|
||||
// by another program on the system. (e.g. a DHCP client)
|
||||
func (m *directManager) checkForFileTrample() { |
||||
m.mu.Lock() |
||||
want := m.wantResolvConf |
||||
lastWarn := m.lastWarnContents |
||||
m.mu.Unlock() |
||||
|
||||
if want == nil { |
||||
return |
||||
} |
||||
|
||||
cur, err := m.fs.ReadFile(resolvConf) |
||||
if err != nil { |
||||
m.logf("trample: read error: %v", err) |
||||
return |
||||
} |
||||
if bytes.Equal(cur, want) { |
||||
m.health.SetHealthy(resolvTrampleWarnable) |
||||
if lastWarn != nil { |
||||
m.mu.Lock() |
||||
m.lastWarnContents = nil |
||||
m.mu.Unlock() |
||||
m.logf("trample: resolv.conf again matches expected content") |
||||
} |
||||
return |
||||
} |
||||
if bytes.Equal(cur, lastWarn) { |
||||
// We already logged about this, so not worth doing it again.
|
||||
return |
||||
} |
||||
|
||||
m.mu.Lock() |
||||
m.lastWarnContents = cur |
||||
m.mu.Unlock() |
||||
|
||||
show := cur |
||||
if len(show) > 1024 { |
||||
show = show[:1024] |
||||
} |
||||
m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show) |
||||
m.health.SetUnhealthy(resolvTrampleWarnable, nil) |
||||
} |
||||
@ -1,10 +0,0 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux && !android && !ios
|
||||
|
||||
package dns |
||||
|
||||
func (m *directManager) runFileWatcher() { |
||||
// Not implemented on other platforms. Maybe it could resort to polling.
|
||||
} |
||||
Loading…
Reference in new issue