diff --git a/control.go b/control.go index a62b3b6d..08747e7c 100644 --- a/control.go +++ b/control.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "os/exec" "sort" "strconv" "strings" @@ -1039,8 +1040,25 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { } } - if reqData.DNS.Port != 0 && reqData.DNS.Port != config.DNS.Port { + if reqData.DNS.Port != 0 { err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + + if errorIsAddrInUse(err) { + canAutofix := checkDNSStubListener() + if canAutofix && reqData.DNS.Autofix { + + err = disableDNSStubListener() + if err != nil { + log.Error("Couldn't disable DNSStubListener: %s", err) + } + + err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + canAutofix = false + } + + respData.DNS.CanAutofix = canAutofix + } + if err != nil { respData.DNS.Status = fmt.Sprintf("%v", err) } @@ -1054,6 +1072,56 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { } } +// Check if DNSStubListener is active +func checkDNSStubListener() bool { + cmd := exec.Command("systemctl", "is-enabled", "systemd-resolved") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err := cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + log.Error("command %s has failed: %v code:%d", + cmd.Path, err, cmd.ProcessState.ExitCode()) + return false + } + + cmd = exec.Command("grep", "-E", "#?DNSStubListener=yes", "/etc/systemd/resolved.conf") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err = cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + log.Error("command %s has failed: %v code:%d", + cmd.Path, err, cmd.ProcessState.ExitCode()) + return false + } + + return true +} + +// Deactivate DNSStubListener +func disableDNSStubListener() error { + cmd := exec.Command("sed", "-r", "-i.orig", "s/#?DNSStubListener=yes/DNSStubListener=no/g", "/etc/systemd/resolved.conf") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err := cmd.Output() + if err != nil { + return err + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("process %s exited with an error: %d", + cmd.Path, cmd.ProcessState.ExitCode()) + } + + cmd = exec.Command("systemctl", "reload-or-restart", "systemd-resolved") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err = cmd.Output() + if err != nil { + return err + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("process %s exited with an error: %d", + cmd.Path, cmd.ProcessState.ExitCode()) + } + + return nil +} + type applyConfigReqEnt struct { IP string `json:"ip"` Port int `json:"port"` diff --git a/helpers.go b/helpers.go index 184789d4..e4bb0d10 100644 --- a/helpers.go +++ b/helpers.go @@ -15,6 +15,7 @@ import ( "runtime" "strconv" "strings" + "syscall" "time" "github.com/AdguardTeam/dnsproxy/upstream" @@ -346,6 +347,31 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err return nil, firstErr } +// check if error is "address already in use" +func errorIsAddrInUse(err error) bool { + errOpError, ok := err.(*net.OpError) + if !ok { + return false + } + + errSyscallError, ok := errOpError.Err.(*os.SyscallError) + if !ok { + return false + } + + errErrno, ok := errSyscallError.Err.(syscall.Errno) + if !ok { + return false + } + + if runtime.GOOS == "windows" { + const WSAEADDRINUSE = 10048 + return errErrno == WSAEADDRINUSE + } + + return errErrno == syscall.EADDRINUSE +} + // --------------------- // debug logging helpers // ---------------------