diff --git a/CHANGELOG.md b/CHANGELOG.md index e48ba88c381..d9a6d2198ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ NOTE: Add new changes BELOW THIS COMMENT. - Support for link-local subnets, i.e. `fe80::/16`, as client identifiers ([#6312]). +- Issues with QUIC and HTTP/3 upstreams on older Linux kernel versions + ([#6422]). - YouTube restricted mode is not enforced by HTTPS queries on Firefox. - Support for link-local subnets, i.e. `fe80::/16`, in the access settings ([#6192]). @@ -52,6 +54,7 @@ NOTE: Add new changes BELOW THIS COMMENT. [#5812]: https://github.com/AdguardTeam/AdGuardHome/issues/5812 [#6192]: https://github.com/AdguardTeam/AdGuardHome/issues/6192 [#6312]: https://github.com/AdguardTeam/AdGuardHome/issues/6312 +[#6422]: https://github.com/AdguardTeam/AdGuardHome/issues/6422 [#6854]: https://github.com/AdguardTeam/AdGuardHome/issues/6854 [#6875]: https://github.com/AdguardTeam/AdGuardHome/issues/6875 [#6882]: https://github.com/AdguardTeam/AdGuardHome/issues/6882 diff --git a/go.mod b/go.mod index dd21a23d384..691f547225e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/AdguardTeam/AdGuardHome go 1.22.2 require ( - github.com/AdguardTeam/dnsproxy v0.70.0 + // TODO(a.garipov): Use a tagged version once released. + github.com/AdguardTeam/dnsproxy v0.70.1-0.20240424112457-69feed2dd25e github.com/AdguardTeam/golibs v0.23.2 github.com/AdguardTeam/urlfilter v0.18.0 github.com/NYTimes/gziphandler v1.1.1 @@ -28,7 +29,8 @@ require ( // own code for that. Perhaps, use gopacket. github.com/mdlayher/raw v0.1.0 github.com/miekg/dns v1.1.58 - github.com/quic-go/quic-go v0.42.0 + // TODO(a.garipov): Use a tagged version once released. + github.com/quic-go/quic-go v0.42.1-0.20240424132812-713525777535 github.com/stretchr/testify v1.9.0 github.com/ti-mo/netfilter v0.5.1 go.etcd.io/bbolt v1.3.9 diff --git a/go.sum b/go.sum index 59758a60716..a5c9a929a60 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/AdguardTeam/dnsproxy v0.70.0 h1:lwPQ+pfyCuorrP6RS90K628bRn8uvvTlnRyQuLKnf2o= -github.com/AdguardTeam/dnsproxy v0.70.0/go.mod h1:zpA9eBxakSyjKC/bUac+UPSYTp/Q43aOmNlBV2/D6ug= +github.com/AdguardTeam/dnsproxy v0.70.1-0.20240424112457-69feed2dd25e h1:ju0wprmCakjAOIuvKrmLU+hUFiStIsreWVk4JWmQPm0= +github.com/AdguardTeam/dnsproxy v0.70.1-0.20240424112457-69feed2dd25e/go.mod h1:eWyFj9zVMdJ4tjHULulfFIXiu6GID/aVvewLVVXLXWE= github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao= github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0= github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ= @@ -101,8 +101,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= -github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/quic-go v0.42.1-0.20240424132812-713525777535 h1:63/XLGwhqZUU0L4DPWihJH9tyqwmvfaxNPjU9I/Av/M= +github.com/quic-go/quic-go v0.42.1-0.20240424132812-713525777535/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= diff --git a/internal/dnsforward/beforerequest.go b/internal/dnsforward/beforerequest.go index 21bc43a3985..8a1b0272359 100644 --- a/internal/dnsforward/beforerequest.go +++ b/internal/dnsforward/beforerequest.go @@ -18,7 +18,7 @@ var _ proxy.BeforeRequestHandler = (*Server)(nil) // including logs. It performs access checks and puts the client ID, if there // is one, into the server's cache. // -// TODO(e.burkov): Write tests. +// TODO(d.kolyshev): Extract to separate package. func (s *Server) HandleBefore( _ *proxy.Proxy, pctx *proxy.DNSContext, diff --git a/internal/dnsforward/beforerequest_internal_test.go b/internal/dnsforward/beforerequest_internal_test.go new file mode 100644 index 00000000000..7e0d6e9b939 --- /dev/null +++ b/internal/dnsforward/beforerequest_internal_test.go @@ -0,0 +1,299 @@ +package dnsforward + +import ( + "crypto/tls" + "net" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + blockedHost = "blockedhost.org" + testFQDN = "example.org." + dnsClientTimeout = 200 * time.Millisecond +) + +func TestServer_HandleBefore_tls(t *testing.T) { + t.Parallel() + + const clientID = "client-1" + + testCases := []struct { + clientSrvName string + name string + host string + allowedClients []string + disallowedClients []string + blockedHosts []string + wantRCode int + }{{ + clientSrvName: tlsServerName, + name: "allow_all", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantRCode: dns.RcodeSuccess, + }, { + clientSrvName: "%" + "." + tlsServerName, + name: "invalid_client_id", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantRCode: dns.RcodeServerFailure, + }, { + clientSrvName: clientID + "." + tlsServerName, + name: "allowed_client_allowed", + host: testFQDN, + allowedClients: []string{clientID}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantRCode: dns.RcodeSuccess, + }, { + clientSrvName: "client-2." + tlsServerName, + name: "allowed_client_rejected", + host: testFQDN, + allowedClients: []string{clientID}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantRCode: dns.RcodeRefused, + }, { + clientSrvName: tlsServerName, + name: "disallowed_client_allowed", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{clientID}, + blockedHosts: []string{}, + wantRCode: dns.RcodeSuccess, + }, { + clientSrvName: clientID + "." + tlsServerName, + name: "disallowed_client_rejected", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{clientID}, + blockedHosts: []string{}, + wantRCode: dns.RcodeRefused, + }, { + clientSrvName: tlsServerName, + name: "blocked_hosts_allowed", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{blockedHost}, + wantRCode: dns.RcodeSuccess, + }, { + clientSrvName: tlsServerName, + name: "blocked_hosts_rejected", + host: dns.Fqdn(blockedHost), + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{blockedHost}, + wantRCode: dns.RcodeRefused, + }} + + localAns := []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: testFQDN, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 3600, + Rdlength: 4, + }, + A: net.IP{1, 2, 3, 4}, + }} + localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) { + resp := (&dns.Msg{}).SetReply(req) + resp.Answer = localAns + + require.NoError(t, w.WriteMsg(resp)) + }) + localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + s, _ := createTestTLS(t, TLSConfig{ + TLSListenAddrs: []*net.TCPAddr{{}}, + ServerName: tlsServerName, + }) + + s.conf.UpstreamDNS = []string{localUpsAddr} + + s.conf.AllowedClients = tc.allowedClients + s.conf.DisallowedClients = tc.disallowedClients + s.conf.BlockedHosts = tc.blockedHosts + + err := s.Prepare(&s.conf) + require.NoError(t, err) + + startDeferStop(t, s) + + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: tc.clientSrvName, + } + + client := &dns.Client{ + Net: "tcp-tls", + TLSConfig: tlsConfig, + Timeout: dnsClientTimeout, + } + + req := createTestMessage(tc.host) + addr := s.dnsProxy.Addr(proxy.ProtoTLS).String() + + reply, _, err := client.Exchange(req, addr) + require.NoError(t, err) + + assert.Equal(t, tc.wantRCode, reply.Rcode) + if tc.wantRCode == dns.RcodeSuccess { + assert.Equal(t, localAns, reply.Answer) + } else { + assert.Empty(t, reply.Answer) + } + }) + } +} + +func TestServer_HandleBefore_udp(t *testing.T) { + t.Parallel() + + const ( + clientIPv4 = "127.0.0.1" + clientIPv6 = "::1" + ) + + clientIPs := []string{clientIPv4, clientIPv6} + + testCases := []struct { + name string + host string + allowedClients []string + disallowedClients []string + blockedHosts []string + wantTimeout bool + }{{ + name: "allow_all", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantTimeout: false, + }, { + name: "allowed_client_allowed", + host: testFQDN, + allowedClients: clientIPs, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantTimeout: false, + }, { + name: "allowed_client_rejected", + host: testFQDN, + allowedClients: []string{"1:2:3::4"}, + disallowedClients: []string{}, + blockedHosts: []string{}, + wantTimeout: true, + }, { + name: "disallowed_client_allowed", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{"1:2:3::4"}, + blockedHosts: []string{}, + wantTimeout: false, + }, { + name: "disallowed_client_rejected", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: clientIPs, + blockedHosts: []string{}, + wantTimeout: true, + }, { + name: "blocked_hosts_allowed", + host: testFQDN, + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{blockedHost}, + wantTimeout: false, + }, { + name: "blocked_hosts_rejected", + host: dns.Fqdn(blockedHost), + allowedClients: []string{}, + disallowedClients: []string{}, + blockedHosts: []string{blockedHost}, + wantTimeout: true, + }} + + localAns := []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: testFQDN, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 3600, + Rdlength: 4, + }, + A: net.IP{1, 2, 3, 4}, + }} + localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) { + resp := (&dns.Msg{}).SetReply(req) + resp.Answer = localAns + + require.NoError(t, w.WriteMsg(resp)) + }) + localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, + Config: Config{ + AllowedClients: tc.allowedClients, + DisallowedClients: tc.disallowedClients, + BlockedHosts: tc.blockedHosts, + UpstreamDNS: []string{localUpsAddr}, + UpstreamMode: UpstreamModeLoadBalance, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, + ServePlainDNS: true, + }) + + startDeferStop(t, s) + + client := &dns.Client{ + Net: "udp", + Timeout: dnsClientTimeout, + } + + req := createTestMessage(tc.host) + addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() + + reply, _, err := client.Exchange(req, addr) + if tc.wantTimeout { + wantErr := &net.OpError{} + require.ErrorAs(t, err, &wantErr) + assert.True(t, wantErr.Timeout()) + + assert.Nil(t, reply) + } else { + require.NoError(t, err) + require.NotNil(t, reply) + + assert.Equal(t, dns.RcodeSuccess, reply.Rcode) + assert.Equal(t, localAns, reply.Answer) + } + }) + } +}