Files
ctrld/cmd/cli/resolvconf.go
Cuong Manh Le 54f58cc2e5 feat: capitalize all log messages for better readability
Capitalize the first letter of all log messages throughout the codebase
to improve readability and consistency in logging output.

Key improvements:
- All log messages now start with capital letters
- Consistent formatting across all logging statements
- Improved readability for debugging and monitoring
- Enhanced user experience with better formatted messages

Files updated:
- CLI commands and service management
- Internal client information discovery
- Network operations and configuration
- DNS resolver and proxy operations
- Platform-specific implementations

This completes the final phase of the logging improvement project,
ensuring all log messages follow consistent capitalization standards
for better readability and professional appearance.
2025-10-09 19:12:06 +07:00

154 lines
4.5 KiB
Go

package cli
import (
"net"
"net/netip"
"path/filepath"
"time"
"github.com/fsnotify/fsnotify"
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
)
// parseResolvConfNameservers reads the resolv.conf file and returns the nameservers found.
// Returns nil if no nameservers are found.
// This function parses the system DNS configuration to understand current nameserver settings
func (p *prog) parseResolvConfNameservers(path string) ([]string, error) {
return resolvconffile.NameserversFromFile(path)
}
// watchResolvConf watches any changes to /etc/resolv.conf file,
// and reverting to the original config set by ctrld.
// This ensures that DNS settings are not overridden by other applications or system processes
func (p *prog) watchResolvConf(iface *net.Interface, ns []netip.Addr, setDnsFn func(iface *net.Interface, ns []netip.Addr) error) {
resolvConfPath := "/etc/resolv.conf"
// Evaluating symbolics link to watch the target file that /etc/resolv.conf point to.
// This handles systems where resolv.conf is a symlink to another location
if rp, _ := filepath.EvalSymlinks(resolvConfPath); rp != "" {
resolvConfPath = rp
}
p.Debug().Msgf("Start watching %s file", resolvConfPath)
watcher, err := fsnotify.NewWatcher()
if err != nil {
p.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
// This is necessary because some systems don't properly notify on file changes
watchDir := filepath.Dir(resolvConfPath)
if err := watcher.Add(watchDir); err != nil {
p.Warn().Err(err).Msgf("Could not add %s to watcher list", watchDir)
return
}
for {
select {
case <-p.dnsWatcherStopCh:
return
case <-p.stopCh:
p.Debug().Msgf("Stopping watcher for %s", resolvConfPath)
return
case event, ok := <-watcher.Events:
if p.recoveryRunning.Load() {
return
}
if !ok {
return
}
if event.Name != resolvConfPath { // skip if not /etc/resolv.conf changes.
continue
}
if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) {
p.Debug().Msgf("/etc/resolv.conf changes detected, reading changes...")
// Convert expected nameservers to strings for comparison
// This allows us to detect when the resolv.conf has been modified
expectedNS := make([]string, len(ns))
for i, addr := range ns {
expectedNS[i] = addr.String()
}
var foundNS []string
var err error
maxRetries := 1
for retry := 0; retry < maxRetries; retry++ {
foundNS, err = p.parseResolvConfNameservers(resolvConfPath)
if err != nil {
p.Error().Err(err).Msg("Failed to read resolv.conf content")
break
}
// If we found nameservers, break out of retry loop
// This handles cases where the file is being written but not yet complete
if len(foundNS) > 0 {
break
}
// Only retry if we found no nameservers
// This handles temporary file states during updates
if retry < maxRetries-1 {
p.Debug().Msgf("resolv.conf has no nameserver entries, retry %d/%d in 2 seconds", retry+1, maxRetries)
select {
case <-p.stopCh:
return
case <-p.dnsWatcherStopCh:
return
case <-time.After(2 * time.Second):
continue
}
} else {
p.Debug().Msg("resolv.conf remained empty after all retries")
}
}
// If we found nameservers, check if they match what we expect
if len(foundNS) > 0 {
// Check if the nameservers match exactly what we expect
matches := len(foundNS) == len(expectedNS)
if matches {
for i := range foundNS {
if foundNS[i] != expectedNS[i] {
matches = false
break
}
}
}
p.Debug().
Strs("found", foundNS).
Strs("expected", expectedNS).
Bool("matches", matches).
Msg("checking nameservers")
// Only revert if the nameservers don't match
if !matches {
if err := watcher.Remove(watchDir); err != nil {
p.Error().Err(err).Msg("Failed to pause watcher")
continue
}
if err := setDnsFn(iface, ns); err != nil {
p.Error().Err(err).Msg("Failed to revert /etc/resolv.conf changes")
}
if err := watcher.Add(watchDir); err != nil {
p.Error().Err(err).Msg("Failed to continue running watcher")
return
}
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
p.Error().Err(err).Msg("Could not get event for /etc/resolv.conf")
}
}
}