+ dhcp custom options

Squashed commit of the following:

commit 140ac16568383cab2270e5d5ba895959902dd943
Merge: d5ed73b5 cb6ca3b0
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Tue Aug 25 13:46:34 2020 +0300

    Merge remote-tracking branch 'origin/master' into 1585-dhcp-options

commit d5ed73b5e4f068b823fe97ab1161753670d10387
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 21 18:16:41 2020 +0300

    minor

commit f5208a0b050c2dd462b32edee0379758cc6e5003
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 1 14:09:39 2020 +0300

    + dhcpv4 custom options
This commit is contained in:
Simon Zolin 2020-08-25 14:07:11 +03:00
parent cb6ca3b0c4
commit 9c999f98fb
6 changed files with 130 additions and 10 deletions

View File

@ -25,12 +25,13 @@ Contents:
* API: Find clients by IP * API: Find clients by IP
* DHCP server * DHCP server
* DHCP server in DNS * DHCP server in DNS
* "Show DHCP interfaces" command * DHCP Custom Options
* "Show DHCP status" command * API: Show DHCP interfaces
* "Check DHCP" command * API: Show DHCP status
* "Enable DHCP" command * API: Check DHCP
* API: Enable DHCP
* Static IP check/set * Static IP check/set
* Add a static lease * API: Add a static lease
* API: Reset DHCP configuration * API: Reset DHCP configuration
* DNS general settings * DNS general settings
* API: Get DNS general settings * API: Get DNS general settings
@ -429,7 +430,20 @@ DHCP leases are used in several ways by DNS module.
> PTR 100.1.168.192.in-addr.arpa. = bills-notebook. > PTR 100.1.168.192.in-addr.arpa. = bills-notebook.
### "Show DHCP interfaces" command ### DHCP Custom Options
Option with arbitrary hexadecimal data:
DEC_CODE hex HEX_DATA
where DEC_CODE is a decimal DHCPv4 option code in range [1..255]
Option with IP data (only 1 IP is supported):
DEC_CODE ip IP_ADDR
### API: Show DHCP interfaces
Request: Request:
@ -452,7 +466,7 @@ Response:
} }
### "Show DHCP status" command ### API: Show DHCP status
Request: Request:
@ -487,7 +501,7 @@ Response:
} }
### "Check DHCP" command ### API: Check DHCP
Request: Request:
@ -535,7 +549,7 @@ If `static_ip.static` is:
In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually. In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.
### "Enable DHCP" command ### API: Enable DHCP
Request: Request:
@ -662,7 +676,7 @@ or:
systemctl restart system-networkd systemctl restart system-networkd
### Add a static lease ### API: Add a static lease
Request: Request:

View File

@ -1,11 +1,15 @@
package dhcpd package dhcpd
import ( import (
"encoding/hex"
"net" "net"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strconv"
"strings"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
@ -214,3 +218,43 @@ func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
func (s *Server) AddStaticLease(lease Lease) error { func (s *Server) AddStaticLease(lease Lease) error {
return s.srv4.AddStaticLease(lease) return s.srv4.AddStaticLease(lease)
} }
// Parse option string
// Format:
// CODE TYPE VALUE
func parseOptionString(s string) (uint8, []byte) {
s = strings.TrimSpace(s)
scode := util.SplitNext(&s, ' ')
t := util.SplitNext(&s, ' ')
sval := util.SplitNext(&s, ' ')
code, err := strconv.Atoi(scode)
if err != nil || code <= 0 || code > 255 {
return 0, nil
}
var val []byte
switch t {
case "hex":
val, err = hex.DecodeString(sval)
if err != nil {
return 0, nil
}
case "ip":
ip := net.ParseIP(sval)
if ip == nil {
return 0, nil
}
val = ip
if ip.To4() != nil {
val = ip.To4()
}
default:
return 0, nil
}
return uint8(code), val
}

View File

@ -108,3 +108,25 @@ func TestNormalizeLeases(t *testing.T) {
assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4})) assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4}))
assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5})) assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5}))
} }
func TestOptions(t *testing.T) {
code, val := parseOptionString(" 12 hex abcdef ")
assert.Equal(t, uint8(12), code)
assert.True(t, bytes.Equal([]byte{0xab, 0xcd, 0xef}, val))
code, _ = parseOptionString(" 12 hex abcdef1 ")
assert.Equal(t, uint8(0), code)
code, val = parseOptionString("123 ip 1.2.3.4")
assert.Equal(t, uint8(123), code)
assert.Equal(t, "1.2.3.4", net.IP(string(val)).String())
code, _ = parseOptionString("256 ip 1.1.1.1")
assert.Equal(t, uint8(0), code)
code, _ = parseOptionString("-1 ip 1.1.1.1")
assert.Equal(t, uint8(0), code)
code, _ = parseOptionString("12 ip 1.1.1.1x")
assert.Equal(t, uint8(0), code)
code, _ = parseOptionString("12 x 1.1.1.1")
assert.Equal(t, uint8(0), code)
}

