* POST /control/dhcp/find_active_dhcp: add dhcpv6 server info

This commit is contained in:
Simon Zolin 2020-08-13 18:45:27 +03:00
parent 23752377b7
commit e7bef3a448
5 changed files with 225 additions and 59 deletions

View File

@ -500,6 +500,7 @@ Response:
200 OK 200 OK
{ {
v4: {
"other_server": { "other_server": {
"found": "yes|no|error", "found": "yes|no|error",
"error": "Error message", // set if found=error "error": "Error message", // set if found=error
@ -509,6 +510,13 @@ Response:
"ip": "<Current dynamic IP address>", // set if static=no "ip": "<Current dynamic IP address>", // set if static=no
} }
} }
v6: {
"other_server": {
"found": "yes|no|error",
"error": "Error message", // set if found=error
},
}
}
If `other_server.found` is: If `other_server.found` is:
* `no`: everything is fine - there is no other DHCP server * `no`: everything is fine - there is no other DHCP server

View File

@ -13,12 +13,14 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
"github.com/insomniacslk/dhcp/iana" "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 // 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) iface, err := net.InterfaceByName(ifaceName)
if err != nil { if err != nil {
return false, wrapErrPrint(err, "Couldn't find interface by name %s", ifaceName) 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 // bind to 0.0.0.0:68
log.Tracef("Listening to udp4 %+v", udpAddr) log.Tracef("Listening to udp4 %+v", udpAddr)
c, err := nclient4.NewRawUDPConn(ifaceName, 68) c, err := nclient4.NewRawUDPConn(ifaceName, 68)
if c != nil {
defer c.Close()
}
if err != nil { if err != nil {
return false, wrapErrPrint(err, "Couldn't listen on :68") return false, wrapErrPrint(err, "Couldn't listen on :68")
} }
if c != nil {
defer c.Close()
}
// send to 255.255.255.255:67 // send to 255.255.255.255:67
_, err = c.WriteTo(req.ToBytes(), dstAddr) _, err = c.WriteTo(req.ToBytes(), dstAddr)
@ -119,3 +121,95 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
return true, nil 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")
}
}

View File

