Pull request: 3835 check ports properly

Merge in DNS/adguard-home from 3835-imp-error-msg to master

Updates #3835.

Squashed commit of the following:

commit ba31cb67833df9f293fe13be96a35c2a823f115b
Merge: 19c7dfc9 4be69d35
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 16 20:07:25 2021 +0300

    Merge branch 'master' into 3835-imp-error-msg

commit 19c7dfc96284a271d30d7111c86c439be3461389
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 16 19:42:10 2021 +0300

    all: imp more

commit 5b9c6a3e357238bf44ef800a6033a7671f27d469
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 16 18:57:02 2021 +0300

    all: introduce aghhttp

commit 29caa17200957aad2b98461573bb33d80931adcf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 16 14:23:53 2021 +0300

    all: imp more

commit 754c020191d7b9518cb0e789f3f5741ba38c3cf4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Dec 15 20:53:41 2021 +0300

    all: imp code, log changes

commit ec712dd562f31fcc2fbc27e7035f926c79827444
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Dec 15 18:40:54 2021 +0300

    home: check ports properly
This commit is contained in:
Eugene Burkov
2021-12-16 20:54:59 +03:00
parent 4be69d35eb
commit b3210cfa7e
31 changed files with 675 additions and 348 deletions

View File

@@ -13,6 +13,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -417,7 +418,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
req := loginJSON{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(w, http.StatusBadRequest, "json decode: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
return
}
@@ -429,7 +430,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
//
// TODO(e.burkov): Use realIP when the issue will be fixed.
if remoteAddr, err = netutil.SplitHost(r.RemoteAddr); err != nil {
httpError(w, http.StatusBadRequest, "auth: getting remote address: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "auth: getting remote address: %s", err)
return
}
@@ -437,7 +438,8 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
if blocker := Context.auth.blocker; blocker != nil {
if left := blocker.check(remoteAddr); left > 0 {
w.Header().Set("Retry-After", strconv.Itoa(int(left.Seconds())))
httpError(
aghhttp.Error(
r,
w,
http.StatusTooManyRequests,
"auth: blocked for %s",
@@ -451,7 +453,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
var cookie string
cookie, err = Context.auth.httpCookie(req, remoteAddr)
if err != nil {
httpError(w, http.StatusBadRequest, "crypto rand reader: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "crypto rand reader: %s", err)
return
}
@@ -480,7 +482,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
returnOK(w)
aghhttp.OK(w)
}
func handleLogout(w http.ResponseWriter, r *http.Request) {

View File

@@ -6,6 +6,7 @@ import (
"net"
"net/http"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
)
@@ -58,7 +59,7 @@ type clientListJSON struct {
}
// respond with information about configured clients
func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http.Request) {
func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http.Request) {
data := clientListJSON{}
clients.lock.Lock()
@@ -106,7 +107,14 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http
w.Header().Set("Content-Type", "application/json")
e := json.NewEncoder(w).Encode(data)
if e != nil {
httpError(w, http.StatusInternalServerError, "Failed to encode to json: %v", e)
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Failed to encode to json: %v",
e,
)
return
}
}
@@ -154,7 +162,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
cj := clientJSON{}
err := json.NewDecoder(r.Body).Decode(&cj)
if err != nil {
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "failed to process request body: %s", err)
return
}
@@ -162,11 +170,14 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
c := jsonToClient(cj)
ok, err := clients.Add(c)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
if !ok {
httpError(w, http.StatusBadRequest, "Client already exists")
aghhttp.Error(r, w, http.StatusBadRequest, "Client already exists")
return
}
@@ -178,19 +189,19 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
cj := clientJSON{}
err := json.NewDecoder(r.Body).Decode(&cj)
if err != nil {
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "failed to process request body: %s", err)
return
}
if len(cj.Name) == 0 {
httpError(w, http.StatusBadRequest, "client's name must be non-empty")
aghhttp.Error(r, w, http.StatusBadRequest, "client's name must be non-empty")
return
}
if !clients.Del(cj.Name) {
httpError(w, http.StatusBadRequest, "Client not found")
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
return
}
@@ -207,20 +218,22 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
dj := updateJSON{}
err := json.NewDecoder(r.Body).Decode(&dj)
if err != nil {
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "failed to process request body: %s", err)
return
}
if len(dj.Name) == 0 {
httpError(w, http.StatusBadRequest, "Invalid request")
aghhttp.Error(r, w, http.StatusBadRequest, "Invalid request")
return
}
c := jsonToClient(dj.Data)
err = clients.Update(dj.Name, c)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -256,7 +269,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write response: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write response: %s", err)
}
}

