package dns import ( "dove/config" "fmt" "os" "os/exec" "runtime" "strings" ) type SystemDnsStatus struct { Configured bool `json:"configured"` Address string `json:"address"` Platform string `json:"platform"` } func CheckSystemDns() SystemDnsStatus { doveAddress := fmt.Sprintf("%s", config.BindAddress) status := SystemDnsStatus{ Address: fmt.Sprintf("%s:%d", config.BindAddress, config.DnsPort), Platform: runtime.GOOS, } switch runtime.GOOS { case "linux": status.Configured = checkLinuxDns(doveAddress) case "darwin": status.Configured = checkDarwinDns(doveAddress) case "windows": status.Configured = checkWindowsDns(doveAddress) } return status } func ConfigureSystemDns() error { doveAddress := config.BindAddress switch runtime.GOOS { case "linux": return configureLinuxDns(doveAddress) case "darwin": return configureDarwinDns(doveAddress) case "windows": return configureWindowsDns(doveAddress) } return fmt.Errorf("Unsupported platform: %s.", runtime.GOOS) } func DisableSystemDns() error { doveAddress := config.BindAddress switch runtime.GOOS { case "linux": return disableLinuxDns(doveAddress) case "darwin": return disableDarwinDns(doveAddress) case "windows": return disableWindowsDns(doveAddress) } return fmt.Errorf("Unsupported platform: %s.", runtime.GOOS) } func checkLinuxDns(doveAddress string) bool { if isSystemdResolved() { output, commandError := exec.Command("resolvectl", "dns").Output() if commandError != nil { return false } return strings.Contains(string(output), doveAddress) } resolvContents, readError := os.ReadFile("/etc/resolv.conf") if readError != nil { return false } expectedLine := fmt.Sprintf("nameserver %s", doveAddress) for _, line := range strings.Split(string(resolvContents), "\n") { if strings.TrimSpace(line) == expectedLine { return true } } return false } func configureLinuxDns(doveAddress string) error { if isSystemdResolved() { activeInterfaces, interfaceError := getActiveLinuxInterfaces() if interfaceError != nil { return interfaceError } for _, networkInterface := range activeInterfaces { configureError := exec.Command("resolvectl", "dns", networkInterface, doveAddress).Run() if configureError != nil { return fmt.Errorf("Failed to configure DNS on %s: %w.", networkInterface, configureError) } } return nil } return prependNameserverToResolvConf(doveAddress) } func disableLinuxDns(doveAddress string) error { if isSystemdResolved() { activeInterfaces, interfaceError := getActiveLinuxInterfaces() if interfaceError != nil { return interfaceError } for _, networkInterface := range activeInterfaces { exec.Command("resolvectl", "revert", networkInterface).Run() } return nil } return removeNameserverFromResolvConf(doveAddress) } func isSystemdResolved() bool { _, lookupError := exec.LookPath("resolvectl") if lookupError != nil { return false } serviceCheckError := exec.Command("systemctl", "is-active", "--quiet", "systemd-resolved").Run() return serviceCheckError == nil } func getActiveLinuxInterfaces() ([]string, error) { output, commandError := exec.Command("ip", "-o", "link", "show", "up").Output() if commandError != nil { return nil, fmt.Errorf("Failed to list network interfaces: %w.", commandError) } var activeInterfaces []string for _, line := range strings.Split(string(output), "\n") { fields := strings.Fields(line) if len(fields) >= 2 { interfaceName := strings.TrimSuffix(fields[1], ":") if interfaceName != "lo" { activeInterfaces = append(activeInterfaces, interfaceName) } } } if len(activeInterfaces) == 0 { return nil, fmt.Errorf("No active network interfaces found.") } return activeInterfaces, nil } func prependNameserverToResolvConf(doveAddress string) error { resolvContents, readError := os.ReadFile("/etc/resolv.conf") if readError != nil { return fmt.Errorf("Failed to read /etc/resolv.conf: %w.", readError) } newLine := fmt.Sprintf("nameserver %s", doveAddress) existingLines := strings.Split(string(resolvContents), "\n") for _, line := range existingLines { if strings.TrimSpace(line) == newLine { return nil } } updatedContents := newLine + "\n" + string(resolvContents) return os.WriteFile("/etc/resolv.conf", []byte(updatedContents), 0644) } func removeNameserverFromResolvConf(doveAddress string) error { resolvContents, readError := os.ReadFile("/etc/resolv.conf") if readError != nil { return fmt.Errorf("Failed to read /etc/resolv.conf: %w.", readError) } targetLine := fmt.Sprintf("nameserver %s", doveAddress) existingLines := strings.Split(string(resolvContents), "\n") var filteredLines []string for _, line := range existingLines { if strings.TrimSpace(line) != targetLine { filteredLines = append(filteredLines, line) } } return os.WriteFile("/etc/resolv.conf", []byte(strings.Join(filteredLines, "\n")), 0644) } func checkDarwinDns(doveAddress string) bool { output, commandError := exec.Command("scutil", "--dns").Output() if commandError != nil { return false } return strings.Contains(string(output), doveAddress) } func configureDarwinDns(doveAddress string) error { output, commandError := exec.Command("networksetup", "-listallnetworkservices").Output() if commandError != nil { return fmt.Errorf("Failed to list network services: %w.", commandError) } lines := strings.Split(string(output), "\n") for _, serviceName := range lines { serviceName = strings.TrimSpace(serviceName) if serviceName == "" || strings.HasPrefix(serviceName, "An asterisk") { continue } existingDns, _ := exec.Command("networksetup", "-getdnsservers", serviceName).Output() existingServers := strings.TrimSpace(string(existingDns)) var dnsServers []string dnsServers = append(dnsServers, doveAddress) if existingServers != "" && !strings.Contains(existingServers, "aren't any") { for _, server := range strings.Split(existingServers, "\n") { server = strings.TrimSpace(server) if server != "" && server != doveAddress { dnsServers = append(dnsServers, server) } } } configureError := exec.Command("networksetup", "-setdnsservers", serviceName, strings.Join(dnsServers, " ")).Run() if configureError != nil { return fmt.Errorf("Failed to configure DNS on %s: %w.", serviceName, configureError) } } return nil } func disableDarwinDns(doveAddress string) error { output, commandError := exec.Command("networksetup", "-listallnetworkservices").Output() if commandError != nil { return fmt.Errorf("Failed to list network services: %w.", commandError) } lines := strings.Split(string(output), "\n") for _, serviceName := range lines { serviceName = strings.TrimSpace(serviceName) if serviceName == "" || strings.HasPrefix(serviceName, "An asterisk") { continue } existingDns, _ := exec.Command("networksetup", "-getdnsservers", serviceName).Output() existingServers := strings.TrimSpace(string(existingDns)) var remainingServers []string if existingServers != "" && !strings.Contains(existingServers, "aren't any") { for _, server := range strings.Split(existingServers, "\n") { server = strings.TrimSpace(server) if server != "" && server != doveAddress { remainingServers = append(remainingServers, server) } } } if len(remainingServers) == 0 { exec.Command("networksetup", "-setdnsservers", serviceName, "Empty").Run() } else { exec.Command("networksetup", "-setdnsservers", serviceName, strings.Join(remainingServers, " ")).Run() } } return nil } func checkWindowsDns(doveAddress string) bool { output, commandError := exec.Command("powershell", "-Command", "Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses").Output() if commandError != nil { return false } return strings.Contains(string(output), doveAddress) } func configureWindowsDns(doveAddress string) error { output, commandError := exec.Command("powershell", "-Command", "Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -ExpandProperty InterfaceAlias").Output() if commandError != nil { return fmt.Errorf("Failed to list network adapters: %w.", commandError) } adapters := strings.Split(strings.TrimSpace(string(output)), "\n") for _, adapterName := range adapters { adapterName = strings.TrimSpace(adapterName) if adapterName == "" { continue } existingOutput, _ := exec.Command("powershell", "-Command", fmt.Sprintf("(Get-DnsClientServerAddress -InterfaceAlias '%s' -AddressFamily IPv4).ServerAddresses -join ','", adapterName)).Output() existingServers := strings.TrimSpace(string(existingOutput)) var dnsServers []string dnsServers = append(dnsServers, fmt.Sprintf("'%s'", doveAddress)) if existingServers != "" { for _, server := range strings.Split(existingServers, ",") { server = strings.TrimSpace(server) if server != "" && server != doveAddress { dnsServers = append(dnsServers, fmt.Sprintf("'%s'", server)) } } } setCommand := fmt.Sprintf("Set-DnsClientServerAddress -InterfaceAlias '%s' -ServerAddresses (%s)", adapterName, strings.Join(dnsServers, ",")) configureError := exec.Command("powershell", "-Command", setCommand).Run() if configureError != nil { return fmt.Errorf("Failed to configure DNS on %s: %w.", adapterName, configureError) } } return nil } func disableWindowsDns(doveAddress string) error { output, commandError := exec.Command("powershell", "-Command", "Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -ExpandProperty InterfaceAlias").Output() if commandError != nil { return fmt.Errorf("Failed to list network adapters: %w.", commandError) } adapters := strings.Split(strings.TrimSpace(string(output)), "\n") for _, adapterName := range adapters { adapterName = strings.TrimSpace(adapterName) if adapterName == "" { continue } existingOutput, _ := exec.Command("powershell", "-Command", fmt.Sprintf("(Get-DnsClientServerAddress -InterfaceAlias '%s' -AddressFamily IPv4).ServerAddresses -join ','", adapterName)).Output() var remainingServers []string for _, server := range strings.Split(strings.TrimSpace(string(existingOutput)), ",") { server = strings.TrimSpace(server) if server != "" && server != doveAddress { remainingServers = append(remainingServers, fmt.Sprintf("'%s'", server)) } } if len(remainingServers) == 0 { exec.Command("powershell", "-Command", fmt.Sprintf("Set-DnsClientServerAddress -InterfaceAlias '%s' -ResetServerAddresses", adapterName)).Run() } else { setCommand := fmt.Sprintf("Set-DnsClientServerAddress -InterfaceAlias '%s' -ServerAddresses (%s)", adapterName, strings.Join(remainingServers, ",")) exec.Command("powershell", "-Command", setCommand).Run() } } return nil }