Merge branch 'master' into 4728-cap-check
This commit is contained in:
@@ -5,10 +5,11 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// ARPDB: The Network Neighborhood Database
|
||||
@@ -54,7 +55,7 @@ type Neighbor struct {
|
||||
Name string
|
||||
|
||||
// IP contains either IPv4 or IPv6.
|
||||
IP net.IP
|
||||
IP netip.Addr
|
||||
|
||||
// MAC contains the hardware address.
|
||||
MAC net.HardwareAddr
|
||||
@@ -64,8 +65,8 @@ type Neighbor struct {
|
||||
func (n Neighbor) Clone() (clone Neighbor) {
|
||||
return Neighbor{
|
||||
Name: n.Name,
|
||||
IP: netutil.CloneIP(n.IP),
|
||||
MAC: netutil.CloneMAC(n.MAC),
|
||||
IP: n.IP,
|
||||
MAC: slices.Clone(n.MAC),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//go:build darwin || freebsd
|
||||
// +build darwin freebsd
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -33,8 +33,7 @@ func newARPDB() (arp *cmdARPDB) {
|
||||
// parseArpA parses the output of the "arp -a -n" command on macOS and FreeBSD.
|
||||
// The expected input format:
|
||||
//
|
||||
// host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
|
||||
//
|
||||
// host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
|
||||
func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
ns = make([]Neighbor, 0, lenHint)
|
||||
for sc.Scan() {
|
||||
@@ -49,22 +48,28 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
if ipStr := fields[1]; len(ipStr) < 2 {
|
||||
continue
|
||||
} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
|
||||
} else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil {
|
||||
log.Debug("arpdb: parsing arp output: ip: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
}
|
||||
|
||||
hwStr := fields[3]
|
||||
if mac, err := net.ParseMAC(hwStr); err != nil {
|
||||
mac, err := net.ParseMAC(hwStr)
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: mac: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.MAC = mac
|
||||
}
|
||||
|
||||
host := fields[0]
|
||||
if err := netutil.ValidateDomainName(host); err != nil {
|
||||
log.Debug("parsing arp output: %s", err)
|
||||
err = netutil.ValidateDomainName(host)
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: host: %s", err)
|
||||
} else {
|
||||
n.Name = host
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:build darwin || freebsd
|
||||
// +build darwin freebsd
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const arpAOutput = `
|
||||
@@ -18,14 +18,14 @@ hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [
|
||||
|
||||
var wantNeighs = []Neighbor{{
|
||||
Name: "hostname.one",
|
||||
IP: net.IPv4(192, 168, 1, 2),
|
||||
IP: netip.MustParseAddr("192.168.1.2"),
|
||||
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
|
||||
}, {
|
||||
Name: "hostname.two",
|
||||
IP: net.ParseIP("::ffff:ffff"),
|
||||
IP: netip.MustParseAddr("::ffff:ffff"),
|
||||
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
|
||||
}, {
|
||||
Name: "",
|
||||
IP: net.ParseIP("::1234"),
|
||||
IP: netip.MustParseAddr("::1234"),
|
||||
MAC: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -8,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -95,7 +95,8 @@ func (arp *fsysARPDB) Refresh() (err error) {
|
||||
}
|
||||
|
||||
n := Neighbor{}
|
||||
if n.IP = net.ParseIP(fields[0]); n.IP == nil || n.IP.IsUnspecified() {
|
||||
n.IP, err = netip.ParseAddr(fields[0])
|
||||
if err != nil || n.IP.IsUnspecified() {
|
||||
continue
|
||||
} else if n.MAC, err = net.ParseMAC(fields[3]); err != nil {
|
||||
continue
|
||||
@@ -117,9 +118,8 @@ func (arp *fsysARPDB) Neighbors() (ns []Neighbor) {
|
||||
// parseArpAWrt parses the output of the "arp -a -n" command on OpenWrt. The
|
||||
// expected input format:
|
||||
//
|
||||
// IP address HW type Flags HW address Mask Device
|
||||
// 192.168.11.98 0x1 0x2 5a:92:df:a9:7e:28 * wan
|
||||
//
|
||||
// IP address HW type Flags HW address Mask Device
|
||||
// 192.168.11.98 0x1 0x2 5a:92:df:a9:7e:28 * wan
|
||||
func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
if !sc.Scan() {
|
||||
// Skip the header.
|
||||
@@ -137,15 +137,19 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
n := Neighbor{}
|
||||
|
||||
if ip := net.ParseIP(fields[0]); ip == nil || n.IP.IsUnspecified() {
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil || n.IP.IsUnspecified() {
|
||||
log.Debug("arpdb: parsing arp output: ip: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
}
|
||||
|
||||
hwStr := fields[3]
|
||||
if mac, err := net.ParseMAC(hwStr); err != nil {
|
||||
log.Debug("parsing arp output: %s", err)
|
||||
mac, err := net.ParseMAC(hwStr)
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: mac: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
@@ -161,8 +165,7 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
// parseArpA parses the output of the "arp -a -n" command on Linux. The
|
||||
// expected input format:
|
||||
//
|
||||
// hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3
|
||||
//
|
||||
// hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3
|
||||
func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
ns = make([]Neighbor, 0, lenHint)
|
||||
for sc.Scan() {
|
||||
@@ -177,7 +180,9 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
if ipStr := fields[1]; len(ipStr) < 2 {
|
||||
continue
|
||||
} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
|
||||
} else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil {
|
||||
log.Debug("arpdb: parsing arp output: ip: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
@@ -185,7 +190,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
hwStr := fields[3]
|
||||
if mac, err := net.ParseMAC(hwStr); err != nil {
|
||||
log.Debug("parsing arp output: %s", err)
|
||||
log.Debug("arpdb: parsing arp output: mac: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
@@ -194,7 +199,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
host := fields[0]
|
||||
if verr := netutil.ValidateDomainName(host); verr != nil {
|
||||
log.Debug("parsing arp output: %s", verr)
|
||||
log.Debug("arpdb: parsing arp output: host: %s", verr)
|
||||
} else {
|
||||
n.Name = host
|
||||
}
|
||||
@@ -208,8 +213,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
// parseIPNeigh parses the output of the "ip neigh" command on Linux. The
|
||||
// expected input format:
|
||||
//
|
||||
// 192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE
|
||||
//
|
||||
// 192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE
|
||||
func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
ns = make([]Neighbor, 0, lenHint)
|
||||
for sc.Scan() {
|
||||
@@ -222,14 +226,18 @@ func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
n := Neighbor{}
|
||||
|
||||
if ip := net.ParseIP(fields[0]); ip == nil {
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: ip: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
}
|
||||
|
||||
if mac, err := net.ParseMAC(fields[4]); err != nil {
|
||||
log.Debug("parsing arp output: %s", err)
|
||||
mac, err := net.ParseMAC(fields[4])
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: mac: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
@@ -34,10 +34,10 @@ const ipNeighOutput = `
|
||||
::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE`
|
||||
|
||||
var wantNeighs = []Neighbor{{
|
||||
IP: net.IPv4(192, 168, 1, 2),
|
||||
IP: netip.MustParseAddr("192.168.1.2"),
|
||||
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
|
||||
}, {
|
||||
IP: net.ParseIP("::ffff:ffff"),
|
||||
IP: netip.MustParseAddr("::ffff:ffff"),
|
||||
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
|
||||
}}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -32,9 +32,8 @@ func newARPDB() (arp *cmdARPDB) {
|
||||
// parseArpA parses the output of the "arp -a -n" command on OpenBSD. The
|
||||
// expected input format:
|
||||
//
|
||||
// Host Ethernet Address Netif Expire Flags
|
||||
// 192.168.1.1 ab:cd:ef:ab:cd:ef em0 19m59s
|
||||
//
|
||||
// Host Ethernet Address Netif Expire Flags
|
||||
// 192.168.1.1 ab:cd:ef:ab:cd:ef em0 19m59s
|
||||
func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
// Skip the header.
|
||||
if !sc.Scan() {
|
||||
@@ -52,14 +51,18 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
n := Neighbor{}
|
||||
|
||||
if ip := net.ParseIP(fields[0]); ip == nil {
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: ip: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
}
|
||||
|
||||
if mac, err := net.ParseMAC(fields[1]); err != nil {
|
||||
log.Debug("parsing arp output: %s", err)
|
||||
mac, err := net.ParseMAC(fields[1])
|
||||
if err != nil {
|
||||
log.Debug("arpdb: parsing arp output: mac: %s", err)
|
||||
|
||||
continue
|
||||
} else {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const arpAOutput = `
|
||||
@@ -16,9 +16,9 @@ Host Ethernet Address Netif Expire Flags
|
||||
`
|
||||
|
||||
var wantNeighs = []Neighbor{{
|
||||
IP: net.IPv4(192, 168, 1, 2),
|
||||
IP: netip.MustParseAddr("192.168.1.2"),
|
||||
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
|
||||
}, {
|
||||
IP: net.ParseIP("::ffff:ffff"),
|
||||
IP: netip.MustParseAddr("::ffff:ffff"),
|
||||
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
|
||||
}}
|
||||
|
||||
@@ -2,6 +2,7 @@ package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -35,7 +36,7 @@ func (arp *TestARPDB) Neighbors() (ns []Neighbor) {
|
||||
}
|
||||
|
||||
func TestARPDBS(t *testing.T) {
|
||||
knownIP := net.IP{1, 2, 3, 4}
|
||||
knownIP := netip.MustParseAddr("1.2.3.4")
|
||||
knownMAC := net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}
|
||||
|
||||
succRefrCount, failRefrCount := 0, 0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -25,12 +25,10 @@ func newARPDB() (arp *cmdARPDB) {
|
||||
// parseArpA parses the output of the "arp /a" command on Windows. The expected
|
||||
// input format (the first line is empty):
|
||||
//
|
||||
//
|
||||
// Interface: 192.168.56.16 --- 0x7
|
||||
// Internet Address Physical Address Type
|
||||
// 192.168.56.1 0a-00-27-00-00-00 dynamic
|
||||
// 192.168.56.255 ff-ff-ff-ff-ff-ff static
|
||||
//
|
||||
// Interface: 192.168.56.16 --- 0x7
|
||||
// Internet Address Physical Address Type
|
||||
// 192.168.56.1 0a-00-27-00-00-00 dynamic
|
||||
// 192.168.56.255 ff-ff-ff-ff-ff-ff static
|
||||
func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
ns = make([]Neighbor, 0, lenHint)
|
||||
for sc.Scan() {
|
||||
@@ -46,13 +44,15 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
|
||||
|
||||
n := Neighbor{}
|
||||
|
||||
if ip := net.ParseIP(fields[0]); ip == nil {
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
n.IP = ip
|
||||
}
|
||||
|
||||
if mac, err := net.ParseMAC(fields[1]); err != nil {
|
||||
mac, err := net.ParseMAC(fields[1])
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
n.MAC = mac
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const arpAOutput = `
|
||||
@@ -15,9 +15,9 @@ Interface: 192.168.1.1 --- 0x7
|
||||
::ffff:ffff ef-cd-ab-ef-cd-ab static`
|
||||
|
||||
var wantNeighs = []Neighbor{{
|
||||
IP: net.IPv4(192, 168, 1, 2),
|
||||
IP: netip.MustParseAddr("192.168.1.2"),
|
||||
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
|
||||
}, {
|
||||
IP: net.ParseIP("::ffff:ffff"),
|
||||
IP: netip.MustParseAddr("::ffff:ffff"),
|
||||
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
//go:build darwin || freebsd || linux || openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -7,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -39,48 +39,44 @@ func checkOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
|
||||
}
|
||||
|
||||
// ifaceIPv4Subnet returns the first suitable IPv4 subnetwork iface has.
|
||||
func ifaceIPv4Subnet(iface *net.Interface) (subnet *net.IPNet, err error) {
|
||||
func ifaceIPv4Subnet(iface *net.Interface) (subnet netip.Prefix, err error) {
|
||||
var addrs []net.Addr
|
||||
if addrs, err = iface.Addrs(); err != nil {
|
||||
return nil, err
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
var ip net.IP
|
||||
var maskLen int
|
||||
switch a := a.(type) {
|
||||
case *net.IPAddr:
|
||||
subnet = &net.IPNet{
|
||||
IP: a.IP,
|
||||
Mask: a.IP.DefaultMask(),
|
||||
}
|
||||
ip = a.IP
|
||||
maskLen, _ = ip.DefaultMask().Size()
|
||||
case *net.IPNet:
|
||||
subnet = a
|
||||
ip = a.IP
|
||||
maskLen, _ = a.Mask.Size()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if ip4 := subnet.IP.To4(); ip4 != nil {
|
||||
subnet.IP = ip4
|
||||
|
||||
return subnet, nil
|
||||
if ip = ip.To4(); ip != nil {
|
||||
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ip)), maskLen), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("interface %s has no ipv4 addresses", iface.Name)
|
||||
return netip.Prefix{}, fmt.Errorf("interface %s has no ipv4 addresses", iface.Name)
|
||||
}
|
||||
|
||||
// checkOtherDHCPv4 sends a DHCP request to the specified network interface, and
|
||||
// waits for a response for a period defined by defaultDiscoverTime.
|
||||
func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
|
||||
var subnet *net.IPNet
|
||||
var subnet netip.Prefix
|
||||
if subnet, err = ifaceIPv4Subnet(iface); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Resolve broadcast addr.
|
||||
dst := netutil.IPPort{
|
||||
IP: BroadcastFromIPNet(subnet),
|
||||
Port: 67,
|
||||
}.String()
|
||||
dst := netip.AddrPortFrom(BroadcastFromPref(subnet), 67).String()
|
||||
var dstAddr *net.UDPAddr
|
||||
if dstAddr, err = net.ResolveUDPAddr("udp4", dst); err != nil {
|
||||
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ func generateIPv6Hostname(ipv6 net.IP) (hostname string) {
|
||||
// GenerateHostname generates the hostname from ip. In case of using IPv4 the
|
||||
// result should be like:
|
||||
//
|
||||
// 192-168-10-1
|
||||
// 192-168-10-1
|
||||
//
|
||||
// In case of using IPv6, the result is like:
|
||||
//
|
||||
// ff80-f076-0000-0000-0000-0000-0000-0010
|
||||
// ff80-f076-0000-0000-0000-0000-0000-0010
|
||||
//
|
||||
// ip must be either an IPv4 or an IPv6.
|
||||
func GenerateHostname(ip net.IP) (hostname string) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// DefaultHostsPaths returns the slice of paths default for the operating system
|
||||
@@ -55,7 +56,7 @@ func (rm *requestMatcher) MatchRequest(
|
||||
) (res *urlfilter.DNSResult, ok bool) {
|
||||
switch req.DNSType {
|
||||
case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
|
||||
log.Debug("%s: handling the request", hostsContainerPref)
|
||||
log.Debug("%s: handling the request for %s", hostsContainerPref, req.Hostname)
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
@@ -70,8 +71,7 @@ func (rm *requestMatcher) MatchRequest(
|
||||
// rule or an empty string if the last doesn't exist. The returned rules are in
|
||||
// a processed format like:
|
||||
//
|
||||
// ip host1 host2 ...
|
||||
//
|
||||
// ip host1 host2 ...
|
||||
func (rm *requestMatcher) Translate(rule string) (hostRule string) {
|
||||
rm.stateLock.RLock()
|
||||
defer rm.stateLock.RUnlock()
|
||||
@@ -107,10 +107,10 @@ type HostsContainer struct {
|
||||
done chan struct{}
|
||||
|
||||
// updates is the channel for receiving updated hosts.
|
||||
updates chan *netutil.IPMap
|
||||
updates chan HostsRecords
|
||||
|
||||
// last is the set of hosts that was cached within last detected change.
|
||||
last *netutil.IPMap
|
||||
last HostsRecords
|
||||
|
||||
// fsys is the working file system to read hosts files from.
|
||||
fsys fs.FS
|
||||
@@ -125,6 +125,27 @@ type HostsContainer struct {
|
||||
listID int
|
||||
}
|
||||
|
||||
// HostsRecords is a mapping of an IP address to its hosts data.
|
||||
type HostsRecords map[netip.Addr]*HostsRecord
|
||||
|
||||
// HostsRecord represents a single hosts file record.
|
||||
type HostsRecord struct {
|
||||
Aliases *stringutil.Set
|
||||
Canonical string
|
||||
}
|
||||
|
||||
// equal returns true if all fields of rec are equal to field in other or they
|
||||
// both are nil.
|
||||
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
|
||||
if rec == nil {
|
||||
return other == nil
|
||||
} else if other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
|
||||
}
|
||||
|
||||
// ErrNoHostsPaths is returned when there are no valid paths to watch passed to
|
||||
// the HostsContainer.
|
||||
const ErrNoHostsPaths errors.Error = "no valid paths to hosts files provided"
|
||||
@@ -159,7 +180,7 @@ func NewHostsContainer(
|
||||
},
|
||||
listID: listID,
|
||||
done: make(chan struct{}, 1),
|
||||
updates: make(chan *netutil.IPMap, 1),
|
||||
updates: make(chan HostsRecords, 1),
|
||||
fsys: fsys,
|
||||
w: w,
|
||||
patterns: patterns,
|
||||
@@ -197,9 +218,8 @@ func (hc *HostsContainer) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upd returns the channel into which the updates are sent. The receivable
|
||||
// map's values are guaranteed to be of type of *stringutil.Set.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
||||
// Upd returns the channel into which the updates are sent.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan HostsRecords) {
|
||||
return hc.updates
|
||||
}
|
||||
|
||||
@@ -265,7 +285,7 @@ type hostsParser struct {
|
||||
|
||||
// table stores only the unique IP-hostname pairs. It's also sent to the
|
||||
// updates channel afterwards.
|
||||
table *netutil.IPMap
|
||||
table HostsRecords
|
||||
}
|
||||
|
||||
// newHostsParser creates a new *hostsParser with buffers of size taken from the
|
||||
@@ -274,7 +294,7 @@ func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
||||
return &hostsParser{
|
||||
rulesBuilder: &strings.Builder{},
|
||||
translations: map[string]string{},
|
||||
table: netutil.NewIPMap(hc.last.Len()),
|
||||
table: make(HostsRecords, len(hc.last)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,25 +306,26 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
ip, hosts := hp.parseLine(s.Text())
|
||||
if ip == nil || len(hosts) == 0 {
|
||||
if ip == (netip.Addr{}) || len(hosts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hp.addPairs(ip, hosts)
|
||||
hp.addRecord(ip, hosts)
|
||||
}
|
||||
|
||||
return nil, true, s.Err()
|
||||
}
|
||||
|
||||
// parseLine parses the line having the hosts syntax ignoring invalid ones.
|
||||
func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
func (hp *hostsParser) parseLine(line string) (ip netip.Addr, hosts []string) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
return nil, nil
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
if ip = net.ParseIP(fields[0]); ip == nil {
|
||||
return nil, nil
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil {
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
for _, f := range fields[1:] {
|
||||
@@ -322,7 +343,7 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
|
||||
//
|
||||
// TODO(e.burkov): Investigate if hosts may contain DNS-SD domains.
|
||||
err := netutil.ValidateDomainName(f)
|
||||
err = netutil.ValidateDomainName(f)
|
||||
if err != nil {
|
||||
log.Error("%s: host %q is invalid, ignoring", hostsContainerPref, f)
|
||||
|
||||
@@ -335,43 +356,47 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
return ip, hosts
|
||||
}
|
||||
|
||||
// addPair puts the pair of ip and host to the rules builder if needed. For
|
||||
// each ip the first member of hosts will become the main one.
|
||||
func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
|
||||
v, ok := hp.table.Get(ip)
|
||||
// addRecord puts the record for the IP address to the rules builder if needed.
|
||||
// The first host is considered to be the canonical name for the IP address.
|
||||
// hosts must have at least one name.
|
||||
func (hp *hostsParser) addRecord(ip netip.Addr, hosts []string) {
|
||||
line := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
||||
|
||||
rec, ok := hp.table[ip]
|
||||
if !ok {
|
||||
// This ip is added at the first time.
|
||||
v = stringutil.NewSet()
|
||||
hp.table.Set(ip, v)
|
||||
rec = &HostsRecord{
|
||||
Aliases: stringutil.NewSet(),
|
||||
}
|
||||
|
||||
rec.Canonical, hosts = hosts[0], hosts[1:]
|
||||
hp.addRules(ip, rec.Canonical, line)
|
||||
hp.table[ip] = rec
|
||||
}
|
||||
|
||||
var set *stringutil.Set
|
||||
set, ok = v.(*stringutil.Set)
|
||||
if !ok {
|
||||
log.Debug("%s: adding pairs: unexpected value type %T", hostsContainerPref, v)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
processed := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
||||
for _, h := range hosts {
|
||||
if set.Has(h) {
|
||||
for _, host := range hosts {
|
||||
if rec.Canonical == host || rec.Aliases.Has(host) {
|
||||
continue
|
||||
}
|
||||
|
||||
set.Add(h)
|
||||
rec.Aliases.Add(host)
|
||||
|
||||
rule, rulePtr := hp.writeRules(h, ip)
|
||||
hp.translations[rule], hp.translations[rulePtr] = processed, processed
|
||||
|
||||
log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, h)
|
||||
hp.addRules(ip, host, line)
|
||||
}
|
||||
}
|
||||
|
||||
// addRules adds rules and rule translations for the line.
|
||||
func (hp *hostsParser) addRules(ip netip.Addr, host, line string) {
|
||||
rule, rulePtr := hp.writeRules(host, ip)
|
||||
hp.translations[rule], hp.translations[rulePtr] = line, line
|
||||
|
||||
log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, host)
|
||||
}
|
||||
|
||||
// writeRules writes the actual rule for the qtype and the PTR for the host-ip
|
||||
// pair into internal builders.
|
||||
func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
|
||||
arpa, err := netutil.IPToReversedAddr(ip)
|
||||
func (hp *hostsParser) writeRules(host string, ip netip.Addr) (rule, rulePtr string) {
|
||||
// TODO(a.garipov): Add a netip.Addr version to netutil.
|
||||
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
@@ -389,7 +414,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
|
||||
var qtype string
|
||||
// The validation of the IP address has been performed earlier so it is
|
||||
// guaranteed to be either an IPv4 or an IPv6.
|
||||
if ip.To4() != nil {
|
||||
if ip.Is4() {
|
||||
qtype = "A"
|
||||
} else {
|
||||
qtype = "AAAA"
|
||||
@@ -416,37 +441,8 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
|
||||
return rule, rulePtr
|
||||
}
|
||||
|
||||
// equalSet returns true if the internal hosts table just parsed equals target.
|
||||
func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
||||
if target == nil {
|
||||
// hp.table shouldn't appear nil since it's initialized on each refresh.
|
||||
return target == hp.table
|
||||
}
|
||||
|
||||
if hp.table.Len() != target.Len() {
|
||||
return false
|
||||
}
|
||||
|
||||
hp.table.Range(func(ip net.IP, b interface{}) (cont bool) {
|
||||
// ok is set to true if the target doesn't contain ip or if the
|
||||
// appropriate hosts set isn't equal to the checked one.
|
||||
if a, hasIP := target.Get(ip); !hasIP {
|
||||
ok = true
|
||||
} else if hosts, aok := a.(*stringutil.Set); aok {
|
||||
ok = !hosts.Equal(b.(*stringutil.Set))
|
||||
}
|
||||
|
||||
// Continue only if maps has no discrepancies.
|
||||
return !ok
|
||||
})
|
||||
|
||||
// Return true if every value from the IP map has no discrepancies with the
|
||||
// appropriate one from the target.
|
||||
return !ok
|
||||
}
|
||||
|
||||
// sendUpd tries to send the parsed data to the ch.
|
||||
func (hp *hostsParser) sendUpd(ch chan *netutil.IPMap) {
|
||||
func (hp *hostsParser) sendUpd(ch chan HostsRecords) {
|
||||
log.Debug("%s: sending upd", hostsContainerPref)
|
||||
|
||||
upd := hp.table
|
||||
@@ -484,14 +480,15 @@ func (hc *HostsContainer) refresh() (err error) {
|
||||
return fmt.Errorf("refreshing : %w", err)
|
||||
}
|
||||
|
||||
if hp.equalSet(hc.last) {
|
||||
// hc.last is nil on the first refresh, so let that one through.
|
||||
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
|
||||
log.Debug("%s: no changes detected", hostsContainerPref)
|
||||
|
||||
return nil
|
||||
}
|
||||
defer hp.sendUpd(hc.updates)
|
||||
|
||||
hc.last = hp.table.ShallowClone()
|
||||
hc.last = maps.Clone(hp.table)
|
||||
|
||||
var rulesStrg *filterlist.RuleStorage
|
||||
if rulesStrg, err = hp.newStrg(hc.listID); err != nil {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !(windows || linux)
|
||||
// +build !windows,!linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package aghnet
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -10,8 +11,10 @@ import (
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
@@ -134,7 +137,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
func TestHostsContainer_refresh(t *testing.T) {
|
||||
// TODO(e.burkov): Test the case with no actual updates.
|
||||
|
||||
ip := net.IP{127, 0, 0, 1}
|
||||
ip := netutil.IPv4Localhost()
|
||||
ipStr := ip.String()
|
||||
|
||||
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
|
||||
@@ -159,31 +162,37 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||
|
||||
checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
|
||||
upd, ok := <-hc.Upd()
|
||||
checkRefresh := func(t *testing.T, want *HostsRecord) {
|
||||
t.Helper()
|
||||
|
||||
upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
|
||||
assert.Equal(t, 1, upd.Len())
|
||||
assert.Len(t, upd, 1)
|
||||
|
||||
v, ok := upd.Get(ip)
|
||||
rec, ok := upd[ip]
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, rec)
|
||||
|
||||
var set *stringutil.Set
|
||||
set, ok = v.(*stringutil.Set)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.True(t, set.Equal(wantHosts))
|
||||
assert.Truef(t, rec.equal(want), "%+v != %+v", rec, want)
|
||||
}
|
||||
|
||||
t.Run("initial_refresh", func(t *testing.T) {
|
||||
checkRefresh(t, stringutil.NewSet("hostname"))
|
||||
checkRefresh(t, &HostsRecord{
|
||||
Aliases: stringutil.NewSet(),
|
||||
Canonical: "hostname",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("second_refresh", func(t *testing.T) {
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
|
||||
eventsCh <- event{}
|
||||
checkRefresh(t, stringutil.NewSet("hostname", "alias"))
|
||||
|
||||
checkRefresh(t, &HostsRecord{
|
||||
Aliases: stringutil.NewSet("alias"),
|
||||
Canonical: "hostname",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("double_refresh", func(t *testing.T) {
|
||||
@@ -363,10 +372,15 @@ func TestHostsContainer(t *testing.T) {
|
||||
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
|
||||
|
||||
testCases := []struct {
|
||||
want []*rules.DNSRewrite
|
||||
name string
|
||||
req *urlfilter.DNSRequest
|
||||
name string
|
||||
want []*rules.DNSRewrite
|
||||
}{{
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "simplehost",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "simple",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 1),
|
||||
@@ -376,27 +390,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
Value: net.ParseIP("::1"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "simple",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "simplehost",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "hello_alias",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "hello.world",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "hello_alias",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
@@ -406,26 +405,41 @@ func TestHostsContainer(t *testing.T) {
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "other_line_alias",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "hello.world.again",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "other_line_alias",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "hello_subdomain",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "say.hello",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "hello_subdomain",
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "hello_alias_subdomain",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "say.hello.world",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "hello_alias_subdomain",
|
||||
want: []*rules.DNSRewrite{},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "for.testing",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "lots_of_aliases",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -435,37 +449,37 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::2"),
|
||||
}},
|
||||
name: "lots_of_aliases",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "for.testing",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypePTR,
|
||||
},
|
||||
name: "reverse",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypePTR,
|
||||
Value: "simplehost.",
|
||||
}},
|
||||
name: "reverse",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypePTR,
|
||||
},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "non-existing",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "nonexisting",
|
||||
Hostname: "nonexistent.example",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "non-existing",
|
||||
want: []*rules.DNSRewrite{},
|
||||
}, {
|
||||
want: nil,
|
||||
name: "bad_type",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypeSRV,
|
||||
},
|
||||
name: "bad_type",
|
||||
want: nil,
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "issue_4216_4_6",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -475,12 +489,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::42"),
|
||||
}},
|
||||
name: "issue_4216_4_6",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain",
|
||||
Hostname: "domain4",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "issue_4216_4",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -490,12 +504,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeA,
|
||||
Value: net.IPv4(1, 3, 5, 7),
|
||||
}},
|
||||
name: "issue_4216_4",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain4",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain6",
|
||||
DNSType: dns.TypeAAAA,
|
||||
},
|
||||
name: "issue_4216_6",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeAAAA,
|
||||
@@ -505,11 +519,6 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::31"),
|
||||
}},
|
||||
name: "issue_4216_6",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain6",
|
||||
DNSType: dns.TypeAAAA,
|
||||
},
|
||||
}}
|
||||
|
||||
stubWatcher := aghtest.FSWatcher{
|
||||
@@ -551,13 +560,13 @@ func TestHostsContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
ip := net.IP{127, 0, 0, 1}
|
||||
ip := netutil.IPv4Localhost()
|
||||
ipStr := ip.String()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
line string
|
||||
wantIP net.IP
|
||||
wantIP netip.Addr
|
||||
wantHosts []string
|
||||
}{{
|
||||
name: "simple",
|
||||
@@ -572,7 +581,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
}, {
|
||||
name: "invalid_line",
|
||||
line: ipStr,
|
||||
wantIP: nil,
|
||||
wantIP: netip.Addr{},
|
||||
wantHosts: nil,
|
||||
}, {
|
||||
name: "invalid_line_hostname",
|
||||
@@ -587,7 +596,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
}, {
|
||||
name: "whole_comment",
|
||||
line: `# ` + ipStr + ` hostname`,
|
||||
wantIP: nil,
|
||||
wantIP: netip.Addr{},
|
||||
wantHosts: nil,
|
||||
}, {
|
||||
name: "partial_comment",
|
||||
@@ -597,7 +606,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
}, {
|
||||
name: "empty",
|
||||
line: ``,
|
||||
wantIP: nil,
|
||||
wantIP: netip.Addr{},
|
||||
wantHosts: nil,
|
||||
}}
|
||||
|
||||
@@ -605,7 +614,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
hp := hostsParser{}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, hosts := hp.parseLine(tc.line)
|
||||
assert.True(t, tc.wantIP.Equal(got))
|
||||
assert.Equal(t, tc.wantIP, got)
|
||||
assert.Equal(t, tc.wantHosts, hosts)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
func defaultHostsPaths() (paths []string) {
|
||||
sysDir, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
log.Error("getting system directory: %s", err)
|
||||
log.Error("aghnet: getting system directory: %s", err)
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd netbsd openbsd solaris
|
||||
//go:build darwin || freebsd || openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ type IpsetManager interface {
|
||||
//
|
||||
// The syntax of the ipsetConf is:
|
||||
//
|
||||
// DOMAIN[,DOMAIN].../IPSET_NAME[,IPSET_NAME]...
|
||||
// DOMAIN[,DOMAIN].../IPSET_NAME[,IPSET_NAME]...
|
||||
//
|
||||
// If ipsetConf is empty, msg and err are nil. The error is of type
|
||||
// *aghos.UnsupportedError if the OS is not supported.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -19,27 +18,18 @@ import (
|
||||
|
||||
// How to test on a real Linux machine:
|
||||
//
|
||||
// 1. Run:
|
||||
// 1. Run "sudo ipset create example_set hash:ip family ipv4".
|
||||
//
|
||||
// sudo ipset create example_set hash:ip family ipv4
|
||||
// 2. Run "sudo ipset list example_set". The Members field should be empty.
|
||||
//
|
||||
// 2. Run:
|
||||
// 3. Add the line "example.com/example_set" to your AdGuardHome.yaml.
|
||||
//
|
||||
// sudo ipset list example_set
|
||||
// 4. Start AdGuardHome.
|
||||
//
|
||||
// The Members field should be empty.
|
||||
// 5. Make requests to example.com and its subdomains.
|
||||
//
|
||||
// 3. Add the line "example.com/example_set" to your AdGuardHome.yaml.
|
||||
//
|
||||
// 4. Start AdGuardHome.
|
||||
//
|
||||
// 5. Make requests to example.com and its subdomains.
|
||||
//
|
||||
// 6. Run:
|
||||
//
|
||||
// sudo ipset list example_set
|
||||
//
|
||||
// The Members field should contain the resolved IP addresses.
|
||||
// 6. Run "sudo ipset list example_set". The Members field should contain the
|
||||
// resolved IP addresses.
|
||||
|
||||
// newIpsetMgr returns a new Linux ipset manager.
|
||||
func newIpsetMgr(ipsetConf []string) (set IpsetManager, err error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// Variables and functions to substitute in tests.
|
||||
@@ -47,26 +47,31 @@ func IfaceSetStaticIP(ifaceName string) (err error) {
|
||||
//
|
||||
// TODO(e.burkov): Investigate if the gateway address may be fetched in another
|
||||
// way since not every machine has the software installed.
|
||||
func GatewayIP(ifaceName string) (ip net.IP) {
|
||||
func GatewayIP(ifaceName string) (ip netip.Addr) {
|
||||
code, out, err := aghosRunCommand("ip", "route", "show", "dev", ifaceName)
|
||||
if err != nil {
|
||||
log.Debug("%s", err)
|
||||
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
} else if code != 0 {
|
||||
log.Debug("fetching gateway ip: unexpected exit code: %d", code)
|
||||
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
fields := bytes.Fields(out)
|
||||
// The meaningful "ip route" command output should contain the word
|
||||
// "default" at first field and default gateway IP address at third field.
|
||||
if len(fields) < 3 || string(fields[0]) != "default" {
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
return net.ParseIP(string(fields[2]))
|
||||
ip, err = netip.ParseAddr(string(fields[2]))
|
||||
if err != nil {
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
// CanBindPrivilegedPorts checks if current process can bind to privileged
|
||||
@@ -78,9 +83,9 @@ func CanBindPrivilegedPorts() (can bool, err error) {
|
||||
// NetInterface represents an entry of network interfaces map.
|
||||
type NetInterface struct {
|
||||
// Addresses are the network interface addresses.
|
||||
Addresses []net.IP `json:"ip_addresses,omitempty"`
|
||||
Addresses []netip.Addr `json:"ip_addresses,omitempty"`
|
||||
// Subnets are the IP networks for this network interface.
|
||||
Subnets []*net.IPNet `json:"-"`
|
||||
Subnets []netip.Prefix `json:"-"`
|
||||
Name string `json:"name"`
|
||||
HardwareAddr net.HardwareAddr `json:"hardware_address"`
|
||||
Flags net.Flags `json:"flags"`
|
||||
@@ -101,63 +106,88 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
|
||||
niface = &NetInterface{
|
||||
Name: iface.Name,
|
||||
HardwareAddr: iface.HardwareAddr,
|
||||
Flags: iface.Flags,
|
||||
MTU: iface.MTU,
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
|
||||
}
|
||||
|
||||
// Collect network interface addresses.
|
||||
for _, addr := range addrs {
|
||||
n, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// Should be *net.IPNet, this is weird.
|
||||
return nil, fmt.Errorf("expected %[2]s to be %[1]T, got %[2]T", n, addr)
|
||||
} else if ip4 := n.IP.To4(); ip4 != nil {
|
||||
n.IP = ip4
|
||||
}
|
||||
|
||||
ip, ok := netip.AddrFromSlice(n.IP)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("bad address %s", n.IP)
|
||||
}
|
||||
|
||||
ip = ip.Unmap()
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
// Ignore link-local IPv4.
|
||||
if ip.Is4() {
|
||||
continue
|
||||
}
|
||||
|
||||
ip = ip.WithZone(iface.Name)
|
||||
}
|
||||
|
||||
ones, _ := n.Mask.Size()
|
||||
p := netip.PrefixFrom(ip, ones)
|
||||
|
||||
niface.Addresses = append(niface.Addresses, ip)
|
||||
niface.Subnets = append(niface.Subnets, p)
|
||||
}
|
||||
|
||||
return niface, nil
|
||||
}
|
||||
|
||||
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and
|
||||
// WEB only we do not return link-local addresses here.
|
||||
//
|
||||
// TODO(e.burkov): Can't properly test the function since it's nontrivial to
|
||||
// substitute net.Interface.Addrs and the net.InterfaceAddrs can't be used.
|
||||
func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) {
|
||||
func GetValidNetInterfacesForWeb() (nifaces []*NetInterface, err error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get interfaces: %w", err)
|
||||
return nil, fmt.Errorf("getting interfaces: %w", err)
|
||||
} else if len(ifaces) == 0 {
|
||||
return nil, errors.Error("couldn't find any legible interface")
|
||||
return nil, errors.Error("no legible interfaces")
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
var addrs []net.Addr
|
||||
addrs, err = iface.Addrs()
|
||||
for i := range ifaces {
|
||||
var niface *NetInterface
|
||||
niface, err = NetInterfaceFrom(&ifaces[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
|
||||
}
|
||||
|
||||
netIface := &NetInterface{
|
||||
MTU: iface.MTU,
|
||||
Name: iface.Name,
|
||||
HardwareAddr: iface.HardwareAddr,
|
||||
Flags: iface.Flags,
|
||||
}
|
||||
|
||||
// Collect network interface addresses.
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// Should be net.IPNet, this is weird.
|
||||
return nil, fmt.Errorf("got %s that is not net.IPNet, it is %T", addr, addr)
|
||||
}
|
||||
|
||||
// Ignore link-local.
|
||||
if ipNet.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
netIface.Addresses = append(netIface.Addresses, ipNet.IP)
|
||||
netIface.Subnets = append(netIface.Subnets, ipNet)
|
||||
}
|
||||
|
||||
// Discard interfaces with no addresses.
|
||||
if len(netIface.Addresses) != 0 {
|
||||
netIfaces = append(netIfaces, netIface)
|
||||
return nil, err
|
||||
} else if len(niface.Addresses) != 0 {
|
||||
// Discard interfaces with no addresses.
|
||||
nifaces = append(nifaces, niface)
|
||||
}
|
||||
}
|
||||
|
||||
return netIfaces, nil
|
||||
return nifaces, nil
|
||||
}
|
||||
|
||||
// GetInterfaceByIP returns the name of interface containing provided ip.
|
||||
// InterfaceByIP returns the name of the interface bound to ip.
|
||||
//
|
||||
// TODO(e.burkov): See TODO on GetValidInterfacesForWeb.
|
||||
func GetInterfaceByIP(ip net.IP) string {
|
||||
// TODO(a.garipov, e.burkov): This function is technically incorrect, since one
|
||||
// IP address can be shared by multiple interfaces in some configurations.
|
||||
//
|
||||
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
||||
func InterfaceByIP(ip netip.Addr) (ifaceName string) {
|
||||
ifaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -165,7 +195,7 @@ func GetInterfaceByIP(ip net.IP) string {
|
||||
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
if ip.Equal(addr) {
|
||||
if ip == addr {
|
||||
return iface.Name
|
||||
}
|
||||
}
|
||||
@@ -174,15 +204,16 @@ func GetInterfaceByIP(ip net.IP) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetSubnet returns pointer to net.IPNet for the specified interface or nil if
|
||||
// GetSubnet returns the subnet corresponding to the interface of zero prefix if
|
||||
// the search fails.
|
||||
//
|
||||
// TODO(e.burkov): See TODO on GetValidInterfacesForWeb.
|
||||
func GetSubnet(ifaceName string) *net.IPNet {
|
||||
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
||||
func GetSubnet(ifaceName string) (p netip.Prefix) {
|
||||
netIfaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
log.Error("Could not get network interfaces info: %v", err)
|
||||
return nil
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
for _, netIface := range netIfaces {
|
||||
@@ -191,14 +222,14 @@ func GetSubnet(ifaceName string) *net.IPNet {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return p
|
||||
}
|
||||
|
||||
// CheckPort checks if the port is available for binding. network is expected
|
||||
// to be one of "udp" and "tcp".
|
||||
func CheckPort(network string, ip net.IP, port int) (err error) {
|
||||
func CheckPort(network string, ipp netip.AddrPort) (err error) {
|
||||
var c io.Closer
|
||||
addr := netutil.IPPort{IP: ip, Port: port}.String()
|
||||
addr := ipp.String()
|
||||
switch network {
|
||||
case "tcp":
|
||||
c, err = net.Listen(network, addr)
|
||||
@@ -248,18 +279,23 @@ func CollectAllIfacesAddrs() (addrs []string, err error) {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// BroadcastFromIPNet calculates the broadcast IP address for n.
|
||||
func BroadcastFromIPNet(n *net.IPNet) (dc net.IP) {
|
||||
dc = netutil.CloneIP(n.IP)
|
||||
|
||||
mask := n.Mask
|
||||
if mask == nil {
|
||||
mask = dc.DefaultMask()
|
||||
// BroadcastFromPref calculates the broadcast IP address for p.
|
||||
func BroadcastFromPref(p netip.Prefix) (bc netip.Addr) {
|
||||
bc = p.Addr().Unmap()
|
||||
if !bc.IsValid() {
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
for i, b := range mask {
|
||||
dc[i] |= ^b
|
||||
maskLen, addrLen := p.Bits(), bc.BitLen()
|
||||
if maskLen == addrLen {
|
||||
return bc
|
||||
}
|
||||
|
||||
return dc
|
||||
ipBytes := bc.AsSlice()
|
||||
for i := maskLen; i < addrLen; i++ {
|
||||
ipBytes[i/8] |= 1 << (7 - (i % 8))
|
||||
}
|
||||
bc, _ = netip.AddrFromSlice(ipBytes)
|
||||
|
||||
return bc
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build darwin || freebsd || openbsd
|
||||
// +build darwin freebsd openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -7,12 +6,13 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/google/renameio/maybe"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -22,17 +22,27 @@ import (
|
||||
const dhcpcdConf = "etc/dhcpcd.conf"
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
cnbs, err := unix.PrctlRetInt(
|
||||
res, err := unix.PrctlRetInt(
|
||||
unix.PR_CAP_AMBIENT,
|
||||
unix.PR_CAP_AMBIENT_RAISE,
|
||||
unix.CAP_NET_BIND_SERVICE,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
// Older versions of Linux kernel do not support this. Print a
|
||||
// warning and check admin rights.
|
||||
log.Info("warning: cannot check capability cap_net_bind_service: %s", err)
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Don't check the error because it's always nil on Linux.
|
||||
adm, _ := aghos.HaveAdminRights()
|
||||
|
||||
return cnbs == 1 || adm, err
|
||||
return res == 1 || adm, nil
|
||||
}
|
||||
|
||||
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
|
||||
@@ -141,7 +151,7 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
|
||||
// interface through dhcpcd.conf.
|
||||
func ifaceSetStaticIP(ifaceName string) (err error) {
|
||||
ipNet := GetSubnet(ifaceName)
|
||||
if ipNet.IP == nil {
|
||||
if !ipNet.Addr().IsValid() {
|
||||
return errors.Error("can't get IP address")
|
||||
}
|
||||
|
||||
@@ -164,7 +174,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
|
||||
|
||||
// dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that
|
||||
// configure the interface to have a static IP.
|
||||
func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) {
|
||||
func dhcpcdConfIface(ifaceName string, subnet netip.Prefix, gateway netip.Addr) (conf string) {
|
||||
b := &strings.Builder{}
|
||||
stringutil.WriteToBuilder(
|
||||
b,
|
||||
@@ -173,15 +183,15 @@ func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf stri
|
||||
" added by AdGuard Home.\ninterface ",
|
||||
ifaceName,
|
||||
"\nstatic ip_address=",
|
||||
ipNet.String(),
|
||||
subnet.String(),
|
||||
"\n",
|
||||
)
|
||||
|
||||
if gwIP != nil {
|
||||
stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
|
||||
if gateway != (netip.Addr{}) {
|
||||
stringutil.WriteToBuilder(b, "static routers=", gateway.String(), "\n")
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n")
|
||||
stringutil.WriteToBuilder(b, "static domain_name_servers=", subnet.Addr().String(), "\n\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testdata is the filesystem containing data for testing the package.
|
||||
@@ -93,34 +93,29 @@ func TestGatewayIP(t *testing.T) {
|
||||
const cmd = "ip route show dev " + ifaceName
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
shell mapShell
|
||||
want net.IP
|
||||
want netip.Addr
|
||||
name string
|
||||
}{{
|
||||
name: "success_v4",
|
||||
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
|
||||
want: net.IP{1, 2, 3, 4}.To16(),
|
||||
want: netip.MustParseAddr("1.2.3.4"),
|
||||
name: "success_v4",
|
||||
}, {
|
||||
name: "success_v6",
|
||||
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
|
||||
want: net.IP{
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0xFF, 0xFF,
|
||||
},
|
||||
want: netip.MustParseAddr("::ffff"),
|
||||
name: "success_v6",
|
||||
}, {
|
||||
name: "bad_output",
|
||||
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
|
||||
want: nil,
|
||||
want: netip.Addr{},
|
||||
name: "bad_output",
|
||||
}, {
|
||||
name: "err_runcmd",
|
||||
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
|
||||
want: nil,
|
||||
want: netip.Addr{},
|
||||
name: "err_runcmd",
|
||||
}, {
|
||||
name: "bad_code",
|
||||
shell: theOnlyCmd(cmd, 1, "", nil),
|
||||
want: nil,
|
||||
want: netip.Addr{},
|
||||
name: "bad_code",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -132,7 +127,7 @@ func TestGatewayIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInterfaceByIP(t *testing.T) {
|
||||
func TestInterfaceByIP(t *testing.T) {
|
||||
ifaces, err := GetValidNetInterfacesForWeb()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, ifaces)
|
||||
@@ -142,7 +137,7 @@ func TestGetInterfaceByIP(t *testing.T) {
|
||||
require.NotEmpty(t, iface.Addresses)
|
||||
|
||||
for _, ip := range iface.Addresses {
|
||||
ifaceName := GetInterfaceByIP(ip)
|
||||
ifaceName := InterfaceByIP(ip)
|
||||
require.Equal(t, iface.Name, ifaceName)
|
||||
}
|
||||
})
|
||||
@@ -150,65 +145,61 @@ func TestGetInterfaceByIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBroadcastFromIPNet(t *testing.T) {
|
||||
known6 := net.IP{
|
||||
1, 2, 3, 4,
|
||||
5, 6, 7, 8,
|
||||
9, 10, 11, 12,
|
||||
13, 14, 15, 16,
|
||||
}
|
||||
known4 := netip.MustParseAddr("192.168.0.1")
|
||||
fullBroadcast4 := netip.MustParseAddr("255.255.255.255")
|
||||
|
||||
known6 := netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:f10")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
subnet *net.IPNet
|
||||
want net.IP
|
||||
pref netip.Prefix
|
||||
want netip.Addr
|
||||
name string
|
||||
}{{
|
||||
pref: netip.PrefixFrom(known4, 0),
|
||||
want: fullBroadcast4,
|
||||
name: "full",
|
||||
subnet: &net.IPNet{
|
||||
IP: net.IP{192, 168, 0, 1},
|
||||
Mask: net.IPMask{255, 255, 15, 0},
|
||||
},
|
||||
want: net.IP{192, 168, 240, 255},
|
||||
}, {
|
||||
name: "ipv6_no_mask",
|
||||
subnet: &net.IPNet{
|
||||
IP: known6,
|
||||
},
|
||||
pref: netip.PrefixFrom(known4, 20),
|
||||
want: netip.MustParseAddr("192.168.15.255"),
|
||||
name: "full",
|
||||
}, {
|
||||
pref: netip.PrefixFrom(known6, netutil.IPv6BitLen),
|
||||
want: known6,
|
||||
name: "ipv6_no_mask",
|
||||
}, {
|
||||
pref: netip.PrefixFrom(known4, netutil.IPv4BitLen),
|
||||
want: known4,
|
||||
name: "ipv4_no_mask",
|
||||
subnet: &net.IPNet{
|
||||
IP: net.IP{192, 168, 1, 2},
|
||||
},
|
||||
want: net.IP{192, 168, 1, 255},
|
||||
}, {
|
||||
pref: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
||||
want: fullBroadcast4,
|
||||
name: "unspecified",
|
||||
subnet: &net.IPNet{
|
||||
IP: net.IP{0, 0, 0, 0},
|
||||
Mask: net.IPMask{0, 0, 0, 0},
|
||||
},
|
||||
want: net.IPv4bcast,
|
||||
}, {
|
||||
pref: netip.Prefix{},
|
||||
want: netip.Addr{},
|
||||
name: "invalid",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
bc := BroadcastFromIPNet(tc.subnet)
|
||||
assert.True(t, bc.Equal(tc.want), bc)
|
||||
assert.Equal(t, tc.want, BroadcastFromPref(tc.pref))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckPort(t *testing.T) {
|
||||
laddr := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
|
||||
|
||||
t.Run("tcp_bound", func(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:")
|
||||
l, err := net.Listen("tcp", laddr.String())
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, l.Close)
|
||||
|
||||
ipp := netutil.IPPortFromAddr(l.Addr())
|
||||
require.NotNil(t, ipp)
|
||||
require.NotNil(t, ipp.IP)
|
||||
require.NotZero(t, ipp.Port)
|
||||
ipp := testutil.RequireTypeAssert[*net.TCPAddr](t, l.Addr()).AddrPort()
|
||||
require.Equal(t, laddr.Addr(), ipp.Addr())
|
||||
require.NotZero(t, ipp.Port())
|
||||
|
||||
err = CheckPort("tcp", ipp.IP, ipp.Port)
|
||||
err = CheckPort("tcp", ipp)
|
||||
target := &net.OpError{}
|
||||
require.ErrorAs(t, err, &target)
|
||||
|
||||
@@ -216,16 +207,15 @@ func TestCheckPort(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("udp_bound", func(t *testing.T) {
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:")
|
||||
conn, err := net.ListenPacket("udp", laddr.String())
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, conn.Close)
|
||||
|
||||
ipp := netutil.IPPortFromAddr(conn.LocalAddr())
|
||||
require.NotNil(t, ipp)
|
||||
require.NotNil(t, ipp.IP)
|
||||
require.NotZero(t, ipp.Port)
|
||||
ipp := testutil.RequireTypeAssert[*net.UDPAddr](t, conn.LocalAddr()).AddrPort()
|
||||
require.Equal(t, laddr.Addr(), ipp.Addr())
|
||||
require.NotZero(t, ipp.Port())
|
||||
|
||||
err = CheckPort("udp", ipp.IP, ipp.Port)
|
||||
err = CheckPort("udp", ipp)
|
||||
target := &net.OpError{}
|
||||
require.ErrorAs(t, err, &target)
|
||||
|
||||
@@ -233,12 +223,12 @@ func TestCheckPort(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("bad_network", func(t *testing.T) {
|
||||
err := CheckPort("bad_network", nil, 0)
|
||||
err := CheckPort("bad_network", netip.AddrPortFrom(netip.Addr{}, 0))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("can_bind", func(t *testing.T) {
|
||||
err := CheckPort("udp", net.IP{0, 0, 0, 0}, 0)
|
||||
err := CheckPort("udp", netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -322,18 +312,18 @@ func TestNetInterface_MarshalJSON(t *testing.T) {
|
||||
`"mtu":1500` +
|
||||
`}` + "\n"
|
||||
|
||||
ip4, ip6 := net.IP{1, 2, 3, 4}, net.IP{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||
mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen)
|
||||
ip4, ok := netip.AddrFromSlice([]byte{1, 2, 3, 4})
|
||||
require.True(t, ok)
|
||||
|
||||
ip6, ok := netip.AddrFromSlice([]byte{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
||||
require.True(t, ok)
|
||||
|
||||
net4 := netip.PrefixFrom(ip4, 24)
|
||||
net6 := netip.PrefixFrom(ip6, 8)
|
||||
|
||||
iface := &NetInterface{
|
||||
Addresses: []net.IP{ip4, ip6},
|
||||
Subnets: []*net.IPNet{{
|
||||
IP: ip4.Mask(mask4),
|
||||
Mask: mask4,
|
||||
}, {
|
||||
IP: ip6.Mask(mask6),
|
||||
Mask: mask6,
|
||||
}},
|
||||
Addresses: []netip.Addr{ip4, ip6},
|
||||
Subnets: []netip.Prefix{net4, net6},
|
||||
Name: "iface0",
|
||||
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
||||
Flags: net.FlagUp | net.FlagMulticast,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build openbsd || freebsd || linux || darwin
|
||||
// +build openbsd freebsd linux darwin
|
||||
//go:build darwin || freebsd || linux || openbsd
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ type SystemResolvers interface {
|
||||
}
|
||||
|
||||
// NewSystemResolvers returns a SystemResolvers with the cache refresh rate
|
||||
// defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0. If
|
||||
// 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,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -7,6 +6,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -18,9 +18,8 @@ func createTestSystemResolversImpl(
|
||||
t.Helper()
|
||||
|
||||
sr := createTestSystemResolvers(t, hostGenFunc)
|
||||
require.IsType(t, (*systemResolvers)(nil), sr)
|
||||
|
||||
return sr.(*systemResolvers)
|
||||
return testutil.RequireTypeAssert[*systemResolvers](t, sr)
|
||||
}
|
||||
|
||||
func TestSystemResolvers_Refresh(t *testing.T) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
@@ -62,9 +61,8 @@ func writeExit(w io.WriteCloser) {
|
||||
// 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
|
||||
//
|
||||
// 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())
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
|
||||
Reference in New Issue
Block a user