View File

@@ -1,7 +1,6 @@
package home
import (
"fmt"
"net"
"os"
"path/filepath"
@@ -274,17 +273,34 @@ func getLogSettings() logSettings {
}
// parseConfig loads configuration from the YAML file
func parseConfig() error {
configFile := config.getConfigFilename()
log.Debug("Reading config file: %s", configFile)
yamlFile, err := readConfigFile()
func parseConfig() (err error) {
var fileData []byte
fileData, err = readConfigFile()
if err != nil {
return err
}
config.fileData = nil
err = yaml.Unmarshal(yamlFile, &config)
err = yaml.Unmarshal(fileData, &config)
if err != nil {
log.Error("Couldn't parse config file: %s", err)
return err
}
pm := portsMap{}
pm.add(
config.BindPort,
config.BetaBindPort,
config.DNS.Port,
)
if config.TLS.Enabled {
pm.add(
config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt,
)
}
if err = pm.validate(); err != nil {
return err
}
@@ -299,18 +315,17 @@ func parseConfig() error {
return nil
}
// readConfigFile reads config file contents if it exists
func readConfigFile() ([]byte, error) {
if len(config.fileData) != 0 {
// readConfigFile reads configuration file contents.
func readConfigFile() (fileData []byte, err error) {
if len(config.fileData) > 0 {
return config.fileData, nil
}
configFile := config.getConfigFilename()
d, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("couldn't read config file %s: %w", configFile, err)
}
return d, nil
name := config.getConfigFilename()
log.Debug("reading config file: %s", name)
// Do not wrap the error because it's informative enough as is.
return os.ReadFile(name)
}
// Saves configuration to the YAML file and also saves the user filter contents to a file

View File

@@ -9,6 +9,7 @@ import (
"runtime"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/version"
@@ -17,23 +18,6 @@ import (
"github.com/NYTimes/gziphandler"
)
// ----------------
// helper functions
// ----------------
func returnOK(w http.ResponseWriter) {
_, err := fmt.Fprintf(w, "OK\n")
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
}
}
func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
log.Info(text)
http.Error(w, text, code)
}
// appendDNSAddrs is a convenient helper for appending a formatted form of DNS
// addresses to a slice of strings.
func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
@@ -125,12 +109,12 @@ type statusResponse struct {
Language string `json:"language"`
}
func handleStatus(w http.ResponseWriter, _ *http.Request) {
func handleStatus(w http.ResponseWriter, r *http.Request) {
dnsAddrs, err := collectDNSAddresses()
if err != nil {
// Don't add a lot of formatting, since the error is already
// wrapped by collectDNSAddresses.
httpError(w, http.StatusInternalServerError, "%s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
@@ -165,7 +149,7 @@ func handleStatus(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
return
}
@@ -182,7 +166,7 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(pj)
if err != nil {
httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "json.Marshal: %s", err)
return
}
_, _ = w.Write(data)
@@ -295,7 +279,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
host, err := netutil.SplitHost(r.Host)
if err != nil {
httpError(w, http.StatusBadRequest, "bad host: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "bad host: %s", err)
return false
}

View File

@@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
)
@@ -49,7 +50,8 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
fj := filterAddJSON{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse request body json: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse request body json: %s", err)
return
}
@@ -63,7 +65,8 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
// Check for duplicates
if filterExists(fj.URL) {
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", fj.URL)
aghhttp.Error(r, w, http.StatusBadRequest, "Filter URL already added -- %s", fj.URL)
return
}
@@ -79,17 +82,35 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
// Download the filter contents
ok, err := f.update(&filt)
if err != nil {
httpError(w, http.StatusBadRequest, "Couldn't fetch filter from url %s: %s", filt.URL, err)
return
}
if !ok {
httpError(w, http.StatusBadRequest, "Filter at the url %s is invalid (maybe it points to blank page?)", filt.URL)
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Couldn't fetch filter from url %s: %s",
filt.URL,
err,
)
return
}
// URL is deemed valid, append it to filters, update config, write new filter file and tell dns to reload it
if !ok {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Filter at the url %s is invalid (maybe it points to blank page?)",
filt.URL,
)
return
}
// URL is assumed valid so append it to filters, update config, write new
// file and reload it to engines.
if !filterAdd(filt) {
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", filt.URL)
aghhttp.Error(r, w, http.StatusBadRequest, "Filter URL already added -- %s", filt.URL)
return
}
@@ -98,7 +119,7 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
_, err = fmt.Fprintf(w, "OK %d rules\n", filt.RulesCount)
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
}
}
@@ -111,7 +132,8 @@ func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
req := request{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(w, http.StatusBadRequest, "failed to parse request body json: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "failed to parse request body json: %s", err)
return
}
@@ -152,7 +174,7 @@ func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
_, err = fmt.Fprintf(w, "OK %d rules\n", deleted.RulesCount)
if err != nil {
httpError(w, http.StatusInternalServerError, "couldn't write body: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "couldn't write body: %s", err)
}
}
@@ -172,7 +194,8 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
fj := filterURLReq{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
httpError(w, http.StatusBadRequest, "json decode: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
return
}
@@ -228,7 +251,8 @@ func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Reque
// This use of ReadAll is safe, because request's body is now limited.
body, err := io.ReadAll(r.Body)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
return
}
@@ -250,7 +274,8 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
req := Req{}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(w, http.StatusBadRequest, "json decode: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
return
}
@@ -270,13 +295,15 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
}()
if err != nil {
httpError(w, http.StatusInternalServerError, "%s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
js, err := json.Marshal(resp)
if err != nil {
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
@@ -335,13 +362,14 @@ func (f *Filtering) handleFilteringStatus(w http.ResponseWriter, r *http.Request
jsonVal, err := json.Marshal(resp)
if err != nil {
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
httpError(w, http.StatusInternalServerError, "http write: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
}
}
@@ -350,12 +378,14 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
req := filteringConfig{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(w, http.StatusBadRequest, "json decode: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
return
}
if !checkFiltersUpdateIntervalHours(req.Interval) {
httpError(w, http.StatusBadRequest, "Unsupported interval")
aghhttp.Error(r, w, http.StatusBadRequest, "Unsupported interval")
return
}
@@ -408,7 +438,15 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
Context.dnsFilter.ApplyBlockedServices(&setts, nil, true)
result, err := Context.dnsFilter.CheckHost(host, dns.TypeA, &setts)
if err != nil {
httpError(w, http.StatusInternalServerError, "couldn't apply filtering: %s: %s", host, err)
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"couldn't apply filtering: %s: %s",
host,
err,
)
return
}
@@ -433,7 +471,8 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
js, err := json.Marshal(resp)
if err != nil {
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")

View File

@@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -34,7 +35,8 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
@@ -46,7 +48,14 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err)
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Unable to marshal default addresses to json: %s",
err,
)
return
}
}
@@ -84,23 +93,32 @@ type checkConfigResp struct {
func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
reqData := checkConfigReq{}
respData := checkConfigResp{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort && reqData.Web.Port != config.BetaBindPort {
err = aghnet.CheckPortAvailable(reqData.Web.IP, reqData.Web.Port)
pm := portsMap{}
pm.add(config.BindPort, config.BetaBindPort, reqData.Web.Port)
if err = pm.validate(); err != nil {
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()
}
}
if reqData.DNS.Port != 0 {
err = aghnet.CheckPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
pm.add(reqData.DNS.Port)
if err = pm.validate(); err != nil {
respData.DNS.Status = err.Error()
} else if reqData.DNS.Port != 0 {
err = aghnet.CheckPort("udp", reqData.DNS.IP, reqData.DNS.Port)
if aghnet.ErrorIsAddrInUse(err) {
if aghnet.IsAddrInUse(err) {
canAutofix := checkDNSStubListener()
if canAutofix && reqData.DNS.Autofix {
@@ -109,7 +127,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
log.Error("Couldn't disable DNSStubListener: %s", err)
}
err = aghnet.CheckPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
err = aghnet.CheckPort("udp", reqData.DNS.IP, reqData.DNS.Port)
canAutofix = false
}
@@ -117,7 +135,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
}
if err == nil {
err = aghnet.CheckPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
err = aghnet.CheckPort("tcp", reqData.DNS.IP, reqData.DNS.Port)
}
if err != nil {
@@ -130,7 +148,8 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(respData)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal JSON: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Unable to marshal JSON: %s", err)
return
}
}
@@ -287,21 +306,21 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
err = aghnet.CheckPacketPortAvailable(req.DNS.IP, req.DNS.Port)
err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
err = aghnet.CheckPortAvailable(req.DNS.IP, req.DNS.Port)
err = aghnet.CheckPort("tcp", req.DNS.IP, req.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -315,28 +334,29 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
config.DNS.BindHosts = []net.IP{req.DNS.IP}
config.DNS.Port = req.DNS.Port
// TODO(e.burkov): StartMods() should be put in a separate goroutine at
// the moment we'll allow setting up TLS in the initial configuration or
// the configuration itself will use HTTPS protocol, because the
// underlying functions potentially restart the HTTPS server.
// TODO(e.burkov): StartMods() should be put in a separate goroutine at the
// moment we'll allow setting up TLS in the initial configuration or the
// configuration itself will use HTTPS protocol, because the underlying
// functions potentially restart the HTTPS server.
err = StartMods()
if err != nil {
Context.firstRun = true
copyInstallSettings(config, curConfig)
httpError(w, http.StatusInternalServerError, "%s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
u := User{}
u.Name = req.Username
Context.auth.UserAdd(&u, req.Password)
u := &User{
Name: req.Username,
}
Context.auth.UserAdd(u, req.Password)
err = config.write()
if err != nil {
Context.firstRun = true
copyInstallSettings(config, curConfig)
httpError(w, http.StatusInternalServerError, "Couldn't write config: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write config: %s", err)
return
}
@@ -347,7 +367,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
registerControlHandlers()
returnOK(w)
aghhttp.OK(w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
@@ -386,7 +406,7 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
restartHTTP = !config.BindHost.Equal(req.Web.IP) || config.BindPort != req.Web.Port
if restartHTTP {
err = aghnet.CheckPortAvailable(req.Web.IP, req.Web.Port)
err = aghnet.CheckPort("tcp", req.Web.IP, req.Web.Port)
if err != nil {
return nil, false, fmt.Errorf(
"checking address %s:%d: %w",
@@ -437,12 +457,14 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
reqData := checkConfigReqBeta{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 {
httpError(w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
aghhttp.Error(r, w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
return
}
@@ -464,7 +486,14 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to encode 'check_config' JSON data: %s", err)
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Failed to encode 'check_config' JSON data: %s",
err,
)
return
}
body := nonBetaReqBody.String()
@@ -505,12 +534,14 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques
reqData := applyConfigReqBeta{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 {
httpError(w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
aghhttp.Error(r, w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
return
}
@@ -531,7 +562,14 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to encode 'check_config' JSON data: %s", err)
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"Failed to encode 'check_config' JSON data: %s",
err,
)
return
}
body := nonBetaReqBody.String()
@@ -564,7 +602,8 @@ func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Req
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
@@ -573,7 +612,14 @@ func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Req
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err)
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Unable to marshal default addresses to json: %s",
err,
)
return
}
}

View File

@@ -11,6 +11,7 @@ import (
"syscall"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/golibs/errors"
@@ -43,7 +44,8 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
if r.ContentLength != 0 {
err = json.NewDecoder(r.Body).Decode(req)
if err != nil {
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "JSON parse: %s", err)
return
}
}
@@ -77,7 +79,15 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
if err != nil {
vcu := Context.updater.VersionCheckURL()
// TODO(a.garipov): Figure out the purpose of %T verb.
httpError(w, http.StatusBadGateway, "Couldn't get version check json from %s: %T %s\n", vcu, err, err)
aghhttp.Error(
r,
w,
http.StatusBadGateway,
"Couldn't get version check json from %s: %T %s\n",
vcu,
err,
err,
)
return
}
@@ -87,24 +97,26 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
}
}
// handleUpdate performs an update to the latest available version procedure.
func handleUpdate(w http.ResponseWriter, _ *http.Request) {
func handleUpdate(w http.ResponseWriter, r *http.Request) {
if Context.updater.NewVersion() == "" {
httpError(w, http.StatusBadRequest, "/update request isn't allowed now")
aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now")
return
}
err := Context.updater.Update()
if err != nil {
httpError(w, http.StatusInternalServerError, "%s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
returnOK(w)
aghhttp.OK(w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}

View File

@@ -159,14 +159,11 @@ func setupContext(args options) {
}
if !Context.firstRun {
// Do the upgrade if necessary
// Do the upgrade if necessary.
err := upgradeConfig()
if err != nil {
log.Fatal(err)
}
fatalOnError(err)
err = parseConfig()
if err != nil {
if err = parseConfig(); err != nil {
log.Error("parsing configuration file: %s", err)
os.Exit(1)
@@ -186,13 +183,13 @@ func setupContext(args options) {
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns
// nil. Otherise, it returns err.
func logIfUnsupported(msg string, err error) (outErr error) {
if unsupErr := (&aghos.UnsupportedError{}); errors.As(err, &unsupErr) {
if errors.As(err, new(*aghos.UnsupportedError)) {
log.Debug(msg, err)
} else if err != nil {
return err
return nil
}
return nil
return err
}
// configureOS sets the OS-related configuration.
@@ -297,13 +294,32 @@ func setupConfig(args options) (err error) {
Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts)
if args.bindPort != 0 {
pm := portsMap{}
pm.add(
args.bindPort,
config.BetaBindPort,
config.DNS.Port,
)
if config.TLS.Enabled {
pm.add(
config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt,
)
}
if err = pm.validate(); err != nil {
return err
}
config.BindPort = args.bindPort
}
// override bind host/port from the console
if args.bindHost != nil {
config.BindHost = args.bindHost
}
if args.bindPort != 0 {
config.BindPort = args.bindPort
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
Context.pidFileName = args.pidFile
}
@@ -766,8 +782,7 @@ func printHTTPAddresses(proto string) {
port = tlsConf.PortHTTPS
}
// TODO(e.burkov): Inspect and perhaps merge with the previous
// condition.
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
if proto == schemeHTTPS && tlsConf.ServerName != "" {
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS, 0)

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
)
@@ -96,5 +97,5 @@ func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
}()
onConfigModified()
returnOK(w)
aghhttp.OK(w)
}

63
internal/home/portsmap.go Normal file
View File

@@ -0,0 +1,63 @@
package home
import (
"fmt"
"strconv"
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
)
// portsMap is a helper type for mapping a network port to the number of its
// users.
type portsMap map[int]int
// add binds each of ps. Zeroes are skipped.
func (pm portsMap) add(ps ...int) {
for _, p := range ps {
if p == 0 {
continue
}
pm[p]++
}
}
// validate returns an error about all the ports bound several times.
func (pm portsMap) validate() (err error) {
overbound := []int{}
for p, num := range pm {
if num > 1 {
overbound = append(overbound, p)
pm[p] = 1
}
}
switch len(overbound) {
case 0:
return nil
case 1:
return fmt.Errorf("port %d is already used", overbound[0])
default:
b := &strings.Builder{}
// TODO(e.burkov, a.garipov): Add JoinToBuilder helper to stringutil.
stringutil.WriteToBuilder(b, "ports ", strconv.Itoa(overbound[0]))
for _, p := range overbound[1:] {
stringutil.WriteToBuilder(b, ", ", strconv.Itoa(p))
}
stringutil.WriteToBuilder(b, " are already used")
return errors.Error(b.String())
}
}
// validatePorts is a helper function for a single-step ports binding
// validation.
func validatePorts(ps ...int) (err error) {
pm := portsMap{}
pm.add(ps...)
return pm.validate()
}

View File

@@ -20,6 +20,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -224,7 +225,7 @@ type tlsConfigSettingsExt struct {
PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
}
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, _ *http.Request) {
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
t.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
@@ -233,13 +234,14 @@ func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, _ *http.Request) {
tlsConfigStatus: t.status,
}
t.confLock.Unlock()
marshalTLS(w, data)
marshalTLS(w, r, data)
}
func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
@@ -247,8 +249,31 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts.PrivateKey = t.conf.PrivateKey
}
if setts.Enabled {
if err = validatePorts(
config.BindPort,
config.BetaBindPort,
config.DNS.Port,
setts.PortHTTPS,
setts.PortDNSOverTLS,
setts.PortDNSOverQUIC,
setts.PortDNSCrypt,
); err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !WebCheckPortAvailable(setts.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
setts.PortHTTPS,
)
return
}
@@ -261,7 +286,8 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
tlsConfigSettingsExt: setts,
tlsConfigStatus: status,
}
marshalTLS(w, data)
marshalTLS(w, r, data)
}
func (t *TLSMod) setConfig(newConf tlsConfigSettings, status tlsConfigStatus) (restartHTTPS bool) {
@@ -302,7 +328,8 @@ func (t *TLSMod) setConfig(newConf tlsConfigSettings, status tlsConfigStatus) (r
func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
data, err := unmarshalTLS(r)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
@@ -310,8 +337,32 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
data.PrivateKey = t.conf.PrivateKey
}
if data.Enabled {
if err = validatePorts(
config.BindPort,
config.BetaBindPort,
config.DNS.Port,
data.PortHTTPS,
data.PortDNSOverTLS,
data.PortDNSOverQUIC,
data.PortDNSCrypt,
); err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !WebCheckPortAvailable(data.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
data.PortHTTPS,
)
return
}
@@ -321,7 +372,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
tlsConfigSettingsExt: data,
tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
marshalTLS(w, r, data2)
return
}
@@ -334,7 +385,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
err = reconfigureDNSServer()
if err != nil {
httpError(w, http.StatusInternalServerError, "%s", err)
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
@@ -344,15 +395,15 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
marshalTLS(w, r, data2)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps
// context with timeout on its own and shuts down the server, which
// handles current request. It is also should be done in a separate
// goroutine due to the same reason.
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartHTTPS {
go func() {
Context.web.TLSConfigChanged(context.Background(), data.tlsConfigSettings)
@@ -595,7 +646,7 @@ func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
return data, nil
}
func marshalTLS(w http.ResponseWriter, data tlsConfig) {
func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) {
w.Header().Set("Content-Type", "application/json")
if data.CertificateChain != "" {
@@ -610,8 +661,13 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
err := json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Failed to marshal json with TLS status: %s", err)
return
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Failed to marshal json with TLS status: %s",
err,
)
}
}

View File

@@ -114,17 +114,8 @@ func CreateWeb(conf *webConfig) *Web {
// WebCheckPortAvailable - check if port is available
// BUT: if we are already using this port, no need
func WebCheckPortAvailable(port int) bool {
alreadyRunning := false
if Context.web.httpsServer.server != nil {
alreadyRunning = true
}
if !alreadyRunning {
err := aghnet.CheckPortAvailable(config.BindHost, port)
if err != nil {
return false
}
}
return true
return Context.web.httpsServer.server != nil ||
aghnet.CheckPort("tcp", config.BindHost, port) == nil
}
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server