Skip to content

Commit 48e9c9d

Browse files
author
waffen29
committed
home: add IPv6 support for encrypted listeners
1 parent 5c44c4b commit 48e9c9d

6 files changed

Lines changed: 147 additions & 19 deletions

File tree

internal/home/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ var config = &configuration{
465465
AuthAttempts: 5,
466466
AuthBlockMin: 15,
467467
HTTPConfig: httpConfig{
468-
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
468+
Address: netip.AddrPortFrom(netip.IPv6Unspecified(), 3000),
469469
SessionTTL: timeutil.Duration(30 * timeutil.Day),
470470
Pprof: &httpPprofConfig{
471471
Enabled: false,
@@ -482,7 +482,7 @@ var config = &configuration{
482482
},
483483
},
484484
DNS: dnsConfig{
485-
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
485+
BindHosts: []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()},
486486
Port: defaultPortDNS,
487487
Config: dnsforward.Config{
488488
Ratelimit: 20,

internal/home/config_internal_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package home
22

33
import (
4+
"net/netip"
45
"os"
56
"path/filepath"
67
"testing"
78

9+
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
10+
"github.com/AdguardTeam/golibs/netutil"
811
"github.com/AdguardTeam/golibs/testutil"
912
"github.com/stretchr/testify/assert"
1013
"github.com/stretchr/testify/require"
@@ -89,3 +92,73 @@ func TestConfigFilePath(t *testing.T) {
8992
})
9093
}
9194
}
95+
96+
func TestNewServerConfig_DefaultHosts(t *testing.T) {
97+
dnsConf := &dnsConfig{
98+
BindHosts: nil,
99+
Port: 53,
100+
PendingRequests: &pendingRequests{
101+
Enabled: false,
102+
},
103+
}
104+
tlsConf := &tlsConfigSettings{}
105+
dohConf := &doHConfig{}
106+
107+
conf, err := newServerConfig(
108+
dnsConf,
109+
&clientSourcesConfig{},
110+
tlsConf,
111+
dohConf,
112+
&tlsManager{},
113+
&aghtest.Registrar{},
114+
nil, // clientsContainer
115+
&aghtest.ConfigModifier{},
116+
)
117+
require.NoError(t, err)
118+
require.Len(t, conf.UDPListenAddrs, 2)
119+
120+
assert.Equal(t, netutil.IPv4Localhost().String(), conf.UDPListenAddrs[0].IP.String())
121+
assert.Equal(t, netutil.IPv6Localhost().String(), conf.UDPListenAddrs[1].IP.String())
122+
}
123+
124+
func TestNewServerConfig_Issue8363BindHosts(t *testing.T) {
125+
bindHosts := []netip.Addr{
126+
netip.IPv4Unspecified(),
127+
netip.IPv6Unspecified(),
128+
netutil.IPv4Localhost(),
129+
netutil.IPv6Localhost(),
130+
}
131+
dnsConf := &dnsConfig{
132+
BindHosts: bindHosts,
133+
Port: 53,
134+
PendingRequests: &pendingRequests{
135+
Enabled: false,
136+
},
137+
}
138+
tlsConf := &tlsConfigSettings{
139+
Enabled: true,
140+
PortDNSOverTLS: 853,
141+
PortDNSOverQUIC: 853,
142+
CertificateChainData: requireReadFile(t, testCertificatePath),
143+
PrivateKeyData: requireReadFile(t, testPrivateKeyPath),
144+
}
145+
146+
conf, err := newServerConfig(
147+
dnsConf,
148+
&clientSourcesConfig{},
149+
tlsConf,
150+
&doHConfig{},
151+
&tlsManager{},
152+
&aghtest.Registrar{},
153+
nil, // clientsContainer
154+
&aghtest.ConfigModifier{},
155+
)
156+
require.NoError(t, err)
157+
require.Len(t, conf.TLSConf.TLSListenAddrs, len(bindHosts))
158+
require.Len(t, conf.TLSConf.QUICListenAddrs, len(bindHosts))
159+
160+
for i, host := range bindHosts {
161+
assert.Equal(t, host.String(), conf.TLSConf.TLSListenAddrs[i].IP.String())
162+
assert.Equal(t, host.String(), conf.TLSConf.QUICListenAddrs[i].IP.String())
163+
}
164+
}

internal/home/control.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
7171
// tlsMgr must not be nil.
7272
func collectDNSAddresses(tlsMgr *tlsManager) (addrs []string, err error) {
7373
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
74-
addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost())
74+
addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost(), netutil.IPv6Localhost())
7575
} else {
7676
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
7777
if err != nil {

internal/home/dns.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ func newServerConfig(
270270
clientsContainer dnsforward.ClientsContainer,
271271
confModifier agh.ConfigModifier,
272272
) (newConf *dnsforward.ServerConfig, err error) {
273-
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
273+
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{
274+
netutil.IPv4Localhost(),
275+
netutil.IPv6Localhost(),
276+
})
274277

275278
fwdConf := dnsConf.Config
276279
fwdConf.ClientsContainer = clientsContainer

internal/home/web.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,17 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf *tlsConfigSetti
339339
// loggerKeyServer is the key used by [webAPI] to identify servers.
340340
const loggerKeyServer = "server"
341341

342+
// getBindAddr returns the address string for the server to listen on. If the
343+
// address is unspecified, it returns the ":port" format to enable dual-stack
344+
// listening.
345+
func (web *webAPI) getBindAddr(addr netip.Addr, port uint16) (addrStr string) {
346+
if addr.IsUnspecified() {
347+
return netutil.JoinHostPort("", port)
348+
}
349+
350+
return netip.AddrPortFrom(addr, port).String()
351+
}
352+
342353
// start starts serving HTTP requests.
343354
func (web *webAPI) start(ctx context.Context) {
344355
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
@@ -363,7 +374,7 @@ func (web *webAPI) start(ctx context.Context) {
363374

364375
// Create a new instance, because the Web is not usable after Shutdown.
365376
web.httpServer = &http.Server{
366-
Addr: web.conf.BindAddr.String(),
377+
Addr: web.getBindAddr(web.conf.BindAddr.Addr(), web.conf.BindAddr.Port()),
367378
Handler: hdlr,
368379
ReadTimeout: web.conf.ReadTimeout,
369380
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
@@ -457,13 +468,13 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
457468
portHTTPS = config.TLS.PortHTTPS
458469
}()
459470

460-
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
471+
addrStr := web.getBindAddr(web.conf.BindAddr.Addr(), portHTTPS)
461472
logger := web.baseLogger.With(loggerKeyServer, "https")
462473

463474
hdlr := web.wrapMux(logger)
464475

465476
web.httpsServer.server = &http.Server{
466-
Addr: addr,
477+
Addr: addrStr,
467478
Handler: hdlr,
468479
TLSConfig: &tls.Config{
469480
Certificates: []tls.Certificate{web.httpsServer.certificate()},
@@ -480,7 +491,7 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
480491
printHTTPAddresses(ctx, web.logger, urlutil.SchemeHTTPS, web.tlsManager)
481492

482493
if web.conf.serveHTTP3 {
483-
go web.mustStartHTTP3(ctx, addr)
494+
go web.mustStartHTTP3(ctx, addrStr)
484495
}
485496

486497
logger.InfoContext(ctx, "starting https server")
@@ -523,10 +534,9 @@ func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
523534
}
524535
}
525536

526-
// startPprof launches the debug and profiling server on the provided port.
537+
// startPprof launches the debug and profiling server on the provided port on
538+
// both IPv4 and IPv6 loopback addresses.
527539
func startPprof(baseLogger *slog.Logger, port uint16) {
528-
addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port)
529-
530540
runtime.SetBlockProfileRate(1)
531541
runtime.SetMutexProfileFraction(1)
532542

@@ -536,15 +546,26 @@ func startPprof(baseLogger *slog.Logger, port uint16) {
536546
ctx := context.Background()
537547
logger := baseLogger.With(slogutil.KeyPrefix, "pprof")
538548

539-
go func() {
540-
defer slogutil.RecoverAndLog(ctx, logger)
549+
go servePprof(ctx, logger, mux, netutil.IPv4Localhost(), port)
550+
go servePprof(ctx, logger, mux, netutil.IPv6Localhost(), port)
551+
}
541552

542-
logger.InfoContext(ctx, "listening", "addr", addr)
543-
err := http.ListenAndServe(addr.String(), mux)
544-
if !errors.Is(err, http.ErrServerClosed) {
545-
logger.ErrorContext(ctx, "shutting down", slogutil.KeyError, err)
546-
}
547-
}()
553+
// servePprof serves the pprof HTTP endpoints on the given host and port.
554+
func servePprof(
555+
ctx context.Context,
556+
logger *slog.Logger,
557+
mux *http.ServeMux,
558+
host netip.Addr,
559+
port uint16,
560+
) {
561+
defer slogutil.RecoverAndLog(ctx, logger)
562+
563+
addrStr := netip.AddrPortFrom(host, port).String()
564+
logger.InfoContext(ctx, "listening", "addr", addrStr)
565+
err := http.ListenAndServe(addrStr, mux)
566+
if !errors.Is(err, http.ErrServerClosed) {
567+
logger.ErrorContext(ctx, "shutting down", slogutil.KeyError, err)
568+
}
548569
}
549570

550571
// registerTLSHandlers registers HTTP handlers for TLS configuration.

internal/home/web_internal_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,37 @@ import (
2626
"github.com/stretchr/testify/require"
2727
)
2828

29+
func TestWebAPI_getBindAddr(t *testing.T) {
30+
web := &webAPI{}
31+
testCases := []struct {
32+
name string
33+
addr netip.Addr
34+
want string
35+
}{{
36+
name: "ipv4_unspecified",
37+
addr: netip.IPv4Unspecified(),
38+
want: ":443",
39+
}, {
40+
name: "ipv6_unspecified",
41+
addr: netip.IPv6Unspecified(),
42+
want: ":443",
43+
}, {
44+
name: "ipv4",
45+
addr: netutil.IPv4Localhost(),
46+
want: "127.0.0.1:443",
47+
}, {
48+
name: "ipv6",
49+
addr: netutil.IPv6Localhost(),
50+
want: "[::1]:443",
51+
}}
52+
53+
for _, tc := range testCases {
54+
t.Run(tc.name, func(t *testing.T) {
55+
assert.Equal(t, tc.want, web.getBindAddr(tc.addr, 443))
56+
})
57+
}
58+
}
59+
2960
func TestWebAPI_HandleTLSConfigure(t *testing.T) {
3061
// Store the global state before making any changes.
3162
storeGlobals(t)

0 commit comments

Comments
 (0)