Skip to content

Commit 9e23f7a

Browse files
waffen29waffen29
authored andcommitted
home: add IPv6 support for encrypted listeners
1 parent 2e84763 commit 9e23f7a

6 files changed

Lines changed: 388 additions & 26 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: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package home
22

33
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"net/netip"
47
"os"
58
"path/filepath"
69
"testing"
710

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

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 & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io/fs"
99
"log/slog"
10+
"net"
1011
"net/http"
1112
"net/netip"
1213
"runtime"
@@ -330,6 +331,24 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context) {
330331
// loggerKeyServer is the key used by [webAPI] to identify servers.
331332
const loggerKeyServer = "server"
332333

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

381+
network, addrStr := getBindAddr("tcp", web.conf.BindAddr.Addr(), web.conf.BindAddr.Port())
382+
362383
// Create a new instance, because the Web is not usable after Shutdown.
363384
web.httpServer = &http.Server{
364-
Addr: web.conf.BindAddr.String(),
385+
Addr: addrStr,
365386
Handler: hdlr,
366387
ReadTimeout: web.conf.ReadTimeout,
367388
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
@@ -372,9 +393,16 @@ func (web *webAPI) start(ctx context.Context) {
372393
go func() {
373394
defer slogutil.RecoverAndLog(ctx, logger)
374395

375-
logger.InfoContext(ctx, "starting plain server", "addr", web.httpServer.Addr)
396+
logger.InfoContext(ctx, "starting plain server", "addr", addrStr)
397+
398+
ln, lErr := net.Listen(network, addrStr)
399+
if lErr != nil {
400+
errs <- lErr
376401

377-
errs <- web.httpServer.ListenAndServe()
402+
return
403+
}
404+
405+
errs <- web.httpServer.Serve(ln)
378406
}()
379407

380408
err := <-errs
@@ -455,13 +483,13 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
455483
portHTTPS = config.TLS.PortHTTPS
456484
}()
457485

458-
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
486+
network, addrStr := getBindAddr("tcp", web.conf.BindAddr.Addr(), portHTTPS)
459487
logger := web.baseLogger.With(loggerKeyServer, "https")
460488

461489
hdlr := web.wrapMux(logger)
462490

463491
web.httpsServer.server = &http.Server{
464-
Addr: addr,
492+
Addr: addrStr,
465493
Handler: hdlr,
466494
TLSConfig: web.tlsManager.TLSConfig(),
467495
ReadTimeout: web.conf.ReadTimeout,
@@ -474,11 +502,15 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
474502
printHTTPSAddresses(ctx, web.logger, extTLSConf)
475503

476504
if web.conf.serveHTTP3 {
477-
go web.mustStartHTTP3(ctx, addr)
505+
go web.mustStartHTTP3(ctx, portHTTPS)
478506
}
479507

480508
logger.InfoContext(ctx, "starting https server")
481-
err := web.httpsServer.server.ListenAndServeTLS("", "")
509+
ln, err := net.Listen(network, addrStr)
510+
if err == nil {
511+
err = web.httpsServer.server.ServeTLS(ln, "", "")
512+
}
513+
482514
if !errors.Is(err, http.ErrServerClosed) {
483515
cleanupAlways(ctx, logger, web.pidFilePath)
484516

@@ -488,35 +520,57 @@ func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
488520
return true
489521
}
490522

491-
// mustStartHTTP3 initializes and starts HTTP3 server.
492-
func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
523+
// mustStartHTTP3 initializes and starts HTTP3 server on the configured bind
524+
// address with the given port.
525+
func (web *webAPI) mustStartHTTP3(ctx context.Context, port uint16) {
493526
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
494527

495528
logger := web.baseLogger.With(loggerKeyServer, "http3")
496529
hdlr := web.wrapMux(logger)
497530

531+
network, addrStr := getBindAddr("udp", web.conf.BindAddr.Addr(), port)
532+
498533
web.httpsServer.server3 = &http3.Server{
499534
// TODO(a.garipov): See if there is a way to use the error log as
500535
// well as timeouts here.
501-
Addr: address,
536+
Addr: addrStr,
502537
TLSConfig: web.tlsManager.TLSConfig(),
503538
Handler: hdlr,
504539
}
505540

506541
web.logger.DebugContext(ctx, "starting http/3 server")
507-
err := web.httpsServer.server3.ListenAndServe()
542+
err := serveHTTP3(ctx, logger, web.httpsServer.server3, network, addrStr)
508543
if !errors.Is(err, http.ErrServerClosed) {
509544
cleanupAlways(ctx, logger, web.pidFilePath)
510545

511546
panic(fmt.Errorf("http3: %w", err))
512547
}
513548
}
514549

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

571+
// startPprof launches the debug and profiling server on the provided port on
572+
// both IPv4 and IPv6 loopback addresses. baseLogger must not be nil.
573+
func startPprof(baseLogger *slog.Logger, port uint16) {
520574
runtime.SetBlockProfileRate(1)
521575
runtime.SetMutexProfileFraction(1)
522576

@@ -526,15 +580,26 @@ func startPprof(baseLogger *slog.Logger, port uint16) {
526580
ctx := context.Background()
527581
logger := baseLogger.With(slogutil.KeyPrefix, "pprof")
528582

529-
go func() {
530-
defer slogutil.RecoverAndLog(ctx, logger)
583+
go servePprof(ctx, logger, mux, netutil.IPv4Localhost(), port)
584+
go servePprof(ctx, logger, mux, netutil.IPv6Localhost(), port)
585+
}
531586

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

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

0 commit comments

Comments
 (0)