Pull request 2021: upd golibs
Merge in DNS/adguard-home from upd-golibs to master
Squashed commit of the following:
commit 266b002c5450329761dee21d918c80d08e5d8ab9
Merge: 99eb7745d e305bd8e4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Oct 5 14:21:51 2023 +0300
Merge branch 'master' into upd-golibs
commit 99eb7745d0bee190399f9b16cb7151f34a591b54
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Oct 5 14:14:28 2023 +0300
home: imp alignment
commit 556cde56720ce449aec17b500825681fc8c084bf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Oct 5 13:35:35 2023 +0300
dnsforward: imp naming, docs
commit 1ee99655a3318263db1edbcb9e4eeb33bfe441c8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Oct 5 13:28:39 2023 +0300
home: make ports uint16
commit b228032ea1f5902ab9bac7b5d55d84aaf6354616
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Oct 4 18:56:59 2023 +0300
all: rm system resolvers
commit 4b5becbed5890db80612e53861f000aaf4c869ff
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Oct 4 17:30:16 2023 +0300
all: upd golibs
This commit is contained in:
@@ -12,12 +12,12 @@ import (
|
||||
// listenPacketReusable announces on the local network address additionally
|
||||
// configuring the socket to have a reusable binding.
|
||||
func listenPacketReusable(ifaceName, network, address string) (c net.PacketConn, err error) {
|
||||
var port int
|
||||
var port uint16
|
||||
_, port, err = netutil.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Inspect nclient4.NewRawUDPConn and implement here.
|
||||
return nclient4.NewRawUDPConn(ifaceName, port)
|
||||
return nclient4.NewRawUDPConn(ifaceName, int(port))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package aghnet_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -64,7 +65,7 @@ ParseAddr("256.256.256.256"): IPv4 field has value >255`,
|
||||
want: netip.AddrPort{},
|
||||
}, {
|
||||
name: "error_invalid_port",
|
||||
input: netutil.JoinHostPort(v4addr.String(), -5),
|
||||
input: net.JoinHostPort(v4addr.String(), "-5"),
|
||||
wantErrMsg: `invalid port "-5" parsing "1.2.3.4:-5"
|
||||
ParseAddr("1.2.3.4:-5"): unexpected character (at ":-5")`,
|
||||
want: netip.AddrPort{},
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package aghnet
|
||||
|
||||
// DefaultRefreshIvl is the default period of time between refreshing cached
|
||||
// addresses.
|
||||
// const DefaultRefreshIvl = 5 * time.Minute
|
||||
|
||||
// HostGenFunc is the signature for functions generating fake hostnames. The
|
||||
// implementation must be safe for concurrent use.
|
||||
type HostGenFunc func() (host string)
|
||||
|
||||
// SystemResolvers helps to work with local resolvers' addresses provided by OS.
|
||||
type SystemResolvers interface {
|
||||
// Get returns the slice of local resolvers' addresses. It must be safe for
|
||||
// concurrent use.
|
||||
Get() (rs []string)
|
||||
// refresh refreshes the local resolvers' addresses cache. It must be safe
|
||||
// for concurrent use.
|
||||
refresh() (err error)
|
||||
}
|
||||
|
||||
// NewSystemResolvers returns a SystemResolvers with the cache refresh rate
|
||||
// defined by refreshIvl. It disables auto-refreshing if refreshIvl is 0. If
|
||||
// nil is passed for hostGenFunc, the default generator will be used.
|
||||
func NewSystemResolvers(
|
||||
hostGenFunc HostGenFunc,
|
||||
) (sr SystemResolvers, err error) {
|
||||
sr = newSystemResolvers(hostGenFunc)
|
||||
|
||||
// Fill cache.
|
||||
err = sr.refresh()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sr, nil
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
// defaultHostGen is the default method of generating host for Refresh.
|
||||
func defaultHostGen() (host string) {
|
||||
// TODO(e.burkov): Use strings.Builder.
|
||||
return fmt.Sprintf("test%d.org", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// systemResolvers is a default implementation of SystemResolvers interface.
|
||||
type systemResolvers struct {
|
||||
// addrsLock protects addrs.
|
||||
addrsLock sync.RWMutex
|
||||
// addrs is the set that contains cached local resolvers' addresses.
|
||||
addrs *stringutil.Set
|
||||
|
||||
// resolver is used to fetch the resolvers' addresses.
|
||||
resolver *net.Resolver
|
||||
// hostGenFunc generates hosts to resolve.
|
||||
hostGenFunc HostGenFunc
|
||||
}
|
||||
|
||||
const (
|
||||
// errBadAddrPassed is returned when dialFunc can't parse an IP address.
|
||||
errBadAddrPassed errors.Error = "the passed string is not a valid IP address"
|
||||
|
||||
// errFakeDial is an error which dialFunc is expected to return.
|
||||
errFakeDial errors.Error = "this error signals the successful dialFunc work"
|
||||
|
||||
// errUnexpectedHostFormat is returned by validateDialedHost when the host has
|
||||
// more than one percent sign.
|
||||
errUnexpectedHostFormat errors.Error = "unexpected host format"
|
||||
)
|
||||
|
||||
// refresh implements the SystemResolvers interface for *systemResolvers.
|
||||
func (sr *systemResolvers) refresh() (err error) {
|
||||
defer func() { err = errors.Annotate(err, "systemResolvers: %w") }()
|
||||
|
||||
_, err = sr.resolver.LookupHost(context.Background(), sr.hostGenFunc())
|
||||
dnserr := &net.DNSError{}
|
||||
if errors.As(err, &dnserr) && dnserr.Err == errFakeDial.Error() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func newSystemResolvers(hostGenFunc HostGenFunc) (sr SystemResolvers) {
|
||||
if hostGenFunc == nil {
|
||||
hostGenFunc = defaultHostGen
|
||||
}
|
||||
s := &systemResolvers{
|
||||
resolver: &net.Resolver{
|
||||
PreferGo: true,
|
||||
},
|
||||
hostGenFunc: hostGenFunc,
|
||||
addrs: stringutil.NewSet(),
|
||||
}
|
||||
s.resolver.Dial = s.dialFunc
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// validateDialedHost validated the host used by resolvers in dialFunc.
|
||||
func validateDialedHost(host string) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "parsing %q: %w", host) }()
|
||||
|
||||
parts := strings.Split(host, "%")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
// host
|
||||
case 2:
|
||||
// Remove the zone and check the IP address part.
|
||||
host = parts[0]
|
||||
default:
|
||||
return errUnexpectedHostFormat
|
||||
}
|
||||
|
||||
if _, err = netutil.ParseIP(host); err != nil {
|
||||
return errBadAddrPassed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dockerEmbeddedDNS is the address of Docker's embedded DNS server.
|
||||
//
|
||||
// See
|
||||
// https://github.com/moby/moby/blob/v1.12.0/docs/userguide/networking/dockernetworks.md.
|
||||
const dockerEmbeddedDNS = "127.0.0.11"
|
||||
|
||||
// dialFunc gets the resolver's address and puts it into internal cache.
|
||||
func (sr *systemResolvers) dialFunc(_ context.Context, _, address string) (_ net.Conn, err error) {
|
||||
// Just validate the passed address is a valid IP.
|
||||
var host string
|
||||
host, err = netutil.SplitHost(address)
|
||||
if err != nil {
|
||||
// TODO(e.burkov): Maybe use a structured errBadAddrPassed to
|
||||
// allow unwrapping of the real error.
|
||||
return nil, fmt.Errorf("%s: %w", err, errBadAddrPassed)
|
||||
}
|
||||
|
||||
// Exclude Docker's embedded DNS server, as it may cause recursion if
|
||||
// the container is set as the host system's default DNS server.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3064.
|
||||
//
|
||||
// TODO(a.garipov): Perhaps only do this when we are in the container?
|
||||
// Maybe use an environment variable?
|
||||
if host == dockerEmbeddedDNS {
|
||||
return nil, errFakeDial
|
||||
}
|
||||
|
||||
err = validateDialedHost(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating dialed host: %w", err)
|
||||
}
|
||||
|
||||
sr.addrsLock.Lock()
|
||||
defer sr.addrsLock.Unlock()
|
||||
|
||||
sr.addrs.Add(host)
|
||||
|
||||
return nil, errFakeDial
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) Get() (rs []string) {
|
||||
sr.addrsLock.RLock()
|
||||
defer sr.addrsLock.RUnlock()
|
||||
|
||||
return sr.addrs.Values()
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestSystemResolversImpl(
|
||||
t *testing.T,
|
||||
hostGenFunc HostGenFunc,
|
||||
) (imp *systemResolvers) {
|
||||
t.Helper()
|
||||
|
||||
sr := createTestSystemResolvers(t, hostGenFunc)
|
||||
|
||||
return testutil.RequireTypeAssert[*systemResolvers](t, sr)
|
||||
}
|
||||
|
||||
func TestSystemResolvers_Refresh(t *testing.T) {
|
||||
t.Run("expected_error", func(t *testing.T) {
|
||||
sr := createTestSystemResolvers(t, nil)
|
||||
|
||||
assert.NoError(t, sr.refresh())
|
||||
})
|
||||
|
||||
t.Run("unexpected_error", func(t *testing.T) {
|
||||
_, err := NewSystemResolvers(func() string {
|
||||
return "127.0.0.1::123"
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSystemResolvers_DialFunc(t *testing.T) {
|
||||
imp := createTestSystemResolversImpl(t, nil)
|
||||
|
||||
testCases := []struct {
|
||||
want error
|
||||
name string
|
||||
address string
|
||||
}{{
|
||||
want: errFakeDial,
|
||||
name: "valid_ipv4",
|
||||
address: "127.0.0.1",
|
||||
}, {
|
||||
want: errFakeDial,
|
||||
name: "valid_ipv6_port",
|
||||
address: "[::1]:53",
|
||||
}, {
|
||||
want: errFakeDial,
|
||||
name: "valid_ipv6_zone_port",
|
||||
address: "[::1%lo0]:53",
|
||||
}, {
|
||||
want: errBadAddrPassed,
|
||||
name: "invalid_split_host",
|
||||
address: "127.0.0.1::123",
|
||||
}, {
|
||||
want: errUnexpectedHostFormat,
|
||||
name: "invalid_ipv6_zone_port",
|
||||
address: "[::1%%lo0]:53",
|
||||
}, {
|
||||
want: errBadAddrPassed,
|
||||
name: "invalid_parse_ip",
|
||||
address: "not-ip",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conn, err := imp.dialFunc(context.Background(), "", tc.address)
|
||||
require.Nil(t, conn)
|
||||
|
||||
assert.ErrorIs(t, err, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestSystemResolvers(
|
||||
t *testing.T,
|
||||
hostGenFunc HostGenFunc,
|
||||
) (sr SystemResolvers) {
|
||||
t.Helper()
|
||||
|
||||
var err error
|
||||
sr, err = NewSystemResolvers(hostGenFunc)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sr)
|
||||
|
||||
return sr
|
||||
}
|
||||
|
||||
func TestSystemResolvers_Get(t *testing.T) {
|
||||
sr := createTestSystemResolvers(t, nil)
|
||||
|
||||
var rs []string
|
||||
require.NotPanics(t, func() {
|
||||
rs = sr.Get()
|
||||
})
|
||||
|
||||
assert.NotEmpty(t, rs)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Write tests for refreshWithTicker.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2846.
|
||||
@@ -1,163 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// systemResolvers implementation differs for Windows since Go's resolver
|
||||
// doesn't work there.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/33097.
|
||||
type systemResolvers struct {
|
||||
// addrs is the slice of cached local resolvers' addresses.
|
||||
addrs []string
|
||||
addrsLock sync.RWMutex
|
||||
}
|
||||
|
||||
func newSystemResolvers(_ HostGenFunc) (sr SystemResolvers) {
|
||||
return &systemResolvers{}
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) Get() (rs []string) {
|
||||
sr.addrsLock.RLock()
|
||||
defer sr.addrsLock.RUnlock()
|
||||
|
||||
addrs := sr.addrs
|
||||
rs = make([]string, len(addrs))
|
||||
copy(rs, addrs)
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// writeExit writes "exit" to w and closes it. It is supposed to be run in
|
||||
// a goroutine.
|
||||
func writeExit(w io.WriteCloser) {
|
||||
defer log.OnPanic("systemResolvers: writeExit")
|
||||
|
||||
defer func() {
|
||||
derr := w.Close()
|
||||
if derr != nil {
|
||||
log.Error("systemResolvers: writeExit: closing: %s", derr)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err := io.WriteString(w, "exit")
|
||||
if err != nil {
|
||||
log.Error("systemResolvers: writeExit: writing: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// scanAddrs scans the DNS addresses from nslookup's output. The expected
|
||||
// output of nslookup looks like this:
|
||||
//
|
||||
// Default Server: 192-168-1-1.qualified.domain.ru
|
||||
// Address: 192.168.1.1
|
||||
func scanAddrs(s *bufio.Scanner) (addrs []string) {
|
||||
for s.Scan() {
|
||||
line := strings.TrimSpace(s.Text())
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 || fields[0] != "Address:" {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the address contains port then it is separated with '#'.
|
||||
ipPort := strings.Split(fields[1], "#")
|
||||
if len(ipPort) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := ipPort[0]
|
||||
if net.ParseIP(addr) == nil {
|
||||
log.Debug("systemResolvers: %q is not a valid ip", addr)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// getAddrs gets local resolvers' addresses from OS in a special Windows way.
|
||||
//
|
||||
// TODO(e.burkov): This whole function needs more detailed research on getting
|
||||
// local resolvers addresses on Windows. We execute the external command for
|
||||
// now that is not the most accurate way.
|
||||
func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
||||
var cmdPath string
|
||||
cmdPath, err = exec.LookPath("nslookup.exe")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("looking up cmd path: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cmdPath)
|
||||
|
||||
var stdin io.WriteCloser
|
||||
stdin, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting the command's stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
var stdout io.ReadCloser
|
||||
stdout, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting the command's stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
go writeExit(stdin)
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start command executing: %w", err)
|
||||
}
|
||||
|
||||
s := bufio.NewScanner(stdout)
|
||||
addrs = scanAddrs(s)
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing the command: %w", err)
|
||||
}
|
||||
|
||||
err = s.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scanning output: %w", err)
|
||||
}
|
||||
|
||||
// Don't close StdoutPipe since Wait do it for us in ¿most? cases.
|
||||
//
|
||||
// See [exec.Cmd.StdoutPipe].
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) refresh() (err error) {
|
||||
defer func() { err = errors.Annotate(err, "systemResolvers: %w") }()
|
||||
|
||||
got, err := sr.getAddrs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get addresses: %w", err)
|
||||
}
|
||||
if len(got) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sr.addrsLock.Lock()
|
||||
defer sr.addrsLock.Unlock()
|
||||
|
||||
sr.addrs = got
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
// TODO(e.burkov): Write tests for Windows implementation.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2846.
|
||||
Reference in New Issue
Block a user