Pull request: 4095 fix duplicating port

Merge in DNS/adguard-home from 4095-port-3000 to master

Updates #4095.

Squashed commit of the following:

commit 968cc806264898523d29c4ec20b3ce6a69abb09c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:26:33 2022 +0300

    home: fix typo

commit 03c6798db6a4ca726a7b5a683e475a8a74f79fe1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:20:34 2022 +0300

    all: more naming imps

commit d3d417fcb24a1859f53a743b3533faa81b6bef19
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:10:14 2022 +0300

    aghalgo: rename into aghalg

commit 6e106006d07a747ff4ddf1271532106c3a3e2b20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:05:43 2022 +0300

    all: imp names, docs

commit 12c8d9fde0d0cc5b953da30b042171ba7c53da5d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 19:57:21 2022 +0300

    all: fix log of changes

commit 49c7a705b9b1ad8f2ef68fa807f9b6b8c447b421
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 19:51:00 2022 +0300

    home: fix duplicating port 3000
This commit is contained in:
Eugene Burkov 2022-01-19 20:45:50 +03:00
parent eb15304ff4
commit d82b290251
7 changed files with 140 additions and 107 deletions

View File

@ -31,6 +31,7 @@ TODO(a.garipov): Remove this deprecation, if v0.108.0 is released before the Go
### Fixed ### Fixed
- Wrong set of ports checked for duplicates during the initial setup ([#4095]).
- Incorrectly invalidated service domains ([#4120]). - Incorrectly invalidated service domains ([#4120]).
- Poor testing of domain-specific upstream servers ([#4074]). - Poor testing of domain-specific upstream servers ([#4074]).
- Omitted aliases of hosts specified by another line within the OS's hosts file - Omitted aliases of hosts specified by another line within the OS's hosts file
@ -43,6 +44,7 @@ TODO(a.garipov): Remove this deprecation, if v0.108.0 is released before the Go
[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
[#4074]: https://github.com/AdguardTeam/AdGuardHome/issues/4074 [#4074]: https://github.com/AdguardTeam/AdGuardHome/issues/4074
[#4079]: https://github.com/AdguardTeam/AdGuardHome/issues/4079 [#4079]: https://github.com/AdguardTeam/AdGuardHome/issues/4079
[#4095]: https://github.com/AdguardTeam/AdGuardHome/issues/4095
[#4120]: https://github.com/AdguardTeam/AdGuardHome/issues/4120 [#4120]: https://github.com/AdguardTeam/AdGuardHome/issues/4120
[#4133]: https://github.com/AdguardTeam/AdGuardHome/issues/4133 [#4133]: https://github.com/AdguardTeam/AdGuardHome/issues/4133

View File

@ -1,7 +1,7 @@
// Package aghalgo contains common generic algorithms and data structures. // Package aghalg contains common generic algorithms and data structures.
// //
// TODO(a.garipov): Update to use type parameters in Go 1.18. // TODO(a.garipov): Update to use type parameters in Go 1.18.
package aghalgo package aghalg
import ( import (
"fmt" "fmt"
@ -14,20 +14,20 @@ import (
// TODO(a.garipov): Remove in Go 1.18. // TODO(a.garipov): Remove in Go 1.18.
type comparable = interface{} type comparable = interface{}
// UniquenessValidator allows validating uniqueness of comparable items. // UniqChecker allows validating uniqueness of comparable items.
type UniquenessValidator map[comparable]int64 type UniqChecker map[comparable]int64
// Add adds a value to the validator. v must not be nil. // Add adds a value to the validator. v must not be nil.
func (v UniquenessValidator) Add(elems ...comparable) { func (uc UniqChecker) Add(elems ...comparable) {
for _, e := range elems { for _, e := range elems {
v[e]++ uc[e]++
} }
} }
// Merge returns a validator containing data from both v and other. // Merge returns a validator containing data from both v and other.
func (v UniquenessValidator) Merge(other UniquenessValidator) (merged UniquenessValidator) { func (uc UniqChecker) Merge(other UniqChecker) (merged UniqChecker) {
merged = make(UniquenessValidator, len(v)+len(other)) merged = make(UniqChecker, len(uc)+len(other))
for elem, num := range v { for elem, num := range uc {
merged[elem] += num merged[elem] += num
} }
@ -41,9 +41,9 @@ func (v UniquenessValidator) Merge(other UniquenessValidator) (merged Uniqueness
// Validate returns an error enumerating all elements that aren't unique. // Validate returns an error enumerating all elements that aren't unique.
// isBefore is an optional sorting function to make the error message // isBefore is an optional sorting function to make the error message
// deterministic. // deterministic.
func (v UniquenessValidator) Validate(isBefore func(a, b comparable) (less bool)) (err error) { func (uc UniqChecker) Validate(isBefore func(a, b comparable) (less bool)) (err error) {
var dup []comparable var dup []comparable
for elem, num := range v { for elem, num := range uc {
if num > 1 { if num > 1 {
dup = append(dup, elem) dup = append(dup, elem)
} }
@ -62,13 +62,13 @@ func (v UniquenessValidator) Validate(isBefore func(a, b comparable) (less bool)
return fmt.Errorf("duplicated values: %v", dup) return fmt.Errorf("duplicated values: %v", dup)
} }
// IntIsBefore is a helper sort function for UniquenessValidator.Validate. // IntIsBefore is a helper sort function for UniqChecker.Validate.
// a and b must be of type int. // a and b must be of type int.
func IntIsBefore(a, b comparable) (less bool) { func IntIsBefore(a, b comparable) (less bool) {
return a.(int) < b.(int) return a.(int) < b.(int)
} }
// StringIsBefore is a helper sort function for UniquenessValidator.Validate. // StringIsBefore is a helper sort function for UniqChecker.Validate.
// a and b must be of type string. // a and b must be of type string.
func StringIsBefore(a, b comparable) (less bool) { func StringIsBefore(a, b comparable) (less bool) {
return a.(string) < b.(string) return a.(string) < b.(string)

View File

@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghalgo" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -214,7 +214,7 @@ func validateAccessSet(list *accessListJSON) (err error) {
} }
merged := allowed.Merge(disallowed) merged := allowed.Merge(disallowed)
err = merged.Validate(aghalgo.StringIsBefore) err = merged.Validate(aghalg.StringIsBefore)
if err != nil { if err != nil {
return fmt.Errorf("items in allowed and disallowed clients intersect: %w", err) return fmt.Errorf("items in allowed and disallowed clients intersect: %w", err)
} }
@ -223,13 +223,13 @@ func validateAccessSet(list *accessListJSON) (err error) {
} }
// validateStrUniq returns an informative error if clients are not unique. // validateStrUniq returns an informative error if clients are not unique.
func validateStrUniq(clients []string) (uv aghalgo.UniquenessValidator, err error) { func validateStrUniq(clients []string) (uc aghalg.UniqChecker, err error) {
uv = make(aghalgo.UniquenessValidator, len(clients)) uc = make(aghalg.UniqChecker, len(clients))
for _, c := range clients { for _, c := range clients {
uv.Add(c) uc.Add(c)
} }
return uv, uv.Validate(aghalgo.StringIsBefore) return uc, uc.Validate(aghalg.StringIsBefore)
} }
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) { func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {

View File

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/AdguardTeam/AdGuardHome/internal/aghalgo" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
@ -288,9 +288,9 @@ func parseConfig() (err error) {
return err return err
} }
uv := aghalgo.UniquenessValidator{} uc := aghalg.UniqChecker{}
addPorts( addPorts(
uv, uc,
config.BindPort, config.BindPort,
config.BetaBindPort, config.BetaBindPort,
config.DNS.Port, config.DNS.Port,
@ -298,14 +298,14 @@ func parseConfig() (err error) {
if config.TLS.Enabled { if config.TLS.Enabled {
addPorts( addPorts(
uv, uc,
config.TLS.PortHTTPS, config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS, config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC, config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt, config.TLS.PortDNSCrypt,
) )
} }
if err = uv.Validate(aghalgo.IntIsBefore); err != nil { if err = uc.Validate(aghalg.IntIsBefore); err != nil {
return fmt.Errorf("validating ports: %w", err) return fmt.Errorf("validating ports: %w", err)
} }
@ -321,10 +321,10 @@ func parseConfig() (err error) {
} }
// addPorts is a helper for ports validation. It skips zero ports. // addPorts is a helper for ports validation. It skips zero ports.
func addPorts(uv aghalgo.UniquenessValidator, ports ...int) { func addPorts(uc aghalg.UniqChecker, ports ...int) {
for _, p := range ports { for _, p := range ports {
if p != 0 { if p != 0 {
uv.Add(p) uc.Add(p)
} }
} }
} }

View File

@ -14,7 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalgo" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/AdGuardHome/internal/version"
@ -73,19 +73,19 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request
} }
} }
type checkConfigReqEnt struct { type checkConfReqEnt struct {
IP net.IP `json:"ip"` IP net.IP `json:"ip"`
Port int `json:"port"` Port int `json:"port"`
Autofix bool `json:"autofix"` Autofix bool `json:"autofix"`
} }
type checkConfigReq struct { type checkConfReq struct {
Web checkConfigReqEnt `json:"web"` Web checkConfReqEnt `json:"web"`
DNS checkConfigReqEnt `json:"dns"` DNS checkConfReqEnt `json:"dns"`
SetStaticIP bool `json:"set_static_ip"` SetStaticIP bool `json:"set_static_ip"`
} }
type checkConfigRespEnt struct { type checkConfRespEnt struct {
Status string `json:"status"` Status string `json:"status"`
CanAutofix bool `json:"can_autofix"` CanAutofix bool `json:"can_autofix"`
} }
@ -96,79 +96,110 @@ type staticIPJSON struct {
Error string `json:"error"` Error string `json:"error"`
} }
type checkConfigResp struct { type checkConfResp struct {
StaticIP staticIPJSON `json:"static_ip"` StaticIP staticIPJSON `json:"static_ip"`
Web checkConfigRespEnt `json:"web"` Web checkConfRespEnt `json:"web"`
DNS checkConfigRespEnt `json:"dns"` DNS checkConfRespEnt `json:"dns"`
} }
// Check if ports are available, respond with results // validateWeb returns error is the web part if the initial configuration can't
func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { // be set.
reqData := checkConfigReq{} func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) {
respData := checkConfigResp{} defer func() { err = errors.Annotate(err, "validating ports: %w") }()
err := json.NewDecoder(r.Body).Decode(&reqData) port := req.Web.Port
addPorts(uc, config.BetaBindPort, port)
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
// Avoid duplicating the error into the status of DNS.
uc[port] = 1
return err
}
switch port {
case 0, config.BindPort:
return nil
default:
// Go on and check the port binding only if it's not zero or won't be
// unbound after install.
}
return aghnet.CheckPort("tcp", req.Web.IP, port)
}
// validateDNS returns error if the DNS part of the initial configuration can't
// be set. autofix is true if the port can be unbound by AdGuard Home
// automatically.
func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, err error) {
defer func() { err = errors.Annotate(err, "validating ports: %w") }()
port := req.DNS.Port
addPorts(uc, port)
if err = uc.Validate(aghalg.IntIsBefore); err != nil {
return false, err
}
switch port {
case 0:
return false, nil
case config.BindPort:
// Go on and only check the UDP port since the TCP one is already bound
// by AdGuard Home for web interface.
default:
// Check TCP as well.
err = aghnet.CheckPort("tcp", req.DNS.IP, port)
if err != nil {
return false, err
}
}
err = aghnet.CheckPort("udp", req.DNS.IP, port)
if !aghnet.IsAddrInUse(err) {
return false, err
}
// Try to fix automatically.
canAutofix = checkDNSStubListener()
if canAutofix && req.DNS.Autofix {
if derr := disableDNSStubListener(); derr != nil {
log.Error("disabling DNSStubListener: %s", err)
}
err = aghnet.CheckPort("udp", req.DNS.IP, port)
canAutofix = false
}
return canAutofix, err
}
// handleInstallCheckConfig handles the /check_config endpoint.
func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
req := &checkConfReq{}
err := json.NewDecoder(r.Body).Decode(req)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err) aghhttp.Error(r, w, http.StatusBadRequest, "decoding the request: %s", err)
return return
} }
uv := aghalgo.UniquenessValidator{} resp := &checkConfResp{}
addPorts( uc := aghalg.UniqChecker{}
uv,
config.BindPort, if err = req.validateWeb(uc); err != nil {
config.BetaBindPort, resp.Web.Status = err.Error()
reqData.Web.Port,
)
if err = uv.Validate(aghalgo.IntIsBefore); err != nil {
err = fmt.Errorf("validating ports: %w", err)
respData.Web.Status = err.Error()
} else if reqData.Web.Port != 0 {
err = aghnet.CheckPort("tcp", reqData.Web.IP, reqData.Web.Port)
if err != nil {
respData.Web.Status = err.Error()
}
} }
addPorts(uv, reqData.DNS.Port) if resp.DNS.CanAutofix, err = req.validateDNS(uc); err != nil {
if err = uv.Validate(aghalgo.IntIsBefore); err != nil { resp.DNS.Status = err.Error()
err = fmt.Errorf("validating ports: %w", err) } else if !req.DNS.IP.IsUnspecified() {
respData.DNS.Status = err.Error() resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP)
} else if reqData.DNS.Port != 0 {
err = aghnet.CheckPort("udp", reqData.DNS.IP, reqData.DNS.Port)
if aghnet.IsAddrInUse(err) {
canAutofix := checkDNSStubListener()
if canAutofix && reqData.DNS.Autofix {
err = disableDNSStubListener()
if err != nil {
log.Error("Couldn't disable DNSStubListener: %s", err)
}
err = aghnet.CheckPort("udp", reqData.DNS.IP, reqData.DNS.Port)
canAutofix = false
}
respData.DNS.CanAutofix = canAutofix
}
if err == nil {
err = aghnet.CheckPort("tcp", reqData.DNS.IP, reqData.DNS.Port)
}
if err != nil {
respData.DNS.Status = err.Error()
} else if !reqData.DNS.IP.IsUnspecified() {
respData.StaticIP = handleStaticIP(reqData.DNS.IP, reqData.SetStaticIP)
}
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(respData) err = json.NewEncoder(w).Encode(resp)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to marshal JSON: %s", err) aghhttp.Error(r, w, http.StatusInternalServerError, "encoding the response: %s", err)
return return
} }
@ -494,13 +525,13 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
return return
} }
nonBetaReqData := checkConfigReq{ nonBetaReqData := checkConfReq{
Web: checkConfigReqEnt{ Web: checkConfReqEnt{
IP: reqData.Web.IP[0], IP: reqData.Web.IP[0],
Port: reqData.Web.Port, Port: reqData.Web.Port,
Autofix: reqData.Web.Autofix, Autofix: reqData.Web.Autofix,
}, },
DNS: checkConfigReqEnt{ DNS: checkConfReqEnt{
IP: reqData.DNS.IP[0], IP: reqData.DNS.IP[0],
Port: reqData.DNS.Port, Port: reqData.DNS.Port,
Autofix: reqData.DNS.Autofix, Autofix: reqData.DNS.Autofix,

View File

@ -19,7 +19,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalgo" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
@ -296,23 +296,23 @@ func setupConfig(args options) (err error) {
Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts) Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts)
if args.bindPort != 0 { if args.bindPort != 0 {
uv := aghalgo.UniquenessValidator{} uc := aghalg.UniqChecker{}
addPorts( addPorts(
uv, uc,
args.bindPort, args.bindPort,
config.BetaBindPort, config.BetaBindPort,
config.DNS.Port, config.DNS.Port,
) )
if config.TLS.Enabled { if config.TLS.Enabled {
addPorts( addPorts(
uv, uc,
config.TLS.PortHTTPS, config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS, config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC, config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt, config.TLS.PortDNSCrypt,
) )
} }
if err = uv.Validate(aghalgo.IntIsBefore); err != nil { if err = uc.Validate(aghalg.IntIsBefore); err != nil {
return fmt.Errorf("validating ports: %w", err) return fmt.Errorf("validating ports: %w", err)
} }

View File

@ -20,7 +20,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalgo" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
@ -251,9 +251,9 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
} }
if setts.Enabled { if setts.Enabled {
uv := aghalgo.UniquenessValidator{} uc := aghalg.UniqChecker{}
addPorts( addPorts(
uv, uc,
config.BindPort, config.BindPort,
config.BetaBindPort, config.BetaBindPort,
config.DNS.Port, config.DNS.Port,
@ -263,7 +263,7 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts.PortDNSCrypt, setts.PortDNSCrypt,
) )
err = uv.Validate(aghalgo.IntIsBefore) err = uc.Validate(aghalg.IntIsBefore)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "validating ports: %s", err) aghhttp.Error(r, w, http.StatusBadRequest, "validating ports: %s", err)
@ -344,9 +344,9 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
} }
if data.Enabled { if data.Enabled {
uv := aghalgo.UniquenessValidator{} uc := aghalg.UniqChecker{}
addPorts( addPorts(
uv, uc,
config.BindPort, config.BindPort,
config.BetaBindPort, config.BetaBindPort,
config.DNS.Port, config.DNS.Port,
@ -356,7 +356,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
data.PortDNSCrypt, data.PortDNSCrypt,
) )
err = uv.Validate(aghalgo.IntIsBefore) err = uc.Validate(aghalg.IntIsBefore)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)