diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go index 75778b74..d6971448 100644 --- a/internal/aghnet/arpdb_test.go +++ b/internal/aghnet/arpdb_test.go @@ -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) { diff --git a/internal/aghnet/hostgen.go b/internal/aghnet/hostgen.go index d9278515..683c8d9f 100644 --- a/internal/aghnet/hostgen.go +++ b/internal/aghnet/hostgen.go @@ -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) } diff --git a/internal/aghnet/hostgen_test.go b/internal/aghnet/hostgen_test.go index 37121628..d37e556b 100644 --- a/internal/aghnet/hostgen_test.go +++ b/internal/aghnet/hostgen_test.go @@ -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) }) + }) + } + }) } diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go index d17b9165..cd2edc72 100644 --- a/internal/aghnet/net.go +++ b/internal/aghnet/net.go @@ -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 diff --git a/internal/aghnet/net_darwin.go b/internal/aghnet/net_darwin.go index 63e57dc4..296a18b0 100644 --- a/internal/aghnet/net_darwin.go +++ b/internal/aghnet/net_darwin.go @@ -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 { diff --git a/internal/aghnet/net_freebsd.go b/internal/aghnet/net_freebsd.go index 34d93303..85d40184 100644 --- a/internal/aghnet/net_freebsd.go +++ b/internal/aghnet/net_freebsd.go @@ -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" diff --git a/internal/aghnet/net_freebsd_test.go b/internal/aghnet/net_freebsd_test.go index e00dafa7..2c758360 100644 --- a/internal/aghnet/net_freebsd_test.go +++ b/internal/aghnet/net_freebsd_test.go @@ -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" diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go index c2526524..148abe1f 100644 --- a/internal/aghnet/net_linux.go +++ b/internal/aghnet/net_linux.go @@ -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() } diff --git a/internal/aghnet/net_linux_test.go b/internal/aghnet/net_linux_test.go index e46da4df..838802ff 100644 --- a/internal/aghnet/net_linux_test.go +++ b/internal/aghnet/net_linux_test.go @@ -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) - }) - } -} diff --git a/internal/aghnet/net_nolinux.go b/internal/aghnet/net_nolinux.go new file mode 100644 index 00000000..f429c6fa --- /dev/null +++ b/internal/aghnet/net_nolinux.go @@ -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() +} diff --git a/internal/aghnet/net_openbsd.go b/internal/aghnet/net_openbsd.go index 68ef90e0..cf911105 100644 --- a/internal/aghnet/net_openbsd.go +++ b/internal/aghnet/net_openbsd.go @@ -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) diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go index 29de869b..77eab3be 100644 --- a/internal/aghnet/net_test.go +++ b/internal/aghnet/net_test.go @@ -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) +} diff --git a/internal/aghnet/net_windows.go b/internal/aghnet/net_windows.go index 128f5716..0cea8fe7 100644 --- a/internal/aghnet/net_windows.go +++ b/internal/aghnet/net_windows.go @@ -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") } diff --git a/internal/home/home.go b/internal/home/home.go index 5fae42c8..df1dc95b 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -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 diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 8e646f0c..32d99480 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -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'\