@ -2,6 +2,10 @@ package dhcpd
import "fmt" 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") return false, fmt.Errorf("not supported")
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/util" "github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/jsonutil"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
@ -129,17 +130,27 @@ type dhcpServerConfigJSON struct {
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
newconfig := dhcpServerConfigJSON{} 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 { if err != nil {
httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
return return
} }
var s4 DHCPServer
var s6 DHCPServer
v4Enabled := false
v6Enabled := false
if js.Exists("v4") {
v4conf := v4JSONToServerConf(newconfig.V4) v4conf := v4JSONToServerConf(newconfig.V4)
v4conf.Enabled = newconfig.Enabled v4conf.Enabled = newconfig.Enabled
if len(v4conf.RangeStart) == 0 { if len(v4conf.RangeStart) == 0 {
v4conf.Enabled = false v4conf.Enabled = false
} }
v4Enabled = v4conf.Enabled
v4conf.InterfaceName = newconfig.InterfaceName v4conf.InterfaceName = newconfig.InterfaceName
c4 := V4ServerConf{} c4 := V4ServerConf{}
@ -147,40 +158,54 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
v4conf.notify = c4.notify v4conf.notify = c4.notify
v4conf.ICMPTimeout = c4.ICMPTimeout v4conf.ICMPTimeout = c4.ICMPTimeout
s4, err := v4Create(v4conf) s4, err = v4Create(v4conf)
if err != nil { if err != nil {
httpError(r, w, http.StatusBadRequest, "Invalid DHCPv4 configuration: %s", err) httpError(r, w, http.StatusBadRequest, "Invalid DHCPv4 configuration: %s", err)
return return
} }
}
if js.Exists("v6") {
v6conf := v6JSONToServerConf(newconfig.V6) v6conf := v6JSONToServerConf(newconfig.V6)
v6conf.Enabled = newconfig.Enabled v6conf.Enabled = newconfig.Enabled
if len(v6conf.RangeStart) == 0 { if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false v6conf.Enabled = false
} }
v6Enabled = v6conf.Enabled
v6conf.InterfaceName = newconfig.InterfaceName v6conf.InterfaceName = newconfig.InterfaceName
v6conf.notify = s.onNotify v6conf.notify = s.onNotify
s6, err := v6Create(v6conf) s6, err = v6Create(v6conf)
if s6 == nil { if s6 == nil {
httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err) httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err)
return 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") httpError(r, w, http.StatusBadRequest, "DHCPv4 or DHCPv6 configuration must be complete")
return return
} }
s.Stop() s.Stop()
if js.Exists("enabled") {
s.conf.Enabled = newconfig.Enabled s.conf.Enabled = newconfig.Enabled
}
if js.Exists("interface_name") {
s.conf.InterfaceName = newconfig.InterfaceName s.conf.InterfaceName = newconfig.InterfaceName
}
if s4 != nil {
s.srv4 = s4 s.srv4 = s4
}
if s6 != nil {
s.srv6 = s6 s.srv6 = s6
}
s.conf.ConfigModified() s.conf.ConfigModified()
s.dbLoad()
if newconfig.Enabled { if s.conf.Enabled {
staticIP, err := HasStaticIP(newconfig.InterfaceName) staticIP, err := HasStaticIP(newconfig.InterfaceName)
if !staticIP && err == nil { if !staticIP && err == nil {
err = SetStaticIP(newconfig.InterfaceName) err = SetStaticIP(newconfig.InterfaceName)
@ -291,17 +316,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
return return
} }
found, err := CheckIfOtherDHCPServersPresent(interfaceName) found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
othSrv := map[string]interface{}{}
foundVal := "no"
if found {
foundVal = "yes"
} else if err != nil {
foundVal = "error"
othSrv["error"] = err.Error()
}
othSrv["found"] = foundVal
staticIP := map[string]interface{}{} staticIP := map[string]interface{}{}
isStaticIP, err := HasStaticIP(interfaceName) isStaticIP, err := HasStaticIP(interfaceName)
@ -315,9 +330,36 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
} }
staticIP["static"] = staticIPStatus 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 := map[string]interface{}{}
result["other_server"] = othSrv result["v4"] = v4
result["static_ip"] = staticIP result["v6"] = v6
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(result) err = json.NewEncoder(w).Encode(result)

View File

@ -2,7 +2,7 @@ openapi: 3.0.3
info: info:
title: AdGuard Home title: AdGuard Home
description: AdGuard Home REST API. Admin web interface is built on top of this REST API. description: AdGuard Home REST API. Admin web interface is built on top of this REST API.
version: "0.102" version: "0.104"
contact: contact:
name: "AdGuard Home" name: "AdGuard Home"
url: "https://github.com/AdguardTeam/AdGuardHome" url: "https://github.com/AdguardTeam/AdGuardHome"
@ -1356,14 +1356,30 @@ components:
type: array type: array
items: items:
$ref: "#/components/schemas/DhcpStaticLease" $ref: "#/components/schemas/DhcpStaticLease"
DhcpSearchResult: DhcpSearchResult:
type: object type: object
description: Information about a DHCP server discovered in the current network 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: properties:
other_server: other_server:
$ref: "#/components/schemas/DhcpSearchResultOtherServer" $ref: "#/components/schemas/DhcpSearchResultOtherServer"
static_ip: static_ip:
$ref: "#/components/schemas/DhcpSearchResultStaticIP" $ref: "#/components/schemas/DhcpSearchResultStaticIP"
DhcpSearchV6:
type: object
properties:
other_server:
$ref: "#/components/schemas/DhcpSearchResultOtherServer"
DhcpSearchResultOtherServer: DhcpSearchResultOtherServer:
type: object type: object
properties: properties:
@ -1375,6 +1391,7 @@ components:
type: string type: string
description: Set if found=error description: Set if found=error
example: "" example: ""
DhcpSearchResultStaticIP: DhcpSearchResultStaticIP:
type: object type: object
properties: properties:
@ -1386,6 +1403,7 @@ components:
type: string type: string
description: Set if static=no description: Set if static=no
example: "" example: ""
DnsAnswer: DnsAnswer:
type: object type: object
description: DNS answer section description: DNS answer section