diff --git a/olm/dns_override_darwin.go b/olm/dns_override_darwin.go new file mode 100644 index 0000000..2badcd4 --- /dev/null +++ b/olm/dns_override_darwin.go @@ -0,0 +1,66 @@ +//go:build darwin && !ios + +package olm + +import ( + "fmt" + "net/netip" + + "github.com/fosrl/newt/logger" + "github.com/fosrl/olm/dns" + platform "github.com/fosrl/olm/dns/platform" +) + +// SetupDNSOverride configures the system DNS to use the DNS proxy on macOS +// Uses scutil for DNS configuration +func SetupDNSOverride(interfaceName string, dnsProxy *dns.DNSProxy) error { + if dnsProxy == nil { + return fmt.Errorf("DNS proxy is nil") + } + + var err error + configurator, err = platform.NewDarwinDNSConfigurator() + if err != nil { + return fmt.Errorf("failed to create Darwin DNS configurator: %w", err) + } + + logger.Info("Using Darwin scutil DNS configurator") + + // Get current DNS servers before changing + currentDNS, err := configurator.GetCurrentDNS() + if err != nil { + logger.Warn("Could not get current DNS: %v", err) + } else { + logger.Info("Current DNS servers: %v", currentDNS) + } + + // Set new DNS servers to point to our proxy + newDNS := []netip.Addr{ + dnsProxy.GetProxyIP(), + } + + logger.Info("Setting DNS servers to: %v", newDNS) + originalDNS, err := configurator.SetDNS(newDNS) + if err != nil { + return fmt.Errorf("failed to set DNS: %w", err) + } + + logger.Info("Original DNS servers backed up: %v", originalDNS) + return nil +} + +// RestoreDNSOverride restores the original DNS configuration +func RestoreDNSOverride() error { + if configurator == nil { + logger.Debug("No DNS configurator to restore") + return nil + } + + logger.Info("Restoring original DNS configuration") + if err := configurator.RestoreDNS(); err != nil { + return fmt.Errorf("failed to restore DNS: %w", err) + } + + logger.Info("DNS configuration restored successfully") + return nil +} diff --git a/olm/dns_override_unix.go b/olm/dns_override_unix.go new file mode 100644 index 0000000..10d816f --- /dev/null +++ b/olm/dns_override_unix.go @@ -0,0 +1,102 @@ +//go:build (linux && !android) || freebsd + +package olm + +import ( + "fmt" + "net/netip" + + "github.com/fosrl/newt/logger" + "github.com/fosrl/olm/dns" + platform "github.com/fosrl/olm/dns/platform" +) + +// SetupDNSOverride configures the system DNS to use the DNS proxy on Linux/FreeBSD +// Tries systemd-resolved, NetworkManager, resolvconf, or falls back to /etc/resolv.conf +func SetupDNSOverride(interfaceName string, dnsProxy *dns.DNSProxy) error { + if dnsProxy == nil { + return fmt.Errorf("DNS proxy is nil") + } + + var err error + + // Try systemd-resolved first (most modern) + if platform.IsSystemdResolvedAvailable() && interfaceName != "" { + configurator, err = platform.NewSystemdResolvedDNSConfigurator(interfaceName) + if err == nil { + logger.Info("Using systemd-resolved DNS configurator") + return setDNS(dnsProxy, configurator) + } + logger.Debug("systemd-resolved not available: %v", err) + } + + // Try NetworkManager (common on desktops) + if platform.IsNetworkManagerAvailable() && interfaceName != "" { + configurator, err = platform.NewNetworkManagerDNSConfigurator(interfaceName) + if err == nil { + logger.Info("Using NetworkManager DNS configurator") + return setDNS(dnsProxy, configurator) + } + logger.Debug("NetworkManager not available: %v", err) + } + + // Try resolvconf (common on older systems) + if platform.IsResolvconfAvailable() && interfaceName != "" { + configurator, err = platform.NewResolvconfDNSConfigurator(interfaceName) + if err == nil { + logger.Info("Using resolvconf DNS configurator") + return setDNS(dnsProxy, configurator) + } + logger.Debug("resolvconf not available: %v", err) + } + + // Fall back to direct file manipulation + configurator, err = platform.NewFileDNSConfigurator() + if err != nil { + return fmt.Errorf("failed to create file DNS configurator: %w", err) + } + + logger.Info("Using file-based DNS configurator") + return setDNS(dnsProxy, configurator) +} + +// setDNS is a helper function to set DNS and log the results +func setDNS(dnsProxy *dns.DNSProxy, conf platform.DNSConfigurator) error { + // Get current DNS servers before changing + currentDNS, err := conf.GetCurrentDNS() + if err != nil { + logger.Warn("Could not get current DNS: %v", err) + } else { + logger.Info("Current DNS servers: %v", currentDNS) + } + + // Set new DNS servers to point to our proxy + newDNS := []netip.Addr{ + dnsProxy.GetProxyIP(), + } + + logger.Info("Setting DNS servers to: %v", newDNS) + originalDNS, err := conf.SetDNS(newDNS) + if err != nil { + return fmt.Errorf("failed to set DNS: %w", err) + } + + logger.Info("Original DNS servers backed up: %v", originalDNS) + return nil +} + +// RestoreDNSOverride restores the original DNS configuration +func RestoreDNSOverride() error { + if configurator == nil { + logger.Debug("No DNS configurator to restore") + return nil + } + + logger.Info("Restoring original DNS configuration") + if err := configurator.RestoreDNS(); err != nil { + return fmt.Errorf("failed to restore DNS: %w", err) + } + + logger.Info("DNS configuration restored successfully") + return nil +} diff --git a/olm/dns_override_windows.go b/olm/dns_override_windows.go new file mode 100644 index 0000000..842723a --- /dev/null +++ b/olm/dns_override_windows.go @@ -0,0 +1,78 @@ +//go:build windows + +package olm + +import ( + "fmt" + "net/netip" + + "github.com/fosrl/newt/logger" + "github.com/fosrl/olm/dns" + platform "github.com/fosrl/olm/dns/platform" +) + +// SetupDNSOverride configures the system DNS to use the DNS proxy on Windows +// Uses registry-based configuration (requires interface GUID) +func SetupDNSOverride(interfaceName string, dnsProxy *dns.DNSProxy) error { + if dnsProxy == nil { + return fmt.Errorf("DNS proxy is nil") + } + + // On Windows, we need to get the interface GUID from the TUN device + // The interfaceName parameter is ignored on Windows + if tdev == nil { + return fmt.Errorf("TUN device is not available") + } + + guid, err := GetInterfaceGUIDString(tdev) + if err != nil { + return fmt.Errorf("failed to get interface GUID: %w", err) + } + + logger.Info("Retrieved interface GUID: %s for interface name: %s", guid, interfaceName) + + configurator, err = platform.NewWindowsDNSConfigurator(guid) + if err != nil { + return fmt.Errorf("failed to create Windows DNS configurator: %w", err) + } + + logger.Info("Using Windows registry DNS configurator for GUID: %s", guid) + + // Get current DNS servers before changing + currentDNS, err := configurator.GetCurrentDNS() + if err != nil { + logger.Warn("Could not get current DNS: %v", err) + } else { + logger.Info("Current DNS servers: %v", currentDNS) + } + + // Set new DNS servers to point to our proxy + newDNS := []netip.Addr{ + dnsProxy.GetProxyIP(), + } + + logger.Info("Setting DNS servers to: %v", newDNS) + originalDNS, err := configurator.SetDNS(newDNS) + if err != nil { + return fmt.Errorf("failed to set DNS: %w", err) + } + + logger.Info("Original DNS servers backed up: %v", originalDNS) + return nil +} + +// RestoreDNSOverride restores the original DNS configuration +func RestoreDNSOverride() error { + if configurator == nil { + logger.Debug("No DNS configurator to restore") + return nil + } + + logger.Info("Restoring original DNS configuration") + if err := configurator.RestoreDNS(); err != nil { + return fmt.Errorf("failed to restore DNS: %w", err) + } + + logger.Info("DNS configuration restored successfully") + return nil +} diff --git a/olm/interface_guid_stub.go b/olm/interface_guid_stub.go new file mode 100644 index 0000000..cf0ad6a --- /dev/null +++ b/olm/interface_guid_stub.go @@ -0,0 +1,15 @@ +//go:build !windows + +package olm + +import ( + "fmt" + + "golang.zx2c4.com/wireguard/tun" +) + +// GetInterfaceGUIDString is only implemented for Windows +// This stub is provided for compilation on other platforms +func GetInterfaceGUIDString(tunDevice tun.Device) (string, error) { + return "", fmt.Errorf("GetInterfaceGUIDString is only supported on Windows") +} diff --git a/olm/interface_guid_windows.go b/olm/interface_guid_windows.go new file mode 100644 index 0000000..64ba91d --- /dev/null +++ b/olm/interface_guid_windows.go @@ -0,0 +1,69 @@ +//go:build windows + +package olm + +import ( + "fmt" + "unsafe" + + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/tun" +) + +// GetInterfaceGUIDString retrieves the GUID string for a Windows TUN interface +// This is required for registry-based DNS configuration on Windows +func GetInterfaceGUIDString(tunDevice tun.Device) (string, error) { + if tunDevice == nil { + return "", fmt.Errorf("TUN device is nil") + } + + // The wireguard-go Windows TUN device has a LUID() method + // We need to use type assertion to access it + type nativeTun interface { + LUID() uint64 + } + + nativeDev, ok := tunDevice.(nativeTun) + if !ok { + return "", fmt.Errorf("TUN device does not support LUID retrieval (not a native Windows TUN device)") + } + + luid := nativeDev.LUID() + + // Convert LUID to GUID using Windows API + guid, err := luidToGUID(luid) + if err != nil { + return "", fmt.Errorf("failed to convert LUID to GUID: %w", err) + } + + return guid, nil +} + +// luidToGUID converts a Windows LUID (Locally Unique Identifier) to a GUID string +// using the Windows ConvertInterface* APIs +func luidToGUID(luid uint64) (string, error) { + var guid windows.GUID + + // Load the iphlpapi.dll and get the ConvertInterfaceLuidToGuid function + iphlpapi := windows.NewLazySystemDLL("iphlpapi.dll") + convertLuidToGuid := iphlpapi.NewProc("ConvertInterfaceLuidToGuid") + + // Call the Windows API + // NET_LUID is a 64-bit value on Windows + ret, _, err := convertLuidToGuid.Call( + uintptr(unsafe.Pointer(&luid)), + uintptr(unsafe.Pointer(&guid)), + ) + + if ret != 0 { + return "", fmt.Errorf("ConvertInterfaceLuidToGuid failed with code %d: %w", ret, err) + } + + // Format the GUID as a string with curly braces + guidStr := fmt.Sprintf("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], + guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]) + + return guidStr, nil +} diff --git a/olm/olm.go b/olm/olm.go index 1b4ca39..3e30d3a 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "net" - "net/netip" "runtime" "strings" "time" @@ -577,35 +576,10 @@ func StartTunnel(config TunnelConfig) { peerMonitor.Start() - configurator, err = platform.DetectBestConfigurator(interfaceName) - if err != nil { - log.Fatalf("Failed to detect DNS configurator: %v", err) - } - - fmt.Printf("Using DNS configurator: %s\n", configurator.Name()) - - // Get current DNS servers before changing - currentDNS, err := configurator.GetCurrentDNS() - if err != nil { - log.Printf("Warning: Could not get current DNS: %v", err) - } else { - fmt.Printf("Current DNS servers: %v\n", currentDNS) - } - - // Set new DNS servers - newDNS := []netip.Addr{ - dnsProxy.GetProxyIP(), - // netip.MustParseAddr("8.8.8.8"), // Google - } - - fmt.Printf("Setting DNS servers to: %v\n", newDNS) - originalDNS, err := configurator.SetDNS(newDNS) - if err != nil { - log.Fatalf("Failed to set DNS: %v", err) - } - - for _, addr := range originalDNS { - fmt.Printf("Original DNS server: %v\n", addr) + // Set up DNS override to use our DNS proxy + if err := SetupDNSOverride(interfaceName, dnsProxy); err != nil { + logger.Error("Failed to setup DNS override: %v", err) + return } if err := dnsProxy.Start(); err != nil { @@ -1202,6 +1176,11 @@ func StopTunnel() { Close() + // Restore original DNS configuration + if err := RestoreDNSOverride(); err != nil { + logger.Error("Failed to restore DNS: %v", err) + } + // Reset the connected state connected = false tunnelRunning = false