Pull request: Migrate to netip.Addr vol.1
Merge in DNS/adguard-home from 2926-lla-v6 to master
Updates #2926.
Updates #5035.
Squashed commit of the following:
commit 2e770d4b6d4e1ec3f7762f2f2466662983bf146c
Merge: 25c1afc5 893358ea
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Oct 14 15:14:56 2022 +0300
Merge branch 'master' into 2926-lla-v6
commit 25c1afc5f0a5027fafac9dee77618886aefee29c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Oct 13 18:24:20 2022 +0300
all: imp code, docs
commit 59549c4f74ee17b10eae542d1f1828d4e59894c9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Oct 11 18:49:09 2022 +0300
dhcpd: use netip initially
commit 1af623096b0517d07752385540f2f750f7f5b3bb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Sep 30 18:03:52 2022 +0300
all: imp docs, code
commit e9faeb71dbc0e887b25a7f3d5b33a401805f2ae7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Sep 29 14:56:37 2022 +0300
all: use netip for web
commit 38305e555a6884c3bd1b0839330b942ce0e59093
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Sep 28 19:13:58 2022 +0300
add basic lla
This commit is contained in:
parent
893358ea71
commit
4582b1c919
|
@ -17,6 +17,7 @@ and this project adheres to
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
- The ability to serve DNS on link-local IPv6 addresses ([#2926]).
|
||||||
- The ability to put [ClientIDs][clientid] into DNS-over-HTTPS hostnames as
|
- The ability to put [ClientIDs][clientid] into DNS-over-HTTPS hostnames as
|
||||||
opposed to URL paths ([#3418]). Note that AdGuard Home checks the server name
|
opposed to URL paths ([#3418]). Note that AdGuard Home checks the server name
|
||||||
only if the URL does not contain a ClientID.
|
only if the URL does not contain a ClientID.
|
||||||
|
@ -33,6 +34,7 @@ and this project adheres to
|
||||||
cached now ([#4942]).
|
cached now ([#4942]).
|
||||||
- Web UI not switching to HTTP/3 ([#4986], [#4993]).
|
- Web UI not switching to HTTP/3 ([#4986], [#4993]).
|
||||||
|
|
||||||
|
[#2926]: https://github.com/AdguardTeam/AdGuardHome/issues/2926
|
||||||
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
||||||
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
||||||
[#4986]: https://github.com/AdguardTeam/AdGuardHome/issues/4986
|
[#4986]: https://github.com/AdguardTeam/AdGuardHome/issues/4986
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -38,48 +39,44 @@ func checkOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ifaceIPv4Subnet returns the first suitable IPv4 subnetwork iface has.
|
// 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
|
var addrs []net.Addr
|
||||||
if addrs, err = iface.Addrs(); err != nil {
|
if addrs, err = iface.Addrs(); err != nil {
|
||||||
return nil, err
|
return netip.Prefix{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
var maskLen int
|
||||||
switch a := a.(type) {
|
switch a := a.(type) {
|
||||||
case *net.IPAddr:
|
case *net.IPAddr:
|
||||||
subnet = &net.IPNet{
|
ip = a.IP
|
||||||
IP: a.IP,
|
maskLen, _ = ip.DefaultMask().Size()
|
||||||
Mask: a.IP.DefaultMask(),
|
|
||||||
}
|
|
||||||
case *net.IPNet:
|
case *net.IPNet:
|
||||||
subnet = a
|
ip = a.IP
|
||||||
|
maskLen, _ = a.Mask.Size()
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip4 := subnet.IP.To4(); ip4 != nil {
|
if ip = ip.To4(); ip != nil {
|
||||||
subnet.IP = ip4
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ip)), maskLen), nil
|
||||||
|
|
||||||
return subnet, 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
|
// checkOtherDHCPv4 sends a DHCP request to the specified network interface, and
|
||||||
// waits for a response for a period defined by defaultDiscoverTime.
|
// waits for a response for a period defined by defaultDiscoverTime.
|
||||||
func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
|
func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
|
||||||
var subnet *net.IPNet
|
var subnet netip.Prefix
|
||||||
if subnet, err = ifaceIPv4Subnet(iface); err != nil {
|
if subnet, err = ifaceIPv4Subnet(iface); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve broadcast addr.
|
// Resolve broadcast addr.
|
||||||
dst := netutil.IPPort{
|
dst := netip.AddrPortFrom(BroadcastFromPref(subnet), 67).String()
|
||||||
IP: BroadcastFromIPNet(subnet),
|
|
||||||
Port: 67,
|
|
||||||
}.String()
|
|
||||||
var dstAddr *net.UDPAddr
|
var dstAddr *net.UDPAddr
|
||||||
if dstAddr, err = net.ResolveUDPAddr("udp4", dst); err != nil {
|
if dstAddr, err = net.ResolveUDPAddr("udp4", dst); err != nil {
|
||||||
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
|
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
|
||||||
|
|
|
@ -106,9 +106,13 @@ type HostsContainer struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
|
||||||
// updates is the channel for receiving updated hosts.
|
// updates is the channel for receiving updated hosts.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
|
||||||
updates chan *netutil.IPMap
|
updates chan *netutil.IPMap
|
||||||
|
|
||||||
// last is the set of hosts that was cached within last detected change.
|
// last is the set of hosts that was cached within last detected change.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
|
||||||
last *netutil.IPMap
|
last *netutil.IPMap
|
||||||
|
|
||||||
// fsys is the working file system to read hosts files from.
|
// fsys is the working file system to read hosts files from.
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Variables and functions to substitute in tests.
|
// Variables and functions to substitute in tests.
|
||||||
|
@ -31,6 +31,12 @@ var (
|
||||||
// the IP being static is available.
|
// the IP being static is available.
|
||||||
const ErrNoStaticIPInfo errors.Error = "no information about static ip"
|
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{127, 0, 0, 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.
|
// 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
|
// If it can't give a definitive answer, it returns false and an error for which
|
||||||
// errors.Is(err, ErrNoStaticIPInfo) is true.
|
// errors.Is(err, ErrNoStaticIPInfo) is true.
|
||||||
|
@ -47,26 +53,31 @@ func IfaceSetStaticIP(ifaceName string) (err error) {
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Investigate if the gateway address may be fetched in another
|
// TODO(e.burkov): Investigate if the gateway address may be fetched in another
|
||||||
// way since not every machine has the software installed.
|
// 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)
|
code, out, err := aghosRunCommand("ip", "route", "show", "dev", ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("%s", err)
|
log.Debug("%s", err)
|
||||||
|
|
||||||
return nil
|
return netip.Addr{}
|
||||||
} else if code != 0 {
|
} else if code != 0 {
|
||||||
log.Debug("fetching gateway ip: unexpected exit code: %d", code)
|
log.Debug("fetching gateway ip: unexpected exit code: %d", code)
|
||||||
|
|
||||||
return nil
|
return netip.Addr{}
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := bytes.Fields(out)
|
fields := bytes.Fields(out)
|
||||||
// The meaningful "ip route" command output should contain the word
|
// The meaningful "ip route" command output should contain the word
|
||||||
// "default" at first field and default gateway IP address at third field.
|
// "default" at first field and default gateway IP address at third field.
|
||||||
if len(fields) < 3 || string(fields[0]) != "default" {
|
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
|
// CanBindPrivilegedPorts checks if current process can bind to privileged
|
||||||
|
@ -78,9 +89,9 @@ func CanBindPrivilegedPorts() (can bool, err error) {
|
||||||
// NetInterface represents an entry of network interfaces map.
|
// NetInterface represents an entry of network interfaces map.
|
||||||
type NetInterface struct {
|
type NetInterface struct {
|
||||||
// Addresses are the network interface addresses.
|
// 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 are the IP networks for this network interface.
|
||||||
Subnets []*net.IPNet `json:"-"`
|
Subnets []netip.Prefix `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
HardwareAddr net.HardwareAddr `json:"hardware_address"`
|
HardwareAddr net.HardwareAddr `json:"hardware_address"`
|
||||||
Flags net.Flags `json:"flags"`
|
Flags net.Flags `json:"flags"`
|
||||||
|
@ -101,57 +112,79 @@ 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
|
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and
|
||||||
// WEB only we do not return link-local addresses here.
|
// WEB only we do not return link-local addresses here.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Can't properly test the function since it's nontrivial to
|
// 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.
|
// 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()
|
ifaces, err := net.Interfaces()
|
||||||
if err != nil {
|
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 {
|
} 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 {
|
for i := range ifaces {
|
||||||
var addrs []net.Addr
|
var niface *NetInterface
|
||||||
addrs, err = iface.Addrs()
|
niface, err = NetInterfaceFrom(&ifaces[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
|
return nil, err
|
||||||
}
|
} else if len(niface.Addresses) != 0 {
|
||||||
|
|
||||||
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.
|
// Discard interfaces with no addresses.
|
||||||
if len(netIface.Addresses) != 0 {
|
nifaces = append(nifaces, niface)
|
||||||
netIfaces = append(netIfaces, netIface)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return netIfaces, nil
|
return nifaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InterfaceByIP returns the name of the interface bound to ip.
|
// 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.
|
// IP address can be shared by multiple interfaces in some configurations.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
||||||
func InterfaceByIP(ip net.IP) (ifaceName string) {
|
func InterfaceByIP(ip netip.Addr) (ifaceName string) {
|
||||||
ifaces, err := GetValidNetInterfacesForWeb()
|
ifaces, err := GetValidNetInterfacesForWeb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
@ -168,7 +201,7 @@ func InterfaceByIP(ip net.IP) (ifaceName string) {
|
||||||
|
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
for _, addr := range iface.Addresses {
|
for _, addr := range iface.Addresses {
|
||||||
if ip.Equal(addr) {
|
if ip == addr {
|
||||||
return iface.Name
|
return iface.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,15 +210,16 @@ func InterfaceByIP(ip net.IP) (ifaceName string) {
|
||||||
return ""
|
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.
|
// the search fails.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb.
|
||||||
func GetSubnet(ifaceName string) *net.IPNet {
|
func GetSubnet(ifaceName string) (p netip.Prefix) {
|
||||||
netIfaces, err := GetValidNetInterfacesForWeb()
|
netIfaces, err := GetValidNetInterfacesForWeb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Could not get network interfaces info: %v", err)
|
log.Error("Could not get network interfaces info: %v", err)
|
||||||
return nil
|
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, netIface := range netIfaces {
|
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
|
// CheckPort checks if the port is available for binding. network is expected
|
||||||
// to be one of "udp" and "tcp".
|
// 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
|
var c io.Closer
|
||||||
addr := netutil.IPPort{IP: ip, Port: port}.String()
|
addr := ipp.String()
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
c, err = net.Listen(network, addr)
|
c, err = net.Listen(network, addr)
|
||||||
|
@ -251,18 +285,23 @@ func CollectAllIfacesAddrs() (addrs []string, err error) {
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastFromIPNet calculates the broadcast IP address for n.
|
// BroadcastFromPref calculates the broadcast IP address for p.
|
||||||
func BroadcastFromIPNet(n *net.IPNet) (dc net.IP) {
|
func BroadcastFromPref(p netip.Prefix) (bc netip.Addr) {
|
||||||
dc = netutil.CloneIP(n.IP)
|
bc = p.Addr().Unmap()
|
||||||
|
if !bc.IsValid() {
|
||||||
mask := n.Mask
|
return netip.Addr{}
|
||||||
if mask == nil {
|
|
||||||
mask = dc.DefaultMask()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, b := range mask {
|
maskLen, addrLen := p.Bits(), bc.BitLen()
|
||||||
dc[i] |= ^b
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
|
||||||
// interface through dhcpcd.conf.
|
// interface through dhcpcd.conf.
|
||||||
func ifaceSetStaticIP(ifaceName string) (err error) {
|
func ifaceSetStaticIP(ifaceName string) (err error) {
|
||||||
ipNet := GetSubnet(ifaceName)
|
ipNet := GetSubnet(ifaceName)
|
||||||
if ipNet.IP == nil {
|
if !ipNet.Addr().IsValid() {
|
||||||
return errors.Error("can't get IP address")
|
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
|
// dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that
|
||||||
// configure the interface to have a static IP.
|
// 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{}
|
b := &strings.Builder{}
|
||||||
stringutil.WriteToBuilder(
|
stringutil.WriteToBuilder(
|
||||||
b,
|
b,
|
||||||
|
@ -183,15 +183,15 @@ func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf stri
|
||||||
" added by AdGuard Home.\ninterface ",
|
" added by AdGuard Home.\ninterface ",
|
||||||
ifaceName,
|
ifaceName,
|
||||||
"\nstatic ip_address=",
|
"\nstatic ip_address=",
|
||||||
ipNet.String(),
|
subnet.String(),
|
||||||
"\n",
|
"\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
if gwIP != nil {
|
if gateway != (netip.Addr{}) {
|
||||||
stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
|
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()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -93,34 +94,29 @@ func TestGatewayIP(t *testing.T) {
|
||||||
const cmd = "ip route show dev " + ifaceName
|
const cmd = "ip route show dev " + ifaceName
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
|
||||||
shell mapShell
|
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),
|
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),
|
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
|
||||||
want: net.IP{
|
want: netip.MustParseAddr("::ffff"),
|
||||||
0x0, 0x0, 0x0, 0x0,
|
name: "success_v6",
|
||||||
0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0xFF, 0xFF,
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
name: "bad_output",
|
|
||||||
shell: theOnlyCmd(cmd, 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,
|
want: netip.Addr{},
|
||||||
|
name: "bad_output",
|
||||||
}, {
|
}, {
|
||||||
name: "err_runcmd",
|
|
||||||
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
|
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),
|
shell: theOnlyCmd(cmd, 1, "", nil),
|
||||||
want: nil,
|
want: netip.Addr{},
|
||||||
|
name: "bad_code",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -150,65 +146,64 @@ func TestInterfaceByIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBroadcastFromIPNet(t *testing.T) {
|
func TestBroadcastFromIPNet(t *testing.T) {
|
||||||
known6 := net.IP{
|
known4 := netip.MustParseAddr("192.168.0.1")
|
||||||
1, 2, 3, 4,
|
fullBroadcast4 := netip.MustParseAddr("255.255.255.255")
|
||||||
5, 6, 7, 8,
|
|
||||||
9, 10, 11, 12,
|
known6 := netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:f10")
|
||||||
13, 14, 15, 16,
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
pref netip.Prefix
|
||||||
|
want netip.Addr
|
||||||
name string
|
name string
|
||||||
subnet *net.IPNet
|
|
||||||
want net.IP
|
|
||||||
}{{
|
}{{
|
||||||
|
pref: netip.PrefixFrom(known4, 0),
|
||||||
|
want: fullBroadcast4,
|
||||||
name: "full",
|
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",
|
pref: netip.PrefixFrom(known4, 20),
|
||||||
subnet: &net.IPNet{
|
want: netip.MustParseAddr("192.168.15.255"),
|
||||||
IP: known6,
|
name: "full",
|
||||||
},
|
}, {
|
||||||
|
pref: netip.PrefixFrom(known6, netutil.IPv6BitLen),
|
||||||
want: known6,
|
want: known6,
|
||||||
|
name: "ipv6_no_mask",
|
||||||
}, {
|
}, {
|
||||||
|
pref: netip.PrefixFrom(known4, netutil.IPv4BitLen),
|
||||||
|
want: known4,
|
||||||
name: "ipv4_no_mask",
|
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",
|
name: "unspecified",
|
||||||
subnet: &net.IPNet{
|
}, {
|
||||||
IP: net.IP{0, 0, 0, 0},
|
pref: netip.Prefix{},
|
||||||
Mask: net.IPMask{0, 0, 0, 0},
|
want: netip.Addr{},
|
||||||
},
|
name: "invalid",
|
||||||
want: net.IPv4bcast,
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
bc := BroadcastFromIPNet(tc.subnet)
|
assert.Equal(t, tc.want, BroadcastFromPref(tc.pref))
|
||||||
assert.True(t, bc.Equal(tc.want), bc)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPort(t *testing.T) {
|
func TestCheckPort(t *testing.T) {
|
||||||
|
laddr := netip.AddrPortFrom(IPv4Localhost(), 0)
|
||||||
|
|
||||||
t.Run("tcp_bound", func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, l.Close)
|
testutil.CleanupAndRequireSuccess(t, l.Close)
|
||||||
|
|
||||||
ipp := netutil.IPPortFromAddr(l.Addr())
|
addr := l.Addr()
|
||||||
require.NotNil(t, ipp)
|
require.IsType(t, new(net.TCPAddr), addr)
|
||||||
require.NotNil(t, ipp.IP)
|
|
||||||
require.NotZero(t, ipp.Port)
|
|
||||||
|
|
||||||
err = CheckPort("tcp", ipp.IP, ipp.Port)
|
ipp := addr.(*net.TCPAddr).AddrPort()
|
||||||
|
require.Equal(t, laddr.Addr(), ipp.Addr())
|
||||||
|
require.NotZero(t, ipp.Port())
|
||||||
|
|
||||||
|
err = CheckPort("tcp", ipp)
|
||||||
target := &net.OpError{}
|
target := &net.OpError{}
|
||||||
require.ErrorAs(t, err, &target)
|
require.ErrorAs(t, err, &target)
|
||||||
|
|
||||||
|
@ -216,16 +211,18 @@ func TestCheckPort(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("udp_bound", func(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)
|
require.NoError(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, conn.Close)
|
testutil.CleanupAndRequireSuccess(t, conn.Close)
|
||||||
|
|
||||||
ipp := netutil.IPPortFromAddr(conn.LocalAddr())
|
addr := conn.LocalAddr()
|
||||||
require.NotNil(t, ipp)
|
require.IsType(t, new(net.UDPAddr), addr)
|
||||||
require.NotNil(t, ipp.IP)
|
|
||||||
require.NotZero(t, ipp.Port)
|
|
||||||
|
|
||||||
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{}
|
target := &net.OpError{}
|
||||||
require.ErrorAs(t, err, &target)
|
require.ErrorAs(t, err, &target)
|
||||||
|
|
||||||
|
@ -233,12 +230,12 @@ func TestCheckPort(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad_network", func(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)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can_bind", func(t *testing.T) {
|
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)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -322,18 +319,18 @@ func TestNetInterface_MarshalJSON(t *testing.T) {
|
||||||
`"mtu":1500` +
|
`"mtu":1500` +
|
||||||
`}` + "\n"
|
`}` + "\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}
|
ip4, ok := netip.AddrFromSlice([]byte{1, 2, 3, 4})
|
||||||
mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen)
|
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{
|
iface := &NetInterface{
|
||||||
Addresses: []net.IP{ip4, ip6},
|
Addresses: []netip.Addr{ip4, ip6},
|
||||||
Subnets: []*net.IPNet{{
|
Subnets: []netip.Prefix{net4, net6},
|
||||||
IP: ip4.Mask(mask4),
|
|
||||||
Mask: mask4,
|
|
||||||
}, {
|
|
||||||
IP: ip6.Mask(mask6),
|
|
||||||
Mask: mask6,
|
|
||||||
}},
|
|
||||||
Name: "iface0",
|
Name: "iface0",
|
||||||
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
||||||
Flags: net.FlagUp | net.FlagMulticast,
|
Flags: net.FlagUp | net.FlagMulticast,
|
||||||
|
|
|
@ -3,12 +3,12 @@ package dhcpd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerConfig is the configuration for the DHCP server. The order of YAML
|
// ServerConfig is the configuration for the DHCP server. The order of YAML
|
||||||
|
@ -65,16 +65,16 @@ type V4ServerConf struct {
|
||||||
Enabled bool `yaml:"-" json:"-"`
|
Enabled bool `yaml:"-" json:"-"`
|
||||||
InterfaceName string `yaml:"-" json:"-"`
|
InterfaceName string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
GatewayIP net.IP `yaml:"gateway_ip" json:"gateway_ip"`
|
GatewayIP netip.Addr `yaml:"gateway_ip" json:"gateway_ip"`
|
||||||
SubnetMask net.IP `yaml:"subnet_mask" json:"subnet_mask"`
|
SubnetMask netip.Addr `yaml:"subnet_mask" json:"subnet_mask"`
|
||||||
// broadcastIP is the broadcasting address pre-calculated from the
|
// broadcastIP is the broadcasting address pre-calculated from the
|
||||||
// configured gateway IP and subnet mask.
|
// configured gateway IP and subnet mask.
|
||||||
broadcastIP net.IP
|
broadcastIP netip.Addr
|
||||||
|
|
||||||
// The first & the last IP address for dynamic leases
|
// The first & the last IP address for dynamic leases
|
||||||
// Bytes [0..2] of the last allowed IP address must match the first IP
|
// Bytes [0..2] of the last allowed IP address must match the first IP
|
||||||
RangeStart net.IP `yaml:"range_start" json:"range_start"`
|
RangeStart netip.Addr `yaml:"range_start" json:"range_start"`
|
||||||
RangeEnd net.IP `yaml:"range_end" json:"range_end"`
|
RangeEnd netip.Addr `yaml:"range_end" json:"range_end"`
|
||||||
|
|
||||||
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
||||||
|
|
||||||
|
@ -95,11 +95,11 @@ type V4ServerConf struct {
|
||||||
ipRange *ipRange
|
ipRange *ipRange
|
||||||
|
|
||||||
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
||||||
dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses
|
dnsIPAddrs []netip.Addr // IPv4 addresses to return to DHCP clients as DNS server addresses
|
||||||
|
|
||||||
// subnet contains the DHCP server's subnet. The IP is the IP of the
|
// subnet contains the DHCP server's subnet. The IP is the IP of the
|
||||||
// gateway.
|
// gateway.
|
||||||
subnet *net.IPNet
|
subnet netip.Prefix
|
||||||
|
|
||||||
// notify is a way to signal to other components that leases have been
|
// notify is a way to signal to other components that leases have been
|
||||||
// changed. notify must be called outside of locked sections, since the
|
// changed. notify must be called outside of locked sections, since the
|
||||||
|
@ -113,16 +113,12 @@ type V4ServerConf struct {
|
||||||
// errNilConfig is an error returned by validation method if the config is nil.
|
// errNilConfig is an error returned by validation method if the config is nil.
|
||||||
const errNilConfig errors.Error = "nil config"
|
const errNilConfig errors.Error = "nil config"
|
||||||
|
|
||||||
// ensureV4 returns a 4-byte version of ip. An error is returned if the passed
|
// ensureV4 returns an unmapped version of ip. An error is returned if the
|
||||||
// ip is not an IPv4.
|
// passed ip is not an IPv4.
|
||||||
func ensureV4(ip net.IP) (ip4 net.IP, err error) {
|
func ensureV4(ip netip.Addr, kind string) (ip4 netip.Addr, err error) {
|
||||||
if ip == nil {
|
ip4 = ip.Unmap()
|
||||||
return nil, fmt.Errorf("%v is not an IP address", ip)
|
if !ip4.IsValid() || !ip4.Is4() {
|
||||||
}
|
return netip.Addr{}, fmt.Errorf("%v is not an IPv4 %s", ip, kind)
|
||||||
|
|
||||||
ip4 = ip.To4()
|
|
||||||
if ip4 == nil {
|
|
||||||
return nil, fmt.Errorf("%v is not an IPv4 address", ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip4, nil
|
return ip4, nil
|
||||||
|
@ -139,33 +135,45 @@ func (c *V4ServerConf) Validate() (err error) {
|
||||||
return errNilConfig
|
return errNilConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var gatewayIP net.IP
|
gatewayIP, err := ensureV4(c.GatewayIP, "address")
|
||||||
gatewayIP, err = ensureV4(c.GatewayIP)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap an errors since it's inforative enough as is and there is
|
// Don't wrap an errors since it's informative enough as is and there is
|
||||||
// an annotation deferred already.
|
// an annotation deferred already.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SubnetMask == nil {
|
subnetMask, err := ensureV4(c.SubnetMask, "subnet mask")
|
||||||
return fmt.Errorf("invalid subnet mask: %v", c.SubnetMask)
|
|
||||||
}
|
|
||||||
|
|
||||||
subnetMask := net.IPMask(netutil.CloneIP(c.SubnetMask.To4()))
|
|
||||||
c.subnet = &net.IPNet{
|
|
||||||
IP: gatewayIP,
|
|
||||||
Mask: subnetMask,
|
|
||||||
}
|
|
||||||
c.broadcastIP = aghnet.BroadcastFromIPNet(c.subnet)
|
|
||||||
|
|
||||||
c.ipRange, err = newIPRange(c.RangeStart, c.RangeEnd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap an errors since it's inforative enough as is and there is
|
// Don't wrap an errors since it's informative enough as is and there is
|
||||||
|
// an annotation deferred already.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
maskLen, _ := net.IPMask(subnetMask.AsSlice()).Size()
|
||||||
|
|
||||||
|
c.subnet = netip.PrefixFrom(gatewayIP, maskLen)
|
||||||
|
c.broadcastIP = aghnet.BroadcastFromPref(c.subnet)
|
||||||
|
|
||||||
|
rangeStart, err := ensureV4(c.RangeStart, "address")
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap an errors since it's informative enough as is and there is
|
||||||
|
// an annotation deferred already.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rangeEnd, err := ensureV4(c.RangeEnd, "address")
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap an errors since it's informative enough as is and there is
|
||||||
// an annotation deferred already.
|
// an annotation deferred already.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ipRange.contains(gatewayIP) {
|
c.ipRange, err = newIPRange(rangeStart.AsSlice(), rangeEnd.AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap an errors since it's informative enough as is and there is
|
||||||
|
// an annotation deferred already.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ipRange.contains(gatewayIP.AsSlice()) {
|
||||||
return fmt.Errorf("gateway ip %v in the ip range: %v-%v",
|
return fmt.Errorf("gateway ip %v in the ip range: %v-%v",
|
||||||
gatewayIP,
|
gatewayIP,
|
||||||
c.RangeStart,
|
c.RangeStart,
|
||||||
|
@ -173,14 +181,14 @@ func (c *V4ServerConf) Validate() (err error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.subnet.Contains(c.RangeStart) {
|
if !c.subnet.Contains(rangeStart) {
|
||||||
return fmt.Errorf("range start %v is outside network %v",
|
return fmt.Errorf("range start %v is outside network %v",
|
||||||
c.RangeStart,
|
c.RangeStart,
|
||||||
c.subnet,
|
c.subnet,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.subnet.Contains(c.RangeEnd) {
|
if !c.subnet.Contains(rangeEnd) {
|
||||||
return fmt.Errorf("range end %v is outside network %v",
|
return fmt.Errorf("range end %v is outside network %v",
|
||||||
c.RangeEnd,
|
c.RangeEnd,
|
||||||
c.subnet,
|
c.subnet,
|
||||||
|
|
|
@ -73,10 +73,10 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro
|
||||||
|
|
||||||
return &dhcpConn{
|
return &dhcpConn{
|
||||||
udpConn: bcast,
|
udpConn: bcast,
|
||||||
bcastIP: s.conf.broadcastIP,
|
bcastIP: s.conf.broadcastIP.AsSlice(),
|
||||||
rawConn: ucast,
|
rawConn: ucast,
|
||||||
srcMAC: iface.HardwareAddr,
|
srcMAC: iface.HardwareAddr,
|
||||||
srcIP: s.conf.dnsIPAddrs[0],
|
srcIP: s.conf.dnsIPAddrs[0].AsSlice(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -242,7 +242,7 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||||
v4conf := conf.Conf4
|
v4conf := conf.Conf4
|
||||||
v4conf.InterfaceName = s.conf.InterfaceName
|
v4conf.InterfaceName = s.conf.InterfaceName
|
||||||
v4conf.notify = s.onNotify
|
v4conf.notify = s.onNotify
|
||||||
v4conf.Enabled = s.conf.Enabled && len(v4conf.RangeStart) != 0
|
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
|
||||||
|
|
||||||
s.srv4, err = v4Create(&v4conf)
|
s.srv4, err = v4Create(&v4conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,6 +4,7 @@ package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -33,10 +34,10 @@ func TestDB(t *testing.T) {
|
||||||
|
|
||||||
s.srv4, err = v4Create(&V4ServerConf{
|
s.srv4, err = v4Create(&V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: netip.MustParseAddr("192.168.10.100"),
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: netip.MustParseAddr("192.168.10.200"),
|
||||||
GatewayIP: net.IP{192, 168, 10, 1},
|
GatewayIP: netip.MustParseAddr("192.168.10.1"),
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
notify: testNotify,
|
notify: testNotify,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -113,35 +114,35 @@ func TestNormalizeLeases(t *testing.T) {
|
||||||
func TestV4Server_badRange(t *testing.T) {
|
func TestV4Server_badRange(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
gatewayIP netip.Addr
|
||||||
|
subnetMask netip.Addr
|
||||||
wantErrMsg string
|
wantErrMsg string
|
||||||
gatewayIP net.IP
|
|
||||||
subnetMask net.IP
|
|
||||||
}{{
|
}{{
|
||||||
name: "gateway_in_range",
|
name: "gateway_in_range",
|
||||||
|
gatewayIP: netip.MustParseAddr("192.168.10.120"),
|
||||||
|
subnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
wantErrMsg: "dhcpv4: gateway ip 192.168.10.120 in the ip range: " +
|
wantErrMsg: "dhcpv4: gateway ip 192.168.10.120 in the ip range: " +
|
||||||
"192.168.10.20-192.168.10.200",
|
"192.168.10.20-192.168.10.200",
|
||||||
gatewayIP: net.IP{192, 168, 10, 120},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 0},
|
|
||||||
}, {
|
}, {
|
||||||
name: "outside_range_start",
|
name: "outside_range_start",
|
||||||
|
gatewayIP: netip.MustParseAddr("192.168.10.1"),
|
||||||
|
subnetMask: netip.MustParseAddr("255.255.255.240"),
|
||||||
wantErrMsg: "dhcpv4: range start 192.168.10.20 is outside network " +
|
wantErrMsg: "dhcpv4: range start 192.168.10.20 is outside network " +
|
||||||
"192.168.10.1/28",
|
"192.168.10.1/28",
|
||||||
gatewayIP: net.IP{192, 168, 10, 1},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 240},
|
|
||||||
}, {
|
}, {
|
||||||
name: "outside_range_end",
|
name: "outside_range_end",
|
||||||
|
gatewayIP: netip.MustParseAddr("192.168.10.1"),
|
||||||
|
subnetMask: netip.MustParseAddr("255.255.255.224"),
|
||||||
wantErrMsg: "dhcpv4: range end 192.168.10.200 is outside network " +
|
wantErrMsg: "dhcpv4: range end 192.168.10.200 is outside network " +
|
||||||
"192.168.10.1/27",
|
"192.168.10.1/27",
|
||||||
gatewayIP: net.IP{192, 168, 10, 1},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 224},
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
conf := V4ServerConf{
|
conf := V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 20},
|
RangeStart: netip.MustParseAddr("192.168.10.20"),
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: netip.MustParseAddr("192.168.10.200"),
|
||||||
GatewayIP: tc.gatewayIP,
|
GatewayIP: tc.gatewayIP,
|
||||||
SubnetMask: tc.subnetMask,
|
SubnetMask: tc.subnetMask,
|
||||||
notify: testNotify,
|
notify: testNotify,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
|
@ -17,10 +18,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type v4ServerConfJSON struct {
|
type v4ServerConfJSON struct {
|
||||||
GatewayIP net.IP `json:"gateway_ip"`
|
GatewayIP netip.Addr `json:"gateway_ip"`
|
||||||
SubnetMask net.IP `json:"subnet_mask"`
|
SubnetMask netip.Addr `json:"subnet_mask"`
|
||||||
RangeStart net.IP `json:"range_start"`
|
RangeStart netip.Addr `json:"range_start"`
|
||||||
RangeEnd net.IP `json:"range_end"`
|
RangeEnd netip.Addr `json:"range_end"`
|
||||||
LeaseDuration uint32 `json:"lease_duration"`
|
LeaseDuration uint32 `json:"lease_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ func (j *v4ServerConfJSON) toServerConf() *V4ServerConf {
|
||||||
}
|
}
|
||||||
|
|
||||||
type v6ServerConfJSON struct {
|
type v6ServerConfJSON struct {
|
||||||
RangeStart net.IP `json:"range_start"`
|
RangeStart netip.Addr `json:"range_start"`
|
||||||
LeaseDuration uint32 `json:"lease_duration"`
|
LeaseDuration uint32 `json:"lease_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ func v6JSONToServerConf(j *v6ServerConfJSON) V6ServerConf {
|
||||||
}
|
}
|
||||||
|
|
||||||
return V6ServerConf{
|
return V6ServerConf{
|
||||||
RangeStart: j.RangeStart,
|
RangeStart: j.RangeStart.AsSlice(),
|
||||||
LeaseDuration: j.LeaseDuration,
|
LeaseDuration: j.LeaseDuration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ func (s *server) handleDHCPSetConfigV4(
|
||||||
|
|
||||||
v4Conf := conf.V4.toServerConf()
|
v4Conf := conf.V4.toServerConf()
|
||||||
v4Conf.Enabled = conf.Enabled == aghalg.NBTrue
|
v4Conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||||
if len(v4Conf.RangeStart) == 0 {
|
if !v4Conf.RangeStart.IsValid() {
|
||||||
v4Conf.Enabled = false
|
v4Conf.Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,9 +279,9 @@ type netInterfaceJSON struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
HardwareAddr string `json:"hardware_address"`
|
HardwareAddr string `json:"hardware_address"`
|
||||||
Flags string `json:"flags"`
|
Flags string `json:"flags"`
|
||||||
GatewayIP net.IP `json:"gateway_ip"`
|
GatewayIP netip.Addr `json:"gateway_ip"`
|
||||||
Addrs4 []net.IP `json:"ipv4_addresses"`
|
Addrs4 []netip.Addr `json:"ipv4_addresses"`
|
||||||
Addrs6 []net.IP `json:"ipv6_addresses"`
|
Addrs6 []netip.Addr `json:"ipv6_addresses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -341,13 +342,18 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// ignore link-local
|
// ignore link-local
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Try to listen DHCP on LLA as well.
|
||||||
if ipnet.IP.IsLinkLocalUnicast() {
|
if ipnet.IP.IsLinkLocalUnicast() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ipnet.IP.To4() != nil {
|
|
||||||
jsonIface.Addrs4 = append(jsonIface.Addrs4, ipnet.IP)
|
if ip4 := ipnet.IP.To4(); ip4 != nil {
|
||||||
|
addr := netip.AddrFrom4(*(*[4]byte)(ip4))
|
||||||
|
jsonIface.Addrs4 = append(jsonIface.Addrs4, addr)
|
||||||
} else {
|
} else {
|
||||||
jsonIface.Addrs6 = append(jsonIface.Addrs6, ipnet.IP)
|
addr := netip.AddrFrom16(*(*[16]byte)(ipnet.IP))
|
||||||
|
jsonIface.Addrs6 = append(jsonIface.Addrs6, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
||||||
|
|
|
@ -27,6 +27,8 @@ const maxRangeLen = math.MaxUint32
|
||||||
|
|
||||||
// newIPRange creates a new IP address range. start must be less than end. The
|
// newIPRange creates a new IP address range. start must be less than end. The
|
||||||
// resulting range must not be greater than maxRangeLen.
|
// resulting range must not be greater than maxRangeLen.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use netip.Addr.
|
||||||
func newIPRange(start, end net.IP) (r *ipRange, err error) {
|
func newIPRange(start, end net.IP) (r *ipRange, err error) {
|
||||||
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
|
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
|
||||||
|
|
||||||
|
|
|
@ -372,12 +372,9 @@ func (s *v4Server) prepareOptions() {
|
||||||
dhcpv4.OptGeneric(dhcpv4.OptionTCPKeepaliveGarbage, []byte{0x01}),
|
dhcpv4.OptGeneric(dhcpv4.OptionTCPKeepaliveGarbage, []byte{0x01}),
|
||||||
|
|
||||||
// Values From Configuration
|
// Values From Configuration
|
||||||
|
dhcpv4.OptRouter(s.conf.GatewayIP.AsSlice()),
|
||||||
|
|
||||||
// Set the Router Option to working subnet's IP since it's initialized
|
dhcpv4.OptSubnetMask(s.conf.SubnetMask.AsSlice()),
|
||||||
// with the address of the gateway.
|
|
||||||
dhcpv4.OptRouter(s.conf.subnet.IP),
|
|
||||||
|
|
||||||
dhcpv4.OptSubnetMask(s.conf.subnet.Mask),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set values for explicitly configured options.
|
// Set values for explicitly configured options.
|
||||||
|
|
|
@ -251,8 +251,6 @@ func TestPrepareOptions(t *testing.T) {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
s := &v4Server{
|
s := &v4Server{
|
||||||
conf: &V4ServerConf{
|
conf: &V4ServerConf{
|
||||||
// Just to avoid nil pointer dereference.
|
|
||||||
subnet: &net.IPNet{},
|
|
||||||
Options: tc.opts,
|
Options: tc.opts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -295,7 +296,8 @@ func (s *v4Server) addLease(l *Lease) (err error) {
|
||||||
if l.IsStatic() {
|
if l.IsStatic() {
|
||||||
// TODO(a.garipov, d.seregin): Subnet can be nil when dhcp server is
|
// TODO(a.garipov, d.seregin): Subnet can be nil when dhcp server is
|
||||||
// disabled.
|
// disabled.
|
||||||
if sn := s.conf.subnet; !sn.Contains(l.IP) {
|
addr := netip.AddrFrom4(*(*[4]byte)(l.IP.To4()))
|
||||||
|
if sn := s.conf.subnet; !sn.Contains(addr) {
|
||||||
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
||||||
}
|
}
|
||||||
} else if !inOffset {
|
} else if !inOffset {
|
||||||
|
@ -353,7 +355,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||||
ip := l.IP.To4()
|
ip := l.IP.To4()
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
|
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
|
||||||
} else if gwIP := s.conf.GatewayIP; gwIP.Equal(ip) {
|
} else if gwIP := s.conf.GatewayIP; gwIP == netip.AddrFrom4(*(*[4]byte)(ip)) {
|
||||||
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,7 +703,8 @@ func (s *v4Server) handleSelecting(
|
||||||
// Client inserts the address of the selected server in server identifier,
|
// Client inserts the address of the selected server in server identifier,
|
||||||
// ciaddr MUST be zero.
|
// ciaddr MUST be zero.
|
||||||
mac := req.ClientHWAddr
|
mac := req.ClientHWAddr
|
||||||
if !sid.Equal(s.conf.dnsIPAddrs[0]) {
|
|
||||||
|
if !sid.Equal(s.conf.dnsIPAddrs[0].AsSlice()) {
|
||||||
log.Debug("dhcpv4: bad server identifier in req msg for %s: %s", mac, sid)
|
log.Debug("dhcpv4: bad server identifier in req msg for %s: %s", mac, sid)
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -733,7 +736,8 @@ func (s *v4Server) handleSelecting(
|
||||||
func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, needsReply bool) {
|
func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, needsReply bool) {
|
||||||
mac := req.ClientHWAddr
|
mac := req.ClientHWAddr
|
||||||
|
|
||||||
if ip4 := reqIP.To4(); ip4 == nil {
|
ip4 := reqIP.To4()
|
||||||
|
if ip4 == nil {
|
||||||
log.Debug("dhcpv4: bad requested address in req msg for %s: %s", mac, reqIP)
|
log.Debug("dhcpv4: bad requested address in req msg for %s: %s", mac, reqIP)
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -747,7 +751,7 @@ func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease,
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.conf.subnet.Contains(reqIP) {
|
if !s.conf.subnet.Contains(netip.AddrFrom4(*(*[4]byte)(ip4))) {
|
||||||
// If the DHCP server detects that the client is on the wrong net then
|
// If the DHCP server detects that the client is on the wrong net then
|
||||||
// the server SHOULD send a DHCPNAK message to the client.
|
// the server SHOULD send a DHCPNAK message to the client.
|
||||||
log.Debug("dhcpv4: wrong subnet in init-reboot req msg for %s: %s", mac, reqIP)
|
log.Debug("dhcpv4: wrong subnet in init-reboot req msg for %s: %s", mac, reqIP)
|
||||||
|
@ -972,7 +976,7 @@ func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
|
||||||
// Include server's identifier option since any reply should contain it.
|
// Include server's identifier option since any reply should contain it.
|
||||||
//
|
//
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc2131#page-29.
|
// See https://datatracker.ietf.org/doc/html/rfc2131#page-29.
|
||||||
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0].AsSlice()))
|
||||||
|
|
||||||
// TODO(a.garipov): Refactor this into handlers.
|
// TODO(a.garipov): Refactor this into handlers.
|
||||||
var l *Lease
|
var l *Lease
|
||||||
|
@ -1188,7 +1192,14 @@ func (s *v4Server) Start() (err error) {
|
||||||
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
|
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
for _, ip := range dnsIPAddrs {
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.conf.dnsIPAddrs = append(s.conf.dnsIPAddrs, netip.AddrFrom4(*(*[4]byte)(ip)))
|
||||||
|
}
|
||||||
|
|
||||||
var c net.PacketConn
|
var c net.PacketConn
|
||||||
if c, err = s.newDHCPConn(iface); err != nil {
|
if c, err = s.newDHCPConn(iface); err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ package dhcpd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -22,11 +23,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultRangeStart = net.IP{192, 168, 10, 100}
|
DefaultRangeStart = netip.MustParseAddr("192.168.10.100")
|
||||||
DefaultRangeEnd = net.IP{192, 168, 10, 200}
|
DefaultRangeEnd = netip.MustParseAddr("192.168.10.200")
|
||||||
DefaultGatewayIP = net.IP{192, 168, 10, 1}
|
DefaultGatewayIP = netip.MustParseAddr("192.168.10.1")
|
||||||
DefaultSelfIP = net.IP{192, 168, 10, 2}
|
DefaultSelfIP = netip.MustParseAddr("192.168.10.2")
|
||||||
DefaultSubnetMask = net.IP{255, 255, 255, 0}
|
DefaultSubnetMask = netip.MustParseAddr("255.255.255.0")
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultV4ServerConf returns the default configuration for *v4Server to use in
|
// defaultV4ServerConf returns the default configuration for *v4Server to use in
|
||||||
|
@ -39,7 +40,7 @@ func defaultV4ServerConf() (conf *V4ServerConf) {
|
||||||
GatewayIP: DefaultGatewayIP,
|
GatewayIP: DefaultGatewayIP,
|
||||||
SubnetMask: DefaultSubnetMask,
|
SubnetMask: DefaultSubnetMask,
|
||||||
notify: testNotify,
|
notify: testNotify,
|
||||||
dnsIPAddrs: []net.IP{DefaultSelfIP},
|
dnsIPAddrs: []netip.Addr{DefaultSelfIP},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: anotherMAC,
|
HWAddr: anotherMAC,
|
||||||
IP: anotherIP,
|
IP: anotherIP.AsSlice(),
|
||||||
})
|
})
|
||||||
assert.ErrorIs(t, err, ErrDupHostname)
|
assert.ErrorIs(t, err, ErrDupHostname)
|
||||||
})
|
})
|
||||||
|
@ -96,7 +97,7 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||||
Hostname: anotherName,
|
Hostname: anotherName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: anotherIP,
|
IP: anotherIP.AsSlice(),
|
||||||
})
|
})
|
||||||
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
||||||
})
|
})
|
||||||
|
@ -135,7 +136,7 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
dhcpv4.WithOption(dhcpv4.OptHostName(name)),
|
dhcpv4.WithOption(dhcpv4.OptHostName(name)),
|
||||||
dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(ip)),
|
dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(ip)),
|
||||||
dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte{1, 2, 3, 4, 5, 6, 8})),
|
dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte{1, 2, 3, 4, 5, 6, 8})),
|
||||||
dhcpv4.WithGatewayIP(DefaultGatewayIP),
|
dhcpv4.WithGatewayIP(DefaultGatewayIP.AsSlice()),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -150,7 +151,7 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("same_name", func(t *testing.T) {
|
t.Run("same_name", func(t *testing.T) {
|
||||||
resp := discoverAnOffer(t, staticName, anotherIP, anotherMAC)
|
resp := discoverAnOffer(t, staticName, anotherIP.AsSlice(), anotherMAC)
|
||||||
|
|
||||||
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
||||||
dhcpv4.OptHostName(staticName),
|
dhcpv4.OptHostName(staticName),
|
||||||
|
@ -164,7 +165,7 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("same_mac", func(t *testing.T) {
|
t.Run("same_mac", func(t *testing.T) {
|
||||||
resp := discoverAnOffer(t, anotherName, anotherIP, staticMAC)
|
resp := discoverAnOffer(t, anotherName, anotherIP.AsSlice(), staticMAC)
|
||||||
|
|
||||||
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
||||||
dhcpv4.OptHostName(anotherName),
|
dhcpv4.OptHostName(anotherName),
|
||||||
|
@ -219,7 +220,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
||||||
lease: &Lease{
|
lease: &Lease{
|
||||||
Hostname: "probably-router.local",
|
Hostname: "probably-router.local",
|
||||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
IP: DefaultGatewayIP,
|
IP: DefaultGatewayIP.AsSlice(),
|
||||||
},
|
},
|
||||||
name: "with_gateway_ip",
|
name: "with_gateway_ip",
|
||||||
wantErrMsg: "dhcpv4: adding static lease: " +
|
wantErrMsg: "dhcpv4: adding static lease: " +
|
||||||
|
@ -326,7 +327,7 @@ func TestV4_AddReplace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4Server_handle_optionsPriority(t *testing.T) {
|
func TestV4Server_handle_optionsPriority(t *testing.T) {
|
||||||
defaultIP := net.IP{192, 168, 1, 1}
|
defaultIP := netip.MustParseAddr("192.168.1.1")
|
||||||
knownIP := net.IP{1, 2, 3, 4}
|
knownIP := net.IP{1, 2, 3, 4}
|
||||||
|
|
||||||
// prepareSrv creates a *v4Server and sets the opt6IPs in the initial
|
// prepareSrv creates a *v4Server and sets the opt6IPs in the initial
|
||||||
|
@ -343,14 +344,14 @@ func TestV4Server_handle_optionsPriority(t *testing.T) {
|
||||||
}
|
}
|
||||||
conf.Options = []string{b.String()}
|
conf.Options = []string{b.String()}
|
||||||
} else {
|
} else {
|
||||||
defer func() { s.implicitOpts.Update(dhcpv4.OptDNS(defaultIP)) }()
|
defer func() { s.implicitOpts.Update(dhcpv4.OptDNS(defaultIP.AsSlice())) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s, err = v4Create(conf)
|
s, err = v4Create(conf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{defaultIP}
|
s.conf.dnsIPAddrs = []netip.Addr{defaultIP}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -386,7 +387,7 @@ func TestV4Server_handle_optionsPriority(t *testing.T) {
|
||||||
t.Run("default", func(t *testing.T) {
|
t.Run("default", func(t *testing.T) {
|
||||||
s := prepareSrv(t, nil)
|
s := prepareSrv(t, nil)
|
||||||
|
|
||||||
checkResp(t, s, []net.IP{defaultIP})
|
checkResp(t, s, []net.IP{defaultIP.AsSlice()})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("explicitly_configured", func(t *testing.T) {
|
t.Run("explicitly_configured", func(t *testing.T) {
|
||||||
|
@ -506,8 +507,9 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
dnsAddr := netip.MustParseAddr("192.168.10.1")
|
||||||
s.implicitOpts.Update(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
s.conf.dnsIPAddrs = []netip.Addr{dnsAddr}
|
||||||
|
s.implicitOpts.Update(dhcpv4.OptDNS(dnsAddr.AsSlice()))
|
||||||
|
|
||||||
l := &Lease{
|
l := &Lease{
|
||||||
Hostname: "static-1.local",
|
Hostname: "static-1.local",
|
||||||
|
@ -539,9 +541,12 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
assert.True(t, resp.Router()[0].Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
|
ones, _ := resp.SubnetMask().Size()
|
||||||
|
assert.Equal(t, s.conf.subnet.Bits(), ones)
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -561,16 +566,19 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
assert.True(t, resp.Router()[0].Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
|
ones, _ := resp.SubnetMask().Size()
|
||||||
|
assert.Equal(t, s.conf.subnet.Bits(), ones)
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
})
|
})
|
||||||
|
|
||||||
dnsAddrs := resp.DNS()
|
dnsAddrs := resp.DNS()
|
||||||
require.Len(t, dnsAddrs, 1)
|
require.Len(t, dnsAddrs, 1)
|
||||||
|
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(dnsAddrs[0]))
|
assert.True(t, dnsAddrs[0].Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
t.Run("check_lease", func(t *testing.T) {
|
t.Run("check_lease", func(t *testing.T) {
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
|
@ -591,8 +599,8 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||||
s, err := v4Create(conf)
|
s, err := v4Create(conf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
s.conf.dnsIPAddrs = []netip.Addr{netip.MustParseAddr("192.168.10.1")}
|
||||||
s.implicitOpts.Update(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
s.implicitOpts.Update(dhcpv4.OptDNS(s.conf.dnsIPAddrs[0].AsSlice()))
|
||||||
|
|
||||||
var req, resp *dhcpv4.DHCPv4
|
var req, resp *dhcpv4.DHCPv4
|
||||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
|
@ -617,15 +625,16 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
|
|
||||||
assert.Equal(t, s.conf.RangeStart, resp.YourIPAddr)
|
assert.True(t, resp.YourIPAddr.Equal(s.conf.RangeStart.AsSlice()))
|
||||||
assert.Equal(t, s.conf.GatewayIP, resp.ServerIdentifier())
|
assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
router := resp.Router()
|
router := resp.Router()
|
||||||
require.Len(t, router, 1)
|
require.Len(t, router, 1)
|
||||||
|
|
||||||
assert.Equal(t, s.conf.GatewayIP, router[0])
|
assert.True(t, router[0].Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
ones, _ := resp.SubnetMask().Size()
|
||||||
|
assert.Equal(t, s.conf.subnet.Bits(), ones)
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
assert.Equal(t, []byte("012"), resp.Options.Get(dhcpv4.OptionFQDN))
|
assert.Equal(t, []byte("012"), resp.Options.Get(dhcpv4.OptionFQDN))
|
||||||
|
|
||||||
|
@ -649,15 +658,17 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||||
t.Run("ack", func(t *testing.T) {
|
t.Run("ack", func(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
assert.True(t, resp.YourIPAddr.Equal(s.conf.RangeStart.AsSlice()))
|
||||||
|
|
||||||
router := resp.Router()
|
router := resp.Router()
|
||||||
require.Len(t, router, 1)
|
require.Len(t, router, 1)
|
||||||
|
|
||||||
assert.Equal(t, s.conf.GatewayIP, router[0])
|
assert.True(t, router[0].Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
|
|
||||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice()))
|
||||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
|
||||||
|
ones, _ := resp.SubnetMask().Size()
|
||||||
|
assert.Equal(t, s.conf.subnet.Bits(), ones)
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
// accessCtx controls IP and client blocking that takes place before all other
|
// accessCtx controls IP and client blocking that takes place before all other
|
||||||
// processing. An accessCtx is safe for concurrent use.
|
// processing. An accessCtx is safe for concurrent use.
|
||||||
type accessCtx struct {
|
type accessCtx struct {
|
||||||
|
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
|
||||||
allowedIPs *netutil.IPMap
|
allowedIPs *netutil.IPMap
|
||||||
blockedIPs *netutil.IPMap
|
blockedIPs *netutil.IPMap
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ type Server struct {
|
||||||
tableHostToIP hostToIPTable
|
tableHostToIP hostToIPTable
|
||||||
tableHostToIPLock sync.Mutex
|
tableHostToIPLock sync.Mutex
|
||||||
|
|
||||||
|
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
|
||||||
tableIPToHost *netutil.IPMap
|
tableIPToHost *netutil.IPMap
|
||||||
tableIPToHostLock sync.Mutex
|
tableIPToHostLock sync.Mutex
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,8 @@ type clientsContainer struct {
|
||||||
idIndex map[string]*Client // ID -> client
|
idIndex map[string]*Client // ID -> client
|
||||||
|
|
||||||
// ipToRC is the IP address to *RuntimeClient map.
|
// ipToRC is the IP address to *RuntimeClient map.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
|
||||||
ipToRC *netutil.IPMap
|
ipToRC *netutil.IPMap
|
||||||
|
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
|
|
|
@ -2,6 +2,7 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -287,10 +288,10 @@ func TestClientsAddExisting(t *testing.T) {
|
||||||
DBFilePath: "leases.db",
|
DBFilePath: "leases.db",
|
||||||
Conf4: dhcpd.V4ServerConf{
|
Conf4: dhcpd.V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
GatewayIP: net.IP{1, 2, 3, 1},
|
GatewayIP: netip.MustParseAddr("1.2.3.1"),
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
RangeStart: net.IP{1, 2, 3, 2},
|
RangeStart: netip.MustParseAddr("1.2.3.2"),
|
||||||
RangeEnd: net.IP{1, 2, 3, 10},
|
RangeEnd: netip.MustParseAddr("1.2.3.10"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package home
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -85,19 +85,28 @@ type configuration struct {
|
||||||
// It's reset after config is parsed
|
// It's reset after config is parsed
|
||||||
fileData []byte
|
fileData []byte
|
||||||
|
|
||||||
BindHost net.IP `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
|
// BindHost is the address for the web interface server to listen on.
|
||||||
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
|
BindHost netip.Addr `yaml:"bind_host"`
|
||||||
BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
|
// BindPort is the port for the web interface server to listen on.
|
||||||
Users []webUser `yaml:"users"` // Users that can access HTTP server
|
BindPort int `yaml:"bind_port"`
|
||||||
|
// BetaBindPort is the port for the new client's web interface server to
|
||||||
|
// listen on.
|
||||||
|
BetaBindPort int `yaml:"beta_bind_port"`
|
||||||
|
|
||||||
|
// Users are the clients capable for accessing the web interface.
|
||||||
|
Users []webUser `yaml:"users"`
|
||||||
// AuthAttempts is the maximum number of failed login attempts a user
|
// AuthAttempts is the maximum number of failed login attempts a user
|
||||||
// can do before being blocked.
|
// can do before being blocked.
|
||||||
AuthAttempts uint `yaml:"auth_attempts"`
|
AuthAttempts uint `yaml:"auth_attempts"`
|
||||||
// AuthBlockMin is the duration, in minutes, of the block of new login
|
// AuthBlockMin is the duration, in minutes, of the block of new login
|
||||||
// attempts after AuthAttempts unsuccessful login attempts.
|
// attempts after AuthAttempts unsuccessful login attempts.
|
||||||
AuthBlockMin uint `yaml:"block_auth_min"`
|
AuthBlockMin uint `yaml:"block_auth_min"`
|
||||||
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
|
// ProxyURL is the address of proxy server for the internal HTTP client.
|
||||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
ProxyURL string `yaml:"http_proxy"`
|
||||||
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
|
// Language is a two-letter ISO 639-1 language code.
|
||||||
|
Language string `yaml:"language"`
|
||||||
|
// DebugPProf defines if the profiling HTTP handler will listen on :6060.
|
||||||
|
DebugPProf bool `yaml:"debug_pprof"`
|
||||||
|
|
||||||
// TTL for a web session (in hours)
|
// TTL for a web session (in hours)
|
||||||
// An active session is automatically refreshed once a day.
|
// An active session is automatically refreshed once a day.
|
||||||
|
@ -112,7 +121,7 @@ type configuration struct {
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Move all the filtering configuration fields into the
|
// TODO(e.burkov): Move all the filtering configuration fields into the
|
||||||
// only configuration subsection covering the changes with a single
|
// only configuration subsection covering the changes with a single
|
||||||
// migration.
|
// migration. Also keep the blocked services in mind.
|
||||||
Filters []filtering.FilterYAML `yaml:"filters"`
|
Filters []filtering.FilterYAML `yaml:"filters"`
|
||||||
WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"`
|
WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"`
|
||||||
UserRules []string `yaml:"user_rules"`
|
UserRules []string `yaml:"user_rules"`
|
||||||
|
@ -135,18 +144,26 @@ type configuration struct {
|
||||||
|
|
||||||
// field ordering is important -- yaml fields will mirror ordering from here
|
// field ordering is important -- yaml fields will mirror ordering from here
|
||||||
type dnsConfig struct {
|
type dnsConfig struct {
|
||||||
BindHosts []net.IP `yaml:"bind_hosts"`
|
BindHosts []netip.Addr `yaml:"bind_hosts"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
|
|
||||||
// time interval for statistics (in days)
|
// StatsInterval is the time interval for flushing statistics to the disk in
|
||||||
|
// days.
|
||||||
StatsInterval uint32 `yaml:"statistics_interval"`
|
StatsInterval uint32 `yaml:"statistics_interval"`
|
||||||
|
|
||||||
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
// QueryLogEnabled defines if the query log is enabled.
|
||||||
QueryLogFileEnabled bool `yaml:"querylog_file_enabled"` // if true, query log will be written to a file
|
QueryLogEnabled bool `yaml:"querylog_enabled"`
|
||||||
|
// QueryLogFileEnabled defines, if the query log is written to the file.
|
||||||
|
QueryLogFileEnabled bool `yaml:"querylog_file_enabled"`
|
||||||
// QueryLogInterval is the interval for query log's files rotation.
|
// QueryLogInterval is the interval for query log's files rotation.
|
||||||
QueryLogInterval timeutil.Duration `yaml:"querylog_interval"`
|
QueryLogInterval timeutil.Duration `yaml:"querylog_interval"`
|
||||||
QueryLogMemSize uint32 `yaml:"querylog_size_memory"` // number of entries kept in memory before they are flushed to disk
|
// QueryLogMemSize is the number of entries kept in memory before they are
|
||||||
AnonymizeClientIP bool `yaml:"anonymize_client_ip"` // anonymize clients' IP addresses in logs and stats
|
// flushed to disk.
|
||||||
|
QueryLogMemSize uint32 `yaml:"querylog_size_memory"`
|
||||||
|
|
||||||
|
// AnonymizeClientIP defines if clients' IP addresses should be anonymized
|
||||||
|
// in query log and statistics.
|
||||||
|
AnonymizeClientIP bool `yaml:"anonymize_client_ip"`
|
||||||
|
|
||||||
dnsforward.FilteringConfig `yaml:",inline"`
|
dnsforward.FilteringConfig `yaml:",inline"`
|
||||||
|
|
||||||
|
@ -211,12 +228,12 @@ type tlsConfigSettings struct {
|
||||||
var config = &configuration{
|
var config = &configuration{
|
||||||
BindPort: 3000,
|
BindPort: 3000,
|
||||||
BetaBindPort: 0,
|
BetaBindPort: 0,
|
||||||
BindHost: net.IP{0, 0, 0, 0},
|
BindHost: netip.IPv4Unspecified(),
|
||||||
AuthAttempts: 5,
|
AuthAttempts: 5,
|
||||||
AuthBlockMin: 15,
|
AuthBlockMin: 15,
|
||||||
WebSessionTTLHours: 30 * 24,
|
WebSessionTTLHours: 30 * 24,
|
||||||
DNS: dnsConfig{
|
DNS: dnsConfig{
|
||||||
BindHosts: []net.IP{{0, 0, 0, 0}},
|
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
|
||||||
Port: defaultPortDNS,
|
Port: defaultPortDNS,
|
||||||
StatsInterval: 1,
|
StatsInterval: 1,
|
||||||
QueryLogEnabled: true,
|
QueryLogEnabled: true,
|
||||||
|
|
|
@ -2,8 +2,8 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,11 +20,11 @@ import (
|
||||||
|
|
||||||
// appendDNSAddrs is a convenient helper for appending a formatted form of DNS
|
// appendDNSAddrs is a convenient helper for appending a formatted form of DNS
|
||||||
// addresses to a slice of strings.
|
// 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 {
|
for _, addr := range addrs {
|
||||||
var hostport string
|
var hostport string
|
||||||
if config.DNS.Port != defaultPortDNS {
|
if config.DNS.Port != defaultPortDNS {
|
||||||
hostport = netutil.JoinHostPort(addr.String(), config.DNS.Port)
|
hostport = netip.AddrPortFrom(addr, uint16(config.DNS.Port)).String()
|
||||||
} else {
|
} else {
|
||||||
hostport = addr.String()
|
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
|
// appendDNSAddrsWithIfaces formats and appends all DNS addresses from src to
|
||||||
// dst. It also adds the IP addresses of all network interfaces if src contains
|
// dst. It also adds the IP addresses of all network interfaces if src contains
|
||||||
// an unspecified IP address.
|
// 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
|
ifacesAdded := false
|
||||||
for _, h := range src {
|
for _, h := range src {
|
||||||
if !h.IsUnspecified() {
|
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.
|
// on, including the addresses on all interfaces in cases of unspecified IPs.
|
||||||
func collectDNSAddresses() (addrs []string, err error) {
|
func collectDNSAddresses() (addrs []string, err error) {
|
||||||
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
|
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
|
||||||
addrs = appendDNSAddrs(addrs, net.IP{127, 0, 0, 1})
|
addr := aghnet.IPv4Localhost()
|
||||||
|
|
||||||
|
addrs = appendDNSAddrs(addrs, addr)
|
||||||
} else {
|
} else {
|
||||||
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
|
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -64,7 +64,7 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkConfReqEnt struct {
|
type checkConfReqEnt struct {
|
||||||
IP net.IP `json:"ip"`
|
IP netip.Addr `json:"ip"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Autofix bool `json:"autofix"`
|
Autofix bool `json:"autofix"`
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
|
||||||
// unbound after install.
|
// 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
|
// validateDNS returns error if the DNS part of the initial configuration can't
|
||||||
|
@ -142,13 +142,13 @@ func (req *checkConfReq) validateDNS(
|
||||||
return false, err
|
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 {
|
if err != nil {
|
||||||
return false, err
|
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) {
|
if !aghnet.IsAddrInUse(err) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ func (req *checkConfReq) validateDNS(
|
||||||
log.Error("disabling DNSStubListener: %s", err)
|
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
|
canAutofix = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
|
||||||
// handleStaticIP - handles static IP request
|
// handleStaticIP - handles static IP request
|
||||||
// It either checks if we have a static IP
|
// It either checks if we have a static IP
|
||||||
// Or if set=true, it tries to set it
|
// 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{}
|
resp := staticIPJSON{}
|
||||||
|
|
||||||
interfaceName := aghnet.InterfaceByIP(ip)
|
interfaceName := aghnet.InterfaceByIP(ip)
|
||||||
|
@ -304,7 +304,7 @@ func disableDNSStubListener() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type applyConfigReqEnt struct {
|
type applyConfigReqEnt struct {
|
||||||
IP net.IP `json:"ip"`
|
IP netip.Addr `json:"ip"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,14 +397,14 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
|
@ -417,7 +417,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
Context.firstRun = false
|
Context.firstRun = false
|
||||||
config.BindHost = req.Web.IP
|
config.BindHost = req.Web.IP
|
||||||
config.BindPort = req.Web.Port
|
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
|
config.DNS.Port = req.DNS.Port
|
||||||
|
|
||||||
// TODO(e.burkov): StartMods() should be put in a separate goroutine at the
|
// TODO(e.burkov): StartMods() should be put in a separate goroutine at the
|
||||||
|
@ -490,9 +490,9 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
|
||||||
return nil, false, errors.Error("ports cannot be 0")
|
return nil, false, errors.Error("ports cannot be 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
restartHTTP = !config.BindHost.Equal(req.Web.IP) || config.BindPort != req.Web.Port
|
restartHTTP = config.BindHost != req.Web.IP || config.BindPort != req.Web.Port
|
||||||
if restartHTTP {
|
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 {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf(
|
return nil, false, fmt.Errorf(
|
||||||
"checking address %s:%d: %w",
|
"checking address %s:%d: %w",
|
||||||
|
@ -518,7 +518,7 @@ func (web *Web) registerInstallHandlers() {
|
||||||
// TODO(e.burkov): This should removed with the API v1 when the appropriate
|
// TODO(e.burkov): This should removed with the API v1 when the appropriate
|
||||||
// functionality will appear in default checkConfigReqEnt.
|
// functionality will appear in default checkConfigReqEnt.
|
||||||
type checkConfigReqEntBeta struct {
|
type checkConfigReqEntBeta struct {
|
||||||
IP []net.IP `json:"ip"`
|
IP []netip.Addr `json:"ip"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Autofix bool `json:"autofix"`
|
Autofix bool `json:"autofix"`
|
||||||
}
|
}
|
||||||
|
@ -590,7 +590,7 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ
|
||||||
// TODO(e.burkov): This should removed with the API v1 when the appropriate
|
// TODO(e.burkov): This should removed with the API v1 when the appropriate
|
||||||
// functionality will appear in default applyConfigReqEnt.
|
// functionality will appear in default applyConfigReqEnt.
|
||||||
type applyConfigReqEntBeta struct {
|
type applyConfigReqEntBeta struct {
|
||||||
IP []net.IP `json:"ip"`
|
IP []netip.Addr `json:"ip"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package home
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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 {
|
if ips == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpAddrs = make([]*net.TCPAddr, len(ips))
|
tcpAddrs = make([]*net.TCPAddr, 0, len(ips))
|
||||||
for i, ip := range ips {
|
for _, ip := range ips {
|
||||||
tcpAddrs[i] = &net.TCPAddr{
|
tcpAddrs = append(tcpAddrs, net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port))))
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcpAddrs
|
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 {
|
if ips == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
udpAddrs = make([]*net.UDPAddr, len(ips))
|
udpAddrs = make([]*net.UDPAddr, 0, len(ips))
|
||||||
for i, ip := range ips {
|
for _, ip := range ips {
|
||||||
udpAddrs[i] = &net.UDPAddr{
|
udpAddrs = append(udpAddrs, net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port))))
|
||||||
IP: ip,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return udpAddrs
|
return udpAddrs
|
||||||
|
@ -200,7 +195,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
dnsConf := config.DNS
|
dnsConf := config.DNS
|
||||||
hosts := dnsConf.BindHosts
|
hosts := dnsConf.BindHosts
|
||||||
if len(hosts) == 0 {
|
if len(hosts) == 0 {
|
||||||
hosts = []net.IP{{127, 0, 0, 1}}
|
hosts = []netip.Addr{aghnet.IPv4Localhost()}
|
||||||
}
|
}
|
||||||
|
|
||||||
newConf = dnsforward.ServerConfig{
|
newConf = dnsforward.ServerConfig{
|
||||||
|
@ -257,7 +252,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
return newConf, nil
|
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 == "" {
|
if tlsConf.DNSCryptConfigFile == "" {
|
||||||
return dnscc, errors.Error("no dnscrypt_config_file")
|
return dnscc, errors.Error("no dnscrypt_config_file")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -356,7 +357,7 @@ func setupConfig(opts options) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// override bind host/port from the console
|
// override bind host/port from the console
|
||||||
if opts.bindHost != nil {
|
if opts.bindHost.IsValid() {
|
||||||
config.BindHost = opts.bindHost
|
config.BindHost = opts.bindHost
|
||||||
}
|
}
|
||||||
if len(opts.pidFile) != 0 && writePIDFile(opts.pidFile) {
|
if len(opts.pidFile) != 0 && writePIDFile(opts.pidFile) {
|
||||||
|
@ -556,7 +557,7 @@ func checkPermissions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should check if AdGuard Home is able to bind to port 53
|
// 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 err != nil {
|
||||||
if errors.Is(err, os.ErrPermission) {
|
if errors.Is(err, os.ErrPermission) {
|
||||||
log.Fatal(`Permission check failed.
|
log.Fatal(`Permission check failed.
|
||||||
|
|
|
@ -3,12 +3,11 @@ package home
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
|
@ -28,7 +27,7 @@ func setupDNSIPs(t testing.TB) {
|
||||||
|
|
||||||
config = &configuration{
|
config = &configuration{
|
||||||
DNS: dnsConfig{
|
DNS: dnsConfig{
|
||||||
BindHosts: []net.IP{netutil.IPv4Zero()},
|
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
|
||||||
Port: defaultPortDNS,
|
Port: defaultPortDNS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -35,7 +35,7 @@ type options struct {
|
||||||
serviceControlAction string
|
serviceControlAction string
|
||||||
|
|
||||||
// bindHost is the address on which to serve the HTTP UI.
|
// bindHost is the address on which to serve the HTTP UI.
|
||||||
bindHost net.IP
|
bindHost netip.Addr
|
||||||
|
|
||||||
// bindPort is the port on which to serve the HTTP UI.
|
// bindPort is the port on which to serve the HTTP UI.
|
||||||
bindPort int
|
bindPort int
|
||||||
|
@ -130,14 +130,15 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||||
longName: "work-dir",
|
longName: "work-dir",
|
||||||
shortName: "w",
|
shortName: "w",
|
||||||
}, {
|
}, {
|
||||||
updateWithValue: func(o options, v string) (options, error) {
|
updateWithValue: func(o options, v string) (oo options, err error) {
|
||||||
o.bindHost = net.ParseIP(v)
|
o.bindHost, err = netip.ParseAddr(v)
|
||||||
return o, nil
|
|
||||||
|
return o, err
|
||||||
},
|
},
|
||||||
updateNoValue: nil,
|
updateNoValue: nil,
|
||||||
effect: nil,
|
effect: nil,
|
||||||
serialize: func(o options) (val string, ok bool) {
|
serialize: func(o options) (val string, ok bool) {
|
||||||
if o.bindHost == nil {
|
if !o.bindHost.IsValid() {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -56,11 +56,13 @@ func TestParseWorkDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseBindHost(t *testing.T) {
|
func TestParseBindHost(t *testing.T) {
|
||||||
assert.Nil(t, testParseOK(t).bindHost, "empty is not host")
|
wantAddr := netip.MustParseAddr("1.2.3.4")
|
||||||
assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
|
|
||||||
|
assert.Zero(t, testParseOK(t).bindHost, "empty is not host")
|
||||||
|
assert.Equal(t, wantAddr, testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
|
||||||
testParseParamMissing(t, "-h")
|
testParseParamMissing(t, "-h")
|
||||||
|
|
||||||
assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
|
assert.Equal(t, wantAddr, testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
|
||||||
testParseParamMissing(t, "--host")
|
testParseParamMissing(t, "--host")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,8 +151,8 @@ func TestOptsToArgs(t *testing.T) {
|
||||||
opts: options{workDir: "path"},
|
opts: options{workDir: "path"},
|
||||||
}, {
|
}, {
|
||||||
name: "bind_host",
|
name: "bind_host",
|
||||||
|
opts: options{bindHost: netip.MustParseAddr("1.2.3.4")},
|
||||||
args: []string{"-h", "1.2.3.4"},
|
args: []string{"-h", "1.2.3.4"},
|
||||||
opts: options{bindHost: net.IP{1, 2, 3, 4}},
|
|
||||||
}, {
|
}, {
|
||||||
name: "bind_port",
|
name: "bind_port",
|
||||||
args: []string{"-p", "666"},
|
args: []string{"-p", "666"},
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ type webConfig struct {
|
||||||
clientFS fs.FS
|
clientFS fs.FS
|
||||||
clientBetaFS fs.FS
|
clientBetaFS fs.FS
|
||||||
|
|
||||||
BindHost net.IP
|
BindHost netip.Addr
|
||||||
BindPort int
|
BindPort int
|
||||||
BetaBindPort int
|
BetaBindPort int
|
||||||
PortHTTPS int
|
PortHTTPS int
|
||||||
|
@ -135,8 +135,11 @@ func newWeb(conf *webConfig) (w *Web) {
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Adapt for HTTP/3.
|
// TODO(a.garipov): Adapt for HTTP/3.
|
||||||
func webCheckPortAvailable(port int) (ok bool) {
|
func webCheckPortAvailable(port int) (ok bool) {
|
||||||
return Context.web.httpsServer.server != nil ||
|
if Context.web.httpsServer.server != nil {
|
||||||
aghnet.CheckPort("tcp", config.BindHost, port) == nil
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return aghnet.CheckPort("tcp", netip.AddrPortFrom(config.BindHost, uint16(port))) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
|
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
|
||||||
|
|
Loading…
Reference in New Issue