Skip to content

Commit

Permalink
Pull request: 3225 bsd dhcp
Browse files Browse the repository at this point in the history
Merge in DNS/adguard-home from 3225-bsd-dhcp to master

Closes AdguardTeam#3225.
Closes AdguardTeam#3417.

Squashed commit of the following:

commit e7ea691
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Aug 12 17:02:02 2021 +0300

    all: imp code, docs

commit 5b598fc
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 e3e1577
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Aug 12 14:15:10 2021 +0300

    dhcpd: imp checkother

commit 3cc8b05
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Aug 11 13:20:18 2021 +0300

    all: imp bsd support
  • Loading branch information
EugeneOne1 authored and heyxkhoa committed Mar 17, 2023
1 parent bdd3fa7 commit 14fda03
Show file tree
Hide file tree
Showing 21 changed files with 518 additions and 268 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ and this project adheres to
- Settable timeouts for querying the upstream servers ([#2280]).
- Configuration file parameters to change group and user ID on startup on Unix
([#2763]).
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439]).
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225]).
- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
([#3172]).
- `darwin/arm64` support ([#2443]).
Expand Down Expand Up @@ -64,6 +64,7 @@ and this project adheres to

### Fixed

- Discovering other DHCP servers on `darwin` and `freebsd` ([#3417]).
- Switching listening address to unspecified one when bound to a single
specified IPv4 address on Darwin (macOS) ([#2807]).
- Incomplete HTTP response for static IP address.
Expand Down Expand Up @@ -112,13 +113,15 @@ and this project adheres to
[#3194]: https://github.com/AdguardTeam/AdGuardHome/issues/3194
[#3198]: https://github.com/AdguardTeam/AdGuardHome/issues/3198
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
[#3225]: https://github.com/AdguardTeam/AdGuardHome/issues/3225
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
[#3335]: https://github.com/AdguardTeam/AdGuardHome/issues/3335
[#3343]: https://github.com/AdguardTeam/AdGuardHome/issues/3343
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351
[#3372]: https://github.com/AdguardTeam/AdGuardHome/issues/3372
[#3417]: https://github.com/AdguardTeam/AdGuardHome/issues/3417



Expand Down
6 changes: 6 additions & 0 deletions internal/aghnet/dhcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package aghnet

// CheckOtherDHCP tries to discover another DHCP server in the network.
func CheckOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
return checkOtherDHCP(ifaceName)
}
194 changes: 119 additions & 75 deletions internal/dhcpd/checkother.go → internal/aghnet/dhcp_unix.go
Original file line number Diff line number Diff line change
@@ -1,95 +1,132 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris

package dhcpd
package aghnet

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) {
// defaultDiscoverTime is the
const defaultDiscoverTime = 3 * time.Second

func checkOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
}
err = fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
err4, err6 = err, 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)
return false, false, err4, err6
}

// 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")
ok4, err4 = checkOtherDHCPv4(iface)
ok6, err6 = checkOtherDHCPv6(iface)

return ok4, ok6, err4, err6
}

// ifaceIPv4Subnet returns the first suitable IPv4 subnetwork iface has.
func ifaceIPv4Subnet(iface *net.Interface) (subnet *net.IPNet, err error) {
var addrs []net.Addr
if addrs, err = iface.Addrs(); err != nil {
return nil, err
}

srcIP := ifaceIPNet[0]
src := netutil.JoinHostPort(srcIP.String(), 68)
dst := "255.255.255.255:67"
for _, a := range addrs {
switch a := a.(type) {
case *net.IPAddr:
subnet = &net.IPNet{
IP: a.IP,
Mask: a.IP.DefaultMask(),
}
case *net.IPNet:
subnet = a
default:
continue
}

hostname, _ := os.Hostname()
if ip4 := subnet.IP.To4(); ip4 != nil {
subnet.IP = ip4

req, err := dhcpv4.NewDiscovery(iface.HardwareAddr)
if err != nil {
return false, fmt.Errorf("dhcpv4.NewDiscovery: %w", err)
return subnet, nil
}
}
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)
return nil, fmt.Errorf("interface %s has no ipv4 addresses", iface.Name)
}

// checkOtherDHCPv4 sends a DHCP request to the specified network interface, and
// waits for a response for a period defined by defaultDiscoverTime.
func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
var subnet *net.IPNet
if subnet, err = ifaceIPv4Subnet(iface); err != nil {
return false, err
}

// Resolve broadcast addr.
dst := netutil.IPPort{
IP: BroadcastFromIPNet(subnet),
Port: 67,
}.String()
var dstAddr *net.UDPAddr
if dstAddr, err = net.ResolveUDPAddr("udp4", dst); err != nil {
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
}

if !udpAddr.IP.To4().Equal(srcIP) {
return false, fmt.Errorf("resolved UDP address is not %s: %w", src, err)
var hostname string
if hostname, err = os.Hostname(); err != nil {
return false, fmt.Errorf("couldn't get hostname: %w", 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)
return discover4(iface, dstAddr, hostname)
}

func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok bool, err error) {
var req *dhcpv4.DHCPv4
if req, err = dhcpv4.NewDiscovery(iface.HardwareAddr); err != nil {
return false, fmt.Errorf("dhcpv4.NewDiscovery: %w", err)
}

// bind to 0.0.0.0:68
log.Tracef("Listening to udp4 %+v", udpAddr)
c, err := nclient4.NewRawUDPConn(ifaceName, 68)
if err != nil {
req.Options.Update(dhcpv4.OptClientIdentifier(iface.HardwareAddr))
req.Options.Update(dhcpv4.OptHostName(hostname))
req.SetBroadcast()

// Bind to 0.0.0.0:68.
//
// On OpenBSD binding to the port 68 competes with dhclient's binding,
// so that all incoming packets are ignored and the discovering process
// is spoiled.
//
// It's also known that listening on the specified interface's address
// ignores broadcasted packets when reading.
var c net.PacketConn
if c, err = net.ListenPacket("udp4", ":68"); err != nil {
return false, fmt.Errorf("couldn't listen on :68: %w", err)
}
if c != nil {
defer func() { err = errors.WithDeferred(err, c.Close()) }()
}
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)
// Send to resolved broadcast.
if _, err = c.WriteTo(req.ToBytes(), dstAddr); err != nil {
return false, fmt.Errorf("couldn't send a packet to %s: %w", dstAddr, err)
}

for {
if err = c.SetDeadline(time.Now().Add(defaultDiscoverTime)); err != nil {
return false, fmt.Errorf("setting deadline: %w", err)
}

var next bool
ok, next, err = tryConn4(req, c, iface)
if next {
Expand All @@ -116,12 +153,10 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
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 n > 0 {
log.Debug("received %d bytes: %v", n, b)
}
if err != nil {
if isTimeout(err) {
log.Debug("dhcpv4: didn't receive dhcp response")
Expand Down Expand Up @@ -159,31 +194,21 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
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)
// checkOtherDHCPv6 sends a DHCP request to the specified network interface, and
// waits for a response for a period defined by defaultDiscoverTime.
func checkOtherDHCPv6(iface *net.Interface) (ok bool, err error) {
ifaceIPNet, err := IfaceIPAddrs(iface, IPVersion6)
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)
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", iface.Name, err)
}
if len(ifaceIPNet) == 0 {
return false, fmt.Errorf("interface %s has no ipv6 addresses", ifaceName)
return false, fmt.Errorf("interface %s has no ipv6 addresses", iface.Name)
}

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)
Expand All @@ -198,18 +223,25 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (ok bool, err error) {
return false, fmt.Errorf("dhcpv6: Couldn't resolve UDP address %s: %w", dst, err)
}

return discover6(iface, udpAddr, dstAddr)
}

func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, err error) {
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
if err != nil {
return false, fmt.Errorf("dhcpv6: dhcpv6.NewSolicit: %w", err)
}

log.Debug("DHCPv6: Listening to udp6 %+v", udpAddr)
c, err := nclient6.NewIPv6UDPConn(ifaceName, dhcpv6.DefaultClientPort)
c, err := nclient6.NewIPv6UDPConn(iface.Name, 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()) }()
}
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)
return false, fmt.Errorf("dhcpv6: Couldn't send a packet to %s: %w", dstAddr, err)
}

for {
Expand Down Expand Up @@ -288,3 +320,15 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)

return true, false, nil
}

// isTimeout returns true if err is an operation timeout error from net package.
//
// TODO(e.burkov): Consider moving into netutil.
func isTimeout(err error) (ok bool) {
var operr *net.OpError
if errors.As(err, &operr) {
return operr.Timeout()
}

return false
}
13 changes: 13 additions & 0 deletions internal/aghnet/dhcp_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build windows
// +build windows

package aghnet

import "github.com/AdguardTeam/AdGuardHome/internal/aghos"

func checkOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
return false,
false,
aghos.Unsupported("CheckIfOtherDHCPServersPresentV4"),
aghos.Unsupported("CheckIfOtherDHCPServersPresentV6")
}
Loading

0 comments on commit 14fda03

Please sign in to comment.