Skip to content

Commit 0aae4a8

Browse files
waffen29waffen29
authored andcommitted
home: add IPv6 support for encrypted listeners
1 parent 0c3cc90 commit 0aae4a8

6 files changed

Lines changed: 382 additions & 25 deletions

File tree

internal/home/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ var config = &configuration{
425425
AuthAttempts: 5,
426426
AuthBlockMin: 15,
427427
HTTPConfig: httpConfig{
428-
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
428+
Address: netip.AddrPortFrom(netip.IPv6Unspecified(), 3000),
429429
SessionTTL: timeutil.Duration(30 * timeutil.Day),
430430
Pprof: &httpPprofConfig{
431431
Enabled: false,

internal/home/config_internal_test.go

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

33
import (
4+
"crypto/x509"
5+
"net/netip"
46
"os"
57
"path/filepath"
68
"testing"
79

10+
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
11+
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
12+
"github.com/AdguardTeam/golibs/netutil"
813
"github.com/AdguardTeam/golibs/testutil"
914
"github.com/stretchr/testify/assert"
1015
"github.com/stretchr/testify/require"
@@ -89,3 +94,81 @@ func TestConfigFilePath(t *testing.T) {
8994
})
9095
}
9196
}
97+
98+
// newTestTLSConfigProvider returns a [aghtls.TLSConfigProvider] fake that
99+
// serves the given extended TLS configuration. extTLSConf must not be nil.
100+
func newTestTLSConfigProvider(extTLSConf *aghtls.ExtendedTLSConfig) (p *aghtest.TLSConfigProvider) {
101+
return &aghtest.TLSConfigProvider{
102+
OnExtendedTLSConfig: func() (conf *aghtls.ExtendedTLSConfig) {
103+
return extTLSConf
104+
},
105+
OnRootCAs: func() (pool *x509.CertPool) {
106+
return nil
107+
},
108+
}
109+
}
110+
111+
func TestNewServerConfig_DefaultHosts(t *testing.T) {
112+
dnsConf := &dnsConfig{
113+
BindHosts: nil,
114+
Port: 53,
115+
PendingRequests: &pendingRequests{
116+
Enabled: false,
117+
},
118+
}
119+
dohConf := &doHConfig{}
120+
121+
conf, err := newServerConfig(
122+
dnsConf,
123+
&clientSourcesConfig{},
124+
dohConf,
125+
newTestTLSConfigProvider(&aghtls.ExtendedTLSConfig{}),
126+
&aghtest.Registrar{},
127+
nil, // clientsContainer
128+
&aghtest.ConfigModifier{},
129+
)
130+
require.NoError(t, err)
131+
require.Len(t, conf.UDPListenAddrs, 2)
132+
133+
assert.Equal(t, netutil.IPv4Localhost().String(), conf.UDPListenAddrs[0].IP.String())
134+
assert.Equal(t, netutil.IPv6Localhost().String(), conf.UDPListenAddrs[1].IP.String())
135+
}
136+
137+
func TestNewServerConfig_Issue8363BindHosts(t *testing.T) {
138+
bindHosts := []netip.Addr{
139+
netip.IPv4Unspecified(),
140+
netip.IPv6Unspecified(),
141+
netutil.IPv4Localhost(),
142+
netutil.IPv6Localhost(),
143+
}
144+
dnsConf := &dnsConfig{
145+
BindHosts: bindHosts,
146+
Port: 53,
147+
PendingRequests: &pendingRequests{
148+
Enabled: false,
149+
},
150+
}
151+
extTLSConf := &aghtls.ExtendedTLSConfig{
152+
Enabled: true,
153+
PortDNSOverTLS: 853,
154+
PortDNSOverQUIC: 853,
155+
}
156+
157+
conf, err := newServerConfig(
158+
dnsConf,
159+
&clientSourcesConfig{},
160+
&doHConfig{},
161+
newTestTLSConfigProvider(extTLSConf),
162+
&aghtest.Registrar{},
163+
nil, // clientsContainer
164+
&aghtest.ConfigModifier{},
165+
)
166+
require.NoError(t, err)
167+
require.Len(t, conf.TLSConf.TLSListenAddrs, len(bindHosts))
168+
require.Len(t, conf.TLSConf.QUICListenAddrs, len(bindHosts))
169+
170+
for i, host := range bindHosts {
171+
assert.Equal(t, host.String(), conf.TLSConf.TLSListenAddrs[i].IP.String())
172+
assert.Equal(t, host.String(), conf.TLSConf.QUICListenAddrs[i].IP.String())
173+
}
174+
}

internal/home/control.go

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

internal/home/dns.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,10 @@ func newServerConfig(
265265
clientsContainer dnsforward.ClientsContainer,
266266
confModifier agh.ConfigModifier,
267267
) (newConf *dnsforward.ServerConfig, err error) {
268-
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
268+
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{
269+
netutil.IPv4Localhost(),
270+
netutil.IPv6Localhost(),
271+
})
269272

270273
fwdConf := dnsConf.Config
271274
fwdConf.ClientsContainer = clientsContainer

internal/home/web.go

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io/fs"
88
"log/slog"
9+
"net"
910
"net/http"
1011
"net/netip"
1112
"runtime"
@@ -334,6 +335,24 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf *aghtls.Extende
334335
// loggerKeyServer is the key used by [webAPI] to identify servers.
335336
const loggerKeyServer = "server"
336337

338+
// getBindAddr returns the network and address strings to use when creating a
339+
// listener on addr and port. network must be either "tcp" or "udp". The
340+
// address family of addr is preserved: for the unspecified IPv4 address the
341+
// IPv4-only network is returned, since Go's wildcard listeners otherwise
342+
// accept connections of both address families on platforms that support
343+
// IPv4-mapped IPv6 addresses. For the unspecified IPv6 address the returned
344+
// address is in the ":port" form, which enables dual-stack listening.
345+
func getBindAddr(network string, addr netip.Addr, port uint16) (listenNetwork, addrStr string) {
346+
switch {
347+
case !addr.IsUnspecified():
348+
return network, netip.AddrPortFrom(addr, port).String()
349+
case addr.Is4():
350+
return network + "4", netip.AddrPortFrom(addr, port).String()
351+
default:
352+
return network, netutil.JoinHostPort("", port)
353+
}
354+
}
355+
337356
// start starts serving HTTP requests.
338357
func (web *webAPI) start(ctx context.Context) {
339358
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
@@ -356,9 +375,11 @@ func (web *webAPI) start(ctx context.Context) {
356375
protocols.SetUnencryptedHTTP2(true)
357376
protocols.SetHTTP1(true)
358377

378+
network, addrStr := getBindAddr("tcp", web.conf.BindAddr.Addr(), web.conf.BindAddr.Port())
379+
359380
// Create a new instance, because the Web is not usable after Shutdown.
360381
web.httpServer = &http.Server{
361-
Addr: web.conf.BindAddr.String(),
382+
Addr: addrStr,
362383
Handler: hdlr,
363384
ReadTimeout: web.conf.ReadTimeout,
364385
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
@@ -369,9 +390,16 @@ func (web *webAPI) start(ctx context.Context) {
369390
go func() {
370391
defer slogutil.RecoverAndLog(ctx, logger)
371392

372-
logger.InfoContext(ctx, "starting plain server", "addr", web.httpServer.Addr)
393+
logger.InfoContext(ctx, "starting plain server", "addr", addrStr)
394+
395+
ln, lErr := net.Listen(network, addrStr)
396+
if lErr != nil {
397+
errs <- lErr
373398

374-
errs <- web.httpServer.ListenAndServe()
399+
return
400+
}
401+
402+
errs <- web.httpServer.Serve(ln)
375403
}()
376404

377405
err := <-errs
@@ -452,13 +480,13 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
452480
portHTTPS = config.TLS.PortHTTPS
453481
}()
454482

455-
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
483+
network, addrStr := getBindAddr("tcp", web.conf.BindAddr.Addr(), portHTTPS)
456484
logger := web.baseLogger.With(loggerKeyServer, "https")
457485

458486
hdlr := web.wrapMux(logger)
459487

460488
web.httpsServer.server = &http.Server{
461-
Addr: addr,
489+
Addr: addrStr,
462490
Handler: hdlr,
463491
TLSConfig: web.tlsConfProvider.TLSConfig(),
464492
ReadTimeout: web.conf.ReadTimeout,
@@ -471,11 +499,15 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
471499
printHTTPSAddresses(ctx, web.logger, extTLSConf)
472500

473501
if web.conf.serveHTTP3 {
474-
go web.mustStartHTTP3(ctx, addr)
502+
go web.mustStartHTTP3(ctx, portHTTPS)
475503
}
476504

477505
logger.InfoContext(ctx, "starting https server")
478-
err := web.httpsServer.server.ListenAndServeTLS("", "")
506+
ln, err := net.Listen(network, addrStr)
507+
if err == nil {
508+
err = web.httpsServer.server.ServeTLS(ln, "", "")
509+
}
510+
479511
if !errors.Is(err, http.ErrServerClosed) {
480512
cleanupAlways(ctx, logger, web.pidFilePath)
481513

@@ -485,34 +517,57 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
485517
return true
486518
}
487519

488-
// mustStartHTTP3 initializes and starts HTTP3 server.
489-
func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
520+
// mustStartHTTP3 initializes and starts HTTP3 server on the configured bind
521+
// address with the given port.
522+
func (web *webAPI) mustStartHTTP3(ctx context.Context, port uint16) {
490523
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
491524

492525
logger := web.baseLogger.With(loggerKeyServer, "http3")
493526
hdlr := web.wrapMux(logger)
494527

528+
network, addrStr := getBindAddr("udp", web.conf.BindAddr.Addr(), port)
529+
495530
web.httpsServer.server3 = &http3.Server{
496531
// TODO(a.garipov): See if there is a way to use the error log as
497532
// well as timeouts here.
498-
Addr: address,
533+
Addr: addrStr,
499534
TLSConfig: web.tlsConfProvider.TLSConfig(),
500535
Handler: hdlr,
501536
}
502537

503538
web.logger.DebugContext(ctx, "starting http/3 server")
504-
err := web.httpsServer.server3.ListenAndServe()
539+
err := serveHTTP3(ctx, logger, web.httpsServer.server3, network, addrStr)
505540
if !errors.Is(err, http.ErrServerClosed) {
506541
cleanupAlways(ctx, logger, web.pidFilePath)
507542

508543
panic(fmt.Errorf("http3: %w", err))
509544
}
510545
}
511546

512-
// startPprof launches the debug and profiling server on the provided port.
513-
func startPprof(baseLogger *slog.Logger, port uint16) {
514-
addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port)
547+
// serveHTTP3 listens for UDP packets on the given network and address, and
548+
// serves HTTP/3 requests on srv until it is closed. The created packet
549+
// connection is closed before returning, since [http3.Server.Serve] does not
550+
// close connections provided by the caller. logger and srv must not be nil.
551+
func serveHTTP3(
552+
ctx context.Context,
553+
logger *slog.Logger,
554+
srv *http3.Server,
555+
network string,
556+
addrStr string,
557+
) (err error) {
558+
conn, err := net.ListenPacket(network, addrStr)
559+
if err != nil {
560+
// Don't wrap the error because it's informative enough as is.
561+
return err
562+
}
563+
defer slogutil.CloseAndLog(ctx, logger, conn, slog.LevelDebug)
564+
565+
return srv.Serve(conn)
566+
}
515567

568+
// startPprof launches the debug and profiling server on the provided port on
569+
// both IPv4 and IPv6 loopback addresses.
570+
func startPprof(baseLogger *slog.Logger, port uint16) {
516571
runtime.SetBlockProfileRate(1)
517572
runtime.SetMutexProfileFraction(1)
518573

@@ -522,15 +577,26 @@ func startPprof(baseLogger *slog.Logger, port uint16) {
522577
ctx := context.Background()
523578
logger := baseLogger.With(slogutil.KeyPrefix, "pprof")
524579

525-
go func() {
526-
defer slogutil.RecoverAndLog(ctx, logger)
580+
go servePprof(ctx, logger, mux, netutil.IPv4Localhost(), port)
581+
go servePprof(ctx, logger, mux, netutil.IPv6Localhost(), port)
582+
}
527583

528-
logger.InfoContext(ctx, "listening", "addr", addr)
529-
err := http.ListenAndServe(addr.String(), mux)
530-
if !errors.Is(err, http.ErrServerClosed) {
531-
logger.ErrorContext(ctx, "shutting down", slogutil.KeyError, err)
532-
}
533-
}()
584+
// servePprof serves the pprof HTTP endpoints on the given host and port.
585+
func servePprof(
586+
ctx context.Context,
587+
logger *slog.Logger,
588+
mux *http.ServeMux,
589+
host netip.Addr,
590+
port uint16,
591+
) {
592+
defer slogutil.RecoverAndLog(ctx, logger)
593+
594+
addrStr := netip.AddrPortFrom(host, port).String()
595+
logger.InfoContext(ctx, "listening", "addr", addrStr)
596+
err := http.ListenAndServe(addrStr, mux)
597+
if !errors.Is(err, http.ErrServerClosed) {
598+
logger.ErrorContext(ctx, "shutting down", slogutil.KeyError, err)
599+
}
534600
}
535601

536602
// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.

0 commit comments

Comments
 (0)