mirror of
https://github.com/AdguardTeam/dnsproxy.git
synced 2026-10-12 10:09:55 +00:00
Pull request 285: AG-23599 use hostsfile vol.2
Merge in GO/dnsproxy from AG-23599-use-hostsfile-vol.2 to master Squashed commit of the following: commit b8fd8d5bbd2803ea6908a5ca938d60b9f256530f Author: Eugene Burkov <[email protected]> Date: Fri Nov 10 19:21:33 2023 +0300 upstream: fix nil deref commit 11ec4feb9f1f5e575f12310c0438ed89c81c722e Author: Eugene Burkov <[email protected]> Date: Fri Nov 10 18:54:45 2023 +0300 bootstrap: imp docs commit 93aa3b1c54f762f59bcadd145294028f38fe4d93 Author: Eugene Burkov <[email protected]> Date: Fri Nov 10 17:30:15 2023 +0300 bootstrap: imp doc commit 9779260e70d5ef7a3d8ff1d550bf5558007879d6 Merge: 1240ee1ea0f2b1Author: Eugene Burkov <[email protected]> Date: Fri Nov 10 16:43:54 2023 +0300 Merge branch 'master' into AG-23599-use-hostsfile-vol.2 commit 1240ee16e968b63aca8ac27a024716b6f4159cb4 Author: Eugene Burkov <[email protected]> Date: Thu Nov 9 17:37:58 2023 +0300 upstream: remove sort from upstream resolver commit f22fe9bdfdcaf4ef9b81cbb0f94bd926eb45b6bb Author: Eugene Burkov <[email protected]> Date: Thu Nov 9 15:37:01 2023 +0300 all: upd go commit 45c52151c8ed50a94fc040fbfd585499f3beabae Author: Eugene Burkov <[email protected]> Date: Thu Nov 9 15:34:22 2023 +0300 upstream: remove empty impl commit 8582ed91e27955660bd787896d7945b07e204140 Author: Eugene Burkov <[email protected]> Date: Wed Nov 8 13:00:14 2023 +0300 upstream: imp cognit commit 35ce9dcac57b3eb9e4e80fe6dd0152e568077d73 Author: Eugene Burkov <[email protected]> Date: Wed Nov 8 12:47:25 2023 +0300 all: imp code, fmt commit 1823da58c765fdeb17a1ab70aebb0bd5eee8cdcf Author: Eugene Burkov <[email protected]> Date: Wed Nov 8 12:33:35 2023 +0300 all: imp docs, restore behavior commit aeda1791f483e9ddb6910a66a1415ed424578fbb Author: Eugene Burkov <[email protected]> Date: Tue Nov 7 15:23:17 2023 +0300 main: fix import commit fa3746453f77e32fe90f9cb53f9dbbc10e421dc8 Merge: eb12aec254e5a8Author: Eugene Burkov <[email protected]> Date: Tue Nov 7 14:33:18 2023 +0300 Merge branch 'master' into AG-23599-use-hostsfile-vol.2 commit eb12aecb33afcb6c05f86c4787bffeb8a6018a33 Author: Eugene Burkov <[email protected]> Date: Fri Nov 3 19:44:15 2023 +0300 upstream: fix nil deref commit e8975360323190e91043ad4ec13e302696210971 Author: Eugene Burkov <[email protected]> Date: Fri Nov 3 18:38:00 2023 +0300 all: imp code, docs commit 60e709976413d5c64b54b000eb243ff4d83f6ca2 Merge: 0e6e9ad8d20902Author: Eugene Burkov <[email protected]> Date: Tue Oct 31 17:16:14 2023 +0300 Merge branch 'master' into AG-23599-use-hostsfile-vol.2 commit 0e6e9ad9ca61f11ebf17de6e047f005d94d630d1 Merge: b10f900b4abccfAuthor: Eugene Burkov <[email protected]> Date: Wed Oct 11 17:09:08 2023 +0300 Merge branch 'master' into AG-23599-use-hostsfile-vol.2 commit b10f900a3bb8682e6de2105569c0e6569ef84afb Author: Eugene Burkov <[email protected]> Date: Wed Oct 11 16:30:18 2023 +0300 upstream: imp returning types & docs commit a5f50776da1b0396bd0e66d784996ea4832ab26b Author: Eugene Burkov <[email protected]> Date: Thu Oct 5 18:52:20 2023 +0300 proxy: fix merged test commit a1535566677d6a1b7ccbfd7c89efeff87501d7b5 Author: Eugene Burkov <[email protected]> Date: Thu Oct 5 18:50:27 2023 +0300 netutil: doc more, fix type check commit 718aee887448e5fc38c9069579c69757ba067c38 Merge: 34750a660d2174Author: Eugene Burkov <[email protected]> Date: Thu Oct 5 16:07:10 2023 +0300 Merge branch 'master' into AG-23599-use-hostsfile-vol.2 ... and 8 more commits
This commit is contained in:
@@ -21,12 +21,12 @@ import (
|
||||
// specified at initialization and ignores the addr.
|
||||
type DialHandler func(ctx context.Context, network, addr string) (conn net.Conn, err error)
|
||||
|
||||
// ResolveDialContext returns a DialHandler that uses addresses resolved from
|
||||
// u using resolvers. u must not be nil.
|
||||
// ResolveDialContext returns a DialHandler that uses addresses resolved from u
|
||||
// using resolver. u must not be nil.
|
||||
func ResolveDialContext(
|
||||
u *url.URL,
|
||||
timeout time.Duration,
|
||||
resolvers []Resolver,
|
||||
resolver Resolver,
|
||||
preferIPv6 bool,
|
||||
) (h DialHandler, err error) {
|
||||
defer func() { err = errors.Annotate(err, "dialing %q: %w", u.Host) }()
|
||||
@@ -38,6 +38,10 @@ func ResolveDialContext(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resolver == nil {
|
||||
return nil, fmt.Errorf("resolver is nil: %w", ErrNoResolvers)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if timeout > 0 {
|
||||
var cancel func()
|
||||
@@ -45,7 +49,7 @@ func ResolveDialContext(
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
ips, err := LookupParallel(ctx, resolvers, host)
|
||||
ips, err := resolver.LookupNetIP(ctx, "ip", host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving hostname: %w", err)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestResolveDialContext(t *testing.T) {
|
||||
dialContext, err := bootstrap.ResolveDialContext(
|
||||
&url.URL{Host: netutil.JoinHostPort(hostname, port)},
|
||||
testTimeout,
|
||||
[]bootstrap.Resolver{r},
|
||||
bootstrap.ParallelResolver{r},
|
||||
tc.preferIPv6,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -130,7 +130,7 @@ func TestResolveDialContext(t *testing.T) {
|
||||
dialContext, err := bootstrap.ResolveDialContext(
|
||||
&url.URL{Host: netutil.JoinHostPort(hostname, port)},
|
||||
testTimeout,
|
||||
[]bootstrap.Resolver{r},
|
||||
bootstrap.ParallelResolver{r},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
// Resolver resolves the hostnames to IP addresses.
|
||||
type Resolver interface {
|
||||
// LookupNetIP looks up the IP addresses for the given host. network must
|
||||
// be one of "ip", "ip4" or "ip6".
|
||||
// be one of "ip", "ip4" or "ip6". The response may be empty even if err is
|
||||
// nil.
|
||||
LookupNetIP(ctx context.Context, network, host string) (addrs []netip.Addr, err error)
|
||||
}
|
||||
|
||||
@@ -23,67 +24,71 @@ var _ Resolver = &net.Resolver{}
|
||||
// ErrNoResolvers is returned when zero resolvers specified.
|
||||
const ErrNoResolvers errors.Error = "no resolvers specified"
|
||||
|
||||
// LookupParallel performs lookup for IP address of host with all resolvers
|
||||
// concurrently.
|
||||
func LookupParallel(
|
||||
// ParallelResolver is a slice of resolvers that are queried concurrently. The
|
||||
// first successful response is returned.
|
||||
type ParallelResolver []Resolver
|
||||
|
||||
// type check
|
||||
var _ Resolver = ParallelResolver(nil)
|
||||
|
||||
// LookupNetIP implements the [Resolver] interface for ParallelResolver.
|
||||
func (r ParallelResolver) LookupNetIP(
|
||||
ctx context.Context,
|
||||
resolvers []Resolver,
|
||||
network string,
|
||||
host string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
resolversNum := len(resolvers)
|
||||
resolversNum := len(r)
|
||||
switch resolversNum {
|
||||
case 0:
|
||||
return nil, ErrNoResolvers
|
||||
case 1:
|
||||
return lookup(ctx, resolvers[0], host)
|
||||
return lookup(ctx, r[0], network, host)
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
// Size of channel must accommodate results of lookups from all resolvers,
|
||||
// sending into channel will be block otherwise.
|
||||
ch := make(chan *lookupResult, resolversNum)
|
||||
for _, res := range resolvers {
|
||||
go lookupAsync(ctx, res, host, ch)
|
||||
ch := make(chan any, resolversNum)
|
||||
for _, rslv := range r {
|
||||
go lookupAsync(ctx, rslv, network, host, ch)
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for range resolvers {
|
||||
result := <-ch
|
||||
if result.err == nil {
|
||||
return result.addrs, nil
|
||||
for range r {
|
||||
switch result := <-ch; result := result.(type) {
|
||||
case error:
|
||||
errs = append(errs, result)
|
||||
case []netip.Addr:
|
||||
return result, nil
|
||||
}
|
||||
|
||||
errs = append(errs, result.err)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Use [errors.Join] in Go 1.20.
|
||||
return nil, errors.List("all resolvers failed", errs...)
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// lookupResult is a structure that represents the result of a lookup.
|
||||
type lookupResult struct {
|
||||
err error
|
||||
addrs []netip.Addr
|
||||
}
|
||||
|
||||
// lookupAsync tries to lookup for ip of host with r and sends the result into
|
||||
// resCh. It's inteneded to be used as a goroutine.
|
||||
func lookupAsync(ctx context.Context, r Resolver, host string, resCh chan<- *lookupResult) {
|
||||
// lookupAsync performs a lookup for ip of host with r and sends the result into
|
||||
// resCh. It is intended to be used as a goroutine.
|
||||
func lookupAsync(ctx context.Context, r Resolver, network, host string, resCh chan<- any) {
|
||||
defer log.OnPanic("parallel lookup")
|
||||
|
||||
addrs, err := lookup(ctx, r, host)
|
||||
resCh <- &lookupResult{
|
||||
err: err,
|
||||
addrs: addrs,
|
||||
addrs, err := lookup(ctx, r, network, host)
|
||||
if err != nil {
|
||||
resCh <- err
|
||||
} else {
|
||||
resCh <- addrs
|
||||
}
|
||||
}
|
||||
|
||||
// lookup tries to lookup ip of host with r.
|
||||
func lookup(ctx context.Context, r Resolver, host string) (addrs []netip.Addr, err error) {
|
||||
//
|
||||
// TODO(e.burkov): Get rid of this function? It only wraps the actual lookup
|
||||
// with dubious logging.
|
||||
func lookup(ctx context.Context, r Resolver, network, host string) (addrs []netip.Addr, err error) {
|
||||
start := time.Now()
|
||||
addrs, err = r.LookupNetIP(ctx, "ip", host)
|
||||
addrs, err = r.LookupNetIP(ctx, network, host)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
log.Debug("parallel lookup: lookup for %s failed in %s: %s", host, elapsed, err)
|
||||
} else {
|
||||
|
||||
@@ -2,8 +2,8 @@ package bootstrap_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
@@ -31,7 +31,7 @@ func TestLookupParallel(t *testing.T) {
|
||||
const hostname = "host.name"
|
||||
|
||||
t.Run("no_resolvers", func(t *testing.T) {
|
||||
addrs, err := bootstrap.LookupParallel(context.Background(), nil, "")
|
||||
addrs, err := bootstrap.ParallelResolver(nil).LookupNetIP(context.Background(), "ip", "")
|
||||
assert.ErrorIs(t, err, bootstrap.ErrNoResolvers)
|
||||
assert.Nil(t, addrs)
|
||||
})
|
||||
@@ -49,9 +49,9 @@ func TestLookupParallel(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("one_resolver", func(t *testing.T) {
|
||||
addrs, err := bootstrap.LookupParallel(
|
||||
addrs, err := bootstrap.ParallelResolver{immediate}.LookupNetIP(
|
||||
context.Background(),
|
||||
[]bootstrap.Resolver{immediate},
|
||||
"ip",
|
||||
hostname,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -72,9 +72,9 @@ func TestLookupParallel(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
addrs, err := bootstrap.LookupParallel(
|
||||
addrs, err := bootstrap.ParallelResolver{immediate, delayed}.LookupNetIP(
|
||||
context.Background(),
|
||||
[]bootstrap.Resolver{immediate, delayed},
|
||||
"ip",
|
||||
hostname,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -85,7 +85,8 @@ func TestLookupParallel(t *testing.T) {
|
||||
|
||||
t.Run("all_errors", func(t *testing.T) {
|
||||
err := assert.AnError
|
||||
wantErrMsg := fmt.Sprintf("all resolvers failed: 3 errors: %[1]q, %[1]q, %[1]q", err)
|
||||
errStr := err.Error()
|
||||
wantErrMsg := strings.Join([]string{errStr, errStr, errStr}, "\n")
|
||||
|
||||
r := &testResolver{
|
||||
onLookupNetIP: func(_ context.Context, network, host string) ([]netip.Addr, error) {
|
||||
@@ -93,9 +94,9 @@ func TestLookupParallel(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
addrs, err := bootstrap.LookupParallel(
|
||||
addrs, err := bootstrap.ParallelResolver{r, r, r}.LookupNetIP(
|
||||
context.Background(),
|
||||
[]bootstrap.Resolver{r, r, r},
|
||||
"ip",
|
||||
hostname,
|
||||
)
|
||||
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
||||
|
||||
@@ -44,6 +44,9 @@ type (
|
||||
// It must be initialized with [NewHosts].
|
||||
//
|
||||
// TODO(e.burkov): Think of storing only slices.
|
||||
//
|
||||
// TODO(e.burkov): Move to netutil/hostsfile in module golibs as a default
|
||||
// implementation of some storage interface.
|
||||
type Hosts struct {
|
||||
// names maps each address to its names in original case and in original
|
||||
// adding order without duplicates.
|
||||
@@ -54,9 +57,6 @@ type Hosts struct {
|
||||
addrs map[string]*addrsSet
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ hostsfile.HandleSet = (*Hosts)(nil)
|
||||
|
||||
// NewHosts parses hosts files from r and returns a new Hosts set. readers are
|
||||
// optional, the error is only returned in case of parsing error.
|
||||
func NewHosts(readers ...io.Reader) (h *Hosts, err error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ package netutil
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
glnetutil "github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -93,3 +94,26 @@ func SortNetIPAddrs(addrs []netip.Addr, preferIPv6 bool) {
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
// ParseSubnet parses s either as a CIDR prefix itself, or as an IP address,
|
||||
// returning the corresponding single-IP CIDR prefix.
|
||||
//
|
||||
// TODO(e.burkov): Move to golibs.
|
||||
func ParseSubnet(s string) (p netip.Prefix, err error) {
|
||||
if strings.Contains(s, "/") {
|
||||
p, err = netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
} else {
|
||||
var ip netip.Addr
|
||||
ip, err = netip.ParseAddr(s)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
|
||||
p = netip.PrefixFrom(ip, ip.BitLen())
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Package osutil contains utilities for functions requiring system calls and
|
||||
// other OS-specific APIs, except for network-related ones.
|
||||
package osutil
|
||||
|
||||
import "io/fs"
|
||||
|
||||
// RootDirFS returns the fs.FS rooted at the operating system's root. On
|
||||
// Windows it returns the fs.FS rooted at the volume of the system directory
|
||||
// (usually, C:).
|
||||
//
|
||||
// TODO(e.burkov): Move to golibs.
|
||||
func RootDirFS() (fsys fs.FS) {
|
||||
return rootDirFS()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//go:build !windows
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
func rootDirFS() (fsys fs.FS) {
|
||||
return os.DirFS("/")
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//go:build windows
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func rootDirFS() (fsys fs.FS) {
|
||||
// TODO(a.garipov): Use a better way if golang/go#44279 is ever resolved.
|
||||
sysDir, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
log.Error("aghos: getting root filesystem: %s; using C:", err)
|
||||
|
||||
// Assume that C: is the safe default.
|
||||
return os.DirFS("C:")
|
||||
}
|
||||
|
||||
return os.DirFS(filepath.VolumeName(sysDir))
|
||||
}
|
||||
@@ -8,27 +8,27 @@ require (
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601
|
||||
github.com/kisielk/errcheck v1.6.3
|
||||
github.com/kyoh86/looppointer v0.2.1
|
||||
github.com/securego/gosec/v2 v2.16.0
|
||||
github.com/securego/gosec/v2 v2.17.0
|
||||
github.com/uudashr/gocognit v1.0.7
|
||||
golang.org/x/tools v0.12.0
|
||||
golang.org/x/vuln v1.0.0
|
||||
honnef.co/go/tools v0.4.3
|
||||
golang.org/x/tools v0.13.0
|
||||
golang.org/x/vuln v1.0.1
|
||||
honnef.co/go/tools v0.4.6
|
||||
mvdan.cc/gofumpt v0.5.0
|
||||
mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0
|
||||
mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/kyoh86/nolint v0.0.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230811145659-89c5cff77bcb // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+23
-26
@@ -1,6 +1,7 @@
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
@@ -14,8 +15,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8=
|
||||
@@ -28,16 +29,12 @@ github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fx
|
||||
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
||||
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
|
||||
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U=
|
||||
github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI=
|
||||
github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo=
|
||||
github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
|
||||
@@ -52,8 +49,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230811145659-89c5cff77bcb h1:v3JOchFBzuOEFQgVl0t5JnLg3yx29q2e1IjrEovxAt4=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
@@ -66,7 +63,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -82,24 +79,24 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g=
|
||||
golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
|
||||
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -107,9 +104,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw=
|
||||
honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
|
||||
honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
|
||||
honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
||||
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
||||
mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 h1:NAENkqZ+Xofhqs4R4Af+i3HpZj1M23SFn/lHfRh1D4E=
|
||||
mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0/go.mod h1:flQN1deud3vIpPdF88533Lpp/MvzGLgPIPjB1kgBf4I=
|
||||
mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb h1:xiF91GJnDSbyPdiZB5d52N2VpZfGhjM4Ji75cjzuooQ=
|
||||
mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
|
||||
|
||||
@@ -15,6 +15,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
proxynetutil "github.com/AdguardTeam/dnsproxy/internal/netutil"
|
||||
"github.com/AdguardTeam/dnsproxy/internal/osutil"
|
||||
"github.com/AdguardTeam/dnsproxy/internal/version"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
@@ -399,13 +402,21 @@ func initUpstreams(config *proxy.Config, options *Options) {
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
timeout := options.Timeout.Duration
|
||||
bootOpts := &upstream.Options{
|
||||
HTTPVersions: httpVersions,
|
||||
InsecureSkipVerify: options.Insecure,
|
||||
Timeout: timeout,
|
||||
}
|
||||
boot, err := initBootstrap(options.BootstrapDNS, bootOpts)
|
||||
if err != nil {
|
||||
log.Fatalf("error while initializing bootstrap: %s", err)
|
||||
}
|
||||
|
||||
upsOpts := &upstream.Options{
|
||||
HTTPVersions: httpVersions,
|
||||
InsecureSkipVerify: options.Insecure,
|
||||
Bootstrap: options.BootstrapDNS,
|
||||
Bootstrap: boot,
|
||||
Timeout: timeout,
|
||||
}
|
||||
upstreams := loadServersList(options.Upstreams)
|
||||
@@ -417,7 +428,7 @@ func initUpstreams(config *proxy.Config, options *Options) {
|
||||
|
||||
privUpsOpts := &upstream.Options{
|
||||
HTTPVersions: httpVersions,
|
||||
Bootstrap: options.BootstrapDNS,
|
||||
Bootstrap: boot,
|
||||
Timeout: mathutil.Min(defaultLocalTimeout, timeout),
|
||||
}
|
||||
privUpstreams := loadServersList(options.PrivateRDNSUpstreams)
|
||||
@@ -449,6 +460,39 @@ func initUpstreams(config *proxy.Config, options *Options) {
|
||||
}
|
||||
}
|
||||
|
||||
// initBootstrap initializes the [upstream.Resolver] for bootstrapping upstream
|
||||
// servers. It returns the default resolver if no bootstraps were specified.
|
||||
// The returned resolver will also use system hosts files first.
|
||||
func initBootstrap(bootstraps []string, opts *upstream.Options) (r upstream.Resolver, err error) {
|
||||
var resolvers []upstream.Resolver
|
||||
|
||||
for i, b := range bootstraps {
|
||||
var resolver upstream.Resolver
|
||||
resolver, err = upstream.NewUpstreamResolver(b, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating bootstrap resolver at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, resolver)
|
||||
}
|
||||
|
||||
switch len(resolvers) {
|
||||
case 0:
|
||||
etcHosts, hostsErr := bootstrap.NewDefaultHostsResolver(osutil.RootDirFS())
|
||||
if hostsErr != nil {
|
||||
log.Error("creating default hosts resolver: %s", hostsErr)
|
||||
|
||||
return net.DefaultResolver, nil
|
||||
}
|
||||
|
||||
return upstream.ConsequentResolver{etcHosts, net.DefaultResolver}, nil
|
||||
case 1:
|
||||
return resolvers[0], nil
|
||||
default:
|
||||
return upstream.ParallelResolver(resolvers), nil
|
||||
}
|
||||
}
|
||||
|
||||
// initEDNS inits EDNS-related config
|
||||
func initEDNS(config *proxy.Config, options *Options) {
|
||||
if options.EDNSAddr != "" {
|
||||
@@ -470,15 +514,13 @@ func initBogusNXDomain(config *proxy.Config, options *Options) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range options.BogusNXDomain {
|
||||
subnet, err := netutil.ParseSubnet(s)
|
||||
for i, s := range options.BogusNXDomain {
|
||||
p, err := proxynetutil.ParseSubnet(s)
|
||||
if err != nil {
|
||||
log.Error("%s", err)
|
||||
|
||||
continue
|
||||
log.Error("parsing bogus nxdomain subnet at index %d: %s", i, err)
|
||||
} else {
|
||||
config.BogusNXDomain = append(config.BogusNXDomain, p)
|
||||
}
|
||||
|
||||
config.BogusNXDomain = append(config.BogusNXDomain, subnet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -27,8 +26,8 @@ func (p *Proxy) isBogusNXDomain(m *dns.Msg) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
func containsIP(nets []*net.IPNet, ip net.IP) (ok bool) {
|
||||
if netutil.ValidateIP(ip) != nil {
|
||||
func containsIP(nets []netip.Prefix, ip netip.Addr) (ok bool) {
|
||||
if !ip.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
+24
-43
@@ -2,10 +2,10 @@ package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,19 +16,12 @@ func TestProxy_IsBogusNXDomain(t *testing.T) {
|
||||
prx := createTestProxy(t, nil)
|
||||
prx.CacheEnabled = true
|
||||
|
||||
prx.BogusNXDomain = []*net.IPNet{{
|
||||
IP: net.IP{4, 3, 2, 1},
|
||||
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
|
||||
}, {
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Mask: net.IPv4Mask(255, 0, 0, 0),
|
||||
}, {
|
||||
IP: net.IP{10, 11, 12, 13},
|
||||
Mask: net.CIDRMask(netutil.IPv4BitLen, netutil.IPv4BitLen),
|
||||
}, {
|
||||
IP: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
Mask: net.CIDRMask(120, netutil.IPv6BitLen),
|
||||
}}
|
||||
prx.BogusNXDomain = []netip.Prefix{
|
||||
netip.MustParsePrefix("4.3.2.1/24"),
|
||||
netip.MustParsePrefix("1.2.3.4/8"),
|
||||
netip.MustParsePrefix("10.11.12.13/32"),
|
||||
netip.MustParsePrefix("102:304:506:708:90a:b0c:d0e:f10/120"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -103,64 +96,52 @@ func TestProxy_IsBogusNXDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContainsIP(t *testing.T) {
|
||||
nets := []*net.IPNet{{
|
||||
// IPv4.
|
||||
IP: net.IP{1, 2, 3, 0},
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
}, {
|
||||
// IPv6 from IPv4.
|
||||
IP: net.IPv4(1, 2, 4, 0),
|
||||
Mask: net.CIDRMask(16, 32),
|
||||
}, {
|
||||
// IPv6.
|
||||
IP: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0},
|
||||
Mask: net.CIDRMask(120, net.IPv6len*8),
|
||||
}}
|
||||
nets := []netip.Prefix{
|
||||
netip.MustParsePrefix("1.2.3.0/24"),
|
||||
netip.MustParsePrefix("ffff::1.2.4.0/112"),
|
||||
netip.MustParsePrefix("102:304:506:708:90a:b0c:d0e:f00/120"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want assert.BoolAssertionFunc
|
||||
ip net.IP
|
||||
ip netip.Addr
|
||||
name string
|
||||
}{{
|
||||
name: "ipv4_yes",
|
||||
want: assert.True,
|
||||
ip: net.IP{1, 2, 3, 255},
|
||||
ip: netip.MustParseAddr("1.2.3.255"),
|
||||
}, {
|
||||
name: "ipv4_6_yes",
|
||||
want: assert.True,
|
||||
ip: net.IPv4(1, 2, 4, 254),
|
||||
ip: netip.MustParseAddr("ffff::1.2.4.254"),
|
||||
}, {
|
||||
name: "ipv6_yes",
|
||||
want: assert.True,
|
||||
ip: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
ip: netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:f0f"),
|
||||
}, {
|
||||
name: "ipv6_4_yes",
|
||||
want: assert.True,
|
||||
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 1, 2, 3, 0},
|
||||
ip: netip.MustParseAddr("ffff::1.2.3.0"),
|
||||
}, {
|
||||
name: "ipv4_no",
|
||||
want: assert.False,
|
||||
ip: net.IP{2, 1, 3, 255},
|
||||
ip: netip.MustParseAddr("2.1.3.255"),
|
||||
}, {
|
||||
name: "ipv4_6_no",
|
||||
want: assert.False,
|
||||
ip: net.IPv4(2, 1, 4, 254),
|
||||
ip: netip.MustParseAddr("2.1.4.254"),
|
||||
}, {
|
||||
name: "ipv6_no",
|
||||
want: assert.False,
|
||||
ip: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 15},
|
||||
ip: netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:10f"),
|
||||
}, {
|
||||
name: "ipv6_4_no",
|
||||
want: assert.False,
|
||||
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 2, 1, 4, 0},
|
||||
ip: netip.MustParseAddr("ffff::2.1.4.0"),
|
||||
}, {
|
||||
name: "nil_no",
|
||||
name: "invalid",
|
||||
want: assert.False,
|
||||
ip: nil,
|
||||
}, {
|
||||
name: "bad_ip",
|
||||
want: assert.False,
|
||||
ip: net.IP{42},
|
||||
ip: netip.Addr{},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ type Config struct {
|
||||
// BogusNXDomain is the set of networks used to transform responses into
|
||||
// NXDOMAIN ones if they contain at least a single IP address within these
|
||||
// networks. It's similar to dnsmasq's "bogus-nxdomain".
|
||||
BogusNXDomain []*net.IPNet
|
||||
BogusNXDomain []netip.Prefix
|
||||
|
||||
// Enable EDNS Client Subnet option DNS requests to the upstream server will
|
||||
// contain an OPT record with Client Subnet option. If the original request
|
||||
|
||||
+18
-3
@@ -353,6 +353,11 @@ func TestUpstreamsSort(t *testing.T) {
|
||||
func TestExchangeWithReservedDomains(t *testing.T) {
|
||||
dnsProxy := createTestProxy(t, nil)
|
||||
|
||||
googleRslv, err := upstream.NewUpstreamResolver("8.8.8.8", &upstream.Options{
|
||||
Timeout: 1 * time.Second,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Upstreams specification. Domains adguard.com and google.ru reserved
|
||||
// with fake upstreams, maps.google.ru excluded from dnsmasq.
|
||||
upstreams := []string{
|
||||
@@ -365,7 +370,7 @@ func TestExchangeWithReservedDomains(t *testing.T) {
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
InsecureSkipVerify: false,
|
||||
Bootstrap: []string{"8.8.8.8"},
|
||||
Bootstrap: googleRslv,
|
||||
Timeout: 1 * time.Second,
|
||||
},
|
||||
)
|
||||
@@ -435,6 +440,11 @@ func TestOneByOneUpstreamsExchange(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
googleRslv, err := upstream.NewUpstreamResolver("8.8.8.8:53", &upstream.Options{
|
||||
Timeout: timeOut,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// add one valid and two invalid upstreams
|
||||
upstreams := []string{"https://fake-dns.com/fake-dns-query", "tls://fake-dns.com", "1.1.1.1"}
|
||||
dnsProxy.UpstreamConfig.Upstreams = []upstream.Upstream{}
|
||||
@@ -443,7 +453,7 @@ func TestOneByOneUpstreamsExchange(t *testing.T) {
|
||||
u, err = upstream.AddressToUpstream(
|
||||
line,
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{"8.8.8.8:53"},
|
||||
Bootstrap: googleRslv,
|
||||
Timeout: timeOut,
|
||||
},
|
||||
)
|
||||
@@ -624,11 +634,16 @@ func TestFallbackFromInvalidBootstrap(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
invalidRslv, err := upstream.NewUpstreamResolver("8.8.8.8:555", &upstream.Options{
|
||||
Timeout: 1 * time.Second,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Using a DoT server with invalid bootstrap.
|
||||
u, _ := upstream.AddressToUpstream(
|
||||
"tls://dns.adguard.com",
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{"8.8.8.8:555"},
|
||||
Bootstrap: invalidRslv,
|
||||
Timeout: timeout,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -89,8 +89,6 @@ func ParseUpstreamsConfig(upstreamConfig []string, options *upstream.Options) (*
|
||||
options = &upstream.Options{}
|
||||
}
|
||||
|
||||
log.Debug("Bootstraps: %s", options.Bootstrap)
|
||||
|
||||
p := &configParser{
|
||||
options: options,
|
||||
upstreamsIndex: map[string]upstream.Upstream{},
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestUpstreamConfig_GetUpstreamsForDomain(t *testing.T) {
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
InsecureSkipVerify: false,
|
||||
Bootstrap: []string{},
|
||||
Bootstrap: nil,
|
||||
Timeout: 1 * time.Second,
|
||||
},
|
||||
)
|
||||
@@ -84,7 +84,6 @@ func TestUpstreamConfig_GetUpstreamsForDS(t *testing.T) {
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
InsecureSkipVerify: false,
|
||||
Bootstrap: []string{},
|
||||
Timeout: 1 * time.Second,
|
||||
},
|
||||
)
|
||||
@@ -172,7 +171,7 @@ func TestGetUpstreamsForDomainWithoutDuplicates(t *testing.T) {
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
InsecureSkipVerify: false,
|
||||
Bootstrap: []string{},
|
||||
Bootstrap: nil,
|
||||
Timeout: 1 * time.Second,
|
||||
},
|
||||
)
|
||||
@@ -349,7 +348,7 @@ func BenchmarkGetUpstreamsForDomain(b *testing.B) {
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
InsecureSkipVerify: false,
|
||||
Bootstrap: []string{},
|
||||
Bootstrap: nil,
|
||||
Timeout: 1 * time.Second,
|
||||
},
|
||||
)
|
||||
|
||||
+8
-5
@@ -4,7 +4,7 @@ package proxyutil
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -19,15 +19,18 @@ func AddPrefix(b []byte) (m []byte) {
|
||||
}
|
||||
|
||||
// IPFromRR returns the IP address from rr if any.
|
||||
func IPFromRR(rr dns.RR) (ip net.IP) {
|
||||
func IPFromRR(rr dns.RR) (ip netip.Addr) {
|
||||
var data []byte
|
||||
switch rr := rr.(type) {
|
||||
case *dns.A:
|
||||
ip = rr.A.To4()
|
||||
data = rr.A.To4()
|
||||
case *dns.AAAA:
|
||||
ip = rr.AAAA
|
||||
data = rr.AAAA
|
||||
default:
|
||||
// Go on.
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
ip, _ = netip.AddrFromSlice(data)
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
@@ -173,26 +173,28 @@ run_linter govulncheck ./...
|
||||
run_linter gocyclo --over 10\
|
||||
./internal/bootstrap/\
|
||||
./internal/netutil/\
|
||||
./internal/osutil/\
|
||||
./internal/version/\
|
||||
./proxyutil/\
|
||||
./upstream/\
|
||||
;
|
||||
|
||||
run_linter gocyclo --over 20 ./main.go
|
||||
run_linter gocyclo --over 18 ./fastip/
|
||||
run_linter gocyclo --over 15 ./proxy/
|
||||
run_linter gocyclo --over 14 ./upstream/
|
||||
|
||||
# TODO(a.garipov): Enable for all.
|
||||
run_linter gocognit --over 10\
|
||||
./internal/bootstrap/\
|
||||
./internal/osutil/\
|
||||
./internal/version/\
|
||||
./proxyutil/\
|
||||
./upstream/\
|
||||
;
|
||||
|
||||
run_linter gocognit --over 39 ./main.go
|
||||
run_linter gocognit --over 33 ./proxy/
|
||||
run_linter gocognit --over 32 ./fastip/
|
||||
run_linter gocognit --over 24 ./upstream/
|
||||
run_linter gocognit --over 35 ./main.go
|
||||
run_linter gocognit --over 31 ./proxy/
|
||||
run_linter gocognit --over 29 ./fastip/
|
||||
run_linter gocognit --over 14 ./internal/netutil/
|
||||
|
||||
run_linter ineffassign ./...
|
||||
|
||||
+2
-12
@@ -49,9 +49,6 @@ type dnsOverHTTPS struct {
|
||||
// one.
|
||||
getDialer DialerInitializer
|
||||
|
||||
// closeBoot is the function to close the bootstrap upstreams.
|
||||
closeBoot closeFunc
|
||||
|
||||
// addr is the DNS-over-HTTPS server URL.
|
||||
addr *url.URL
|
||||
|
||||
@@ -93,15 +90,8 @@ func newDoH(addr *url.URL, opts *Options) (u Upstream, err error) {
|
||||
httpVersions = DefaultHTTPVersions
|
||||
}
|
||||
|
||||
getDialer, closeBoot, err := newDialerInitializer(addr, opts)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ups := &dnsOverHTTPS{
|
||||
getDialer: getDialer,
|
||||
closeBoot: closeBoot,
|
||||
getDialer: newDialerInitializer(addr, opts),
|
||||
addr: addr,
|
||||
quicConf: &quic.Config{
|
||||
KeepAlivePeriod: QUICKeepAlivePeriod,
|
||||
@@ -208,7 +198,7 @@ func (p *dnsOverHTTPS) Close() (err error) {
|
||||
err = p.closeClient(p.client)
|
||||
}
|
||||
|
||||
return errors.Join(err, errors.Annotate(p.closeBoot(), "closing bootstrap: %w"))
|
||||
return err
|
||||
}
|
||||
|
||||
// closeClient cleans up resources used by client if necessary. Note, that at
|
||||
|
||||
+4
-16
@@ -31,9 +31,6 @@ type dnsOverTLS struct {
|
||||
// new one.
|
||||
getDialer DialerInitializer
|
||||
|
||||
// closeBoot is the function to close the bootstrap upstreams.
|
||||
closeBoot closeFunc
|
||||
|
||||
// tlsConf is the configuration of TLS.
|
||||
tlsConf *tls.Config
|
||||
|
||||
@@ -50,23 +47,13 @@ type dnsOverTLS struct {
|
||||
conns []net.Conn
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = (*dnsOverTLS)(nil)
|
||||
|
||||
// newDoT returns the DNS-over-TLS Upstream.
|
||||
func newDoT(addr *url.URL, opts *Options) (ups Upstream, err error) {
|
||||
addPort(addr, defaultPortDoT)
|
||||
|
||||
getDialer, closeBoot, err := newDialerInitializer(addr, opts)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsUps := &dnsOverTLS{
|
||||
addr: addr,
|
||||
getDialer: getDialer,
|
||||
closeBoot: closeBoot,
|
||||
getDialer: newDialerInitializer(addr, opts),
|
||||
// #nosec G402 -- TLS certificate verification could be disabled by
|
||||
// configuration.
|
||||
tlsConf: &tls.Config{
|
||||
@@ -90,6 +77,9 @@ func newDoT(addr *url.URL, opts *Options) (ups Upstream, err error) {
|
||||
return tlsUps, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = (*dnsOverTLS)(nil)
|
||||
|
||||
// Address implements the [Upstream] interface for *dnsOverTLS.
|
||||
func (p *dnsOverTLS) Address() string { return p.addr.String() }
|
||||
|
||||
@@ -151,8 +141,6 @@ func (p *dnsOverTLS) Close() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
closeErrs = append(closeErrs, errors.Annotate(p.closeBoot(), "closing bootstrap: %w"))
|
||||
|
||||
return errors.Join(closeErrs...)
|
||||
}
|
||||
|
||||
|
||||
+61
-55
@@ -5,15 +5,19 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// ErrNoUpstreams is returned from the methods that expect at least a single
|
||||
// upstream to work with when no upstreams specified.
|
||||
const ErrNoUpstreams errors.Error = "no upstream specified"
|
||||
const (
|
||||
// ErrNoUpstreams is returned from the methods that expect at least a single
|
||||
// upstream to work with when no upstreams specified.
|
||||
ErrNoUpstreams errors.Error = "no upstream specified"
|
||||
|
||||
// ErrNoReply is returned from [ExchangeAll] when no upstreams replied.
|
||||
ErrNoReply errors.Error = "no reply"
|
||||
)
|
||||
|
||||
// ExchangeParallel returns the dirst successful response from one of u. It
|
||||
// returns an error if all upstreams failed to exchange the request.
|
||||
@@ -30,23 +34,21 @@ func ExchangeParallel(u []Upstream, req *dns.Msg) (reply *dns.Msg, resolved Upst
|
||||
// Go on.
|
||||
}
|
||||
|
||||
ch := make(chan *exchangeResult, upsNum)
|
||||
|
||||
resCh := make(chan *ExchangeAllResult)
|
||||
errCh := make(chan error)
|
||||
for _, f := range u {
|
||||
go exchangeAsync(f, req, ch)
|
||||
go exchangeAsync(f, req, resCh, errCh)
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for range u {
|
||||
rep := <-ch
|
||||
if rep.err != nil {
|
||||
errs = append(errs, rep.err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if rep.reply != nil {
|
||||
return rep.reply, rep.upstream, nil
|
||||
select {
|
||||
case excErr := <-errCh:
|
||||
errs = append(errs, excErr)
|
||||
case rep := <-resCh:
|
||||
if rep.Resp != nil {
|
||||
return rep.Resp, rep.Upstream, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +56,7 @@ func ExchangeParallel(u []Upstream, req *dns.Msg) (reply *dns.Msg, resolved Upst
|
||||
return nil, nil, errors.Error("none of upstream servers responded")
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Use [errors.Join] in Go 1.20.
|
||||
return nil, nil, errors.List("all upstreams failed to respond", errs...)
|
||||
return nil, nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// ExchangeAllResult is the successful result of [ExchangeAll] for a single
|
||||
@@ -81,7 +82,7 @@ func ExchangeAll(ups []Upstream, req *dns.Msg) (res []ExchangeAllResult, err err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if reply == nil {
|
||||
return nil, errors.Error("no reply")
|
||||
return nil, ErrNoReply
|
||||
}
|
||||
|
||||
return []ExchangeAllResult{{Upstream: ups[0], Resp: reply}}, nil
|
||||
@@ -90,33 +91,25 @@ func ExchangeAll(ups []Upstream, req *dns.Msg) (res []ExchangeAllResult, err err
|
||||
}
|
||||
|
||||
res = make([]ExchangeAllResult, 0, upsl)
|
||||
errs := make([]error, 0, upsl)
|
||||
resCh := make(chan *exchangeResult, upsl)
|
||||
var errs []error
|
||||
|
||||
resCh := make(chan *ExchangeAllResult)
|
||||
errCh := make(chan error)
|
||||
|
||||
// Start exchanging concurrently.
|
||||
for _, u := range ups {
|
||||
go exchangeAsync(u, req, resCh)
|
||||
go exchangeAsync(u, req, resCh, errCh)
|
||||
}
|
||||
|
||||
// Wait for all exchanges to finish.
|
||||
for range ups {
|
||||
rep := <-resCh
|
||||
if rep.err != nil {
|
||||
errs = append(errs, rep.err)
|
||||
|
||||
continue
|
||||
var r *ExchangeAllResult
|
||||
r, err = receiveAsyncResult(resCh, errCh)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
res = append(res, *r)
|
||||
}
|
||||
|
||||
if rep.reply == nil {
|
||||
errs = append(errs, errors.Error("no reply"))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, ExchangeAllResult{
|
||||
Resp: rep.reply,
|
||||
Upstream: rep.upstream,
|
||||
})
|
||||
}
|
||||
|
||||
if len(errs) == upsl {
|
||||
@@ -127,26 +120,33 @@ func ExchangeAll(ups []Upstream, req *dns.Msg) (res []ExchangeAllResult, err err
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// exchangeResult represents the result of DNS exchange.
|
||||
type exchangeResult = struct {
|
||||
// upstream is the Upstream that successfully resolved the request.
|
||||
upstream Upstream
|
||||
// receiveAsyncResult receives a single result from resCh or an error from
|
||||
// errCh. It returns either a non-nil result or an error.
|
||||
func receiveAsyncResult(
|
||||
resCh chan *ExchangeAllResult,
|
||||
errCh chan error,
|
||||
) (res *ExchangeAllResult, err error) {
|
||||
select {
|
||||
case err = <-errCh:
|
||||
return nil, err
|
||||
case rep := <-resCh:
|
||||
if rep.Resp == nil {
|
||||
return nil, ErrNoReply
|
||||
}
|
||||
|
||||
// reply is the response DNS request resolved into.
|
||||
reply *dns.Msg
|
||||
|
||||
// err is the error that occurred while resolving the request.
|
||||
err error
|
||||
return rep, nil
|
||||
}
|
||||
}
|
||||
|
||||
// exchangeAsync tries to resolve DNS request with one upstream and sends the
|
||||
// result to respCh.
|
||||
func exchangeAsync(u Upstream, req *dns.Msg, respCh chan *exchangeResult) {
|
||||
res := &exchangeResult{upstream: u}
|
||||
|
||||
res.reply, res.err = exchangeAndLog(u, req)
|
||||
|
||||
respCh <- res
|
||||
func exchangeAsync(u Upstream, req *dns.Msg, respCh chan *ExchangeAllResult, errCh chan error) {
|
||||
reply, err := exchangeAndLog(u, req)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
respCh <- &ExchangeAllResult{Resp: reply, Upstream: u}
|
||||
}
|
||||
}
|
||||
|
||||
// exchangeAndLog wraps the [Upstream.Exchange] method with logging.
|
||||
@@ -169,6 +169,12 @@ func exchangeAndLog(u Upstream, req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
|
||||
// LookupParallel tries to lookup for ip of host with all resolvers
|
||||
// concurrently.
|
||||
func LookupParallel(ctx context.Context, resolvers []Resolver, host string) ([]netip.Addr, error) {
|
||||
return bootstrap.LookupParallel(ctx, resolvers, host)
|
||||
//
|
||||
// Deprecated: Use [ParallelResolver] instead.
|
||||
func LookupParallel(
|
||||
ctx context.Context,
|
||||
resolvers []Resolver,
|
||||
host string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
return ParallelResolver(resolvers).LookupNetIP(ctx, "ip", host)
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ func TestLookupParallelEmpty(t *testing.T) {
|
||||
u2 := testUpstream{}
|
||||
|
||||
resolvers := []Resolver{}
|
||||
resolvers = append(resolvers, &upstreamResolver{Upstream: &u1})
|
||||
resolvers = append(resolvers, &upstreamResolver{Upstream: &u2})
|
||||
resolvers = append(resolvers, &UpstreamResolver{Upstream: &u1})
|
||||
resolvers = append(resolvers, &UpstreamResolver{Upstream: &u2})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
|
||||
defer cancel()
|
||||
|
||||
+5
-14
@@ -37,9 +37,6 @@ type plainDNS struct {
|
||||
// one.
|
||||
getDialer DialerInitializer
|
||||
|
||||
// closeBoot is the function to close the bootstrap upstreams.
|
||||
closeBoot closeFunc
|
||||
|
||||
// net is the network of the connections.
|
||||
net network
|
||||
|
||||
@@ -47,9 +44,6 @@ type plainDNS struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = &plainDNS{}
|
||||
|
||||
// newPlain returns the plain DNS Upstream. addr.Scheme should be either "udp"
|
||||
// or "tcp".
|
||||
func newPlain(addr *url.URL, opts *Options) (u *plainDNS, err error) {
|
||||
@@ -62,20 +56,17 @@ func newPlain(addr *url.URL, opts *Options) (u *plainDNS, err error) {
|
||||
|
||||
addPort(addr, defaultPortPlain)
|
||||
|
||||
getDialer, closeBoot, err := newDialerInitializer(addr, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &plainDNS{
|
||||
addr: addr,
|
||||
getDialer: getDialer,
|
||||
closeBoot: closeBoot,
|
||||
getDialer: newDialerInitializer(addr, opts),
|
||||
net: addr.Scheme,
|
||||
timeout: opts.Timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = &plainDNS{}
|
||||
|
||||
// Address implements the [Upstream] interface for *plainDNS.
|
||||
func (p *plainDNS) Address() string {
|
||||
switch p.net {
|
||||
@@ -179,7 +170,7 @@ func (p *plainDNS) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
|
||||
// Close implements the [Upstream] interface for *plainDNS.
|
||||
func (p *plainDNS) Close() (err error) {
|
||||
return errors.Annotate(p.closeBoot(), "closing bootstrap: %w")
|
||||
return nil
|
||||
}
|
||||
|
||||
// errQuestion is returned when a message has malformed question section.
|
||||
|
||||
+4
-13
@@ -57,9 +57,6 @@ type dnsOverQUIC struct {
|
||||
// one.
|
||||
getDialer DialerInitializer
|
||||
|
||||
// closeBoot is the function to close the bootstrap upstreams.
|
||||
closeBoot closeFunc
|
||||
|
||||
// addr is the DNS-over-QUIC server URL.
|
||||
addr *url.URL
|
||||
|
||||
@@ -93,21 +90,12 @@ type dnsOverQUIC struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = (*dnsOverQUIC)(nil)
|
||||
|
||||
// newDoQ returns the DNS-over-QUIC Upstream.
|
||||
func newDoQ(addr *url.URL, opts *Options) (u Upstream, err error) {
|
||||
addPort(addr, defaultPortDoQ)
|
||||
|
||||
getDialer, closeBoot, err := newDialerInitializer(addr, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u = &dnsOverQUIC{
|
||||
getDialer: getDialer,
|
||||
closeBoot: closeBoot,
|
||||
getDialer: newDialerInitializer(addr, opts),
|
||||
addr: addr,
|
||||
quicConfig: &quic.Config{
|
||||
KeepAlivePeriod: QUICKeepAlivePeriod,
|
||||
@@ -138,6 +126,9 @@ func newDoQ(addr *url.URL, opts *Options) (u Upstream, err error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Upstream = (*dnsOverQUIC)(nil)
|
||||
|
||||
// Address implements the [Upstream] interface for *dnsOverQUIC.
|
||||
func (p *dnsOverQUIC) Address() string { return p.addr.String() }
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Resolver is an alias for the internal [bootstrap.Resolver] to allow custom
|
||||
// implementations. Note, that the [net.Resolver] from standard library also
|
||||
// implements this interface.
|
||||
type Resolver = bootstrap.Resolver
|
||||
|
||||
// UpstreamResolver is a wrapper around Upstream that implements the
|
||||
// [bootstrap.Resolver] interface.
|
||||
type UpstreamResolver struct {
|
||||
// Upstream is used for lookups. It must not be nil.
|
||||
Upstream
|
||||
}
|
||||
|
||||
// NewUpstreamResolver creates an upstream that can be used as bootstrap
|
||||
// [Resolver]. resolverAddress format is the same as in the
|
||||
// [AddressToUpstream]. If the upstream can't be used as a bootstrap, the
|
||||
// returned error will have the underlying type of [NotBootstrapError], and r
|
||||
// itself will be fully usable. Closing r.Upstream is caller's responsibility.
|
||||
func NewUpstreamResolver(resolverAddress string, opts *Options) (r *UpstreamResolver, err error) {
|
||||
upsOpts := &Options{}
|
||||
|
||||
// TODO(ameshkov): Aren't other options needed here?
|
||||
if opts != nil {
|
||||
upsOpts.Timeout = opts.Timeout
|
||||
upsOpts.VerifyServerCertificate = opts.VerifyServerCertificate
|
||||
upsOpts.PreferIPv6 = opts.PreferIPv6
|
||||
}
|
||||
|
||||
ups, err := AddressToUpstream(resolverAddress, upsOpts)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("creating upstream: %w", err)
|
||||
log.Error("upstream bootstrap: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UpstreamResolver{Upstream: ups}, validateBootstrap(ups)
|
||||
}
|
||||
|
||||
// NotBootstrapError is returned by [AddressToUpstream] when the parsed upstream
|
||||
// can't be used as a bootstrap and wraps the actual reason.
|
||||
type NotBootstrapError struct {
|
||||
// err is the actual reason why the upstream can't be used as a bootstrap.
|
||||
err error
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ error = NotBootstrapError{}
|
||||
|
||||
// Error implements the [error] interface for NotBootstrapError.
|
||||
func (e NotBootstrapError) Error() (msg string) {
|
||||
return fmt.Sprintf("not a bootstrap: %s", e.err)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ errors.Wrapper = NotBootstrapError{}
|
||||
|
||||
// Unwrap implements the [errors.Wrapper] interface.
|
||||
func (e NotBootstrapError) Unwrap() (reason error) {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// validateBootstrap returns an error if u can't be used as a bootstrap.
|
||||
func validateBootstrap(u Upstream) (err error) {
|
||||
var upsURL *url.URL
|
||||
switch u := u.(type) {
|
||||
case *dnsCrypt:
|
||||
return nil
|
||||
case *plainDNS:
|
||||
upsURL = u.addr
|
||||
case *dnsOverTLS:
|
||||
upsURL = u.addr
|
||||
case *dnsOverHTTPS:
|
||||
upsURL = u.addr
|
||||
case *dnsOverQUIC:
|
||||
upsURL = u.addr
|
||||
default:
|
||||
return fmt.Errorf("unknown upstream type: %T", u)
|
||||
}
|
||||
|
||||
// Make sure the upstream doesn't need a bootstrap.
|
||||
_, err = netip.ParseAddr(upsURL.Hostname())
|
||||
if err != nil {
|
||||
return NotBootstrapError{err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Resolver = &UpstreamResolver{}
|
||||
|
||||
// LookupNetIP implements the [Resolver] interface for upstreamResolver.
|
||||
//
|
||||
// TODO(e.burkov): Use context.
|
||||
func (r *UpstreamResolver) LookupNetIP(
|
||||
_ context.Context,
|
||||
network string,
|
||||
host string,
|
||||
) (ips []netip.Addr, err error) {
|
||||
if host == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "ip4", "ip6":
|
||||
host = dns.Fqdn(host)
|
||||
ips, err = r.resolve(host, network)
|
||||
case "ip":
|
||||
host = dns.Fqdn(host)
|
||||
resCh := make(chan any, 2)
|
||||
go r.resolveAsync(resCh, host, "ip4")
|
||||
go r.resolveAsync(resCh, host, "ip6")
|
||||
|
||||
var errs []error
|
||||
for i := 0; i < 2; i++ {
|
||||
switch res := <-resCh; res := res.(type) {
|
||||
case error:
|
||||
errs = append(errs, res)
|
||||
case []netip.Addr:
|
||||
ips = append(ips, res...)
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.Join(errs...)
|
||||
default:
|
||||
return []netip.Addr{}, fmt.Errorf("unsupported network %s", network)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
ips = []netip.Addr{}
|
||||
}
|
||||
|
||||
return ips, err
|
||||
}
|
||||
|
||||
// resolve performs a single DNS lookup of host and returns all the valid
|
||||
// addresses from the answer section of the response. network must be either
|
||||
// "ip4" or "ip6".
|
||||
func (r *UpstreamResolver) resolve(host, network string) (addrs []netip.Addr, err error) {
|
||||
qtype := dns.TypeA
|
||||
if network == "ip6" {
|
||||
qtype = dns.TypeAAAA
|
||||
}
|
||||
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: host,
|
||||
Qtype: qtype,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
resp, err := r.Upstream.Exchange(req)
|
||||
if err != nil || resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rr := range resp.Answer {
|
||||
if addr := proxyutil.IPFromRR(rr); addr.IsValid() {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// resolveAsync performs a single DNS lookup and sends the result to ch. It's
|
||||
// intended to be used as a goroutine.
|
||||
func (r *UpstreamResolver) resolveAsync(resCh chan<- any, host, network string) {
|
||||
resp, err := r.resolve(host, network)
|
||||
if err != nil {
|
||||
resCh <- err
|
||||
} else {
|
||||
resCh <- resp
|
||||
}
|
||||
}
|
||||
|
||||
// StaticResolver is a resolver which always responds with an underlying slice
|
||||
// of IP addresses.
|
||||
type StaticResolver []netip.Addr
|
||||
|
||||
// type check
|
||||
var _ Resolver = StaticResolver(nil)
|
||||
|
||||
// LookupNetIP implements the [Resolver] interface for StaticResolver.
|
||||
func (r StaticResolver) LookupNetIP(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
host string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
return slices.Clone(r), nil
|
||||
}
|
||||
|
||||
// ConsequentResolver is a slice of resolvers that are queried in order until
|
||||
// the first successful non-empty response, as opposed to just successful
|
||||
// response requirement in [ParallelResolver].
|
||||
type ConsequentResolver []Resolver
|
||||
|
||||
// type check
|
||||
var _ Resolver = ConsequentResolver(nil)
|
||||
|
||||
// LookupNetIP implements the [Resolver] interface for ConsequentResolver.
|
||||
func (resolvers ConsequentResolver) LookupNetIP(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
host string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
if len(resolvers) == 0 {
|
||||
return nil, bootstrap.ErrNoResolvers
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, r := range resolvers {
|
||||
addrs, err = r.LookupNetIP(ctx, network, host)
|
||||
if err == nil && len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// ParallelResolver is an alias for the internal [bootstrap.ParallelResolver] to
|
||||
// allow it's usage outside of the module.
|
||||
type ParallelResolver = bootstrap.ParallelResolver
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -49,22 +50,22 @@ func TestNewUpstreamResolver_validity(t *testing.T) {
|
||||
}, {
|
||||
name: "invalid_tls",
|
||||
addr: "tls://dns.adguard.com",
|
||||
wantErrMsg: `bootstrap tls://dns.adguard.com:853: ` +
|
||||
`ParseAddr("dns.adguard.com"): unexpected character (at "dns.adguard.com")`,
|
||||
wantErrMsg: `not a bootstrap: ParseAddr("dns.adguard.com"): ` +
|
||||
`unexpected character (at "dns.adguard.com")`,
|
||||
}, {
|
||||
name: "invalid_https",
|
||||
addr: "https://dns.adguard.com/dns-query",
|
||||
wantErrMsg: `bootstrap https://dns.adguard.com:443/dns-query: ` +
|
||||
`ParseAddr("dns.adguard.com"): unexpected character (at "dns.adguard.com")`,
|
||||
wantErrMsg: `not a bootstrap: ParseAddr("dns.adguard.com"): ` +
|
||||
`unexpected character (at "dns.adguard.com")`,
|
||||
}, {
|
||||
name: "invalid_tcp",
|
||||
addr: "tcp://dns.adguard.com",
|
||||
wantErrMsg: `bootstrap tcp://dns.adguard.com:53: ` +
|
||||
`ParseAddr("dns.adguard.com"): unexpected character (at "dns.adguard.com")`,
|
||||
wantErrMsg: `not a bootstrap: ParseAddr("dns.adguard.com"): ` +
|
||||
`unexpected character (at "dns.adguard.com")`,
|
||||
}, {
|
||||
name: "invalid_no_scheme",
|
||||
addr: "dns.adguard.com",
|
||||
wantErrMsg: `bootstrap dns.adguard.com:53: ParseAddr("dns.adguard.com"): ` +
|
||||
wantErrMsg: `not a bootstrap: ParseAddr("dns.adguard.com"): ` +
|
||||
`unexpected character (at "dns.adguard.com")`,
|
||||
}}
|
||||
|
||||
@@ -73,6 +74,9 @@ func TestNewUpstreamResolver_validity(t *testing.T) {
|
||||
r, err := NewUpstreamResolver(tc.addr, withTimeoutOpt)
|
||||
if tc.wantErrMsg != "" {
|
||||
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||
if nberr := (&NotBootstrapError{}); errors.As(err, &nberr) {
|
||||
assert.NotNil(t, r)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+20
-104
@@ -17,7 +17,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/logging"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Upstream is an interface for a DNS resolver.
|
||||
@@ -78,16 +76,9 @@ type Options struct {
|
||||
// CipherSuites is a custom list of TLSv1.2 ciphers.
|
||||
CipherSuites []uint16
|
||||
|
||||
// Bootstrap is a list of DNS servers to be used to resolve DoH/DoT/DoQ
|
||||
// hostnames. Plain DNS, DNSCrypt, or DoH/DoT/DoQ with IP addresses (not
|
||||
// hostnames) could be used. Those servers will be turned to upstream
|
||||
// servers and will be closed as soon as the resolved upstream itself is
|
||||
// closed.
|
||||
Bootstrap []string
|
||||
|
||||
// List of IP addresses of the upstream DNS server. If not empty, bootstrap
|
||||
// DNS servers won't be used at all.
|
||||
ServerIPAddrs []net.IP
|
||||
// Bootstrap is used to resolve upstreams' hostnames. If nil, the
|
||||
// [net.DefaultResolver] will be used.
|
||||
Bootstrap Resolver
|
||||
|
||||
// HTTPVersions is a list of HTTP versions that should be supported by the
|
||||
// DNS-over-HTTPS client. If not set, HTTP/1.1 and HTTP/2 will be used.
|
||||
@@ -110,7 +101,6 @@ func (o *Options) Clone() (clone *Options) {
|
||||
return &Options{
|
||||
Bootstrap: o.Bootstrap,
|
||||
Timeout: o.Timeout,
|
||||
ServerIPAddrs: o.ServerIPAddrs,
|
||||
HTTPVersions: o.HTTPVersions,
|
||||
VerifyServerCertificate: o.VerifyServerCertificate,
|
||||
VerifyConnection: o.VerifyConnection,
|
||||
@@ -244,14 +234,13 @@ func parseStamp(upsURL *url.URL, opts *Options) (u Upstream, err error) {
|
||||
host = stamp.ServerAddrStr
|
||||
}
|
||||
|
||||
// Parse and add to options.
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
var ip netip.Addr
|
||||
ip, err = netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid server stamp address %s", stamp.ServerAddrStr)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Append?
|
||||
opts.ServerIPAddrs = []net.IP{ip}
|
||||
opts.Bootstrap = StaticResolver{ip}
|
||||
}
|
||||
|
||||
switch stamp.Proto {
|
||||
@@ -311,53 +300,25 @@ func logFinish(upsAddr string, n network, err error) {
|
||||
// resolving will be performed only once.
|
||||
type DialerInitializer func() (handler bootstrap.DialHandler, err error)
|
||||
|
||||
// closeFunc is the signature of a function that closes an upstream.
|
||||
type closeFunc func() (err error)
|
||||
|
||||
// nopClose is the [closeFunc] that does nothing.
|
||||
func nopClose() (err error) { return nil }
|
||||
|
||||
// newDialerInitializer creates an initializer of the dialer that will dial the
|
||||
// addresses resolved from u using opts.
|
||||
//
|
||||
// TODO(e.burkov): Returning closeFunc is a temporary solution. It's needed
|
||||
// to close the bootstrap upstreams, which may require closing. It should be
|
||||
// gone when the [Options.Bootstrap] will be turned into [Resolver] and it's
|
||||
// closing will be handled by the caller.
|
||||
func newDialerInitializer(
|
||||
u *url.URL,
|
||||
opts *Options,
|
||||
) (di DialerInitializer, closeBoot closeFunc, err error) {
|
||||
host, port, err := netutil.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return nil, nopClose, fmt.Errorf("invalid address: %s: %w", u.Host, err)
|
||||
}
|
||||
|
||||
if addrsLen := len(opts.ServerIPAddrs); addrsLen > 0 {
|
||||
// Don't resolve the addresses of the server since those from the
|
||||
// options should be used.
|
||||
addrs := make([]string, 0, addrsLen)
|
||||
for _, addr := range opts.ServerIPAddrs {
|
||||
addrs = append(addrs, netutil.JoinHostPort(addr.String(), port))
|
||||
}
|
||||
|
||||
handler := bootstrap.NewDialContext(opts.Timeout, addrs...)
|
||||
|
||||
return func() (h bootstrap.DialHandler, err error) { return handler, nil }, nopClose, nil
|
||||
} else if _, err = netip.ParseAddr(host); err == nil {
|
||||
func newDialerInitializer(u *url.URL, opts *Options) (di DialerInitializer) {
|
||||
if _, err := netip.ParseAddrPort(u.Host); err == nil {
|
||||
// Don't resolve the address of the server since it's already an IP.
|
||||
handler := bootstrap.NewDialContext(opts.Timeout, u.Host)
|
||||
|
||||
return func() (h bootstrap.DialHandler, err error) { return handler, nil }, nopClose, nil
|
||||
return func() (bootstrap.DialHandler, error) { return handler, nil }
|
||||
}
|
||||
|
||||
resolvers, closeBoot, err := newResolvers(opts)
|
||||
if err != nil {
|
||||
return nil, nopClose, errors.Join(err, closeBoot())
|
||||
boot := opts.Bootstrap
|
||||
if boot == nil {
|
||||
// Use the default resolver for bootstrapping.
|
||||
boot = net.DefaultResolver
|
||||
}
|
||||
|
||||
var dialHandler atomic.Pointer[bootstrap.DialHandler]
|
||||
di = func() (h bootstrap.DialHandler, resErr error) {
|
||||
|
||||
return func() (h bootstrap.DialHandler, err error) {
|
||||
// Check if the dial handler has already been created.
|
||||
if hPtr := dialHandler.Load(); hPtr != nil {
|
||||
return *hPtr, nil
|
||||
@@ -366,61 +327,16 @@ func newDialerInitializer(
|
||||
// TODO(e.burkov): It may appear that several exchanges will try to
|
||||
// resolve the upstream hostname at the same time. Currently, the last
|
||||
// successful value will be stored in dialHandler, but ideally we should
|
||||
// resolve only once at a time.
|
||||
h, resolveErr := bootstrap.ResolveDialContext(u, opts.Timeout, resolvers, opts.PreferIPv6)
|
||||
if resolveErr != nil {
|
||||
return nil, fmt.Errorf("creating dial handler: %w", resolveErr)
|
||||
// resolve only once.
|
||||
h, err = bootstrap.ResolveDialContext(u, opts.Timeout, boot, opts.PreferIPv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating dial handler: %w", err)
|
||||
}
|
||||
|
||||
if !dialHandler.CompareAndSwap(nil, &h) {
|
||||
// The dial handler has just been created by another exchange.
|
||||
return *dialHandler.Load(), nil
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
return di, closeBoot, nil
|
||||
}
|
||||
|
||||
// newResolvers prepares resolvers for bootstrapping. If opts.Bootstrap is
|
||||
// empty, the only new [net.Resolver] will be returned. Otherwise, the it will
|
||||
// be added for each occurrence of an empty string in [Options.Bootstrap].
|
||||
func newResolvers(opts *Options) (resolvers []Resolver, closeBoot closeFunc, err error) {
|
||||
bootstraps := opts.Bootstrap
|
||||
l := len(bootstraps)
|
||||
if l == 0 {
|
||||
return []Resolver{&net.Resolver{}}, nopClose, nil
|
||||
}
|
||||
|
||||
resolvers, closeBoots := make([]Resolver, 0, l), make([]closeFunc, 0, l)
|
||||
for i, boot := range bootstraps {
|
||||
if boot == "" {
|
||||
resolvers = append(resolvers, &net.Resolver{})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
r, rErr := NewUpstreamResolver(boot, opts)
|
||||
if rErr != nil {
|
||||
resolvers = nil
|
||||
err = fmt.Errorf("preparing bootstrap resolver at index %d: %w", i, rErr)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, r)
|
||||
closeBoots = append(closeBoots, r.(upstreamResolver).Close)
|
||||
}
|
||||
|
||||
closeBoots = slices.Clip(closeBoots)
|
||||
|
||||
return resolvers, func() (closeErr error) {
|
||||
var errs []error
|
||||
for _, cb := range closeBoots {
|
||||
errs = append(errs, cb())
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}, err
|
||||
}
|
||||
|
||||
+94
-61
@@ -48,9 +48,14 @@ func TestUpstream_bootstrapTimeout(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, udpListener.Close)
|
||||
|
||||
rslv, err := NewUpstreamResolver(udpListener.LocalAddr().String(), &Options{
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an upstream that uses this faulty bootstrap.
|
||||
u, err := AddressToUpstream("tls://random-domain-name", &Options{
|
||||
Bootstrap: []string{udpListener.LocalAddr().String()},
|
||||
Bootstrap: rslv,
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -100,95 +105,105 @@ func TestUpstream_bootstrapTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpstreams(t *testing.T) {
|
||||
googleRslv, err := NewUpstreamResolver("8.8.8.8:53", &Options{
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
cloudflareRslv, err := NewUpstreamResolver("1.0.0.1:53", &Options{
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
upstreams := []struct {
|
||||
bootstrap Resolver
|
||||
address string
|
||||
bootstrap []string
|
||||
}{{
|
||||
bootstrap: googleRslv,
|
||||
address: "8.8.8.8:53",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "1.1.1.1",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: cloudflareRslv,
|
||||
address: "1.1.1.1",
|
||||
bootstrap: []string{"1.0.0.1"},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "tcp://1.1.1.1:53",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "94.140.14.14:5353",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "tls://1.1.1.1",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "tls://9.9.9.9:853",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: googleRslv,
|
||||
address: "tls://dns.adguard.com",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
bootstrap: googleRslv,
|
||||
address: "tls://dns.adguard.com:853",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
bootstrap: googleRslv,
|
||||
address: "tls://dns.adguard.com:853",
|
||||
bootstrap: []string{"8.8.8.8"},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "tls://one.one.one.one",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: googleRslv,
|
||||
address: "https://1dot1dot1dot1.cloudflare-dns.com/dns-query",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "https://dns.google/dns-query",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
bootstrap: nil,
|
||||
address: "https://doh.opendns.com/dns-query",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
// AdGuard DNS (DNSCrypt)
|
||||
bootstrap: nil,
|
||||
address: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
// AdGuard Family (DNSCrypt)
|
||||
bootstrap: googleRslv,
|
||||
address: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMjo1NDQzILgxXdexS27jIKRw3C7Wsao5jMnlhvhdRUXWuMm1AFq6ITIuZG5zY3J5cHQuZmFtaWx5Lm5zMS5hZGd1YXJkLmNvbQ",
|
||||
bootstrap: []string{"8.8.8.8"},
|
||||
}, {
|
||||
// Cloudflare DNS (DNS-over-HTTPS)
|
||||
bootstrap: googleRslv,
|
||||
address: "sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
// Google (Plain)
|
||||
bootstrap: nil,
|
||||
address: "sdns://AAcAAAAAAAAABzguOC44Ljg",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
// AdGuard DNS (DNS-over-TLS)
|
||||
bootstrap: googleRslv,
|
||||
address: "sdns://AwAAAAAAAAAAAAAPZG5zLmFkZ3VhcmQuY29t",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
// AdGuard DNS (DNS-over-QUIC)
|
||||
bootstrap: googleRslv,
|
||||
address: "sdns://BAcAAAAAAAAAAAAXZG5zLmFkZ3VhcmQtZG5zLmNvbTo3ODQ",
|
||||
bootstrap: []string{"8.8.8.8:53"},
|
||||
}, {
|
||||
// Cloudflare DNS (DNS-over-HTTPS)
|
||||
bootstrap: nil,
|
||||
address: "https://1.1.1.1/dns-query",
|
||||
bootstrap: []string{},
|
||||
}, {
|
||||
// AdGuard DNS (DNS-over-QUIC)
|
||||
bootstrap: googleRslv,
|
||||
address: "quic://dns.adguard-dns.com",
|
||||
bootstrap: []string{"1.1.1.1:53"},
|
||||
}, {
|
||||
// Google DNS (HTTP3)
|
||||
bootstrap: nil,
|
||||
address: "h3://dns.google/dns-query",
|
||||
bootstrap: []string{},
|
||||
}}
|
||||
|
||||
for _, test := range upstreams {
|
||||
t.Run(test.address, func(t *testing.T) {
|
||||
u, err := AddressToUpstream(
|
||||
u, upsErr := AddressToUpstream(
|
||||
test.address,
|
||||
&Options{Bootstrap: test.bootstrap, Timeout: timeout},
|
||||
)
|
||||
require.NoErrorf(t, err, "failed to generate upstream from address %s", test.address)
|
||||
require.NoErrorf(t, upsErr, "failed to generate upstream from address %s", test.address)
|
||||
testutil.CleanupAndRequireSuccess(t, u.Close)
|
||||
|
||||
checkUpstream(t, u, test.address)
|
||||
@@ -197,7 +212,10 @@ func TestUpstreams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddressToUpstream(t *testing.T) {
|
||||
opt := &Options{Bootstrap: []string{"1.1.1.1"}}
|
||||
cloudflareRslv, err := NewUpstreamResolver("1.1.1.1", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
opt := &Options{Bootstrap: cloudflareRslv}
|
||||
|
||||
testCases := []struct {
|
||||
addr string
|
||||
@@ -235,8 +253,8 @@ func TestAddressToUpstream(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.addr, func(t *testing.T) {
|
||||
u, err := AddressToUpstream(tc.addr, tc.opt)
|
||||
require.NoError(t, err)
|
||||
u, upsErr := AddressToUpstream(tc.addr, tc.opt)
|
||||
require.NoError(t, upsErr)
|
||||
testutil.CleanupAndRequireSuccess(t, u.Close)
|
||||
|
||||
assert.Equal(t, tc.want, u.Address())
|
||||
@@ -275,23 +293,28 @@ func TestAddressToUpstream_bads(t *testing.T) {
|
||||
func TestUpstreamDoTBootstrap(t *testing.T) {
|
||||
upstreams := []struct {
|
||||
address string
|
||||
bootstrap []string
|
||||
bootstrap string
|
||||
}{{
|
||||
address: "tls://one.one.one.one/",
|
||||
bootstrap: []string{"tls://1.1.1.1"},
|
||||
bootstrap: "tls://1.1.1.1",
|
||||
}, {
|
||||
address: "tls://one.one.one.one/",
|
||||
bootstrap: []string{"https://1.1.1.1/dns-query"},
|
||||
bootstrap: "https://1.1.1.1/dns-query",
|
||||
}, {
|
||||
address: "tls://one.one.one.one/",
|
||||
// Cisco OpenDNS
|
||||
bootstrap: []string{"sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ"},
|
||||
bootstrap: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ",
|
||||
}}
|
||||
|
||||
for _, tc := range upstreams {
|
||||
t.Run(tc.address, func(t *testing.T) {
|
||||
rslv, err := NewUpstreamResolver(tc.bootstrap, &Options{
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u, err := AddressToUpstream(tc.address, &Options{
|
||||
Bootstrap: tc.bootstrap,
|
||||
Bootstrap: rslv,
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoErrorf(t, err, "failed to generate upstream from address %s", tc.address)
|
||||
@@ -331,8 +354,18 @@ func TestUpstreamsInvalidBootstrap(t *testing.T) {
|
||||
|
||||
for _, tc := range upstreams {
|
||||
t.Run(tc.address, func(t *testing.T) {
|
||||
var rslv ConsequentResolver
|
||||
for _, b := range tc.bootstrap {
|
||||
r, err := NewUpstreamResolver(b, &Options{
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rslv = append(rslv, r)
|
||||
}
|
||||
|
||||
u, err := AddressToUpstream(tc.address, &Options{
|
||||
Bootstrap: tc.bootstrap,
|
||||
Bootstrap: rslv,
|
||||
Timeout: timeout,
|
||||
})
|
||||
require.NoErrorf(t, err, "failed to generate upstream from address %s", tc.address)
|
||||
@@ -342,16 +375,13 @@ func TestUpstreamsInvalidBootstrap(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
_, err := AddressToUpstream("tls://example.org", &Options{
|
||||
Bootstrap: []string{"8.8.8.8", "asdfasdf"},
|
||||
t.Run("bad_bootstrap", func(t *testing.T) {
|
||||
_, err := NewUpstreamResolver("asdfasdf", nil)
|
||||
assert.Error(t, err) // bad bootstrap "asdfasdf"
|
||||
})
|
||||
assert.Error(t, err) // bad bootstrap "asdfasdf"
|
||||
}
|
||||
|
||||
func TestUpstreamsWithServerIP(t *testing.T) {
|
||||
// use invalid bootstrap to make sure it fails if tries to use it
|
||||
invalidBootstrap := []string{"1.2.3.4:55"}
|
||||
|
||||
func TestAddressToUpstream_StaticResolver(t *testing.T) {
|
||||
h := func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
require.NoError(testutil.PanicT{}, w.WriteMsg(respondToTestMessage(m)))
|
||||
}
|
||||
@@ -360,6 +390,8 @@ func TestUpstreamsWithServerIP(t *testing.T) {
|
||||
_, dohPort, err := net.SplitHostPort(dohSrv.addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
badResolver := &UpstreamResolver{Upstream: nil}
|
||||
|
||||
dotStamp := (&dnsstamps.ServerStamp{
|
||||
ServerAddrStr: netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(dotSrv.port)).String(),
|
||||
Proto: dnsstamps.StampProtoTypeTLS,
|
||||
@@ -373,40 +405,41 @@ func TestUpstreamsWithServerIP(t *testing.T) {
|
||||
}).String()
|
||||
|
||||
upstreams := []struct {
|
||||
name string
|
||||
address string
|
||||
serverIPs []net.IP
|
||||
rslv Resolver
|
||||
name string
|
||||
address string
|
||||
}{{
|
||||
name: "dot",
|
||||
address: fmt.Sprintf("tls://some.dns.server:%d", dotSrv.port),
|
||||
serverIPs: []net.IP{netutil.IPv4Localhost().AsSlice()},
|
||||
rslv: StaticResolver{netutil.IPv4Localhost()},
|
||||
name: "dot",
|
||||
address: fmt.Sprintf("tls://some.dns.server:%d", dotSrv.port),
|
||||
}, {
|
||||
name: "doh",
|
||||
address: fmt.Sprintf("https://some.dns.server:%s/dns-query", dohPort),
|
||||
serverIPs: []net.IP{netutil.IPv4Localhost().AsSlice()},
|
||||
rslv: StaticResolver{netutil.IPv4Localhost()},
|
||||
name: "doh",
|
||||
address: fmt.Sprintf("https://some.dns.server:%s/dns-query", dohPort),
|
||||
}, {
|
||||
name: "dot_stamp",
|
||||
address: dotStamp,
|
||||
serverIPs: nil,
|
||||
rslv: badResolver,
|
||||
name: "dot_stamp",
|
||||
address: dotStamp,
|
||||
}, {
|
||||
name: "doh_stamp",
|
||||
address: dohStamp,
|
||||
serverIPs: nil,
|
||||
rslv: badResolver,
|
||||
name: "doh_stamp",
|
||||
address: dohStamp,
|
||||
}}
|
||||
|
||||
for _, tc := range upstreams {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
opts := &Options{
|
||||
Bootstrap: invalidBootstrap,
|
||||
Bootstrap: tc.rslv,
|
||||
Timeout: timeout,
|
||||
ServerIPAddrs: tc.serverIPs,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
u, uErr := AddressToUpstream(tc.address, opts)
|
||||
require.NoError(t, uErr)
|
||||
testutil.CleanupAndRequireSuccess(t, u.Close)
|
||||
|
||||
checkUpstream(t, u, tc.address)
|
||||
assert.NotPanics(t, func() {
|
||||
checkUpstream(t, u, tc.address)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/internal/bootstrap"
|
||||
proxynetutil "github.com/AdguardTeam/dnsproxy/internal/netutil"
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Resolver is an alias for [bootstrap.Resolver] to avoid the import cycle.
|
||||
type Resolver = bootstrap.Resolver
|
||||
|
||||
// NewUpstreamResolver creates an upstream that can be used as [Resolver].
|
||||
// resolverAddress format is the same as in the [AddressToUpstream], except that
|
||||
// it also shouldn't need a bootstrap, i.e. have an IP address in hostname, or
|
||||
// be a DNSCrypt. resolverAddress must not be empty, use another [Resolver]
|
||||
// instead, e.g. [net.Resolver].
|
||||
func NewUpstreamResolver(resolverAddress string, opts *Options) (r Resolver, err error) {
|
||||
upsOpts := &Options{}
|
||||
|
||||
// TODO(ameshkov): Aren't other options needed here?
|
||||
if opts != nil {
|
||||
upsOpts.Timeout = opts.Timeout
|
||||
upsOpts.VerifyServerCertificate = opts.VerifyServerCertificate
|
||||
upsOpts.PreferIPv6 = opts.PreferIPv6
|
||||
}
|
||||
|
||||
ur := upstreamResolver{}
|
||||
|
||||
ur.Upstream, err = AddressToUpstream(resolverAddress, upsOpts)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("creating upstream: %w", err)
|
||||
log.Error("upstream bootstrap: %s", err)
|
||||
|
||||
return ur, err
|
||||
}
|
||||
|
||||
if err = validateBootstrap(ur.Upstream); err != nil {
|
||||
log.Error("upstream bootstrap %s: %s", resolverAddress, err)
|
||||
|
||||
ur.Upstream = nil
|
||||
|
||||
return ur, err
|
||||
}
|
||||
|
||||
return ur, err
|
||||
}
|
||||
|
||||
// validateBootstrap returns error if the upstream is not eligible to be a
|
||||
// bootstrap DNS server. DNSCrypt is always okay. Plain DNS, DNS-over-TLS,
|
||||
// DNS-over-HTTPS, and DNS-over-QUIC are okay only if those are defined by IP.
|
||||
func validateBootstrap(upstream Upstream) (err error) {
|
||||
switch upstream := upstream.(type) {
|
||||
case *dnsCrypt:
|
||||
return nil
|
||||
case *dnsOverTLS:
|
||||
_, err = netip.ParseAddr(upstream.addr.Hostname())
|
||||
case *dnsOverHTTPS:
|
||||
_, err = netip.ParseAddr(upstream.addr.Hostname())
|
||||
case *dnsOverQUIC:
|
||||
_, err = netip.ParseAddr(upstream.addr.Hostname())
|
||||
case *plainDNS:
|
||||
_, err = netip.ParseAddr(upstream.addr.Hostname())
|
||||
default:
|
||||
err = fmt.Errorf("unknown upstream type: %T", upstream)
|
||||
}
|
||||
|
||||
return errors.Annotate(err, "bootstrap %s: %w", upstream.Address())
|
||||
}
|
||||
|
||||
// upstreamResolver is a wrapper around Upstream that implements the
|
||||
// [bootstrap.Resolver] interface. It sorts the resolved addresses preferring
|
||||
// IPv4.
|
||||
type upstreamResolver struct {
|
||||
// Upstream is embedded here to avoid implementing another Upstream's
|
||||
// methods.
|
||||
Upstream
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Resolver = upstreamResolver{}
|
||||
|
||||
// LookupNetIP implements the [Resolver] interface for upstreamResolver.
|
||||
//
|
||||
// TODO(e.burkov): Use context.
|
||||
func (r upstreamResolver) LookupNetIP(
|
||||
_ context.Context,
|
||||
network string,
|
||||
host string,
|
||||
) (ipAddrs []netip.Addr, err error) {
|
||||
// TODO(e.burkov): Investigate when [r.Upstream] is nil and why.
|
||||
if r.Upstream == nil || host == "" {
|
||||
return []netip.Addr{}, nil
|
||||
}
|
||||
|
||||
host = dns.Fqdn(host)
|
||||
|
||||
answers := make([][]dns.RR, 1, 2)
|
||||
var errs []error
|
||||
switch network {
|
||||
case "ip4", "ip6":
|
||||
qtype := dns.TypeA
|
||||
if network == "ip6" {
|
||||
qtype = dns.TypeAAAA
|
||||
}
|
||||
|
||||
var resp *dns.Msg
|
||||
resp, err = r.resolve(host, qtype)
|
||||
if err != nil {
|
||||
return []netip.Addr{}, err
|
||||
}
|
||||
|
||||
answers[0] = resp.Answer
|
||||
case "ip":
|
||||
resCh := make(chan *resolveResult, 2)
|
||||
|
||||
go r.resolveAsync(resCh, host, dns.TypeA)
|
||||
go r.resolveAsync(resCh, host, dns.TypeAAAA)
|
||||
|
||||
answers = answers[:0:cap(answers)]
|
||||
for i := 0; i < 2; i++ {
|
||||
res := <-resCh
|
||||
if res.err != nil {
|
||||
errs = append(errs, res.err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
answers = append(answers, res.resp.Answer)
|
||||
}
|
||||
default:
|
||||
return []netip.Addr{}, fmt.Errorf("unsupported network %s", network)
|
||||
}
|
||||
|
||||
for _, ans := range answers {
|
||||
for _, rr := range ans {
|
||||
if addr, ok := netip.AddrFromSlice(proxyutil.IPFromRR(rr)); ok {
|
||||
ipAddrs = append(ipAddrs, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Use [errors.Join] in Go 1.20.
|
||||
if len(ipAddrs) == 0 && len(errs) > 0 {
|
||||
return []netip.Addr{}, errs[0]
|
||||
}
|
||||
|
||||
// Use the previous dnsproxy behavior: prefer IPv4 by default.
|
||||
//
|
||||
// TODO(a.garipov): Consider unexporting this entire method or
|
||||
// documenting that the order of addrs is undefined.
|
||||
proxynetutil.SortNetIPAddrs(ipAddrs, false)
|
||||
|
||||
return ipAddrs, nil
|
||||
}
|
||||
|
||||
// resolve performs a single DNS lookup of host.
|
||||
func (r upstreamResolver) resolve(host string, qtype uint16) (resp *dns.Msg, err error) {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: host,
|
||||
Qtype: qtype,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
return r.Exchange(req)
|
||||
}
|
||||
|
||||
// resolveResult is the result of a single concurrent lookup.
|
||||
type resolveResult = struct {
|
||||
resp *dns.Msg
|
||||
err error
|
||||
}
|
||||
|
||||
// resolveAsync performs a single DNS lookup and sends the result to ch. It's
|
||||
// intended to be used as a goroutine.
|
||||
func (r upstreamResolver) resolveAsync(
|
||||
resCh chan<- *resolveResult,
|
||||
host string,
|
||||
qtype uint16,
|
||||
) {
|
||||
resp, err := r.resolve(host, qtype)
|
||||
resCh <- &resolveResult{resp: resp, err: err}
|
||||
}
|
||||
Reference in New Issue
Block a user