mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
This commit extends the documentation effort by adding detailed explanatory comments to key CLI components and core functionality throughout the cmd/ directory. The changes focus on explaining WHY certain logic is needed, not just WHAT the code does, improving code maintainability and helping developers understand complex business decisions. Key improvements: - Main entry points: Document CLI initialization, logging setup, and cache configuration with reasoning for design decisions - DNS proxy core: Explain DNS proxy constants, data structures, and core processing pipeline for handling DNS queries - Service management: Document service command structure, configuration patterns, and platform-specific service handling - Logging infrastructure: Explain log buffer management, level encoders, and log formatting decisions for different use cases - Metrics and monitoring: Document Prometheus metrics structure, HTTP endpoints, and conditional metric collection for performance - Network handling: Explain Linux-specific network interface filtering, virtual interface detection, and DNS configuration management - Hostname validation: Document RFC1123 compliance and DNS naming standards for system compatibility - Mobile integration: Explain HTTP retry logic, fallback mechanisms, and mobile platform integration patterns - Connection management: Document connection wrapper design to prevent log pollution during process lifecycle Technical details: - Added explanatory comments to 11 additional files in cmd/cli/ - Maintained consistent documentation style and format - Preserved all existing functionality while improving code clarity - Enhanced understanding of complex business logic and platform-specific behavior These comments help future developers understand the reasoning behind complex decisions, making the codebase more maintainable and reducing the risk of incorrect modifications during maintenance.
154 lines
4.5 KiB
Go
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")
|
|
}
|
|
}
|
|
}
|