- /control/dhcp/find_active_dhcp: fix DHCP server detection

Before this patch we couldn't receive incoming DHCP packets.
Now we bind() to 0.0.0.0 and set the required network interface
 using SO_BINDTODEVICE option.

As an improvement, we now wait until a reply to our request is
 received and skip all unknown packets.
This commit is contained in:
Simon Zolin 2019-04-16 17:36:34 +03:00
parent 69c5f175e8
commit c5ed6da5bd
3 changed files with 106 additions and 40 deletions

View File

@ -1,6 +1,7 @@
package dhcpd package dhcpd
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@ -11,6 +12,7 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/krolaw/dhcp4" "github.com/krolaw/dhcp4"
"golang.org/x/net/ipv4"
) )
// CheckIfOtherDHCPServersPresent sends a DHCP request to the specified network interface, // CheckIfOtherDHCPServersPresent sends a DHCP request to the specified network interface,
@ -32,13 +34,13 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
dst := "255.255.255.255:67" dst := "255.255.255.255:67"
// form a DHCP request packet, try to emulate existing client as much as possible // form a DHCP request packet, try to emulate existing client as much as possible
xID := make([]byte, 8) xID := make([]byte, 4)
n, err := rand.Read(xID) n, err := rand.Read(xID)
if n != 8 && err == nil { if n != 4 && err == nil {
err = fmt.Errorf("Generated less than 8 bytes") err = fmt.Errorf("Generated less than 4 bytes")
} }
if err != nil { if err != nil {
return false, wrapErrPrint(err, "Couldn't generate 8 random bytes") return false, wrapErrPrint(err, "Couldn't generate random bytes")
} }
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
@ -89,58 +91,63 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
// bind to 0.0.0.0:68 // bind to 0.0.0.0:68
log.Tracef("Listening to udp4 %+v", udpAddr) log.Tracef("Listening to udp4 %+v", udpAddr)
c, err := net.ListenPacket("udp4", src) c, err := newBroadcastPacketConn(net.IPv4(0, 0, 0, 0), 68, ifaceName)
if c != nil { if c != nil {
defer c.Close() defer c.Close()
} }
// spew.Dump(c, err) // spew.Dump(c, err)
// spew.Printf("net.ListenUDP returned %v, %v\n", c, err) // spew.Printf("net.ListenUDP returned %v, %v\n", c, err)
if err != nil { if err != nil {
return false, wrapErrPrint(err, "Couldn't listen to %s", src) return false, wrapErrPrint(err, "Couldn't listen on :68")
} }
// send to 255.255.255.255:67 // send to 255.255.255.255:67
_, err = c.WriteTo(packet, dstAddr) cm := ipv4.ControlMessage{}
_, err = c.WriteTo(packet, &cm, dstAddr)
// spew.Dump(n, err) // spew.Dump(n, err)
if err != nil { if err != nil {
return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst) return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst)
} }
// wait for answer for {
log.Tracef("Waiting %v for an answer", defaultDiscoverTime) // wait for answer
// TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts log.Tracef("Waiting %v for an answer", defaultDiscoverTime)
b := make([]byte, 1500) // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts
c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) b := make([]byte, 1500)
n, _, err = c.ReadFrom(b) _ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
if isTimeout(err) { n, _, _, err = c.ReadFrom(b)
// timed out -- no DHCP servers if isTimeout(err) {
return false, nil // timed out -- no DHCP servers
} return false, nil
if err != nil { }
return false, wrapErrPrint(err, "Couldn't receive packet") if err != nil {
} return false, wrapErrPrint(err, "Couldn't receive packet")
if n > 0 { }
b = b[:n] // spew.Dump(n, fromAddr, err, b)
}
// spew.Dump(n, fromAddr, err, b)
if n < 240 { log.Tracef("Received packet (%v bytes)", n)
// packet too small for dhcp
return false, wrapErrPrint(err, "got packet that's too small for DHCP")
}
response := dhcp4.Packet(b[:n]) if n < 240 {
if response.HLen() > 16 { // packet too small for dhcp
// invalid size continue
return false, wrapErrPrint(err, "got malformed packet with HLen() > 16") }
}
parsedOptions := response.ParseOptions() response := dhcp4.Packet(b[:n])
_, ok := parsedOptions[dhcp4.OptionDHCPMessageType] if response.OpCode() != dhcp4.BootReply ||
if !ok { response.HType() != 1 /*Ethernet*/ ||
return false, wrapErrPrint(err, "got malformed packet without DHCP message type") response.HLen() > 16 ||
} !bytes.Equal(response.CHAddr(), iface.HardwareAddr) ||
!bytes.Equal(response.XId(), xID) {
continue
}
// that's a DHCP server there parsedOptions := response.ParseOptions()
return true, nil if t := parsedOptions[dhcp4.OptionDHCPMessageType]; len(t) != 1 {
continue //packet without DHCP message type
}
log.Tracef("The packet is from an active DHCP server")
// that's a DHCP server there
return true, nil
}
} }

47
dhcpd/os_unix.go Normal file
View File

@ -0,0 +1,47 @@
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package dhcpd
import (
"net"
"os"
"syscall"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) {
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
if err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return nil, err
}
if err := syscall.SetsockoptString(s, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifname); err != nil {
return nil, err
}
addr := syscall.SockaddrInet4{Port: port}
copy(addr.Addr[:], bindAddr.To4())
err = syscall.Bind(s, &addr)
if err != nil {
syscall.Close(s)
return nil, err
}
f := os.NewFile(uintptr(s), "")
c, err := net.FilePacketConn(f)
f.Close()
if err != nil {
return nil, err
}
p := ipv4.NewPacketConn(c)
return p, nil
}

12
dhcpd/os_windows.go Normal file
View File

@ -0,0 +1,12 @@
package dhcpd
import (
"net"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) {
return nil, nil
}