View File

@ -50,12 +50,23 @@ type V4ServerConf struct {
// 0: disable // 0: disable
ICMPTimeout uint32 `yaml:"icmp_timeout_msec"` ICMPTimeout uint32 `yaml:"icmp_timeout_msec"`
// Custom Options.
//
// Option with arbitrary hexadecimal data:
// DEC_CODE hex HEX_DATA
// where DEC_CODE is a decimal DHCPv4 option code in range [1..255]
//
// Option with IP data (only 1 IP is supported):
// DEC_CODE ip IP_ADDR
Options []string `yaml:"options"`
ipStart net.IP // starting IP address for dynamic leases ipStart net.IP // starting IP address for dynamic leases
ipEnd net.IP // ending IP address for dynamic leases ipEnd net.IP // ending IP address for dynamic leases
leaseTime time.Duration // the time during which a dynamic lease is considered valid leaseTime time.Duration // the time during which a dynamic lease is considered valid
dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses
routerIP net.IP // value for Option Router routerIP net.IP // value for Option Router
subnetMask net.IPMask // value for Option SubnetMask subnetMask net.IPMask // value for Option SubnetMask
options []dhcpOption
// Server calls this function when leases data changes // Server calls this function when leases data changes
notify func(uint32) notify func(uint32)
@ -79,3 +90,8 @@ type V6ServerConf struct {
// Server calls this function when leases data changes // Server calls this function when leases data changes
notify func(uint32) notify func(uint32)
} }
type dhcpOption struct {
code uint8
val []byte
}

View File

@ -475,6 +475,10 @@ func (s *v4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int {
resp.UpdateOption(dhcpv4.OptRouter(s.conf.routerIP)) resp.UpdateOption(dhcpv4.OptRouter(s.conf.routerIP))
resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnetMask)) resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnetMask))
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...)) resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
for _, opt := range s.conf.options {
resp.Options[opt.code] = opt.val
}
return 1 return 1
} }
@ -619,5 +623,19 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) {
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration) s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
} }
for _, o := range conf.Options {
code, val := parseOptionString(o)
if code == 0 {
log.Debug("DHCPv4: bad option string: %s", o)
continue
}
opt := dhcpOption{
code: code,
val: val,
}
s.conf.options = append(s.conf.options, opt)
}
return s, nil return s, nil
} }

View File

@ -178,6 +178,10 @@ func TestV4DynamicLeaseGet(t *testing.T) {
GatewayIP: "192.168.10.1", GatewayIP: "192.168.10.1",
SubnetMask: "255.255.255.0", SubnetMask: "255.255.255.0",
notify: notify4, notify: notify4,
Options: []string{
"81 hex 303132",
"82 ip 1.2.3.4",
},
} }
sIface, err := v4Create(conf) sIface, err := v4Create(conf)
s := sIface.(*v4Server) s := sIface.(*v4Server)
@ -198,6 +202,8 @@ func TestV4DynamicLeaseGet(t *testing.T) {
assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String())
assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String())
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
assert.Equal(t, "1.2.3.4", net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)]).String())
// "Request" // "Request"
req, _ = dhcpv4.NewRequestFromOffer(resp) req, _ = dhcpv4.NewRequestFromOffer(resp)