Pull request: 2846 cover aghnet vol.4
Merge in DNS/adguard-home from 2846-cover-aghnet-vol.4 to master Updates #2846. Squashed commit of the following: commit 576ef857628a403ce1478c10a4aad23985c09613 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Mar 31 19:38:57 2022 +0300 aghnet: imp code commit 5b4b17ff52867aaab2c9d30a0fc7fc2fe31ff4d5 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Mar 31 14:58:34 2022 +0300 aghnet: imp coverage
This commit is contained in:
parent
a79b61aac3
commit
c70f941bf8
|
@ -181,6 +181,16 @@ func TestCmdARPDB_arpa(t *testing.T) {
|
|||
err := a.Refresh()
|
||||
testutil.AssertErrorMsg(t, "cmd arpdb: running command: unexpected exit code 1", err)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
sh := theOnlyCmd("cmd", 0, "", nil)
|
||||
substShell(t, sh.RunCmd)
|
||||
|
||||
err := a.Refresh()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, a.Neighbors())
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmptyARPDB(t *testing.T) {
|
||||
|
|
|
@ -11,7 +11,7 @@ const (
|
|||
ipv6HostnameMaxLen = len("ff80-f076-0000-0000-0000-0000-0000-0010")
|
||||
)
|
||||
|
||||
// generateIPv4Hostname generates the hostname for specific IP version.
|
||||
// generateIPv4Hostname generates the hostname by IP address version 4.
|
||||
func generateIPv4Hostname(ipv4 net.IP) (hostname string) {
|
||||
hnData := make([]byte, 0, ipv4HostnameMaxLen)
|
||||
for i, part := range ipv4 {
|
||||
|
@ -24,7 +24,7 @@ func generateIPv4Hostname(ipv4 net.IP) (hostname string) {
|
|||
return string(hnData)
|
||||
}
|
||||
|
||||
// generateIPv6Hostname generates the hostname for specific IP version.
|
||||
// generateIPv6Hostname generates the hostname by IP address version 6.
|
||||
func generateIPv6Hostname(ipv6 net.IP) (hostname string) {
|
||||
hnData := make([]byte, 0, ipv6HostnameMaxLen)
|
||||
for i, partsNum := 0, net.IPv6len/2; i < partsNum; i++ {
|
||||
|
@ -51,12 +51,11 @@ func generateIPv6Hostname(ipv6 net.IP) (hostname string) {
|
|||
//
|
||||
// ff80-f076-0000-0000-0000-0000-0000-0010
|
||||
//
|
||||
// ip must be either an IPv4 or an IPv6.
|
||||
func GenerateHostname(ip net.IP) (hostname string) {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
return generateIPv4Hostname(ipv4)
|
||||
} else if ipv6 := ip.To16(); ipv6 != nil {
|
||||
return generateIPv6Hostname(ipv6)
|
||||
}
|
||||
|
||||
return ""
|
||||
return generateIPv6Hostname(ip)
|
||||
}
|
||||
|
|
|
@ -8,41 +8,57 @@ import (
|
|||
)
|
||||
|
||||
func TestGenerateHostName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
ip net.IP
|
||||
}{{
|
||||
name: "good_ipv4",
|
||||
want: "127-0-0-1",
|
||||
ip: net.IP{127, 0, 0, 1},
|
||||
}, {
|
||||
name: "bad_ipv4",
|
||||
want: "",
|
||||
ip: net.IP{127, 0, 0, 1, 0},
|
||||
}, {
|
||||
name: "good_ipv6",
|
||||
want: "fe00-0000-0000-0000-0000-0000-0000-0001",
|
||||
ip: net.ParseIP("fe00::1"),
|
||||
}, {
|
||||
name: "bad_ipv6",
|
||||
want: "",
|
||||
ip: net.IP{
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
},
|
||||
}, {
|
||||
name: "nil",
|
||||
want: "",
|
||||
ip: nil,
|
||||
}}
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
ip net.IP
|
||||
}{{
|
||||
name: "good_ipv4",
|
||||
want: "127-0-0-1",
|
||||
ip: net.IP{127, 0, 0, 1},
|
||||
}, {
|
||||
name: "good_ipv6",
|
||||
want: "fe00-0000-0000-0000-0000-0000-0000-0001",
|
||||
ip: net.ParseIP("fe00::1"),
|
||||
}, {
|
||||
name: "4to6",
|
||||
want: "1-2-3-4",
|
||||
ip: net.ParseIP("::ffff:1.2.3.4"),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hostname := GenerateHostname(tc.ip)
|
||||
assert.Equal(t, tc.want, hostname)
|
||||
})
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hostname := GenerateHostname(tc.ip)
|
||||
assert.Equal(t, tc.want, hostname)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
}{{
|
||||
name: "bad_ipv4",
|
||||
ip: net.IP{127, 0, 0, 1, 0},
|
||||
}, {
|
||||
name: "bad_ipv6",
|
||||
ip: net.IP{
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
},
|
||||
}, {
|
||||
name: "nil",
|
||||
ip: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Panics(t, func() { GenerateHostname(tc.ip) })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,13 +15,17 @@ import (
|
|||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// aghosRunCommand is the function to run shell commands. It's an unexported
|
||||
// variable instead of a direct call to make it substitutable in tests.
|
||||
var aghosRunCommand = aghos.RunCommand
|
||||
// Variables and functions to substitute in tests.
|
||||
var (
|
||||
// aghosRunCommand is the function to run shell commands.
|
||||
aghosRunCommand = aghos.RunCommand
|
||||
|
||||
// rootDirFS is the filesystem pointing to the root directory. It's an
|
||||
// unexported variable instead to make it substitutable in tests.
|
||||
var rootDirFS = aghos.RootDirFS()
|
||||
// netInterfaces is the function to get the available network interfaces.
|
||||
netInterfaceAddrs = net.InterfaceAddrs
|
||||
|
||||
// rootDirFS is the filesystem pointing to the root directory.
|
||||
rootDirFS = aghos.RootDirFS()
|
||||
)
|
||||
|
||||
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
|
||||
// the IP being static is available.
|
||||
|
@ -65,23 +69,6 @@ func GatewayIP(ifaceName string) (ip net.IP) {
|
|||
return net.ParseIP(string(fields[2]))
|
||||
}
|
||||
|
||||
// CanBindPort checks if we can bind to the given port.
|
||||
func CanBindPort(port int) (can bool, err error) {
|
||||
var addr *net.TCPAddr
|
||||
addr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var listener *net.TCPListener
|
||||
listener, err = net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_ = listener.Close()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CanBindPrivilegedPorts checks if current process can bind to privileged
|
||||
// ports.
|
||||
func CanBindPrivilegedPorts() (can bool, err error) {
|
||||
|
@ -100,8 +87,8 @@ type NetInterface struct {
|
|||
MTU int `json:"mtu"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for NetInterface.
|
||||
func (iface NetInterface) MarshalJSON() ([]byte, error) {
|
||||
// MarshalText implements the json.Marshaler interface for NetInterface.
|
||||
func (iface NetInterface) MarshalText() ([]byte, error) {
|
||||
type netInterface NetInterface
|
||||
return json.Marshal(&struct {
|
||||
HardwareAddr string `json:"hardware_address"`
|
||||
|
@ -114,9 +101,12 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
|
|||
})
|
||||
}
|
||||
|
||||
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only
|
||||
// we do not return link-local addresses here
|
||||
func GetValidNetInterfacesForWeb() (netInterfaces []*NetInterface, err error) {
|
||||
// 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) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get interfaces: %w", err)
|
||||
|
@ -157,14 +147,16 @@ func GetValidNetInterfacesForWeb() (netInterfaces []*NetInterface, err error) {
|
|||
|
||||
// Discard interfaces with no addresses.
|
||||
if len(netIface.Addresses) != 0 {
|
||||
netInterfaces = append(netInterfaces, netIface)
|
||||
netIfaces = append(netIfaces, netIface)
|
||||
}
|
||||
}
|
||||
|
||||
return netInterfaces, nil
|
||||
return netIfaces, nil
|
||||
}
|
||||
|
||||
// GetInterfaceByIP returns the name of interface containing provided ip.
|
||||
//
|
||||
// TODO(e.burkov): See TODO on GetValidInterfacesForWeb.
|
||||
func GetInterfaceByIP(ip net.IP) string {
|
||||
ifaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
|
@ -184,6 +176,8 @@ func GetInterfaceByIP(ip net.IP) string {
|
|||
|
||||
// GetSubnet returns pointer to net.IPNet for the specified interface or nil if
|
||||
// the search fails.
|
||||
//
|
||||
// TODO(e.burkov): See TODO on GetValidInterfacesForWeb.
|
||||
func GetSubnet(ifaceName string) *net.IPNet {
|
||||
netIfaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
|
@ -234,29 +228,21 @@ func IsAddrInUse(err error) (ok bool) {
|
|||
// CollectAllIfacesAddrs returns the slice of all network interfaces IP
|
||||
// addresses without port number.
|
||||
func CollectAllIfacesAddrs() (addrs []string, err error) {
|
||||
var ifaces []net.Interface
|
||||
ifaces, err = net.Interfaces()
|
||||
var ifaceAddrs []net.Addr
|
||||
ifaceAddrs, err = netInterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting network interfaces: %w", err)
|
||||
return nil, fmt.Errorf("getting interfaces addresses: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
var ifaceAddrs []net.Addr
|
||||
ifaceAddrs, err = iface.Addrs()
|
||||
for _, addr := range ifaceAddrs {
|
||||
cidr := addr.String()
|
||||
var ip net.IP
|
||||
ip, _, err = net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
|
||||
return nil, fmt.Errorf("parsing cidr: %w", err)
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
cidr := addr.String()
|
||||
var ip net.IP
|
||||
ip, _, err = net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing cidr: %w", err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, ip.String())
|
||||
}
|
||||
addrs = append(addrs, ip.String())
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
|
|
|
@ -24,10 +24,6 @@ type hardwarePortInfo struct {
|
|||
static bool
|
||||
}
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
return aghos.HaveAdminRights()
|
||||
}
|
||||
|
||||
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
||||
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
|
||||
if err != nil {
|
||||
|
|
|
@ -13,10 +13,6 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
)
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
return aghos.HaveAdminRights()
|
||||
}
|
||||
|
||||
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
||||
const rcConfFilename = "etc/rc.conf"
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRcConfStaticConfig(t *testing.T) {
|
||||
func TestIfaceHasStaticIP(t *testing.T) {
|
||||
const (
|
||||
ifaceName = `em0`
|
||||
rcConf = "etc/rc.conf"
|
||||
|
|
|
@ -13,10 +13,28 @@ import (
|
|||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/google/renameio/maybe"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// dhcpсdConf is the name of /etc/dhcpcd.conf file in the root filesystem.
|
||||
const dhcpcdConf = "etc/dhcpcd.conf"
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
cnbs, err := unix.PrctlRetInt(
|
||||
unix.PR_CAP_AMBIENT,
|
||||
unix.PR_CAP_AMBIENT_IS_SET,
|
||||
unix.CAP_NET_BIND_SERVICE,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
// Don't check the error because it's always nil on Linux.
|
||||
adm, _ := aghos.HaveAdminRights()
|
||||
|
||||
return cnbs == 1 || adm, err
|
||||
}
|
||||
|
||||
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
|
||||
// have a static IP.
|
||||
func (n interfaceName) dhcpcdStaticConfig(r io.Reader) (subsources []string, cont bool, err error) {
|
||||
|
@ -89,7 +107,7 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
|||
filename string
|
||||
}{{
|
||||
FileWalker: iface.dhcpcdStaticConfig,
|
||||
filename: "etc/dhcpcd.conf",
|
||||
filename: dhcpcdConf,
|
||||
}, {
|
||||
FileWalker: iface.ifacesStaticConfig,
|
||||
filename: "etc/network/interfaces",
|
||||
|
@ -105,14 +123,6 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
|||
return false, ErrNoStaticIPInfo
|
||||
}
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
cnbs, err := unix.PrctlRetInt(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0)
|
||||
// Don't check the error because it's always nil on Linux.
|
||||
adm, _ := aghos.HaveAdminRights()
|
||||
|
||||
return cnbs == 1 || adm, err
|
||||
}
|
||||
|
||||
// findIfaceLine scans s until it finds the line that declares an interface with
|
||||
// the given name. If findIfaceLine can't find the line, it returns false.
|
||||
func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
|
||||
|
@ -128,25 +138,23 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
|
|||
}
|
||||
|
||||
// ifaceSetStaticIP configures the system to retain its current IP on the
|
||||
// interface through dhcpdc.conf.
|
||||
// interface through dhcpcd.conf.
|
||||
func ifaceSetStaticIP(ifaceName string) (err error) {
|
||||
ipNet := GetSubnet(ifaceName)
|
||||
if ipNet.IP == nil {
|
||||
return errors.Error("can't get IP address")
|
||||
}
|
||||
|
||||
gatewayIP := GatewayIP(ifaceName)
|
||||
add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP, ipNet.IP)
|
||||
|
||||
const filename = "/etc/dhcpcd.conf"
|
||||
|
||||
body, err := os.ReadFile(filename)
|
||||
body, err := os.ReadFile(dhcpcdConf)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
gatewayIP := GatewayIP(ifaceName)
|
||||
add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP)
|
||||
|
||||
body = append(body, []byte(add)...)
|
||||
err = maybe.WriteFile(filename, body, 0o644)
|
||||
err = maybe.WriteFile(dhcpcdConf, body, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing conf: %w", err)
|
||||
}
|
||||
|
@ -156,22 +164,24 @@ 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, gatewayIP, dnsIP net.IP) (conf string) {
|
||||
var body []byte
|
||||
|
||||
add := fmt.Sprintf(
|
||||
"\n# %[1]s added by AdGuard Home.\ninterface %[1]s\nstatic ip_address=%s\n",
|
||||
func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) {
|
||||
b := &strings.Builder{}
|
||||
stringutil.WriteToBuilder(
|
||||
b,
|
||||
"\n# ",
|
||||
ifaceName,
|
||||
ipNet)
|
||||
body = append(body, []byte(add)...)
|
||||
" added by AdGuard Home.\ninterface ",
|
||||
ifaceName,
|
||||
"\nstatic ip_address=",
|
||||
ipNet.String(),
|
||||
"\n",
|
||||
)
|
||||
|
||||
if gatewayIP != nil {
|
||||
add = fmt.Sprintf("static routers=%s\n", gatewayIP)
|
||||
body = append(body, []byte(add)...)
|
||||
if gwIP != nil {
|
||||
stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
|
||||
}
|
||||
|
||||
add = fmt.Sprintf("static domain_name_servers=%s\n\n", dnsIP)
|
||||
body = append(body, []byte(add)...)
|
||||
stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n")
|
||||
|
||||
return string(body)
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ package aghnet
|
|||
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
|
@ -126,38 +125,3 @@ func TestHasStaticIP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStaticIP_dhcpcdConfIface(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
dhcpcdConf string
|
||||
routers net.IP
|
||||
}{{
|
||||
name: "with_gateway",
|
||||
dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl +
|
||||
`interface wlan0` + nl +
|
||||
`static ip_address=192.168.0.2/24` + nl +
|
||||
`static routers=192.168.0.1` + nl +
|
||||
`static domain_name_servers=192.168.0.2` + nl + nl,
|
||||
routers: net.IP{192, 168, 0, 1},
|
||||
}, {
|
||||
name: "without_gateway",
|
||||
dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl +
|
||||
`interface wlan0` + nl +
|
||||
`static ip_address=192.168.0.2/24` + nl +
|
||||
`static domain_name_servers=192.168.0.2` + nl + nl,
|
||||
routers: nil,
|
||||
}}
|
||||
|
||||
ipNet := &net.IPNet{
|
||||
IP: net.IP{192, 168, 0, 2},
|
||||
Mask: net.IPMask{255, 255, 255, 0},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := dhcpcdConfIface("wlan0", ipNet, tc.routers, net.IP{192, 168, 0, 2})
|
||||
assert.Equal(t, tc.dhcpcdConf, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package aghnet
|
||||
|
||||
import "github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
return aghos.HaveAdminRights()
|
||||
}
|
|
@ -13,10 +13,6 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
)
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
return aghos.HaveAdminRights()
|
||||
}
|
||||
|
||||
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
||||
filename := fmt.Sprintf("etc/hostname.%s", ifaceName)
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ type mapShell map[string]struct {
|
|||
code int
|
||||
}
|
||||
|
||||
// theOnlyCmd returns s that only handles a single command and arguments
|
||||
// theOnlyCmd returns mapShell that only handles a single command and arguments
|
||||
// combination from cmd.
|
||||
func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) {
|
||||
return mapShell{cmd: {code: code, out: out, err: err}}
|
||||
|
@ -73,18 +73,34 @@ func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err
|
|||
return ret.code, []byte(ret.out), ret.err
|
||||
}
|
||||
|
||||
// ifaceAddrsFunc is the signature of net.InterfaceAddrs function.
|
||||
type ifaceAddrsFunc func() (ifaces []net.Addr, err error)
|
||||
|
||||
// substNetInterfaceAddrs replaces the the net.InterfaceAddrs function used
|
||||
// throughout the package with f for tests ran under t.
|
||||
func substNetInterfaceAddrs(t *testing.T, f ifaceAddrsFunc) {
|
||||
t.Helper()
|
||||
|
||||
prev := netInterfaceAddrs
|
||||
t.Cleanup(func() { netInterfaceAddrs = prev })
|
||||
netInterfaceAddrs = f
|
||||
}
|
||||
|
||||
func TestGatewayIP(t *testing.T) {
|
||||
const ifaceName = "ifaceName"
|
||||
const cmd = "ip route show dev " + ifaceName
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
shell mapShell
|
||||
want net.IP
|
||||
}{{
|
||||
name: "success_v4",
|
||||
shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via 1.2.3.4 onlink`, nil),
|
||||
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
|
||||
want: net.IP{1, 2, 3, 4}.To16(),
|
||||
}, {
|
||||
name: "success_v6",
|
||||
shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via ::ffff onlink`, nil),
|
||||
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
|
||||
want: net.IP{
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
|
@ -93,15 +109,15 @@ func TestGatewayIP(t *testing.T) {
|
|||
},
|
||||
}, {
|
||||
name: "bad_output",
|
||||
shell: theOnlyCmd("ip route show dev ifaceName", 0, `non-default via 1.2.3.4 onlink`, nil),
|
||||
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
|
||||
want: nil,
|
||||
}, {
|
||||
name: "err_runcmd",
|
||||
shell: theOnlyCmd("ip route show dev ifaceName", 0, "", errors.Error("can't run command")),
|
||||
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
|
||||
want: nil,
|
||||
}, {
|
||||
name: "bad_code",
|
||||
shell: theOnlyCmd("ip route show dev ifaceName", 1, "", nil),
|
||||
shell: theOnlyCmd(cmd, 1, "", nil),
|
||||
want: nil,
|
||||
}}
|
||||
|
||||
|
@ -109,7 +125,7 @@ func TestGatewayIP(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
substShell(t, tc.shell.RunCmd)
|
||||
|
||||
assert.Equal(t, tc.want, GatewayIP("ifaceName"))
|
||||
assert.Equal(t, tc.want, GatewayIP(ifaceName))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -226,12 +242,56 @@ func TestCheckPort(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCollectAllIfacesAddrs(t *testing.T) {
|
||||
t.Skip("TODO(e.burkov): Substitute the net.Interfaces.")
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
addrs []net.Addr
|
||||
wantAddrs []string
|
||||
}{{
|
||||
name: "success",
|
||||
wantErrMsg: ``,
|
||||
addrs: []net.Addr{&net.IPNet{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
|
||||
}, &net.IPNet{
|
||||
IP: net.IP{4, 3, 2, 1},
|
||||
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
|
||||
}},
|
||||
wantAddrs: []string{"1.2.3.4", "4.3.2.1"},
|
||||
}, {
|
||||
name: "not_cidr",
|
||||
wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`,
|
||||
addrs: []net.Addr{&net.IPAddr{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
}},
|
||||
wantAddrs: nil,
|
||||
}, {
|
||||
name: "empty",
|
||||
wantErrMsg: ``,
|
||||
addrs: []net.Addr{},
|
||||
wantAddrs: nil,
|
||||
}}
|
||||
|
||||
addrs, err := CollectAllIfacesAddrs()
|
||||
require.NoError(t, err)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return tc.addrs, nil })
|
||||
|
||||
assert.NotEmpty(t, addrs)
|
||||
addrs, err := CollectAllIfacesAddrs()
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.wantAddrs, addrs)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("internal_error", func(t *testing.T) {
|
||||
const errAddrs errors.Error = "can't get addresses"
|
||||
const wantErrMsg string = `getting interfaces addresses: ` + string(errAddrs)
|
||||
|
||||
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return nil, errAddrs })
|
||||
|
||||
_, err := CollectAllIfacesAddrs()
|
||||
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsAddrInUse(t *testing.T) {
|
||||
|
@ -250,3 +310,33 @@ func TestIsAddrInUse(t *testing.T) {
|
|||
assert.False(t, IsAddrInUse(anotherErr))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetInterface_MarshalText(t *testing.T) {
|
||||
const want = `{` +
|
||||
`"hardware_address":"aa:bb:cc:dd:ee:ff",` +
|
||||
`"flags":"up|multicast",` +
|
||||
`"ip_addresses":["1.2.3.4","aaaa::1"],` +
|
||||
`"name":"iface0",` +
|
||||
`"mtu":1500` +
|
||||
`}`
|
||||
|
||||
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)
|
||||
|
||||
iface := &NetInterface{
|
||||
Addresses: []net.IP{ip4, ip6},
|
||||
Subnets: []*net.IPNet{{
|
||||
IP: ip4.Mask(mask4),
|
||||
Mask: mask4,
|
||||
}, {
|
||||
IP: ip6.Mask(mask6),
|
||||
Mask: mask6,
|
||||
}},
|
||||
Name: "iface0",
|
||||
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
||||
Flags: net.FlagUp | net.FlagMulticast,
|
||||
MTU: 1500,
|
||||
}
|
||||
|
||||
testutil.AssertMarshalText(t, want, iface)
|
||||
}
|
||||
|
|
|
@ -13,10 +13,6 @@ import (
|
|||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func canBindPrivilegedPorts() (can bool, err error) {
|
||||
return aghos.HaveAdminRights()
|
||||
}
|
||||
|
||||
func ifaceHasStaticIP(string) (ok bool, err error) {
|
||||
return false, aghos.Unsupported("checking static ip")
|
||||
}
|
||||
|
|
|
@ -517,27 +517,15 @@ func StartMods() error {
|
|||
func checkPermissions() {
|
||||
log.Info("Checking if AdGuard Home has necessary permissions")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows we need to have admin rights to run properly
|
||||
|
||||
admin, _ := aghos.HaveAdminRights()
|
||||
if admin {
|
||||
return
|
||||
}
|
||||
|
||||
if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil {
|
||||
log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.")
|
||||
}
|
||||
|
||||
// We should check if AdGuard Home is able to bind to port 53
|
||||
ok, err := aghnet.CanBindPort(53)
|
||||
|
||||
if ok {
|
||||
log.Info("AdGuard Home can bind to port 53")
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
msg := `Permission check failed.
|
||||
err := aghnet.CheckPort("tcp", net.IP{127, 0, 0, 1}, defaultPortDNS)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
log.Fatal(`Permission check failed.
|
||||
|
||||
AdGuard Home is not allowed to bind to privileged ports (for instance, port 53).
|
||||
Please note, that this is crucial for a server to be able to use privileged ports.
|
||||
|
@ -545,16 +533,17 @@ Please note, that this is crucial for a server to be able to use privileged port
|
|||
You have two options:
|
||||
1. Run AdGuard Home with root privileges
|
||||
2. On Linux you can grant the CAP_NET_BIND_SERVICE capability:
|
||||
https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`
|
||||
https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`)
|
||||
}
|
||||
|
||||
log.Fatal(msg)
|
||||
log.Info(
|
||||
"AdGuard failed to bind to port 53: %s\n\n"+
|
||||
"Please note, that this is crucial for a DNS server to be able to use that port.",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(`AdGuard failed to bind to port 53 due to %v
|
||||
|
||||
Please note, that this is crucial for a DNS server to be able to use that port.`, err)
|
||||
|
||||
log.Info(msg)
|
||||
log.Info("AdGuard Home can bind to port 53")
|
||||
}
|
||||
|
||||
// Write PID to a file
|
||||
|
|
|
@ -134,9 +134,10 @@ underscores() {
|
|||
-e '_bsd.go'\
|
||||
-e '_darwin.go'\
|
||||
-e '_freebsd.go'\
|
||||
-e '_openbsd.go'\
|
||||
-e '_linux.go'\
|
||||
-e '_little.go'\
|
||||
-e '_nolinux.go'\
|
||||
-e '_openbsd.go'\
|
||||
-e '_others.go'\
|
||||
-e '_test.go'\
|
||||
-e '_unix.go'\
|
||||
|
|
Loading…
Reference in New Issue