Merge branch 'main' into dev

This commit is contained in:
Owen
2026-04-22 12:09:50 -07:00
9 changed files with 245 additions and 2 deletions

View File

@@ -13,4 +13,10 @@ func SetupDNSOverride(interfaceName string, proxyIp netip.Addr) error {
// RestoreDNSOverride is a no-op on Android
func RestoreDNSOverride() error {
return nil
}
}
// CleanupStaleState is a no-op on Android as DNS configuration is handled by the VpnService API
func CleanupStaleState(interfaceName string) error {
_ = interfaceName
return nil
}

View File

@@ -61,3 +61,20 @@ func RestoreDNSOverride() error {
logger.Info("DNS configuration restored successfully")
return nil
}
// CleanupStaleState removes any stale DNS configuration left over from a previous
// unclean shutdown (e.g., system crash, power loss while tunnel was active).
// This function should be called early during startup, before any network operations,
// to ensure DNS is working properly.
//
// On macOS, this cleans up any scutil DNS keys that were created but not removed.
func CleanupStaleState(interfaceName string) error {
_ = interfaceName
if err := platform.CleanupStaleDarwinDNS(); err != nil {
logger.Warn("Failed to cleanup stale Darwin DNS config: %v", err)
return fmt.Errorf("Darwin DNS cleanup: %w", err)
}
logger.Info("Stale DNS state cleanup completed successfully")
return nil
}

View File

@@ -12,4 +12,10 @@ func SetupDNSOverride(interfaceName string, proxyIp netip.Addr) error {
// RestoreDNSOverride is a no-op on iOS as DNS configuration is handled by the system
func RestoreDNSOverride() error {
return nil
}
}
// CleanupStaleState is a no-op on iOS as DNS configuration is handled by the system
func CleanupStaleState(interfaceName string) error {
_ = interfaceName
return nil
}

View File

@@ -98,3 +98,49 @@ func RestoreDNSOverride() error {
logger.Info("DNS configuration restored successfully")
return nil
}
// CleanupStaleState removes any stale DNS configuration left over from a previous
// unclean shutdown (e.g., system crash, power loss while tunnel was active).
// This function should be called early during startup, before any network operations,
// to ensure DNS is working properly.
//
// It checks and cleans up stale state from all supported DNS managers:
// - NetworkManager: removes /etc/NetworkManager/conf.d/olm-dns.conf
// - resolvconf: removes entry for the provided interface
// - File-based: restores /etc/resolv.conf from backup if it exists
//
// This is safe to call even if no stale state exists.
func CleanupStaleState(interfaceName string) error {
var errs []error
// Clean up NetworkManager stale config
if err := platform.CleanupStaleNetworkManagerDNS(); err != nil {
logger.Warn("Failed to cleanup stale NetworkManager DNS config: %v", err)
errs = append(errs, fmt.Errorf("NetworkManager cleanup: %w", err))
} else {
logger.Debug("NetworkManager DNS cleanup completed")
}
// Clean up resolvconf stale entries for the provided interface
if err := platform.CleanupStaleResolvconfDNS(interfaceName); err != nil {
logger.Warn("Failed to cleanup stale resolvconf DNS config: %v", err)
errs = append(errs, fmt.Errorf("resolvconf cleanup: %w", err))
} else {
logger.Debug("resolvconf DNS cleanup completed")
}
// Clean up file-based stale backup
if err := platform.CleanupStaleFileDNS(); err != nil {
logger.Warn("Failed to cleanup stale file-based DNS config: %v", err)
errs = append(errs, fmt.Errorf("file DNS cleanup: %w", err))
} else {
logger.Debug("File-based DNS cleanup completed")
}
if len(errs) > 0 {
return fmt.Errorf("some DNS cleanup operations failed: %v", errs)
}
logger.Info("Stale DNS state cleanup completed successfully")
return nil
}

View File

@@ -61,3 +61,19 @@ func RestoreDNSOverride() error {
logger.Info("DNS configuration restored successfully")
return nil
}
// CleanupStaleState removes any stale DNS configuration left over from a previous
// unclean shutdown (e.g., system crash, power loss while tunnel was active).
// This function should be called early during startup, before any network operations,
// to ensure DNS is working properly.
//
// On Windows, DNS configuration is tied to the interface GUID. When the WireGuard
// interface is recreated, it gets a new GUID, so there's no stale state to clean up.
func CleanupStaleState(interfaceName string) error {
// Windows DNS configuration via registry is interface-specific.
// When the WireGuard interface is recreated, it gets a new GUID,
// so there's no leftover state to clean up from previous sessions.
_ = interfaceName
logger.Debug("Windows DNS cleanup: no stale state to clean (interface-specific)")
return nil
}

View File

