diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go index 2de9c63082a..251330274af 100644 --- a/internal/aghnet/net.go +++ b/internal/aghnet/net.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net" + "net/netip" "syscall" "github.com/AdguardTeam/AdGuardHome/internal/aghos" @@ -31,6 +32,12 @@ var ( // the IP being static is available. const ErrNoStaticIPInfo errors.Error = "no information about static ip" +// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4]. +func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{0: 127, 3: 1}) } + +// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6]. +func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) } + // IfaceHasStaticIP checks if interface is configured to have static IP address. // If it can't give a definitive answer, it returns false and an error for which // errors.Is(err, ErrNoStaticIPInfo) is true. @@ -47,26 +54,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 ip } else if code != 0 { log.Debug("fetching gateway ip: unexpected exit code: %d", code) - return nil + return ip } 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 ip } - 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 +90,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,57 +113,78 @@ 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.WithZone(iface.Name) + + // Ignore link-local IPv4. + // + // TODO(e.burkov): !! or not?? + if ip.Is4() && ip.IsLinkLocalUnicast() { + continue + } + + 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 } // InterfaceByIP returns the name of the interface bound to ip. @@ -160,7 +193,7 @@ func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) { // IP address can be shared by multiple interfaces in some configurations. // // TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb. -func InterfaceByIP(ip net.IP) (ifaceName string) { +func InterfaceByIP(ip netip.Addr) (ifaceName string) { ifaces, err := GetValidNetInterfacesForWeb() if err != nil { return "" @@ -168,7 +201,7 @@ func InterfaceByIP(ip net.IP) (ifaceName string) { for _, iface := range ifaces { for _, addr := range iface.Addresses { - if ip.Equal(addr) { + if ip == addr { return iface.Name } } @@ -177,15 +210,16 @@ func InterfaceByIP(ip net.IP) (ifaceName 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 GetValidNetInterfacesForWeb. -func GetSubnet(ifaceName string) *net.IPNet { +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 { @@ -194,14 +228,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) diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go index d0c3f7fdc6a..86e41705059 100644 --- a/internal/aghnet/net_linux.go +++ b/internal/aghnet/net_linux.go @@ -6,7 +6,7 @@ import ( "bufio" "fmt" "io" - "net" + "net/netip" "os" "strings" @@ -151,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") } @@ -174,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, @@ -183,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.IsValid() { + 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() } diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go index d4ee59ee276..a8f46fa050b 100644 --- a/internal/aghnet/net_test.go +++ b/internal/aghnet/net_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io/fs" "net" + "net/netip" "os" "strings" "testing" @@ -93,34 +94,34 @@ 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.AddrFrom4([4]byte{1, 2, 3, 4}), + name: "success_v4", }, { - name: "success_v6", shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil), - want: net.IP{ + want: netip.AddrFrom16([16]byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, - }, + }), + 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 { @@ -198,17 +199,21 @@ func TestBroadcastFromIPNet(t *testing.T) { } func TestCheckPort(t *testing.T) { + laddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 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) + addr := l.Addr() + require.IsType(t, new(net.TCPAddr), addr) + + ipp := addr.(*net.TCPAddr).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 +221,18 @@ 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) + addr := conn.LocalAddr() + require.IsType(t, new(net.UDPAddr), addr) - err = CheckPort("udp", ipp.IP, ipp.Port) + ipp := addr.(*net.UDPAddr).AddrPort() + require.Equal(t, laddr.Addr(), ipp.Addr()) + require.NotZero(t, ipp.Port()) + + err = CheckPort("udp", ipp) target := &net.OpError{} require.ErrorAs(t, err, &target) @@ -233,12 +240,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 +329,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, diff --git a/internal/dhcpd/http_unix.go b/internal/dhcpd/http_unix.go index e6b1f8fc6d0..7b5b38fa5ec 100644 --- a/internal/dhcpd/http_unix.go +++ b/internal/dhcpd/http_unix.go @@ -341,6 +341,8 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { return } // ignore link-local + // + // TODO(e.burkov): Try to listen DHCP on LLA as well. if ipnet.IP.IsLinkLocalUnicast() { continue } @@ -351,7 +353,7 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 { - jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name) + jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name).AsSlice() response[iface.Name] = jsonIface } } diff --git a/internal/home/config.go b/internal/home/config.go index b5aecea2a62..fea83a41b17 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net" + "net/netip" "os" "path/filepath" "sync" @@ -135,8 +136,8 @@ type configuration struct { // field ordering is important -- yaml fields will mirror ordering from here type dnsConfig struct { - BindHosts []net.IP `yaml:"bind_hosts"` - Port int `yaml:"port"` + BindHosts []netip.Addr `yaml:"bind_hosts"` + Port int `yaml:"port"` // time interval for statistics (in days) StatsInterval uint32 `yaml:"statistics_interval"` @@ -216,7 +217,7 @@ var config = &configuration{ AuthBlockMin: 15, WebSessionTTLHours: 30 * 24, DNS: dnsConfig{ - BindHosts: []net.IP{{0, 0, 0, 0}}, + BindHosts: []netip.Addr{netip.IPv4Unspecified()}, Port: defaultPortDNS, StatsInterval: 1, QueryLogEnabled: true, diff --git a/internal/home/control.go b/internal/home/control.go index 48ac45f4f3c..609913b3f50 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -2,8 +2,8 @@ package home import ( "fmt" - "net" "net/http" + "net/netip" "net/url" "runtime" "strings" @@ -20,11 +20,11 @@ import ( // appendDNSAddrs is a convenient helper for appending a formatted form of DNS // addresses to a slice of strings. -func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) { +func appendDNSAddrs(dst []string, addrs ...netip.Addr) (res []string) { for _, addr := range addrs { var hostport string if config.DNS.Port != defaultPortDNS { - hostport = netutil.JoinHostPort(addr.String(), config.DNS.Port) + hostport = netip.AddrPortFrom(addr, uint16(config.DNS.Port)).String() } else { hostport = addr.String() } @@ -38,7 +38,7 @@ func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) { // appendDNSAddrsWithIfaces formats and appends all DNS addresses from src to // dst. It also adds the IP addresses of all network interfaces if src contains // an unspecified IP address. -func appendDNSAddrsWithIfaces(dst []string, src []net.IP) (res []string, err error) { +func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err error) { ifacesAdded := false for _, h := range src { if !h.IsUnspecified() { @@ -71,7 +71,9 @@ func appendDNSAddrsWithIfaces(dst []string, src []net.IP) (res []string, err err // on, including the addresses on all interfaces in cases of unspecified IPs. func collectDNSAddresses() (addrs []string, err error) { if hosts := config.DNS.BindHosts; len(hosts) == 0 { - addrs = appendDNSAddrs(addrs, net.IP{127, 0, 0, 1}) + addr := netip.AddrFrom4([4]byte{127, 0, 0, 1}) + + addrs = appendDNSAddrs(addrs, addr) } else { addrs, err = appendDNSAddrsWithIfaces(addrs, hosts) if err != nil { diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 7df8d3208b7..4047e4770d8 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -5,8 +5,8 @@ import ( "encoding/json" "fmt" "io" - "net" "net/http" + "net/netip" "os" "os/exec" "path/filepath" @@ -64,9 +64,9 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request } type checkConfReqEnt struct { - IP net.IP `json:"ip"` - Port int `json:"port"` - Autofix bool `json:"autofix"` + IP netip.Addr `json:"ip"` + Port int `json:"port"` + Autofix bool `json:"autofix"` } type checkConfReq struct { @@ -117,7 +117,7 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err // unbound after install. } - return aghnet.CheckPort("tcp", req.Web.IP, portInt) + return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(portInt))) } // validateDNS returns error if the DNS part of the initial configuration can't @@ -142,13 +142,13 @@ func (req *checkConfReq) validateDNS( return false, err } - err = aghnet.CheckPort("tcp", req.DNS.IP, port) + err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.DNS.IP, uint16(port))) if err != nil { return false, err } } - err = aghnet.CheckPort("udp", req.DNS.IP, port) + err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(port))) if !aghnet.IsAddrInUse(err) { return false, err } @@ -160,7 +160,7 @@ func (req *checkConfReq) validateDNS( log.Error("disabling DNSStubListener: %s", err) } - err = aghnet.CheckPort("udp", req.DNS.IP, port) + err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(port))) canAutofix = false } @@ -196,7 +196,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) // handleStaticIP - handles static IP request // It either checks if we have a static IP // Or if set=true, it tries to set it -func handleStaticIP(ip net.IP, set bool) staticIPJSON { +func handleStaticIP(ip netip.Addr, set bool) staticIPJSON { resp := staticIPJSON{} interfaceName := aghnet.InterfaceByIP(ip) @@ -304,8 +304,8 @@ func disableDNSStubListener() error { } type applyConfigReqEnt struct { - IP net.IP `json:"ip"` - Port int `json:"port"` + IP netip.Addr `json:"ip"` + Port int `json:"port"` } type applyConfigReq struct { @@ -397,14 +397,14 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { return } - err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port) + err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, uint16(req.DNS.Port))) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) return } - err = aghnet.CheckPort("tcp", req.DNS.IP, req.DNS.Port) + err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.DNS.IP, uint16(req.DNS.Port))) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) @@ -415,9 +415,9 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { copyInstallSettings(curConfig, config) Context.firstRun = false - config.BindHost = req.Web.IP + config.BindHost = req.Web.IP.AsSlice() config.BindPort = req.Web.Port - config.DNS.BindHosts = []net.IP{req.DNS.IP} + config.DNS.BindHosts = []netip.Addr{req.DNS.IP} config.DNS.Port = req.DNS.Port // TODO(e.burkov): StartMods() should be put in a separate goroutine at the @@ -448,7 +448,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { } web.conf.firstRun = false - web.conf.BindHost = req.Web.IP + web.conf.BindHost = req.Web.IP.AsSlice() web.conf.BindPort = req.Web.Port registerControlHandlers() @@ -490,9 +490,9 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e return nil, false, errors.Error("ports cannot be 0") } - restartHTTP = !config.BindHost.Equal(req.Web.IP) || config.BindPort != req.Web.Port + restartHTTP = !config.BindHost.Equal(req.Web.IP.AsSlice()) || config.BindPort != req.Web.Port if restartHTTP { - err = aghnet.CheckPort("tcp", req.Web.IP, req.Web.Port) + err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port))) if err != nil { return nil, false, fmt.Errorf( "checking address %s:%d: %w", @@ -518,9 +518,9 @@ func (web *Web) registerInstallHandlers() { // TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default checkConfigReqEnt. type checkConfigReqEntBeta struct { - IP []net.IP `json:"ip"` - Port int `json:"port"` - Autofix bool `json:"autofix"` + IP []netip.Addr `json:"ip"` + Port int `json:"port"` + Autofix bool `json:"autofix"` } // checkConfigReqBeta is a struct representing new client's config check request @@ -590,8 +590,8 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ // TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default applyConfigReqEnt. type applyConfigReqEntBeta struct { - IP []net.IP `json:"ip"` - Port int `json:"port"` + IP []netip.Addr `json:"ip"` + Port int `json:"port"` } // applyConfigReqBeta is a struct representing new client's config setting diff --git a/internal/home/dns.go b/internal/home/dns.go index da4628761bb..8cd5a2edaec 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -3,6 +3,7 @@ package home import ( "fmt" "net" + "net/netip" "net/url" "os" "path/filepath" @@ -164,33 +165,27 @@ func onDNSRequest(pctx *proxy.DNSContext) { } } -func ipsToTCPAddrs(ips []net.IP, port int) (tcpAddrs []*net.TCPAddr) { +func ipsToTCPAddrs(ips []netip.Addr, port int) (tcpAddrs []*net.TCPAddr) { if ips == nil { return nil } - tcpAddrs = make([]*net.TCPAddr, len(ips)) - for i, ip := range ips { - tcpAddrs[i] = &net.TCPAddr{ - IP: ip, - Port: port, - } + tcpAddrs = make([]*net.TCPAddr, 0, len(ips)) + for _, ip := range ips { + tcpAddrs = append(tcpAddrs, net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port)))) } return tcpAddrs } -func ipsToUDPAddrs(ips []net.IP, port int) (udpAddrs []*net.UDPAddr) { +func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) { if ips == nil { return nil } - udpAddrs = make([]*net.UDPAddr, len(ips)) - for i, ip := range ips { - udpAddrs[i] = &net.UDPAddr{ - IP: ip, - Port: port, - } + udpAddrs = make([]*net.UDPAddr, 0, len(ips)) + for _, ip := range ips { + udpAddrs = append(udpAddrs, net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port)))) } return udpAddrs @@ -200,7 +195,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { dnsConf := config.DNS hosts := dnsConf.BindHosts if len(hosts) == 0 { - hosts = []net.IP{{127, 0, 0, 1}} + hosts = []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})} } newConf = dnsforward.ServerConfig{ @@ -257,7 +252,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { return newConf, nil } -func newDNSCrypt(hosts []net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { +func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { if tlsConf.DNSCryptConfigFile == "" { return dnscc, errors.Error("no dnscrypt_config_file") } diff --git a/internal/home/home.go b/internal/home/home.go index 289c1c643ec..ea2e6adbca1 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/http/pprof" + "net/netip" "net/url" "os" "os/signal" @@ -556,7 +557,7 @@ func checkPermissions() { } // We should check if AdGuard Home is able to bind to port 53 - err := aghnet.CheckPort("tcp", net.IP{127, 0, 0, 1}, defaultPortDNS) + err := aghnet.CheckPort("tcp", netip.AddrPortFrom(aghnet.IPv4Localhost(), defaultPortDNS)) if err != nil { if errors.Is(err, os.ErrPermission) { log.Fatal(`Permission check failed. diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go index 5230a2ac53d..48783d0d40d 100644 --- a/internal/home/mobileconfig_test.go +++ b/internal/home/mobileconfig_test.go @@ -3,12 +3,11 @@ package home import ( "bytes" "encoding/json" - "net" "net/http" "net/http/httptest" + "net/netip" "testing" - "github.com/AdguardTeam/golibs/netutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "howett.net/plist" @@ -28,7 +27,7 @@ func setupDNSIPs(t testing.TB) { config = &configuration{ DNS: dnsConfig{ - BindHosts: []net.IP{netutil.IPv4Zero()}, + BindHosts: []netip.Addr{netip.IPv4Unspecified()}, Port: defaultPortDNS, }, } diff --git a/internal/home/web.go b/internal/home/web.go index 3e248d80871..2198f0797a5 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -6,6 +6,7 @@ import ( "io/fs" "net" "net/http" + "net/netip" "sync" "time" @@ -37,6 +38,7 @@ type webConfig struct { clientFS fs.FS clientBetaFS fs.FS + // TODO(e.burkov): !! use netip BindHost net.IP BindPort int BetaBindPort int @@ -135,8 +137,13 @@ func newWeb(conf *webConfig) (w *Web) { // // TODO(a.garipov): Adapt for HTTP/3. func webCheckPortAvailable(port int) (ok bool) { - return Context.web.httpsServer.server != nil || - aghnet.CheckPort("tcp", config.BindHost, port) == nil + if Context.web.httpsServer.server != nil { + return true + } + + addr, ok := netip.AddrFromSlice(config.BindHost) + + return ok && aghnet.CheckPort("tcp", netip.AddrPortFrom(addr.Unmap(), uint16(port))) == nil } // TLSConfigChanged updates the TLS configuration and restarts the HTTPS server