From e7bef3a4480ea0cd6d4d6d612ca3f0e3d737a222 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Thu, 13 Aug 2020 18:45:27 +0300 Subject: [PATCH] * POST /control/dhcp/find_active_dhcp: add dhcpv6 server info --- AGHTechDoc.md | 22 +++-- dhcpd/check_other_dhcp.go | 104 +++++++++++++++++++++-- dhcpd/check_other_dhcp_windows.go | 6 +- dhcpd/dhcp_http.go | 132 ++++++++++++++++++++---------- openapi/openapi.yaml | 20 ++++- 5 files changed, 225 insertions(+), 59 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 8661e39c..99bc6a8c 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -500,13 +500,21 @@ Response: 200 OK { - "other_server": { - "found": "yes|no|error", - "error": "Error message", // set if found=error - }, - "static_ip": { - "static": "yes|no|error", - "ip": "", // set if static=no + v4: { + "other_server": { + "found": "yes|no|error", + "error": "Error message", // set if found=error + }, + "static_ip": { + "static": "yes|no|error", + "ip": "", // set if static=no + } + } + v6: { + "other_server": { + "found": "yes|no|error", + "error": "Error message", // set if found=error + }, } } diff --git a/dhcpd/check_other_dhcp.go b/dhcpd/check_other_dhcp.go index 099673f0..d8c8aa33 100644 --- a/dhcpd/check_other_dhcp.go +++ b/dhcpd/check_other_dhcp.go @@ -13,12 +13,14 @@ import ( "github.com/AdguardTeam/golibs/log" "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" ) -// CheckIfOtherDHCPServersPresent sends a DHCP request to the specified network interface, +// CheckIfOtherDHCPServersPresentV4 sends a DHCP request to the specified network interface, // and waits for a response for a period defined by defaultDiscoverTime -func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { +func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return false, wrapErrPrint(err, "Couldn't find interface by name %s", ifaceName) @@ -66,12 +68,12 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { // bind to 0.0.0.0:68 log.Tracef("Listening to udp4 %+v", udpAddr) c, err := nclient4.NewRawUDPConn(ifaceName, 68) - if c != nil { - defer c.Close() - } if err != nil { return false, wrapErrPrint(err, "Couldn't listen on :68") } + if c != nil { + defer c.Close() + } // send to 255.255.255.255:67 _, err = c.WriteTo(req.ToBytes(), dstAddr) @@ -119,3 +121,95 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { return true, 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) (bool, error) { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return false, fmt.Errorf("DHCPv6: net.InterfaceByName: %s: %s", ifaceName, err) + } + + ifaceIPNet := getIfaceIPv6(*iface) + if len(ifaceIPNet) == 0 { + return false, fmt.Errorf("DHCPv6: couldn't find IPv6 address of interface %s %+v", ifaceName, iface) + } + + srcIP := ifaceIPNet[0] + src := net.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: %s", err) + } + + udpAddr, err := net.ResolveUDPAddr("udp6", src) + if err != nil { + return false, wrapErrPrint(err, "DHCPv6: Couldn't resolve UDP address %s", src) + } + + if !udpAddr.IP.To16().Equal(srcIP) { + return false, wrapErrPrint(err, "DHCPv6: Resolved UDP address is not %s", src) + } + + dstAddr, err := net.ResolveUDPAddr("udp6", dst) + if err != nil { + return false, fmt.Errorf("DHCPv6: Couldn't resolve UDP address %s: %s", 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: %s", err) + } + if c != nil { + defer c.Close() + } + + _, err = c.WriteTo(req.ToBytes(), dstAddr) + if err != nil { + return false, fmt.Errorf("DHCPv6: Couldn't send a packet to %s: %s", dst, err) + } + + for { + log.Debug("DHCPv6: Waiting %v for an answer", defaultDiscoverTime) + b := make([]byte, 4096) + _ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) + n, _, err := c.ReadFrom(b) + if isTimeout(err) { + log.Debug("DHCPv6: didn't receive DHCP response") + return false, nil + } + if err != nil { + return false, wrapErrPrint(err, "Couldn't receive packet") + } + + log.Debug("DHCPv6: Received packet (%v bytes)", n) + + resp, err := dhcpv6.FromBytes(b[:n]) + if err != nil { + log.Debug("DHCPv6: dhcpv6.FromBytes: %s", err) + continue + } + + log.Debug("DHCPv6: received message from server: %s", resp.Summary()) + + cid := req.Options.ClientID() + msg, err := resp.GetInnerMessage() + if err != nil { + log.Debug("DHCPv6: resp.GetInnerMessage: %s", err) + continue + } + rcid := msg.Options.ClientID() + if resp.Type() == dhcpv6.MessageTypeAdvertise && + msg.TransactionID == req.TransactionID && + rcid != nil && + cid.Equal(*rcid) { + log.Debug("DHCPv6: The packet is from an active DHCP server") + return true, nil + } + + log.Debug("DHCPv6: received message from server doesn't match our request") + } +} diff --git a/dhcpd/check_other_dhcp_windows.go b/dhcpd/check_other_dhcp_windows.go index bd3849b8..49d90f25 100644 --- a/dhcpd/check_other_dhcp_windows.go +++ b/dhcpd/check_other_dhcp_windows.go @@ -2,6 +2,10 @@ package dhcpd import "fmt" -func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) { +func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { + return false, fmt.Errorf("not supported") +} + +func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) { return false, fmt.Errorf("not supported") } diff --git a/dhcpd/dhcp_http.go b/dhcpd/dhcp_http.go index 34e833ec..7f2e8b8a 100644 --- a/dhcpd/dhcp_http.go +++ b/dhcpd/dhcp_http.go @@ -12,6 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/util" + "github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/log" ) @@ -129,58 +130,82 @@ type dhcpServerConfigJSON struct { func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { newconfig := dhcpServerConfigJSON{} - err := json.NewDecoder(r.Body).Decode(&newconfig) + newconfig.Enabled = s.conf.Enabled + newconfig.InterfaceName = s.conf.InterfaceName + + js, err := jsonutil.DecodeObject(&newconfig, r.Body) if err != nil { httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) return } - v4conf := v4JSONToServerConf(newconfig.V4) - v4conf.Enabled = newconfig.Enabled - if len(v4conf.RangeStart) == 0 { - v4conf.Enabled = false - } - v4conf.InterfaceName = newconfig.InterfaceName + var s4 DHCPServer + var s6 DHCPServer + v4Enabled := false + v6Enabled := false - c4 := V4ServerConf{} - s.srv4.WriteDiskConfig4(&c4) - v4conf.notify = c4.notify - v4conf.ICMPTimeout = c4.ICMPTimeout + if js.Exists("v4") { + v4conf := v4JSONToServerConf(newconfig.V4) + v4conf.Enabled = newconfig.Enabled + if len(v4conf.RangeStart) == 0 { + v4conf.Enabled = false + } + v4Enabled = v4conf.Enabled + v4conf.InterfaceName = newconfig.InterfaceName - s4, err := v4Create(v4conf) - if err != nil { - httpError(r, w, http.StatusBadRequest, "Invalid DHCPv4 configuration: %s", err) - return + c4 := V4ServerConf{} + s.srv4.WriteDiskConfig4(&c4) + v4conf.notify = c4.notify + v4conf.ICMPTimeout = c4.ICMPTimeout + + s4, err = v4Create(v4conf) + if err != nil { + httpError(r, w, http.StatusBadRequest, "Invalid DHCPv4 configuration: %s", err) + return + } } - v6conf := v6JSONToServerConf(newconfig.V6) - v6conf.Enabled = newconfig.Enabled - if len(v6conf.RangeStart) == 0 { - v6conf.Enabled = false - } - v6conf.InterfaceName = newconfig.InterfaceName - v6conf.notify = s.onNotify - s6, err := v6Create(v6conf) - if s6 == nil { - httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err) - return + if js.Exists("v6") { + v6conf := v6JSONToServerConf(newconfig.V6) + v6conf.Enabled = newconfig.Enabled + if len(v6conf.RangeStart) == 0 { + v6conf.Enabled = false + } + v6Enabled = v6conf.Enabled + v6conf.InterfaceName = newconfig.InterfaceName + v6conf.notify = s.onNotify + s6, err = v6Create(v6conf) + if s6 == nil { + httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err) + return + } } - if newconfig.Enabled && !v4conf.Enabled && !v6conf.Enabled { + if newconfig.Enabled && !v4Enabled && !v6Enabled { httpError(r, w, http.StatusBadRequest, "DHCPv4 or DHCPv6 configuration must be complete") return } s.Stop() - s.conf.Enabled = newconfig.Enabled - s.conf.InterfaceName = newconfig.InterfaceName - s.srv4 = s4 - s.srv6 = s6 + if js.Exists("enabled") { + s.conf.Enabled = newconfig.Enabled + } + if js.Exists("interface_name") { + s.conf.InterfaceName = newconfig.InterfaceName + } + + if s4 != nil { + s.srv4 = s4 + } + if s6 != nil { + s.srv6 = s6 + } s.conf.ConfigModified() + s.dbLoad() - if newconfig.Enabled { + if s.conf.Enabled { staticIP, err := HasStaticIP(newconfig.InterfaceName) if !staticIP && err == nil { err = SetStaticIP(newconfig.InterfaceName) @@ -291,17 +316,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque return } - found, err := CheckIfOtherDHCPServersPresent(interfaceName) - - othSrv := map[string]interface{}{} - foundVal := "no" - if found { - foundVal = "yes" - } else if err != nil { - foundVal = "error" - othSrv["error"] = err.Error() - } - othSrv["found"] = foundVal + found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName) staticIP := map[string]interface{}{} isStaticIP, err := HasStaticIP(interfaceName) @@ -315,9 +330,36 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque } staticIP["static"] = staticIPStatus + v4 := map[string]interface{}{} + othSrv := map[string]interface{}{} + foundVal := "no" + if found4 { + foundVal = "yes" + } else if err != nil { + foundVal = "error" + othSrv["error"] = err4.Error() + } + othSrv["found"] = foundVal + v4["other_server"] = othSrv + v4["static_ip"] = staticIP + + found6, err6 := CheckIfOtherDHCPServersPresentV6(interfaceName) + + v6 := map[string]interface{}{} + othSrv = map[string]interface{}{} + foundVal = "no" + if found6 { + foundVal = "yes" + } else if err6 != nil { + foundVal = "error" + othSrv["error"] = err6.Error() + } + othSrv["found"] = foundVal + v6["other_server"] = othSrv + result := map[string]interface{}{} - result["other_server"] = othSrv - result["static_ip"] = staticIP + result["v4"] = v4 + result["v6"] = v6 w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(result) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index bd56fe7c..57d63c70 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: AdGuard Home description: AdGuard Home REST API. Admin web interface is built on top of this REST API. - version: "0.102" + version: "0.104" contact: name: "AdGuard Home" url: "https://github.com/AdguardTeam/AdGuardHome" @@ -1356,14 +1356,30 @@ components: type: array items: $ref: "#/components/schemas/DhcpStaticLease" + DhcpSearchResult: type: object description: Information about a DHCP server discovered in the current network + properties: + v4: + $ref: "#/components/schemas/DhcpSearchV4" + v6: + $ref: "#/components/schemas/DhcpSearchV6" + + DhcpSearchV4: + type: object properties: other_server: $ref: "#/components/schemas/DhcpSearchResultOtherServer" static_ip: $ref: "#/components/schemas/DhcpSearchResultStaticIP" + + DhcpSearchV6: + type: object + properties: + other_server: + $ref: "#/components/schemas/DhcpSearchResultOtherServer" + DhcpSearchResultOtherServer: type: object properties: @@ -1375,6 +1391,7 @@ components: type: string description: Set if found=error example: "" + DhcpSearchResultStaticIP: type: object properties: @@ -1386,6 +1403,7 @@ components: type: string description: Set if static=no example: "" + DnsAnswer: type: object description: DNS answer section