Pull request: 3225 bsd dhcp
Merge in DNS/adguard-home from 3225-bsd-dhcp to master Closes #3225. Closes #3417. Squashed commit of the following: commit e7ea691824c7ebc8cafd8c9e206679346cbc8592 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Aug 12 17:02:02 2021 +0300 all: imp code, docs commit 5b598fc18a9b69a0256569f4c691bb6a2193dfbd Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Aug 12 16:28:12 2021 +0300 all: mv logic, imp code, docs, log changes commit e3e1577a668fe3e5c61d075c390e4bd7268181ba Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Aug 12 14:15:10 2021 +0300 dhcpd: imp checkother commit 3cc8b058195c30a7ef0b7741ee8463270d9e47ff Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Aug 11 13:20:18 2021 +0300 all: imp bsd support
This commit is contained in:
@@ -1,290 +0,0 @@
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
)
|
||||
|
||||
// CheckIfOtherDHCPServersPresentV4 sends a DHCP request to the specified network interface,
|
||||
// and waits for a response for a period defined by defaultDiscoverTime
|
||||
func CheckIfOtherDHCPServersPresentV4(ifaceName string) (ok bool, err error) {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
||||
}
|
||||
if len(ifaceIPNet) == 0 {
|
||||
return false, fmt.Errorf("interface %s has no ipv4 addresses", ifaceName)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Find out what this is about. Perhaps this
|
||||
// information is outdated or at least incomplete.
|
||||
if runtime.GOOS == "darwin" {
|
||||
return false, aghos.Unsupported("CheckIfOtherDHCPServersPresentV4")
|
||||
}
|
||||
|
||||
srcIP := ifaceIPNet[0]
|
||||
src := netutil.JoinHostPort(srcIP.String(), 68)
|
||||
dst := "255.255.255.255:67"
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
|
||||
req, err := dhcpv4.NewDiscovery(iface.HardwareAddr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv4.NewDiscovery: %w", err)
|
||||
}
|
||||
req.Options.Update(dhcpv4.OptClientIdentifier(iface.HardwareAddr))
|
||||
req.Options.Update(dhcpv4.OptHostName(hostname))
|
||||
|
||||
// resolve 0.0.0.0:68
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", src)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", src, err)
|
||||
}
|
||||
|
||||
if !udpAddr.IP.To4().Equal(srcIP) {
|
||||
return false, fmt.Errorf("resolved UDP address is not %s: %w", src, err)
|
||||
}
|
||||
|
||||
// resolve 255.255.255.255:67
|
||||
dstAddr, err := net.ResolveUDPAddr("udp4", dst)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
|
||||
}
|
||||
|
||||
// bind to 0.0.0.0:68
|
||||
log.Tracef("Listening to udp4 %+v", udpAddr)
|
||||
c, err := nclient4.NewRawUDPConn(ifaceName, 68)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't listen on :68: %w", err)
|
||||
}
|
||||
if c != nil {
|
||||
defer func() { err = errors.WithDeferred(err, c.Close()) }()
|
||||
}
|
||||
|
||||
// send to 255.255.255.255:67
|
||||
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't send a packet to %s: %w", dst, err)
|
||||
}
|
||||
|
||||
for {
|
||||
var next bool
|
||||
ok, next, err = tryConn4(req, c, iface)
|
||||
if next {
|
||||
if err != nil {
|
||||
log.Debug("dhcpv4: trying a connection: %s", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Refactor further. Inspect error handling, remove parameter
|
||||
// next, address the TODO, merge with tryConn6, etc.
|
||||
func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) {
|
||||
// TODO: replicate dhclient's behavior of retrying several times with
|
||||
// progressively longer timeouts.
|
||||
log.Tracef("dhcpv4: waiting %v for an answer", defaultDiscoverTime)
|
||||
|
||||
b := make([]byte, 1500)
|
||||
err = c.SetDeadline(time.Now().Add(defaultDiscoverTime))
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("setting deadline: %w", err)
|
||||
}
|
||||
|
||||
n, _, err := c.ReadFrom(b)
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
log.Debug("dhcpv4: didn't receive dhcp response")
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
return false, false, fmt.Errorf("receiving packet: %w", err)
|
||||
}
|
||||
|
||||
log.Tracef("dhcpv4: received packet, %d bytes", n)
|
||||
|
||||
response, err := dhcpv4.FromBytes(b[:n])
|
||||
if err != nil {
|
||||
log.Debug("dhcpv4: encoding: %s", err)
|
||||
|
||||
return false, true, err
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: received message from server: %s", response.Summary())
|
||||
|
||||
if !(response.OpCode == dhcpv4.OpcodeBootReply &&
|
||||
response.HWType == iana.HWTypeEthernet &&
|
||||
bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) &&
|
||||
bytes.Equal(response.TransactionID[:], req.TransactionID[:]) &&
|
||||
response.Options.Has(dhcpv4.OptionDHCPMessageType)) {
|
||||
|
||||
log.Debug("dhcpv4: received message from server doesn't match our request")
|
||||
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
log.Tracef("dhcpv4: the packet is from an active dhcp server")
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// CheckIfOtherDHCPServersPresentV6 sends a DHCP request to the specified network interface,
|
||||
// and waits for a response for a period defined by defaultDiscoverTime
|
||||
func CheckIfOtherDHCPServersPresentV6(ifaceName string) (ok bool, err error) {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
||||
}
|
||||
if len(ifaceIPNet) == 0 {
|
||||
return false, fmt.Errorf("interface %s has no ipv6 addresses", ifaceName)
|
||||
}
|
||||
|
||||
srcIP := ifaceIPNet[0]
|
||||
src := netutil.JoinHostPort(srcIP.String(), 546)
|
||||
dst := "[ff02::1:2]:547"
|
||||
|
||||
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: dhcpv6.NewSolicit: %w", err)
|
||||
}
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp6", src)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: Couldn't resolve UDP address %s: %w", src, err)
|
||||
}
|
||||
|
||||
if !udpAddr.IP.To16().Equal(srcIP) {
|
||||
return false, fmt.Errorf("dhcpv6: Resolved UDP address is not %s: %w", src, err)
|
||||
}
|
||||
|
||||
dstAddr, err := net.ResolveUDPAddr("udp6", dst)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: Couldn't resolve UDP address %s: %w", dst, err)
|
||||
}
|
||||
|
||||
log.Debug("DHCPv6: Listening to udp6 %+v", udpAddr)
|
||||
c, err := nclient6.NewIPv6UDPConn(ifaceName, dhcpv6.DefaultClientPort)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: Couldn't listen on :546: %w", err)
|
||||
}
|
||||
if c != nil {
|
||||
defer func() { err = errors.WithDeferred(err, c.Close()) }()
|
||||
}
|
||||
|
||||
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("dhcpv6: Couldn't send a packet to %s: %w", dst, err)
|
||||
}
|
||||
|
||||
for {
|
||||
var next bool
|
||||
ok, next, err = tryConn6(req, c)
|
||||
if next {
|
||||
if err != nil {
|
||||
log.Debug("dhcpv6: trying a connection: %s", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(a.garipov): See the comment on tryConn4. Sigh…
|
||||
func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) {
|
||||
// TODO: replicate dhclient's behavior of retrying several times with
|
||||
// progressively longer timeouts.
|
||||
log.Tracef("dhcpv6: waiting %v for an answer", defaultDiscoverTime)
|
||||
|
||||
b := make([]byte, 4096)
|
||||
err = c.SetDeadline(time.Now().Add(defaultDiscoverTime))
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("setting deadline: %w", err)
|
||||
}
|
||||
|
||||
n, _, err := c.ReadFrom(b)
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
log.Debug("dhcpv6: didn't receive dhcp response")
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
return false, false, fmt.Errorf("receiving packet: %w", err)
|
||||
}
|
||||
|
||||
log.Tracef("dhcpv6: received packet, %d bytes", n)
|
||||
|
||||
response, err := dhcpv6.FromBytes(b[:n])
|
||||
if err != nil {
|
||||
log.Debug("dhcpv6: encoding: %s", err)
|
||||
|
||||
return false, true, err
|
||||
}
|
||||
|
||||
log.Debug("dhcpv6: received message from server: %s", response.Summary())
|
||||
|
||||
cid := req.Options.ClientID()
|
||||
msg, err := response.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Debug("dhcpv6: resp.GetInnerMessage(): %s", err)
|
||||
|
||||
return false, true, err
|
||||
}
|
||||
|
||||
rcid := msg.Options.ClientID()
|
||||
if !(response.Type() == dhcpv6.MessageTypeAdvertise &&
|
||||
msg.TransactionID == req.TransactionID &&
|
||||
rcid != nil &&
|
||||
cid.Equal(*rcid)) {
|
||||
|
||||
log.Debug("dhcpv6: received message from server doesn't match our request")
|
||||
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
log.Tracef("dhcpv6: the packet is from an active dhcp server")
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dhcpd
|
||||
|
||||
import "github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
|
||||
func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||
return false, aghos.Unsupported("CheckIfOtherDHCPServersPresentV4")
|
||||
}
|
||||
|
||||
func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||
return false, aghos.Unsupported("CheckIfOtherDHCPServersPresentV6")
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDiscoverTime = time.Second * 3
|
||||
// leaseExpireStatic is used to define the Expiry field for static
|
||||
// leases.
|
||||
//
|
||||
|
||||
@@ -6,14 +6,6 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func isTimeout(err error) bool {
|
||||
operr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return operr.Timeout()
|
||||
}
|
||||
|
||||
func tryTo4(ip net.IP) (ip4 net.IP, err error) {
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("%v is not an IP address", ip)
|
||||
|
||||
@@ -398,60 +398,63 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||
log.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
interfaceName := strings.TrimSpace(string(body))
|
||||
if interfaceName == "" {
|
||||
ifaceName := strings.TrimSpace(string(body))
|
||||
if ifaceName == "" {
|
||||
msg := "empty interface name specified"
|
||||
log.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result := dhcpSearchResult{
|
||||
V4: dhcpSearchV4Result{
|
||||
OtherServer: dhcpSearchOtherResult{},
|
||||
OtherServer: dhcpSearchOtherResult{
|
||||
Found: "no",
|
||||
},
|
||||
StaticIP: dhcpStaticIPStatus{
|
||||
Static: "yes",
|
||||
},
|
||||
},
|
||||
V6: dhcpSearchV6Result{
|
||||
OtherServer: dhcpSearchOtherResult{},
|
||||
OtherServer: dhcpSearchOtherResult{
|
||||
Found: "no",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
|
||||
|
||||
isStaticIP, err := aghnet.IfaceHasStaticIP(interfaceName)
|
||||
if err != nil {
|
||||
if isStaticIP, serr := aghnet.IfaceHasStaticIP(ifaceName); serr != nil {
|
||||
result.V4.StaticIP.Static = "error"
|
||||
result.V4.StaticIP.Error = err.Error()
|
||||
result.V4.StaticIP.Error = serr.Error()
|
||||
} else if !isStaticIP {
|
||||
result.V4.StaticIP.Static = "no"
|
||||
result.V4.StaticIP.IP = aghnet.GetSubnet(interfaceName).String()
|
||||
// TODO(e.burkov): The returned IP should only be of version 4.
|
||||
result.V4.StaticIP.IP = aghnet.GetSubnet(ifaceName).String()
|
||||
}
|
||||
|
||||
if found4 {
|
||||
result.V4.OtherServer.Found = "yes"
|
||||
} else if err4 != nil {
|
||||
found4, found6, err4, err6 := aghnet.CheckOtherDHCP(ifaceName)
|
||||
if err4 != nil {
|
||||
result.V4.OtherServer.Found = "error"
|
||||
result.V4.OtherServer.Error = err4.Error()
|
||||
} else if found4 {
|
||||
result.V4.OtherServer.Found = "yes"
|
||||
}
|
||||
|
||||
found6, err6 := CheckIfOtherDHCPServersPresentV6(interfaceName)
|
||||
|
||||
if found6 {
|
||||
result.V6.OtherServer.Found = "yes"
|
||||
} else if err6 != nil {
|
||||
if err6 != nil {
|
||||
result.V6.OtherServer.Found = "error"
|
||||
result.V6.OtherServer.Error = err6.Error()
|
||||
} else if found6 {
|
||||
result.V6.OtherServer.Found = "yes"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(result)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,7 +971,12 @@ func (s *v4Server) Start() (err error) {
|
||||
|
||||
log.Debug("dhcpv4: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
|
||||
iface,
|
||||
aghnet.IPVersion4,
|
||||
defaultMaxAttempts,
|
||||
defaultBackoff,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("interface %s: %w", ifaceName, err)
|
||||
}
|
||||
@@ -1061,12 +1066,7 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
||||
IP: routerIP,
|
||||
Mask: subnetMask,
|
||||
}
|
||||
|
||||
bcastIP := netutil.CloneIP(routerIP)
|
||||
for i, b := range subnetMask {
|
||||
bcastIP[i] |= ^b
|
||||
}
|
||||
s.conf.broadcastIP = bcastIP
|
||||
s.conf.broadcastIP = aghnet.BroadcastFromIPNet(s.conf.subnet)
|
||||
|
||||
s.conf.ipRange, err = newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,125 +1,12 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// ipVersion is a documentational alias for int. Use it when the integer means
|
||||
// IP version.
|
||||
type ipVersion = int
|
||||
|
||||
// IP version constants.
|
||||
const (
|
||||
ipVersion4 ipVersion = 4
|
||||
ipVersion6 ipVersion = 6
|
||||
)
|
||||
|
||||
// netIface is the interface for network interface methods.
|
||||
type netIface interface {
|
||||
Addrs() ([]net.Addr, error)
|
||||
}
|
||||
|
||||
// ifaceIPAddrs returns the interface's IP addresses.
|
||||
func ifaceIPAddrs(iface netIface, ipv ipVersion) (ips []net.IP, err error) {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
var ip net.IP
|
||||
switch a := a.(type) {
|
||||
case *net.IPAddr:
|
||||
ip = a.IP
|
||||
case *net.IPNet:
|
||||
ip = a.IP
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Assume that net.(*Interface).Addrs can only return valid IPv4
|
||||
// and IPv6 addresses. Thus, if it isn't an IPv4 address, it
|
||||
// must be an IPv6 one.
|
||||
switch ipv {
|
||||
case ipVersion4:
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ips = append(ips, ip4)
|
||||
}
|
||||
case ipVersion6:
|
||||
if ip6 := ip.To4(); ip6 == nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid ip version %d", ipv)
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// Currently used defaults for ifaceDNSAddrs.
|
||||
const (
|
||||
defaultMaxAttempts int = 10
|
||||
|
||||
defaultBackoff time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// ifaceDNSIPAddrs returns IP addresses of the interface suitable to send to
|
||||
// clients as DNS addresses. If err is nil, addrs contains either no addresses
|
||||
// or at least two.
|
||||
//
|
||||
// It makes up to maxAttempts attempts to get the addresses if there are none,
|
||||
// each time using the provided backoff. Sometimes an interface needs a few
|
||||
// seconds to really ititialize.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2304.
|
||||
func ifaceDNSIPAddrs(
|
||||
iface netIface,
|
||||
ipv ipVersion,
|
||||
maxAttempts int,
|
||||
backoff time.Duration,
|
||||
) (addrs []net.IP, err error) {
|
||||
var n int
|
||||
for n = 1; n <= maxAttempts; n++ {
|
||||
addrs, err = ifaceIPAddrs(iface, ipv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting ip addrs: %w", err)
|
||||
}
|
||||
|
||||
if len(addrs) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
log.Debug("dhcpv%d: attempt %d: no ip addresses", ipv, n)
|
||||
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
|
||||
switch len(addrs) {
|
||||
case 0:
|
||||
// Don't return errors in case the users want to try and enable
|
||||
// the DHCP server later.
|
||||
t := time.Duration(n) * backoff
|
||||
log.Error("dhcpv%d: no ip for iface after %d attempts and %s", ipv, n, t)
|
||||
|
||||
return nil, nil
|
||||
case 1:
|
||||
// Some Android devices use 8.8.8.8 if there is not a secondary
|
||||
// DNS server. Fix that by setting the secondary DNS address to
|
||||
// the same address.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
|
||||
log.Debug("dhcpv%d: setting secondary dns ip to itself", ipv)
|
||||
addrs = append(addrs, addrs[0])
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
log.Debug("dhcpv%d: got addresses %s after %d attempts", ipv, addrs, n)
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeIface struct {
|
||||
addrs []net.Addr
|
||||
err error
|
||||
}
|
||||
|
||||
// Addrs implements the netIface interface for *fakeIface.
|
||||
func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||
if iface.err != nil {
|
||||
return nil, iface.err
|
||||
}
|
||||
|
||||
return iface.addrs, nil
|
||||
}
|
||||
|
||||
func TestIfaceIPAddrs(t *testing.T) {
|
||||
const errTest errors.Error = "test error"
|
||||
|
||||
ip4 := net.IP{1, 2, 3, 4}
|
||||
addr4 := &net.IPNet{IP: ip4}
|
||||
|
||||
ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}
|
||||
addr6 := &net.IPNet{IP: ip6}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
iface netIface
|
||||
ipv ipVersion
|
||||
want []net.IP
|
||||
wantErr error
|
||||
}{{
|
||||
name: "ipv4_success",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv4_success_with_ipv6",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv4_error",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||
ipv: ipVersion4,
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}, {
|
||||
name: "ipv6_success",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv6_success_with_ipv4",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv6_error",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||
ipv: ipVersion6,
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv)
|
||||
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type waitingFakeIface struct {
|
||||
addrs []net.Addr
|
||||
err error
|
||||
n int
|
||||
}
|
||||
|
||||
// Addrs implements the netIface interface for *waitingFakeIface.
|
||||
func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||
if iface.err != nil {
|
||||
return nil, iface.err
|
||||
}
|
||||
|
||||
if iface.n == 0 {
|
||||
return iface.addrs, nil
|
||||
}
|
||||
|
||||
iface.n--
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestIfaceDNSIPAddrs(t *testing.T) {
|
||||
const errTest errors.Error = "test error"
|
||||
|
||||
ip4 := net.IP{1, 2, 3, 4}
|
||||
addr4 := &net.IPNet{IP: ip4}
|
||||
|
||||
ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}
|
||||
addr6 := &net.IPNet{IP: ip6}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
iface netIface
|
||||
ipv ipVersion
|
||||
want []net.IP
|
||||
wantErr error
|
||||
}{{
|
||||
name: "ipv4_success",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4, ip4},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv4_success_with_ipv6",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4, ip4},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv4_error",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||
ipv: ipVersion4,
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}, {
|
||||
name: "ipv4_wait",
|
||||
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4, ip4},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv6_success",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6, ip6},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv6_success_with_ipv4",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6, ip6},
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "ipv6_error",
|
||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||
ipv: ipVersion6,
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}, {
|
||||
name: "ipv6_wait",
|
||||
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6, ip6},
|
||||
wantErr: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
||||
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -607,7 +608,12 @@ func (s *v6Server) Start() (err error) {
|
||||
|
||||
log.Debug("dhcpv6: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion6, defaultMaxAttempts, defaultBackoff)
|
||||
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
|
||||
iface,
|
||||
aghnet.IPVersion6,
|
||||
defaultMaxAttempts,
|
||||
defaultBackoff,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("interface %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user