From c5ed6da5bdcdb07fee2f28df14f6bb4961f70add Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 16 Apr 2019 17:36:34 +0300 Subject: [PATCH 1/3] - /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. --- dhcpd/check_other_dhcp.go | 87 +++++++++++++++++++++------------------ dhcpd/os_unix.go | 47 +++++++++++++++++++++ dhcpd/os_windows.go | 12 ++++++ 3 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 dhcpd/os_unix.go create mode 100644 dhcpd/os_windows.go diff --git a/dhcpd/check_other_dhcp.go b/dhcpd/check_other_dhcp.go index 46fbec57..c40e771d 100644 --- a/dhcpd/check_other_dhcp.go +++ b/dhcpd/check_other_dhcp.go @@ -1,6 +1,7 @@ package dhcpd import ( + "bytes" "crypto/rand" "encoding/binary" "fmt" @@ -11,6 +12,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/krolaw/dhcp4" + "golang.org/x/net/ipv4" ) // 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" // 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) - if n != 8 && err == nil { - err = fmt.Errorf("Generated less than 8 bytes") + if n != 4 && err == nil { + err = fmt.Errorf("Generated less than 4 bytes") } 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() if err != nil { @@ -89,58 +91,63 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { // bind to 0.0.0.0:68 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 { defer c.Close() } // spew.Dump(c, err) // spew.Printf("net.ListenUDP returned %v, %v\n", c, err) 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 - _, err = c.WriteTo(packet, dstAddr) + cm := ipv4.ControlMessage{} + _, err = c.WriteTo(packet, &cm, dstAddr) // spew.Dump(n, err) if err != nil { return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst) } - // wait for answer - log.Tracef("Waiting %v for an answer", defaultDiscoverTime) - // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts - b := make([]byte, 1500) - c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) - n, _, err = c.ReadFrom(b) - if isTimeout(err) { - // timed out -- no DHCP servers - return false, nil - } - if err != nil { - return false, wrapErrPrint(err, "Couldn't receive packet") - } - if n > 0 { - b = b[:n] - } - // spew.Dump(n, fromAddr, err, b) + for { + // wait for answer + log.Tracef("Waiting %v for an answer", defaultDiscoverTime) + // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts + b := make([]byte, 1500) + _ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) + n, _, _, err = c.ReadFrom(b) + if isTimeout(err) { + // timed out -- no DHCP servers + return false, nil + } + if err != nil { + return false, wrapErrPrint(err, "Couldn't receive packet") + } + // spew.Dump(n, fromAddr, err, b) - if n < 240 { - // packet too small for dhcp - return false, wrapErrPrint(err, "got packet that's too small for DHCP") - } + log.Tracef("Received packet (%v bytes)", n) - response := dhcp4.Packet(b[:n]) - if response.HLen() > 16 { - // invalid size - return false, wrapErrPrint(err, "got malformed packet with HLen() > 16") - } + if n < 240 { + // packet too small for dhcp + continue + } - parsedOptions := response.ParseOptions() - _, ok := parsedOptions[dhcp4.OptionDHCPMessageType] - if !ok { - return false, wrapErrPrint(err, "got malformed packet without DHCP message type") - } + response := dhcp4.Packet(b[:n]) + if response.OpCode() != dhcp4.BootReply || + response.HType() != 1 /*Ethernet*/ || + response.HLen() > 16 || + !bytes.Equal(response.CHAddr(), iface.HardwareAddr) || + !bytes.Equal(response.XId(), xID) { + continue + } - // that's a DHCP server there - return true, nil + parsedOptions := response.ParseOptions() + 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 + } } diff --git a/dhcpd/os_unix.go b/dhcpd/os_unix.go new file mode 100644 index 00000000..638f2a6c --- /dev/null +++ b/dhcpd/os_unix.go @@ -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 +} diff --git a/dhcpd/os_windows.go b/dhcpd/os_windows.go new file mode 100644 index 00000000..5eb7223a --- /dev/null +++ b/dhcpd/os_windows.go @@ -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 +} From 043e89a1a445a2d20e30ce1427b81a2c9a46dbbc Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Mon, 22 Apr 2019 18:01:04 +0300 Subject: [PATCH 2/3] * update openapi.yaml --- openapi/openapi.yaml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5395ca99..fd8b09e6 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1142,14 +1142,33 @@ definitions: DhcpSearchResult: type: "object" description: "Information about a DHCP server discovered in the current network" - required: - - "found" + properties: + other_server: + $ref: "#/definitions/DhcpSearchResultOtherServer" + static_ip: + $ref: "#/definitions/DhcpSearchResultStaticIP" + DhcpSearchResultOtherServer: + type: "object" properties: found: - type: "boolean" - gateway_ip: type: "string" - example: "192.168.1.1" + description: "yes|no|error" + example: "no" + error: + type: "string" + description: "Set if found=error" + example: "" + DhcpSearchResultStaticIP: + type: "object" + properties: + static: + type: "string" + description: "yes|no|error" + example: "yes" + ip: + type: "string" + description: "Set if static=no" + example: "" DnsAnswer: type: "object" description: "DNS answer section" From 68dc8a13411c09cd8c382a1bf986953659166506 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 23 Apr 2019 12:15:49 +0300 Subject: [PATCH 3/3] * control: update "DHCP server not found" message --- client/src/__locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 171df718..3b67dafc 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -12,8 +12,8 @@ "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.", "dhcp_enable": "Enable DHCP server", "dhcp_disable": "Disable DHCP server", - "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.", - "dhcp_found": "Some active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.", + "dhcp_not_found": "It is safe to enable the built-in DHCP server - we didn't find any active DHCP servers on the network. However, we encourage you to re-check it manually as our automatic test currently doesn't give 100% guarantee.", + "dhcp_found": "An active DHCP server is found on the network. It is not safe to enable the built-in DHCP server.", "dhcp_leases": "DHCP leases", "dhcp_leases_not_found": "No DHCP leases found", "dhcp_config_saved": "Saved DHCP server config",