diff --git a/.gitignore b/.gitignore
index 0be5afd7..2d3d46fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
*.db
*.log
*.snap
+/agh-backup/
/bin/
/build/*
/build2/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9bbb7e8..33984912 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ and this project adheres to
### Added
+- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
+ ([#3172]).
- `darwin/arm64` support ([#2443]).
- `freebsd/arm64` support ([#2441]).
- Output of the default addresses of the upstreams used for resolving PTRs for
@@ -53,6 +55,7 @@ released by then.
[#2441]: https://github.com/AdguardTeam/AdGuardHome/issues/2441
[#2443]: https://github.com/AdguardTeam/AdGuardHome/issues/2443
[#3136]: https://github.com/AdguardTeam/AdGuardHome/issues/3136
+[#3172]: https://github.com/AdguardTeam/AdGuardHome/issues/3172
[#3184]: https://github.com/AdguardTeam/AdGuardHome/issues/3184
[#3185]: https://github.com/AdguardTeam/AdGuardHome/issues/3185
[#3186]: https://github.com/AdguardTeam/AdGuardHome/issues/3186
diff --git a/client/src/components/ui/Guide/Guide.js b/client/src/components/ui/Guide/Guide.js
index cf1af0b0..53cf82a7 100644
--- a/client/src/components/ui/Guide/Guide.js
+++ b/client/src/components/ui/Guide/Guide.js
@@ -154,7 +154,8 @@ const getTabs = ({
tlsAddress,
httpsAddress,
showDnsPrivacyNotice,
- server_name,
+ serverName,
+ portHttps,
t,
}) => ({
Router: {
@@ -276,9 +277,10 @@ const getTabs = ({
>
@@ -311,7 +313,8 @@ const renderContent = ({ title, list, getTitle }) => (
const Guide = ({ dnsAddresses }) => {
const { t } = useTranslation();
- const server_name = useSelector((state) => state.encryption?.server_name);
+ const serverName = useSelector((state) => state.encryption?.server_name);
+ const portHttps = useSelector((state) => state.encryption?.port_https);
const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? '';
const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? '';
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
@@ -322,7 +325,8 @@ const Guide = ({ dnsAddresses }) => {
tlsAddress,
httpsAddress,
showDnsPrivacyNotice,
- server_name,
+ serverName,
+ portHttps,
t,
});
diff --git a/client/src/components/ui/Guide/MobileConfigForm.js b/client/src/components/ui/Guide/MobileConfigForm.js
index e1726d99..ce9a6942 100644
--- a/client/src/components/ui/Guide/MobileConfigForm.js
+++ b/client/src/components/ui/Guide/MobileConfigForm.js
@@ -7,14 +7,17 @@ import i18next from 'i18next';
import cn from 'classnames';
import { getPathWithQueryString } from '../../../helpers/helpers';
-import { FORM_NAME, MOBILE_CONFIG_LINKS } from '../../../helpers/constants';
+import { FORM_NAME, MOBILE_CONFIG_LINKS, STANDARD_HTTPS_PORT } from '../../../helpers/constants';
import {
renderInputField,
renderSelectField,
+ toNumber,
} from '../../../helpers/form';
import {
validateClientId,
validateServerName,
+ validatePort,
+ validateIsSafePort,
} from '../../../helpers/validators';
const getDownloadLink = (host, clientId, protocol, invalid) => {
@@ -53,7 +56,9 @@ const MobileConfigForm = ({ invalid }) => {
return null;
}
- const { host, clientId, protocol } = formValues;
+ const {
+ host, clientId, protocol, port,
+ } = formValues;
const githubLink = (
{
);
+ const getHostName = () => {
+ if (port
+ && port !== STANDARD_HTTPS_PORT
+ && protocol === MOBILE_CONFIG_LINKS.DOH
+ ) {
+ return `${host}:${port}`;
+ }
+
+ return host;
+ };
+
return (
);
};
diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index ddb26d56..13ace026 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -186,7 +186,7 @@ func GetSubnet(ifaceName string) *net.IPNet {
// CheckPortAvailable - check if TCP port is available
func CheckPortAvailable(host net.IP, port int) error {
- ln, err := net.Listen("tcp", net.JoinHostPort(host.String(), strconv.Itoa(port)))
+ ln, err := net.Listen("tcp", JoinHostPort(host.String(), port))
if err != nil {
return err
}
@@ -200,7 +200,7 @@ func CheckPortAvailable(host net.IP, port int) error {
// CheckPacketPortAvailable - check if UDP port is available
func CheckPacketPortAvailable(host net.IP, port int) error {
- ln, err := net.ListenPacket("udp", net.JoinHostPort(host.String(), strconv.Itoa(port)))
+ ln, err := net.ListenPacket("udp", JoinHostPort(host.String(), port))
if err != nil {
return err
}
@@ -424,3 +424,9 @@ func CollectAllIfacesAddrs() (addrs []string, err error) {
return addrs, nil
}
+
+// JoinHostPort is a convinient wrapper for net.JoinHostPort with port of type
+// int.
+func JoinHostPort(host string, port int) (hostport string) {
+ return net.JoinHostPort(host, strconv.Itoa(port))
+}
diff --git a/internal/aghstrings/strings.go b/internal/aghstrings/strings.go
index 201a319c..58219f9b 100644
--- a/internal/aghstrings/strings.go
+++ b/internal/aghstrings/strings.go
@@ -21,7 +21,8 @@ func CloneSlice(a []string) (b []string) {
// Coalesce returns the first non-empty string. It is named after the function
// COALESCE in SQL except that since strings in Go are non-nullable, it uses an
-// empty string as a NULL value. If strs is empty, it returns an empty string.
+// empty string as a NULL value. If strs or all it's elements are empty, it
+// returns an empty string.
func Coalesce(strs ...string) (res string) {
for _, s := range strs {
if s != "" {
diff --git a/internal/dhcpd/checkother.go b/internal/dhcpd/checkother.go
index 1e5de1c0..c6345cd7 100644
--- a/internal/dhcpd/checkother.go
+++ b/internal/dhcpd/checkother.go
@@ -12,6 +12,7 @@ import (
"runtime"
"time"
+ "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/insomniacslk/dhcp/dhcpv4"
@@ -44,7 +45,7 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (ok bool, err error) {
}
srcIP := ifaceIPNet[0]
- src := net.JoinHostPort(srcIP.String(), "68")
+ src := aghnet.JoinHostPort(srcIP.String(), 68)
dst := "255.255.255.255:67"
hostname, _ := os.Hostname()
@@ -175,7 +176,7 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (ok bool, err error) {
}
srcIP := ifaceIPNet[0]
- src := net.JoinHostPort(srcIP.String(), "546")
+ src := aghnet.JoinHostPort(srcIP.String(), 546)
dst := "[ff02::1:2]:547"
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
diff --git a/internal/home/control.go b/internal/home/control.go
index a4f4301d..06682b46 100644
--- a/internal/home/control.go
+++ b/internal/home/control.go
@@ -7,7 +7,6 @@ import (
"net/http"
"net/url"
"runtime"
- "strconv"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
@@ -38,9 +37,11 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface
// addresses to a slice of strings.
func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
for _, addr := range addrs {
- hostport := addr.String()
+ var hostport string
if config.DNS.Port != 53 {
- hostport = net.JoinHostPort(hostport, strconv.Itoa(config.DNS.Port))
+ hostport = aghnet.JoinHostPort(addr.String(), config.DNS.Port)
+ } else {
+ hostport = addr.String()
}
dst = append(dst, hostport)
@@ -303,8 +304,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
if r.TLS == nil && web.forceHTTPS {
hostPort := host
if port := web.conf.PortHTTPS; port != defaultHTTPSPort {
- portStr := strconv.Itoa(port)
- hostPort = net.JoinHostPort(host, portStr)
+ hostPort = aghnet.JoinHostPort(host, port)
}
httpsURL := &url.URL{
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 97a0c084..3c9569a4 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -11,7 +11,6 @@ import (
"os/exec"
"path/filepath"
"runtime"
- "strconv"
"strings"
"time"
@@ -286,42 +285,48 @@ func shutdownSrv(ctx context.Context, cancel context.CancelFunc, srv *http.Serve
// Apply new configuration, start DNS server, restart Web server
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
- newSettings := applyConfigReq{}
- err := json.NewDecoder(r.Body).Decode(&newSettings)
+ req := applyConfigReq{}
+ err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'configure' JSON: %s", err)
return
}
- if newSettings.Web.Port == 0 || newSettings.DNS.Port == 0 {
+ if req.Web.Port == 0 || req.DNS.Port == 0 {
httpError(w, http.StatusBadRequest, "port value can't be 0")
return
}
restartHTTP := true
- if config.BindHost.Equal(newSettings.Web.IP) && config.BindPort == newSettings.Web.Port {
+ if config.BindHost.Equal(req.Web.IP) && config.BindPort == req.Web.Port {
// no need to rebind
restartHTTP = false
}
// validate that hosts and ports are bindable
if restartHTTP {
- err = aghnet.CheckPortAvailable(newSettings.Web.IP, newSettings.Web.Port)
+ err = aghnet.CheckPortAvailable(req.Web.IP, req.Web.Port)
if err != nil {
- httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s",
- net.JoinHostPort(newSettings.Web.IP.String(), strconv.Itoa(newSettings.Web.Port)), err)
+ httpError(
+ w,
+ http.StatusBadRequest,
+ "can not listen on IP:port %s: %s",
+ aghnet.JoinHostPort(req.Web.IP.String(), req.Web.Port),
+ err,
+ )
+
return
}
}
- err = aghnet.CheckPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
+ err = aghnet.CheckPacketPortAvailable(req.DNS.IP, req.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
- err = aghnet.CheckPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
+ err = aghnet.CheckPortAvailable(req.DNS.IP, req.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
@@ -331,10 +336,10 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
copyInstallSettings(&curConfig, &config)
Context.firstRun = false
- config.BindHost = newSettings.Web.IP
- config.BindPort = newSettings.Web.Port
- config.DNS.BindHosts = []net.IP{newSettings.DNS.IP}
- config.DNS.Port = newSettings.DNS.Port
+ config.BindHost = req.Web.IP
+ config.BindPort = req.Web.Port
+ 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
@@ -349,8 +354,8 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
}
u := User{}
- u.Name = newSettings.Username
- Context.auth.UserAdd(&u, newSettings.Password)
+ u.Name = req.Username
+ Context.auth.UserAdd(&u, req.Password)
err = config.write()
if err != nil {
@@ -361,8 +366,8 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
}
web.conf.firstRun = false
- web.conf.BindHost = newSettings.Web.IP
- web.conf.BindPort = newSettings.Web.Port
+ web.conf.BindHost = req.Web.IP
+ web.conf.BindPort = req.Web.Port
registerControlHandlers()
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 8f4741d3..f4092a95 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -6,7 +6,6 @@ import (
"net/url"
"os"
"path/filepath"
- "strconv"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
@@ -254,7 +253,7 @@ func getDNSEncryption() (de dnsEncryption) {
if tlsConf.PortHTTPS != 0 {
addr := hostname
if tlsConf.PortHTTPS != 443 {
- addr = net.JoinHostPort(addr, strconv.Itoa(tlsConf.PortHTTPS))
+ addr = aghnet.JoinHostPort(addr, tlsConf.PortHTTPS)
}
de.https = (&url.URL{
@@ -267,14 +266,14 @@ func getDNSEncryption() (de dnsEncryption) {
if tlsConf.PortDNSOverTLS != 0 {
de.tls = (&url.URL{
Scheme: "tls",
- Host: net.JoinHostPort(hostname, strconv.Itoa(tlsConf.PortDNSOverTLS)),
+ Host: aghnet.JoinHostPort(hostname, tlsConf.PortDNSOverTLS),
}).String()
}
if tlsConf.PortDNSOverQUIC != 0 {
de.quic = (&url.URL{
Scheme: "quic",
- Host: net.JoinHostPort(hostname, strconv.Itoa(int(tlsConf.PortDNSOverQUIC))),
+ Host: aghnet.JoinHostPort(hostname, tlsConf.PortDNSOverQUIC),
}).String()
}
}
diff --git a/internal/home/home.go b/internal/home/home.go
index f40c9569..7b024440 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -15,7 +15,6 @@ import (
"os/signal"
"path/filepath"
"runtime"
- "strconv"
"sync"
"syscall"
"time"
@@ -630,7 +629,28 @@ func loadOptions() options {
return o
}
-// printHTTPAddresses prints the IP addresses which user can use to open the
+// printWebAddrs prints addresses built from proto, addr, and an appropriate
+// port. At least one address is printed with the value of port. If the value
+// of betaPort is 0, the second address is not printed. The output example:
+//
+// Go to http://127.0.0.1:80
+// Go to http://127.0.0.1:3000 (BETA)
+//
+func printWebAddrs(proto, addr string, port, betaPort int) {
+ const (
+ hostMsg = "Go to %s://%s"
+ hostBetaMsg = hostMsg + " (BETA)"
+ )
+
+ log.Printf(hostMsg, proto, aghnet.JoinHostPort(addr, port))
+ if betaPort == 0 {
+ return
+ }
+
+ log.Printf(hostBetaMsg, proto, aghnet.JoinHostPort(addr, config.BetaBindPort))
+}
+
+// printHTTPAddresses prints the IP addresses which user can use to access the
// admin interface. proto is either schemeHTTP or schemeHTTPS.
func printHTTPAddresses(proto string) {
tlsConf := tlsConfigSettings{}
@@ -638,45 +658,40 @@ func printHTTPAddresses(proto string) {
Context.tls.WriteDiskConfig(&tlsConf)
}
- port := strconv.Itoa(config.BindPort)
+ port := config.BindPort
if proto == schemeHTTPS {
- port = strconv.Itoa(tlsConf.PortHTTPS)
+ port = tlsConf.PortHTTPS
}
- var hostStr string
+ // TODO(e.burkov): Inspect and perhaps merge with the previous
+ // condition.
if proto == schemeHTTPS && tlsConf.ServerName != "" {
- if tlsConf.PortHTTPS == 443 {
- log.Printf("Go to https://%s", tlsConf.ServerName)
- } else {
- log.Printf("Go to https://%s:%s", tlsConf.ServerName, port)
- }
- } else if config.BindHost.IsUnspecified() {
- log.Println("AdGuard Home is available on the following addresses:")
- ifaces, err := aghnet.GetValidNetInterfacesForWeb()
- if err != nil {
- // That's weird, but we'll ignore it
- hostStr = config.BindHost.String()
- log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, port))
- if config.BetaBindPort != 0 {
- log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort)))
- }
- return
- }
+ printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS, 0)
- for _, iface := range ifaces {
- for _, addr := range iface.Addresses {
- hostStr = addr.String()
- log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BindPort)))
- if config.BetaBindPort != 0 {
- log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort)))
- }
- }
- }
- } else {
- hostStr = config.BindHost.String()
- log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, port))
- if config.BetaBindPort != 0 {
- log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort)))
+ return
+ }
+
+ bindhost := config.BindHost
+ if !bindhost.IsUnspecified() {
+ printWebAddrs(proto, bindhost.String(), port, config.BetaBindPort)
+
+ return
+ }
+
+ ifaces, err := aghnet.GetValidNetInterfacesForWeb()
+ if err != nil {
+ log.Error("web: getting iface ips: %s", err)
+ // That's weird, but we'll ignore it.
+ //
+ // TODO(e.burkov): Find out when it happens.
+ printWebAddrs(proto, bindhost.String(), port, config.BetaBindPort)
+
+ return
+ }
+
+ for _, iface := range ifaces {
+ for _, addr := range iface.Addresses {
+ printWebAddrs(proto, addr.String(), config.BindPort, config.BetaBindPort)
}
}
}
diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go
index 895512f1..a0380de5 100644
--- a/internal/home/mobileconfig.go
+++ b/internal/home/mobileconfig.go
@@ -8,6 +8,7 @@ import (
"path"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
+ "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
uuid "github.com/satori/go.uuid"
"howett.net/plist"
@@ -53,27 +54,27 @@ const (
func getMobileConfig(d dnsSettings) ([]byte, error) {
var dspName string
- switch d.DNSProtocol {
+ switch proto := d.DNSProtocol; proto {
case dnsProtoHTTPS:
dspName = fmt.Sprintf("%s DoH", d.ServerName)
-
u := &url.URL{
Scheme: schemeHTTPS,
Host: d.ServerName,
- Path: "/dns-query",
+ Path: path.Join("/dns-query", d.clientID),
}
- if d.clientID != "" {
- u.Path = path.Join(u.Path, d.clientID)
- }
-
d.ServerURL = u.String()
+ // Empty the ServerName field since it is only must be presented
+ // in DNS-over-TLS configuration.
+ //
+ // See https://developer.apple.com/documentation/devicemanagement/dnssettings/dnssettings.
+ d.ServerName = ""
case dnsProtoTLS:
dspName = fmt.Sprintf("%s DoT", d.ServerName)
if d.clientID != "" {
d.ServerName = d.clientID + "." + d.ServerName
}
default:
- return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol)
+ return nil, fmt.Errorf("bad dns protocol %q", proto)
}
data := mobileConfig{
@@ -99,25 +100,25 @@ func getMobileConfig(d dnsSettings) ([]byte, error) {
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
}
+func respondJSONError(w http.ResponseWriter, status int, msg string) {
+ w.WriteHeader(http.StatusInternalServerError)
+ err := json.NewEncoder(w).Encode(&jsonError{
+ Message: msg,
+ })
+ if err != nil {
+ log.Debug("writing %d json response: %s", status, err)
+ }
+}
+
+const errEmptyHost errors.Error = "no host in query parameters and no server_name"
+
func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
var err error
q := r.URL.Query()
host := q.Get("host")
if host == "" {
- host = Context.tls.conf.ServerName
- }
-
- if host == "" {
- w.WriteHeader(http.StatusInternalServerError)
-
- const msg = "no host in query parameters and no server_name"
- err = json.NewEncoder(w).Encode(&jsonError{
- Message: msg,
- })
- if err != nil {
- log.Debug("writing 500 json response: %s", err)
- }
+ respondJSONError(w, http.StatusInternalServerError, string(errEmptyHost))
return
}
@@ -126,14 +127,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
if clientID != "" {
err = dnsforward.ValidateClientID(clientID)
if err != nil {
- w.WriteHeader(http.StatusBadRequest)
-
- err = json.NewEncoder(w).Encode(&jsonError{
- Message: err.Error(),
- })
- if err != nil {
- log.Debug("writing 400 json response: %s", err)
- }
+ respondJSONError(w, http.StatusBadRequest, err.Error())
return
}
@@ -147,7 +141,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
mobileconfig, err := getMobileConfig(d)
if err != nil {
- httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err)
+ respondJSONError(w, http.StatusInternalServerError, err.Error())
return
}
diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go
index 2a0e6d43..e902d5e3 100644
--- a/internal/home/mobileconfig_test.go
+++ b/internal/home/mobileconfig_test.go
@@ -1,6 +1,8 @@
package home
import (
+ "bytes"
+ "encoding/json"
"net/http"
"net/http/httptest"
"testing"
@@ -13,7 +15,7 @@ import (
func TestHandleMobileConfigDOH(t *testing.T) {
t.Run("success", func(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
w := httptest.NewRecorder()
@@ -22,39 +24,11 @@ func TestHandleMobileConfigDOH(t *testing.T) {
var mc mobileConfig
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
+ require.NoError(t, err)
require.Len(t, mc.PayloadContent, 1)
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
- assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
- assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
- })
-
- t.Run("success_no_host", func(t *testing.T) {
- oldTLSConf := Context.tls
- t.Cleanup(func() { Context.tls = oldTLSConf })
-
- Context.tls = &TLSMod{
- conf: tlsConfigSettings{ServerName: "example.org"},
- }
-
- r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
- require.Nil(t, err)
-
- w := httptest.NewRecorder()
-
- handleMobileConfigDOH(w, r)
- require.Equal(t, http.StatusOK, w.Code)
-
- var mc mobileConfig
- _, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
-
- require.Len(t, mc.PayloadContent, 1)
- assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
- assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
- assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
})
@@ -65,17 +39,24 @@ func TestHandleMobileConfigDOH(t *testing.T) {
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
+
+ b := &bytes.Buffer{}
+ err = json.NewEncoder(b).Encode(&jsonError{
+ Message: errEmptyHost.Error(),
+ })
+ require.NoError(t, err)
w := httptest.NewRecorder()
handleMobileConfigDOH(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
+ assert.JSONEq(t, w.Body.String(), b.String())
})
t.Run("client_id", func(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org&client_id=cli42", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
w := httptest.NewRecorder()
@@ -84,12 +65,11 @@ func TestHandleMobileConfigDOH(t *testing.T) {
var mc mobileConfig
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
+ require.NoError(t, err)
require.Len(t, mc.PayloadContent, 1)
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
- assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
assert.Equal(t, "https://example.org/dns-query/cli42", mc.PayloadContent[0].DNSSettings.ServerURL)
})
}
@@ -97,7 +77,7 @@ func TestHandleMobileConfigDOH(t *testing.T) {
func TestHandleMobileConfigDOT(t *testing.T) {
t.Run("success", func(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
w := httptest.NewRecorder()
@@ -106,33 +86,7 @@ func TestHandleMobileConfigDOT(t *testing.T) {
var mc mobileConfig
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
-
- require.Len(t, mc.PayloadContent, 1)
- assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
- assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
- assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
- })
-
- t.Run("success_no_host", func(t *testing.T) {
- oldTLSConf := Context.tls
- t.Cleanup(func() { Context.tls = oldTLSConf })
-
- Context.tls = &TLSMod{
- conf: tlsConfigSettings{ServerName: "example.org"},
- }
-
- r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
- require.Nil(t, err)
-
- w := httptest.NewRecorder()
-
- handleMobileConfigDOT(w, r)
- require.Equal(t, http.StatusOK, w.Code)
-
- var mc mobileConfig
- _, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
+ require.NoError(t, err)
require.Len(t, mc.PayloadContent, 1)
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
@@ -147,17 +101,25 @@ func TestHandleMobileConfigDOT(t *testing.T) {
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
+
+ b := &bytes.Buffer{}
+ err = json.NewEncoder(b).Encode(&jsonError{
+ Message: errEmptyHost.Error(),
+ })
+ require.NoError(t, err)
w := httptest.NewRecorder()
handleMobileConfigDOT(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
+
+ assert.JSONEq(t, w.Body.String(), b.String())
})
t.Run("client_id", func(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org&client_id=cli42", nil)
- require.Nil(t, err)
+ require.NoError(t, err)
w := httptest.NewRecorder()
@@ -166,7 +128,7 @@ func TestHandleMobileConfigDOT(t *testing.T) {
var mc mobileConfig
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
- require.Nil(t, err)
+ require.NoError(t, err)
require.Len(t, mc.PayloadContent, 1)
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
diff --git a/internal/home/service.go b/internal/home/service.go
index d881e9f6..c1b88af9 100644
--- a/internal/home/service.go
+++ b/internal/home/service.go
@@ -243,7 +243,8 @@ func handleServiceInstallCommand(s service.Service) {
log.Printf(`Almost ready!
AdGuard Home is successfully installed and will automatically start on boot.
There are a few more things that must be configured before you can use it.
-Click on the link below and follow the Installation Wizard steps to finish setup.`)
+Click on the link below and follow the Installation Wizard steps to finish setup.
+AdGuard Home is now available at the following addresses:`)
printHTTPAddresses(schemeHTTP)
}
}
diff --git a/internal/home/web.go b/internal/home/web.go
index a987a835..a22c6bd0 100644
--- a/internal/home/web.go
+++ b/internal/home/web.go
@@ -6,7 +6,6 @@ import (
"io/fs"
"net"
"net/http"
- "strconv"
"sync"
"time"
@@ -162,6 +161,8 @@ func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings)
// Start - start serving HTTP requests
func (web *Web) Start() {
+ log.Println("AdGuard Home is available at the following addresses:")
+
// for https, we have a separate goroutine loop
go web.tlsServerLoop()
@@ -174,7 +175,7 @@ func (web *Web) Start() {
// we need to have new instance, because after Shutdown() the Server is not usable
web.httpServer = &http.Server{
ErrorLog: log.StdLog("web: plain", log.DEBUG),
- Addr: net.JoinHostPort(hostStr, strconv.Itoa(web.conf.BindPort)),
+ Addr: aghnet.JoinHostPort(hostStr, web.conf.BindPort),
Handler: withMiddlewares(Context.mux, limitRequestBody),
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
@@ -187,7 +188,7 @@ func (web *Web) Start() {
if web.conf.BetaBindPort != 0 {
web.httpServerBeta = &http.Server{
ErrorLog: log.StdLog("web: plain", log.DEBUG),
- Addr: net.JoinHostPort(hostStr, strconv.Itoa(web.conf.BetaBindPort)),
+ Addr: aghnet.JoinHostPort(hostStr, web.conf.BetaBindPort),
Handler: withMiddlewares(Context.mux, limitRequestBody, web.wrapIndexBeta),
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
@@ -248,7 +249,7 @@ func (web *Web) tlsServerLoop() {
web.httpsServer.cond.L.Unlock()
// prepare HTTPS server
- address := net.JoinHostPort(web.conf.BindHost.String(), strconv.Itoa(web.conf.PortHTTPS))
+ address := aghnet.JoinHostPort(web.conf.BindHost.String(), web.conf.PortHTTPS)
web.httpsServer.server = &http.Server{
ErrorLog: log.StdLog("web: https", log.DEBUG),
Addr: address,
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index e00bd39e..2f34c442 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,12 @@
## v0.107: API changes
+### The parameter `"host"` in `GET /apple/*.mobileconfig` is now required.
+
+* The parameter `"host"` in `GET` requests for `/apple/doh.mobileconfig` and
+ `/apple/doh.mobileconfig` is now required to prevent unexpected server name's
+ value.
+
### The new field `"default_local_ptr_upstreams"` in `GET /control/dns_info`
* The new optional field `"default_local_ptr_upstreams"` is the list of IP
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 90e25992..07194338 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1129,6 +1129,7 @@
'example': 'example.org'
'in': 'query'
'name': 'host'
+ 'required': true
'schema':
'type': 'string'
- 'description': >
@@ -1163,6 +1164,7 @@
'example': 'example.org'
'in': 'query'
'name': 'host'
+ 'required': true
'schema':
'type': 'string'
- 'description': >