@@ -417,3 +417,59 @@ func (d *DarwinDNSConfigurator) clearState() error {
logger.Debug("Cleared DNS state file")
return nil
}
// CleanupStaleDarwinDNS removes any stale DNS configuration left by the Darwin
// configurator from a previous unclean shutdown. This is a static function that can be
// called without creating a configurator instance, useful for cleanup before network operations.
func CleanupStaleDarwinDNS() error {
stateFilePath := getDNSStateFilePath()
// Check if state file exists
data, err := os.ReadFile(stateFilePath)
if err != nil {
if os.IsNotExist(err) {
// No state file, nothing to clean up
return nil
}
return fmt.Errorf("read state file: %w", err)
}
var state DNSPersistentState
if err := json.Unmarshal(data, &state); err != nil {
// Invalid state file, remove it
os.Remove(stateFilePath)
return nil
}
if len(state.CreatedKeys) == 0 {
// No keys to clean up
return nil
}
logger.Info("Found DNS state from previous session, cleaning up %d keys", len(state.CreatedKeys))
// Remove all keys from previous session using scutil directly
for _, key := range state.CreatedKeys {
logger.Debug("Removing leftover DNS key: %s", key)
cmd := fmt.Sprintf("open\nremove %s\nquit\n", key)
scutilCmd := exec.Command(scutilPath)
scutilCmd.Stdin = strings.NewReader(cmd)
if err := scutilCmd.Run(); err != nil {
logger.Warn("Failed to remove DNS key %s: %v", key, err)
}
}
// Clear state file
if err := os.Remove(stateFilePath); err != nil && !os.IsNotExist(err) {
logger.Warn("Failed to clear DNS state file: %v", err)
}
// Flush DNS cache after cleanup
cacheCmd := exec.Command(dscacheutilPath, "-flushcache")
_ = cacheCmd.Run()
killCmd := exec.Command("killall", "-HUP", "mDNSResponder")
_ = killCmd.Run()
return nil
}

View File

@@ -218,3 +218,27 @@ func copyFile(src, dst string) error {
return nil
}
// CleanupStaleFileDNS removes any stale DNS configuration left by the file-based
// configurator from a previous unclean shutdown. This is a static function that can be
// called without creating a configurator instance, useful for cleanup before network operations.
func CleanupStaleFileDNS() error {
// Check if backup file exists from a previous session
if _, err := os.Stat(resolvConfBackupPath); os.IsNotExist(err) {
// No backup file, nothing to clean up
return nil
}
// A backup exists, which means we crashed while DNS was configured
// Restore the original resolv.conf
if err := copyFile(resolvConfBackupPath, resolvConfPath); err != nil {
return fmt.Errorf("restore from backup during cleanup: %w", err)
}
// Remove backup file
if err := os.Remove(resolvConfBackupPath); err != nil {
return fmt.Errorf("remove backup file during cleanup: %w", err)
}
return nil
}

View File

@@ -323,3 +323,41 @@ func GetNetworkManagerVersion() (string, error) {
return version, nil
}
// CleanupStaleNetworkManagerDNS removes any stale DNS configuration left by NetworkManager
// configurator from a previous unclean shutdown. This is a static function that can be called
// without creating a configurator instance, useful for cleanup before network operations.
func CleanupStaleNetworkManagerDNS() error {
confPath := networkManagerConfDir + "/" + networkManagerDNSConfFile
// Check if our config file exists from a previous session
if _, err := os.Stat(confPath); os.IsNotExist(err) {
// No config file, nothing to clean up
return nil
}
// Remove the stale configuration file
if err := os.Remove(confPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("remove stale DNS config file: %w", err)
}
// Try to reload NetworkManager if it's available
if IsNetworkManagerAvailable() {
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("connect to system bus for reload: %w", err)
}
defer conn.Close()
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := obj.CallWithContext(ctx, networkManagerDest+".Reload", 0, uint32(0)).Store(); err != nil {
return fmt.Errorf("reload NetworkManager after cleanup: %w", err)
}
}
return nil
}

View File

@@ -219,3 +219,37 @@ func IsResolvconfAvailable() bool {
cmd := exec.Command(resolvconfCommand, "--version")
return cmd.Run() == nil
}
// CleanupStaleResolvconfDNS removes any stale DNS configuration left by the resolvconf
// configurator from a previous unclean shutdown. This is a static function that can be
// called without creating a configurator instance, useful for cleanup before network operations.
// The interfaceName parameter specifies which interface entry to clean up (typically "olm").
func CleanupStaleResolvconfDNS(interfaceName string) error {
if !IsResolvconfAvailable() {
// resolvconf not available, nothing to clean up
return nil
}
// Detect resolvconf implementation type
implType, err := detectResolvconfType()
if err != nil {
// Can't detect type, try default
implType = "resolvconf"
}
// Try to delete any existing entry for this interface
// This is idempotent - if no entry exists, resolvconf will just return success
var cmd *exec.Cmd
switch implType {
case "openresolv":
cmd = exec.Command(resolvconfCommand, "-f", "-d", interfaceName)
default:
cmd = exec.Command(resolvconfCommand, "-d", interfaceName)
}
// Ignore errors - the entry may not exist, which is fine
_ = cmd.Run()
return nil
}