Pull request: 2704 local addresses vol.2
Merge in DNS/adguard-home from 2704-local-addresses-vol.2 to master Updates #2704. Updates #2829. Squashed commit of the following: commit 507d038c2709de59246fc0b65c3c4ab8e38d1990 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 31 14:33:05 2021 +0300 aghtest: fix file name commit 8e19f99337bee1d88ad6595adb96f9bb23fa3c41 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 31 14:06:43 2021 +0300 aghnet: rm redundant mutexes commit 361fa418b33ed160ca20862be1c455ab9378c03f Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 31 13:45:30 2021 +0300 all: fix names, docs commit 14034f4f0230d7aaa3645054946ae5c278089a99 Merge: 35e265cca72ce1cfAuthor: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 31 13:38:15 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.2 commit 35e265cc8cd308ef1fda414b58c0217cb5f258e4 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 31 13:33:35 2021 +0300 aghnet: imp naming commit 7a7edac7208a40697d7bc50682b923a144e28e2b Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Mar 30 20:59:54 2021 +0300 changelog: oops, nope yet commit d26a5d2513daf662ac92053b5e235189a64cc022 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Mar 30 20:55:53 2021 +0300 all: some renaming for the glory of semantics commit 9937fa619452b0742616217b975e3ff048d58acb Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Mar 29 15:34:42 2021 +0300 all: log changes commit d8d9e6dfeea8474466ee25f27021efdd3ddb1592 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Mar 26 18:32:23 2021 +0300 all: imp localresolver, imp cutting off own addresses commit 344140df449b85925f19b460fd7dc7c08e29c35a Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Mar 26 14:53:33 2021 +0300 all: imp code quality commit 1c5c0babec73b125044e23dd3aa75d8eefc19b28 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Mar 25 20:44:08 2021 +0300 all: fix go.mod commit 0b9fb3c2369a752e893af8ddc45a86bb9fb27ce5 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Mar 25 20:38:51 2021 +0300 all: add error handling commit a7a2e51f57fc6f8f74b95a264ad345cd2a9e026e Merge: c13be63427f4f052Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Mar 25 19:48:36 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.2 commit c13be634f47bcaed9320a732a51c0e4752d0dad0 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Mar 25 18:52:28 2021 +0300 all: cover rdns with tests, imp aghnet functionality commit 48bed9025944530c613ee53e7961d6d5fbabf8be Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 24 20:18:07 2021 +0300 home: make rdns great again commit 1dbacfc8d5b6895807797998317fe3cc814617c1 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Mar 24 16:07:52 2021 +0300 all: imp external client restriction commit 1208a319a7f4ffe7b7fa8956f245d7a19437c0a4 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Mar 22 15:26:45 2021 +0300 all: finish local ptr processor commit c8827fc3db289e1a5d7a11d057743bab39957b02 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Mar 2 13:41:22 2021 +0300 all: imp ipdetector, add local ptr processor
This commit is contained in:
79
internal/aghnet/exchanger.go
Normal file
79
internal/aghnet/exchanger.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// This package is not the best place for this functionality, but we put it here
|
||||
// since we need to use it in both rDNS (home) and dnsServer (dnsforward).
|
||||
|
||||
// NoUpstreamsErr should be returned when there are no upstreams inside
|
||||
// Exchanger implementation.
|
||||
const NoUpstreamsErr agherr.Error = "no upstreams specified"
|
||||
|
||||
// Exchanger represents an object able to resolve DNS messages.
|
||||
//
|
||||
// TODO(e.burkov): Maybe expand with method like ExchangeParallel to be able to
|
||||
// use user's upstream mode settings. Also, think about Update method to
|
||||
// refresh the internal state.
|
||||
type Exchanger interface {
|
||||
Exchange(req *dns.Msg) (resp *dns.Msg, err error)
|
||||
}
|
||||
|
||||
// multiAddrExchanger is the default implementation of Exchanger interface.
|
||||
type multiAddrExchanger struct {
|
||||
ups []upstream.Upstream
|
||||
}
|
||||
|
||||
// NewMultiAddrExchanger creates an Exchanger instance from passed addresses.
|
||||
// It returns an error if any of addrs failed to become an upstream.
|
||||
func NewMultiAddrExchanger(addrs []string, timeout time.Duration) (e Exchanger, err error) {
|
||||
defer agherr.Annotate("exchanger: %w", &err)
|
||||
|
||||
if len(addrs) == 0 {
|
||||
return &multiAddrExchanger{}, nil
|
||||
}
|
||||
|
||||
var ups []upstream.Upstream = make([]upstream.Upstream, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
var u upstream.Upstream
|
||||
u, err = upstream.AddressToUpstream(addr, upstream.Options{Timeout: timeout})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ups = append(ups, u)
|
||||
}
|
||||
|
||||
return &multiAddrExchanger{ups: ups}, nil
|
||||
}
|
||||
|
||||
// Exсhange performs a query to each resolver until first response.
|
||||
func (e *multiAddrExchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
defer agherr.Annotate("exchanger: %w", &err)
|
||||
|
||||
// TODO(e.burkov): Maybe prohibit the initialization without upstreams.
|
||||
if len(e.ups) == 0 {
|
||||
return nil, NoUpstreamsErr
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, u := range e.ups {
|
||||
resp, err = u.Exchange(req)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, agherr.Many("can't exchange", errs...)
|
||||
}
|
||||
64
internal/aghnet/exchanger_test.go
Normal file
64
internal/aghnet/exchanger_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewMultiAddrExchanger(t *testing.T) {
|
||||
var e Exchanger
|
||||
var err error
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{}, 0)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, e)
|
||||
})
|
||||
|
||||
t.Run("successful", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{"www.example.com"}, 0)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, e)
|
||||
})
|
||||
|
||||
t.Run("unsuccessful", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{"invalid-proto://www.example.com"}, 0)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, e)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultiAddrExchanger_Exchange(t *testing.T) {
|
||||
e := &multiAddrExchanger{}
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
e.ups = []upstream.Upstream{&aghtest.TestErrUpstream{}}
|
||||
|
||||
resp, err := e.Exchange(nil)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, resp)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
e.ups = []upstream.Upstream{&aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"abc": {"cba"},
|
||||
},
|
||||
}}
|
||||
|
||||
resp, err := e.Exchange(&dns.Msg{
|
||||
Question: []dns.Question{{
|
||||
Name: "abc",
|
||||
Qtype: dns.TypePTR,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Answer, 1)
|
||||
assert.Equal(t, "cba", resp.Answer[0].Header().Name)
|
||||
})
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package aghnet
|
||||
|
||||
import "net"
|
||||
|
||||
// IPDetector describes IP address properties.
|
||||
type IPDetector struct {
|
||||
nets []*net.IPNet
|
||||
}
|
||||
|
||||
// NewIPDetector returns a new IP detector.
|
||||
func NewIPDetector() (ipd *IPDetector, err error) {
|
||||
specialNetworks := []string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.0.0/29",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"240.0.0.0/4",
|
||||
"255.255.255.255/32",
|
||||
"::1/128",
|
||||
"::/128",
|
||||
"64:ff9b::/96",
|
||||
// Since this network is used for mapping IPv4 addresses, we
|
||||
// don't include it.
|
||||
// "::ffff:0:0/96",
|
||||
"100::/64",
|
||||
"2001::/23",
|
||||
"2001::/32",
|
||||
"2001:2::/48",
|
||||
"2001:db8::/32",
|
||||
"2001:10::/28",
|
||||
"2002::/16",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
}
|
||||
|
||||
ipd = &IPDetector{
|
||||
nets: make([]*net.IPNet, len(specialNetworks)),
|
||||
}
|
||||
for i, ipnetStr := range specialNetworks {
|
||||
var ipnet *net.IPNet
|
||||
_, ipnet, err = net.ParseCIDR(ipnetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipd.nets[i] = ipnet
|
||||
}
|
||||
|
||||
return ipd, nil
|
||||
}
|
||||
|
||||
// DetectSpecialNetwork returns true if IP address is contained by any of
|
||||
// special-purpose IP address registries according to RFC-6890
|
||||
// (https://tools.ietf.org/html/rfc6890).
|
||||
func (ipd *IPDetector) DetectSpecialNetwork(ip net.IP) bool {
|
||||
for _, ipnet := range ipd.nets {
|
||||
if ipnet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -97,25 +97,10 @@ func (iface *NetInterface) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
||||
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
||||
func GetValidNetInterfaces() ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get list of interfaces: %w", err)
|
||||
}
|
||||
|
||||
netIfaces := []net.Interface{}
|
||||
|
||||
netIfaces = append(netIfaces, ifaces...)
|
||||
|
||||
return netIfaces, nil
|
||||
}
|
||||
|
||||
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only
|
||||
// we do not return link-local addresses here
|
||||
func GetValidNetInterfacesForWeb() ([]*NetInterface, error) {
|
||||
ifaces, err := GetValidNetInterfaces()
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get interfaces: %w", err)
|
||||
}
|
||||
@@ -273,3 +258,138 @@ func SplitHost(hostport string) (host string, err error) {
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Inspect the charToHex, ipParseARPA6, ipReverse and
|
||||
// UnreverseAddr and maybe refactor it.
|
||||
|
||||
// charToHex converts character to a hexadecimal.
|
||||
func charToHex(n byte) int8 {
|
||||
if n >= '0' && n <= '9' {
|
||||
return int8(n) - '0'
|
||||
} else if (n|0x20) >= 'a' && (n|0x20) <= 'f' {
|
||||
return (int8(n) | 0x20) - 'a' + 10
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ipParseARPA6 parse IPv6 reverse address
|
||||
func ipParseARPA6(s string) (ip6 net.IP) {
|
||||
if len(s) != 63 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ip6 = make(net.IP, 16)
|
||||
|
||||
for i := 0; i != 64; i += 4 {
|
||||
// parse "0.1."
|
||||
n := charToHex(s[i])
|
||||
n2 := charToHex(s[i+2])
|
||||
if s[i+1] != '.' || (i != 60 && s[i+3] != '.') ||
|
||||
n < 0 || n2 < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ip6[16-i/4-1] = byte(n2<<4) | byte(n&0x0f)
|
||||
}
|
||||
return ip6
|
||||
}
|
||||
|
||||
// ipReverse inverts byte order of ip.
|
||||
func ipReverse(ip net.IP) (rev net.IP) {
|
||||
ipLen := len(ip)
|
||||
rev = make(net.IP, ipLen)
|
||||
for i, b := range ip {
|
||||
rev[ipLen-i-1] = b
|
||||
}
|
||||
|
||||
return rev
|
||||
}
|
||||
|
||||
// ARPA addresses' suffixes.
|
||||
const (
|
||||
arpaV4Suffix = ".in-addr.arpa"
|
||||
arpaV6Suffix = ".ip6.arpa"
|
||||
)
|
||||
|
||||
// UnreverseAddr tries to convert reversed ARPA to a normal IP address.
|
||||
func UnreverseAddr(arpa string) (unreversed net.IP) {
|
||||
// Unify the input data.
|
||||
arpa = strings.TrimSuffix(arpa, ".")
|
||||
arpa = strings.ToLower(arpa)
|
||||
|
||||
if strings.HasSuffix(arpa, arpaV4Suffix) {
|
||||
ip := strings.TrimSuffix(arpa, arpaV4Suffix)
|
||||
ip4 := net.ParseIP(ip).To4()
|
||||
if ip4 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ipReverse(ip4)
|
||||
|
||||
} else if strings.HasSuffix(arpa, arpaV6Suffix) {
|
||||
ip := strings.TrimSuffix(arpa, arpaV6Suffix)
|
||||
return ipParseARPA6(ip)
|
||||
}
|
||||
|
||||
// The suffix unrecognizable.
|
||||
return nil
|
||||
}
|
||||
|
||||
// The length of extreme cases of arpa formatted addresses.
|
||||
//
|
||||
// The example of IPv4 with maximum length:
|
||||
//
|
||||
// 49.91.20.104.in-addr.arpa
|
||||
//
|
||||
// The example of IPv6 with maximum length:
|
||||
//
|
||||
// 1.3.b.5.4.1.8.6.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.7.4.6.0.6.2.ip6.arpa
|
||||
//
|
||||
const (
|
||||
arpaV4MaxLen = len("000.000.000.000") + len(arpaV4Suffix)
|
||||
arpaV6MaxLen = len("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0") +
|
||||
len(arpaV6Suffix)
|
||||
)
|
||||
|
||||
// ReverseAddr returns the ARPA hostname of the ip suitable for reverse DNS
|
||||
// (PTR) record lookups. This is the modified version of ReverseAddr from
|
||||
// github.com/miekg/dns package with no error among returned values.
|
||||
func ReverseAddr(ip net.IP) (arpa string) {
|
||||
var strLen int
|
||||
var suffix string
|
||||
// Don't handle errors in implementations since strings.WriteString
|
||||
// never returns non-nil errors.
|
||||
var writeByte func(val byte)
|
||||
b := &strings.Builder{}
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
strLen, suffix = arpaV4MaxLen, arpaV4Suffix[1:]
|
||||
ip = ip4
|
||||
writeByte = func(val byte) {
|
||||
_, _ = b.WriteString(strconv.Itoa(int(val)))
|
||||
_, _ = b.WriteRune('.')
|
||||
}
|
||||
|
||||
} else if ip6 := ip.To16(); ip6 != nil {
|
||||
strLen, suffix = arpaV6MaxLen, arpaV6Suffix[1:]
|
||||
ip = ip6
|
||||
writeByte = func(val byte) {
|
||||
lByte, rByte := val&0xF, val>>4
|
||||
|
||||
_, _ = b.WriteString(strconv.FormatUint(uint64(lByte), 16))
|
||||
_, _ = b.WriteRune('.')
|
||||
_, _ = b.WriteString(strconv.FormatUint(uint64(rByte), 16))
|
||||
_, _ = b.WriteRune('.')
|
||||
}
|
||||
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
b.Grow(strLen)
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
writeByte(ip[i])
|
||||
}
|
||||
_, _ = b.WriteString(suffix)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func ifaceHasStaticIP(ifaceName string) (bool, error) {
|
||||
return portInfo.static, nil
|
||||
}
|
||||
|
||||
// getCurrentHardwarePortInfo gets information the specified network interface.
|
||||
// getCurrentHardwarePortInfo gets information for the specified network interface.
|
||||
func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
|
||||
// First of all we should find hardware port name
|
||||
m := getNetworkSetupHardwareReports()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -14,3 +16,92 @@ func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
||||
require.NotEmptyf(t, iface.Addresses, "no addresses found for %s", iface.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnreverseAddr(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have string
|
||||
want net.IP
|
||||
}{{
|
||||
name: "good_ipv4",
|
||||
have: "1.0.0.127.in-addr.arpa",
|
||||
want: net.IP{127, 0, 0, 1},
|
||||
}, {
|
||||
name: "good_ipv6",
|
||||
have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||
want: net.ParseIP("::abcd:1234"),
|
||||
}, {
|
||||
name: "good_ipv6_case",
|
||||
have: "4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.iP6.ArPa",
|
||||
want: net.ParseIP("::abcd:1234"),
|
||||
}, {
|
||||
name: "good_ipv4_dot",
|
||||
have: "1.0.0.127.in-addr.arpa.",
|
||||
want: net.IP{127, 0, 0, 1},
|
||||
}, {
|
||||
name: "good_ipv4_case",
|
||||
have: "1.0.0.127.In-Addr.Arpa",
|
||||
want: net.IP{127, 0, 0, 1},
|
||||
}, {
|
||||
name: "wrong_ipv4",
|
||||
have: ".0.0.127.in-addr.arpa",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "wrong_ipv6",
|
||||
have: ".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "bad_ipv6_dot",
|
||||
have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "bad_ipv6_space",
|
||||
have: "4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||
want: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ip := UnreverseAddr(tc.have)
|
||||
assert.True(t, tc.want.Equal(ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseAddr(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
ip net.IP
|
||||
}{{
|
||||
name: "valid_ipv4",
|
||||
want: "4.3.2.1.in-addr.arpa",
|
||||
ip: net.IP{1, 2, 3, 4},
|
||||
}, {
|
||||
name: "valid_ipv6",
|
||||
want: "1.3.b.5.4.1.8.6.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.7.4.6.0.6.2.ip6.arpa",
|
||||
ip: net.ParseIP("2606:4700:10::6814:5b31"),
|
||||
}, {
|
||||
name: "nil_ip",
|
||||
want: "",
|
||||
ip: nil,
|
||||
}, {
|
||||
name: "unspecified_ipv6",
|
||||
want: "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||
ip: net.IPv6unspecified,
|
||||
}, {
|
||||
name: "unspecified_ipv4",
|
||||
want: "0.0.0.0.in-addr.arpa",
|
||||
ip: net.IPv4zero,
|
||||
}, {
|
||||
name: "wrong_length_ip",
|
||||
want: "",
|
||||
ip: net.IP{1, 2, 3, 4, 5},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, ReverseAddr(tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
155
internal/aghnet/subnetdetector.go
Normal file
155
internal/aghnet/subnetdetector.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// SubnetDetector describes IP address properties.
|
||||
type SubnetDetector struct {
|
||||
// spNets is the slice of special-purpose address registries as defined
|
||||
// by RFC-6890 (https://tools.ietf.org/html/rfc6890).
|
||||
spNets []*net.IPNet
|
||||
|
||||
// locServedNets is the slice of locally-served networks as defined by
|
||||
// RFC-6303 (https://tools.ietf.org/html/rfc6303).
|
||||
locServedNets []*net.IPNet
|
||||
}
|
||||
|
||||
// NewSubnetDetector returns a new IP detector.
|
||||
func NewSubnetDetector() (snd *SubnetDetector, err error) {
|
||||
spNets := []string{
|
||||
// "This" network.
|
||||
"0.0.0.0/8",
|
||||
// Private-Use Networks.
|
||||
"10.0.0.0/8",
|
||||
// Shared Address Space.
|
||||
"100.64.0.0/10",
|
||||
// Loopback.
|
||||
"127.0.0.0/8",
|
||||
// Link Local.
|
||||
"169.254.0.0/16",
|
||||
// Private-Use Networks.
|
||||
"172.16.0.0/12",
|
||||
// IETF Protocol Assignments.
|
||||
"192.0.0.0/24",
|
||||
// DS-Lite.
|
||||
"192.0.0.0/29",
|
||||
// TEST-NET-1
|
||||
"192.0.2.0/24",
|
||||
// 6to4 Relay Anycast.
|
||||
"192.88.99.0/24",
|
||||
// Private-Use Networks.
|
||||
"192.168.0.0/16",
|
||||
// Network Interconnect Device Benchmark Testing.
|
||||
"198.18.0.0/15",
|
||||
// TEST-NET-2.
|
||||
"198.51.100.0/24",
|
||||
// TEST-NET-3.
|
||||
"203.0.113.0/24",
|
||||
// Reserved for Future Use.
|
||||
"240.0.0.0/4",
|
||||
// Limited Broadcast.
|
||||
"255.255.255.255/32",
|
||||
|
||||
// Loopback.
|
||||
"::1/128",
|
||||
// Unspecified.
|
||||
"::/128",
|
||||
// IPv4-IPv6 Translation Address.
|
||||
"64:ff9b::/96",
|
||||
|
||||
// IPv4-Mapped Address. Since this network is used for mapping
|
||||
// IPv4 addresses, we don't include it.
|
||||
// "::ffff:0:0/96",
|
||||
|
||||
// Discard-Only Prefix.
|
||||
"100::/64",
|
||||
// IETF Protocol Assignments.
|
||||
"2001::/23",
|
||||
// TEREDO.
|
||||
"2001::/32",
|
||||
// Benchmarking.
|
||||
"2001:2::/48",
|
||||
// Documentation.
|
||||
"2001:db8::/32",
|
||||
// ORCHID.
|
||||
"2001:10::/28",
|
||||
// 6to4.
|
||||
"2002::/16",
|
||||
// Unique-Local.
|
||||
"fc00::/7",
|
||||
// Linked-Scoped Unicast.
|
||||
"fe80::/10",
|
||||
}
|
||||
|
||||
// TODO(e.burkov): It's a subslice of the slice above. Should be done
|
||||
// smarter.
|
||||
locServedNets := []string{
|
||||
// IPv4.
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"255.255.255.255/32",
|
||||
// IPv6.
|
||||
"::/128",
|
||||
"::1/128",
|
||||
"fe80::/10",
|
||||
"2001:db8::/32",
|
||||
}
|
||||
|
||||
snd = &SubnetDetector{
|
||||
spNets: make([]*net.IPNet, len(spNets)),
|
||||
locServedNets: make([]*net.IPNet, len(locServedNets)),
|
||||
}
|
||||
for i, ipnetStr := range spNets {
|
||||
var ipnet *net.IPNet
|
||||
_, ipnet, err = net.ParseCIDR(ipnetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snd.spNets[i] = ipnet
|
||||
}
|
||||
for i, ipnetStr := range locServedNets {
|
||||
var ipnet *net.IPNet
|
||||
_, ipnet, err = net.ParseCIDR(ipnetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snd.locServedNets[i] = ipnet
|
||||
}
|
||||
|
||||
return snd, nil
|
||||
}
|
||||
|
||||
// anyNetContains ranges through the given ipnets slice searching for the one
|
||||
// which contains the ip. For internal use only.
|
||||
//
|
||||
// TODO(e.burkov): Think about memoization.
|
||||
func anyNetContains(ipnets *[]*net.IPNet, ip net.IP) (is bool) {
|
||||
for _, ipnet := range *ipnets {
|
||||
if ipnet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSpecialNetwork returns true if IP address is contained by any of
|
||||
// special-purpose IP address registries. It's safe for concurrent use.
|
||||
func (snd *SubnetDetector) IsSpecialNetwork(ip net.IP) (is bool) {
|
||||
return anyNetContains(&snd.spNets, ip)
|
||||
}
|
||||
|
||||
// IsLocallyServedNetwork returns true if IP address is contained by any of
|
||||
// locally-served IP address registries. It's safe for concurrent use.
|
||||
func (snd *SubnetDetector) IsLocallyServedNetwork(ip net.IP) (is bool) {
|
||||
return anyNetContains(&snd.locServedNets, ip)
|
||||
}
|
||||
@@ -8,11 +8,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||
var ipd *IPDetector
|
||||
var err error
|
||||
|
||||
ipd, err = NewIPDetector()
|
||||
func TestSubnetDetector_DetectSpecialNetwork(t *testing.T) {
|
||||
snd, err := NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
@@ -139,7 +136,109 @@ func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, ipd.DetectSpecialNetwork(tc.ip))
|
||||
assert.Equal(t, tc.want, snd.IsSpecialNetwork(tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubnetDetector_DetectLocallyServedNetwork(t *testing.T) {
|
||||
snd, err := NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
want bool
|
||||
}{{
|
||||
name: "not_specific",
|
||||
ip: net.ParseIP("8.8.8.8"),
|
||||
want: false,
|
||||
}, {
|
||||
name: "private-Use",
|
||||
ip: net.ParseIP("10.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "loopback",
|
||||
ip: net.ParseIP("127.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "link_local",
|
||||
ip: net.ParseIP("169.254.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "private-use",
|
||||
ip: net.ParseIP("172.16.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-1)",
|
||||
ip: net.ParseIP("192.0.2.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "private-use",
|
||||
ip: net.ParseIP("192.168.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-2)",
|
||||
ip: net.ParseIP("198.51.100.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-3)",
|
||||
ip: net.ParseIP("203.0.113.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "limited_broadcast",
|
||||
ip: net.ParseIP("255.255.255.255"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "loopback_address",
|
||||
ip: net.ParseIP("::1"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "unspecified_address",
|
||||
ip: net.ParseIP("::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation",
|
||||
ip: net.ParseIP("2001:db8::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "linked-scoped_unicast",
|
||||
ip: net.ParseIP("fe80::"),
|
||||
want: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, snd.IsLocallyServedNetwork(tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubnetDetector_Detect_parallel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
snd, err := NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
testFunc := func() {
|
||||
for _, ip := range []net.IP{
|
||||
net.IPv4allrouter,
|
||||
net.IPv4allsys,
|
||||
net.IPv4bcast,
|
||||
net.IPv4zero,
|
||||
net.IPv6interfacelocalallnodes,
|
||||
net.IPv6linklocalallnodes,
|
||||
net.IPv6linklocalallrouters,
|
||||
net.IPv6loopback,
|
||||
net.IPv6unspecified,
|
||||
} {
|
||||
_ = snd.IsSpecialNetwork(ip)
|
||||
_ = snd.IsLocallyServedNetwork(ip)
|
||||
}
|
||||
}
|
||||
|
||||
const goroutinesNum = 50
|
||||
for i := 0; i < goroutinesNum; i++ {
|
||||
go testFunc()
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,9 @@ type SystemResolvers interface {
|
||||
// Get returns the slice of local resolvers' addresses.
|
||||
// It should be safe for concurrent use.
|
||||
Get() (rs []string)
|
||||
// Refresh refreshes the local resolvers' addresses cache. It should be
|
||||
// refresh refreshes the local resolvers' addresses cache. It should be
|
||||
// safe for concurrent use.
|
||||
Refresh() (err error)
|
||||
refresh() (err error)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -42,7 +42,7 @@ func refreshWithTicker(sr SystemResolvers, tickCh <-chan time.Time) {
|
||||
|
||||
// TODO(e.burkov): Implement a functionality to stop ticker.
|
||||
for range tickCh {
|
||||
err := sr.Refresh()
|
||||
err := sr.refresh()
|
||||
if err != nil {
|
||||
log.Error("systemResolvers: error in refreshing goroutine: %s", err)
|
||||
|
||||
@@ -63,7 +63,7 @@ func NewSystemResolvers(
|
||||
sr = newSystemResolvers(refreshIvl, hostGenFunc)
|
||||
|
||||
// Fill cache.
|
||||
err = sr.Refresh()
|
||||
err = sr.refresh()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type systemResolvers struct {
|
||||
addrsLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) Refresh() (err error) {
|
||||
func (sr *systemResolvers) refresh() (err error) {
|
||||
defer agherr.Annotate("systemResolvers: %w", &err)
|
||||
|
||||
_, err = sr.resolver.LookupHost(context.Background(), sr.hostGenFunc())
|
||||
@@ -75,7 +75,7 @@ func (sr *systemResolvers) dialFunc(_ context.Context, _, address string) (_ net
|
||||
sr.addrsLock.Lock()
|
||||
defer sr.addrsLock.Unlock()
|
||||
|
||||
sr.addrs[address] = unit{}
|
||||
sr.addrs[host] = unit{}
|
||||
|
||||
return nil, fakeDialErr
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestSystemResolvers_Refresh(t *testing.T) {
|
||||
t.Run("expected_error", func(t *testing.T) {
|
||||
sr := createTestSystemResolvers(t, 0, nil)
|
||||
|
||||
assert.NoError(t, sr.Refresh())
|
||||
assert.NoError(t, sr.refresh())
|
||||
})
|
||||
|
||||
t.Run("unexpected_error", func(t *testing.T) {
|
||||
|
||||
@@ -138,7 +138,7 @@ func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) Refresh() (err error) {
|
||||
func (sr *systemResolvers) refresh() (err error) {
|
||||
defer agherr.Annotate("systemResolvers: %w", &err)
|
||||
|
||||
got, err := sr.getAddrs()
|
||||
|
||||
Reference in New Issue
Block a user