diff --git a/cmd/cli/os_linux.go b/cmd/cli/os_linux.go index 7fb692c..3036d03 100644 --- a/cmd/cli/os_linux.go +++ b/cmd/cli/os_linux.go @@ -9,10 +9,12 @@ import ( "net" "net/netip" "os/exec" + "path/filepath" "strings" "syscall" "time" + "github.com/fsnotify/fsnotify" "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6/client6" @@ -23,7 +25,10 @@ import ( "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) -const resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system" +const ( + resolvConfPath = "/etc/resolv.conf" + resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system" +) // allocate loopback ip // sudo ip a add 127.0.0.2/24 dev lo @@ -64,6 +69,11 @@ func setDNS(iface *net.Interface, nameservers []string) error { Nameservers: ns, SearchDomains: []dnsname.FQDN{}, } + defer func() { + if r.Mode() == "direct" { + go watchResolveConf(osConfig) + } + }() trySystemdResolve := false for i := 0; i < maxSetDNSAttempts; i++ { @@ -299,3 +309,59 @@ func sliceIndex[S ~[]E, E comparable](s S, v E) int { } return -1 } + +// watchResolveConf watches any changes to /etc/resolv.conf file, +// and reverting to the original config set by ctrld. +func watchResolveConf(oc dns.OSConfig) { + mainLog.Load().Debug().Msg("start watching /etc/resolv.conf file") + watcher, err := fsnotify.NewWatcher() + if err != nil { + mainLog.Load().Warn().Err(err).Msg("could not create watcher for /etc/resolv.conf") + return + } + + // We watch /etc instead of /etc/resolv.conf directly, + // see: https://github.com/fsnotify/fsnotify#watching-a-file-doesnt-work-well + watchDir := filepath.Dir(resolvConfPath) + if err := watcher.Add(watchDir); err != nil { + mainLog.Load().Warn().Err(err).Msg("could not add /etc/resolv.conf to watcher list") + return + } + + r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter. + if err != nil { + mainLog.Load().Error().Err(err).Msg("failed to create DNS OS configurator") + return + } + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Name != resolvConfPath { // skip if not /etc/resolv.conf changes. + continue + } + if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { + mainLog.Load().Debug().Msg("/etc/resolv.conf changes detected, reverting to ctrld setting") + if err := watcher.Remove(watchDir); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to pause watcher") + continue + } + if err := r.SetDNS(oc); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to revert /etc/resolv.conf changes") + } + if err := watcher.Add(watchDir); err != nil { + mainLog.Load().Error().Err(err).Msg("failed to continue running watcher") + return + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + mainLog.Load().Err(err).Msg("could not get event for /etc/resolv.conf") + } + } +}