mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-08-10 13:20:33 +02:00
cmd/cli: watch resolv.conf on all unix platforms
This commit is contained in:
@@ -9,12 +9,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6/client6"
|
"github.com/insomniacslk/dhcp/dhcpv6/client6"
|
||||||
@@ -25,11 +23,6 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
|
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
resolvConfPath = "/etc/resolv.conf"
|
|
||||||
resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// allocate loopback ip
|
// allocate loopback ip
|
||||||
// sudo ip a add 127.0.0.2/24 dev lo
|
// sudo ip a add 127.0.0.2/24 dev lo
|
||||||
func allocateIP(ip string) error {
|
func allocateIP(ip string) error {
|
||||||
@@ -69,12 +62,6 @@ func setDNS(iface *net.Interface, nameservers []string) error {
|
|||||||
Nameservers: ns,
|
Nameservers: ns,
|
||||||
SearchDomains: []dnsname.FQDN{},
|
SearchDomains: []dnsname.FQDN{},
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if r.Mode() == "direct" {
|
|
||||||
go watchResolveConf(osConfig)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
trySystemdResolve := false
|
trySystemdResolve := false
|
||||||
for i := 0; i < maxSetDNSAttempts; i++ {
|
for i := 0; i < maxSetDNSAttempts; i++ {
|
||||||
if err := r.SetDNS(osConfig); err != nil {
|
if err := r.SetDNS(osConfig); err != nil {
|
||||||
@@ -314,59 +301,3 @@ func sliceIndex[S ~[]E, E comparable](s S, v E) int {
|
|||||||
}
|
}
|
||||||
return -1
|
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -468,6 +468,13 @@ func (p *prog) setDNS() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debug().Msg("setting DNS successfully")
|
logger.Debug().Msg("setting DNS successfully")
|
||||||
|
if shouldWatchResolvconf() {
|
||||||
|
servers := make([]netip.Addr, len(nameservers))
|
||||||
|
for i := range nameservers {
|
||||||
|
servers[i] = netip.MustParseAddr(nameservers[i])
|
||||||
|
}
|
||||||
|
go watchResolvConf(netIface, servers, setResolvConf)
|
||||||
|
}
|
||||||
if allIfaces {
|
if allIfaces {
|
||||||
withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error {
|
withEachPhysicalInterfaces(netIface.Name, "set DNS", func(i *net.Interface) error {
|
||||||
return setDNS(i, nameservers)
|
return setDNS(i, nameservers)
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resolvConfPath = "/etc/resolv.conf"
|
||||||
|
resolvConfBackupFailedMsg = "open /etc/resolv.pre-ctrld-backup.conf: read-only file system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// watchResolvConf watches any changes to /etc/resolv.conf file,
|
||||||
|
// and reverting to the original config set by ctrld.
|
||||||
|
func watchResolvConf(iface *net.Interface, ns []netip.Addr, setDnsFn func(iface *net.Interface, ns []netip.Addr) error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := setDnsFn(iface, ns); 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setResolvConf sets the content of resolv.conf file using the given nameservers list.
|
||||||
|
func setResolvConf(iface *net.Interface, ns []netip.Addr) error {
|
||||||
|
servers := make([]string, len(ns))
|
||||||
|
for i := range ns {
|
||||||
|
servers[i] = ns[i].String()
|
||||||
|
}
|
||||||
|
return setDNS(iface, servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator.
|
||||||
|
func shouldWatchResolvconf() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build unix && !darwin
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"tailscale.com/util/dnsname"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setResolvConf sets the content of resolv.conf file using the given nameservers list.
|
||||||
|
func setResolvConf(iface *net.Interface, ns []netip.Addr) error {
|
||||||
|
r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oc := dns.OSConfig{
|
||||||
|
Nameservers: ns,
|
||||||
|
SearchDomains: []dnsname.FQDN{},
|
||||||
|
}
|
||||||
|
return r.SetDNS(oc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator.
|
||||||
|
func shouldWatchResolvconf() bool {
|
||||||
|
r, err := dns.NewOSConfigurator(func(format string, args ...any) {}, "lo") // interface name does not matter.
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch r.Mode() {
|
||||||
|
case "direct", "resolvconf":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setResolvConf sets the content of resolv.conf file using the given nameservers list.
|
||||||
|
func setResolvConf(_ *net.Interface, _ []netip.Addr) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldWatchResolvconf reports whether ctrld should watch changes to resolv.conf file with given OS configurator.
|
||||||
|
func shouldWatchResolvconf() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user