AdGuardHome/internal/dhcpd/dhcpd.go
Eugene Burkov 2313eda123 Pull request: 2302 static ip
Merge in DNS/adguard-home from 2302-static-ip to master

Closes #2302.

Squashed commit of the following:

commit e62b7b033861f1c55f0d06811ca005b3934ddc5b
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:38:15 2020 +0300

    all: format changelog

commit 4127aa7630674ddcfe84f712e6c7c8d06b1cab9a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:24:53 2020 +0300

    all: fix changelog typo

commit f8a432056d3682facae0cdec99d7d80a5c2bd9b5
Merge: b809a866e e4383189a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:22:27 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit b809a866e49147354f9c6952b2f958b6b56ad873
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 19:20:05 2020 +0300

    all: log changes

commit 248c35ba411af731d6eae755a901cdbc77980628
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 18:57:15 2020 +0300

    sysutil: use bufio.Scanner

commit 0dc19dd5c232fbe9552a2b0d846e048274d73a74
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 17:26:18 2020 +0300

    sysutil: fix linux tests

commit 91202d6763595cff187040516dd1db10a20762b7
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 17:15:29 2020 +0300

    sysutil: fix linux files

commit 40fbdbb95876322ebaeef1cbdaa8e3299b83ca7e
Merge: d9a43ffb6 9b963fc77
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:52:35 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit d9a43ffb68a2ddbbcf185b69fc75aed139cd6919
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:49:22 2020 +0300

    sysutil: add main test

commit bfef89186035ab0423d23246d46511584c26534c
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Dec 7 16:21:59 2020 +0300

    sysutil: improve code quality

commit a5f57a373f736971fdeb0c03371da7c8138b3a82
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Dec 4 14:14:08 2020 +0300

    all: move system functionality from dhcpd package to sysutil.

commit 020b51864f85a39ca80e2b4e06faeb47cf318e11
Merge: 967e111a6 ab8defdb0
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Dec 2 14:53:43 2020 +0300

    Merge branch 'master' into 2302-static-ip

commit 967e111a663c18b5f4a87f3ae38d076f3ab6c200
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Dec 2 13:52:17 2020 +0300

    all: improve code quality
2020-12-07 19:48:24 +03:00

275 lines
6.0 KiB
Go

// Package dhcpd provides a DHCP server.
package dhcpd
import (
"encoding/hex"
"net"
"net/http"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/log"
)
const (
defaultDiscoverTime = time.Second * 3
leaseExpireStatic = 1
)
var webHandlersRegistered = false
// Lease contains the necessary information about a DHCP lease
type Lease struct {
HWAddr net.HardwareAddr `json:"mac"`
IP net.IP `json:"ip"`
Hostname string `json:"hostname"`
// Lease expiration time
// 1: static lease
Expiry time.Time `json:"expires"`
}
// ServerConfig - DHCP server configuration
// field ordering is important -- yaml fields will mirror ordering from here
type ServerConfig struct {
Enabled bool `yaml:"enabled"`
InterfaceName string `yaml:"interface_name"`
Conf4 V4ServerConf `yaml:"dhcpv4"`
Conf6 V6ServerConf `yaml:"dhcpv6"`
WorkDir string `yaml:"-"`
DBFilePath string `yaml:"-"` // path to DB file
// Called when the configuration is changed by HTTP request
ConfigModified func() `yaml:"-"`
// Register an HTTP handler
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
}
// OnLeaseChangedT is a callback for lease changes.
type OnLeaseChangedT func(flags int)
// flags for onLeaseChanged()
const (
LeaseChangedAdded = iota
LeaseChangedAddedStatic
LeaseChangedRemovedStatic
LeaseChangedDBStore
)
// Server - the current state of the DHCP server
type Server struct {
srv4 DHCPServer
srv6 DHCPServer
conf ServerConfig
// Called when the leases DB is modified
onLeaseChanged []OnLeaseChangedT
}
// ServerInterface is an interface for servers.
type ServerInterface interface {
Leases(flags int) []Lease
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
}
// Create - create object
func Create(config ServerConfig) *Server {
s := &Server{}
s.conf.Enabled = config.Enabled
s.conf.InterfaceName = config.InterfaceName
s.conf.HTTPRegister = config.HTTPRegister
s.conf.ConfigModified = config.ConfigModified
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
if runtime.GOOS == "windows" {
// Our DHCP server doesn't work on Windows yet, so
// signal that to the front with an HTTP 501.
//
// TODO(a.garipov): This needs refactoring. We
// shouldn't even try and initialize a DHCP server on
// Windows, but there are currently too many
// interconnected parts--such as HTTP handlers and
// frontend--to make that work properly.
s.registerNotImplementedHandlers()
} else {
s.registerHandlers()
}
webHandlersRegistered = true
}
var err4, err6 error
v4conf := config.Conf4
v4conf.Enabled = s.conf.Enabled
if len(v4conf.RangeStart) == 0 {
v4conf.Enabled = false
}
v4conf.InterfaceName = s.conf.InterfaceName
v4conf.notify = s.onNotify
s.srv4, err4 = v4Create(v4conf)
v6conf := config.Conf6
v6conf.Enabled = s.conf.Enabled
if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false
}
v6conf.InterfaceName = s.conf.InterfaceName
v6conf.notify = s.onNotify
s.srv6, err6 = v6Create(v6conf)
if err4 != nil {
log.Error("%s", err4)
return nil
}
if err6 != nil {
log.Error("%s", err6)
return nil
}
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
log.Error("Can't enable DHCP server because neither DHCPv4 nor DHCPv6 servers are configured")
return nil
}
// we can't delay database loading until DHCP server is started,
// because we need static leases functionality available beforehand
s.dbLoad()
return s
}
// server calls this function after DB is updated
func (s *Server) onNotify(flags uint32) {
if flags == LeaseChangedDBStore {
s.dbStore()
return
}
s.notify(int(flags))
}
// SetOnLeaseChanged - set callback
func (s *Server) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) {
s.onLeaseChanged = append(s.onLeaseChanged, onLeaseChanged)
}
func (s *Server) notify(flags int) {
if len(s.onLeaseChanged) == 0 {
return
}
for _, f := range s.onLeaseChanged {
f(flags)
}
}
// WriteDiskConfig - write configuration
func (s *Server) WriteDiskConfig(c *ServerConfig) {
c.Enabled = s.conf.Enabled
c.InterfaceName = s.conf.InterfaceName
s.srv4.WriteDiskConfig4(&c.Conf4)
s.srv6.WriteDiskConfig6(&c.Conf6)
}
// Start will listen on port 67 and serve DHCP requests.
func (s *Server) Start() error {
err := s.srv4.Start()
if err != nil {
log.Error("DHCPv4: start: %s", err)
return err
}
err = s.srv6.Start()
if err != nil {
log.Error("DHCPv6: start: %s", err)
return err
}
return nil
}
// Stop closes the listening UDP socket
func (s *Server) Stop() {
s.srv4.Stop()
s.srv6.Stop()
}
// flags for Leases() function
const (
LeasesDynamic = 1
LeasesStatic = 2
LeasesAll = LeasesDynamic | LeasesStatic
)
// Leases returns the list of current DHCP leases (thread-safe)
func (s *Server) Leases(flags int) []Lease {
result := s.srv4.GetLeases(flags)
v6leases := s.srv6.GetLeases(flags)
result = append(result, v6leases...)
return result
}
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
if ip.To4() != nil {
return s.srv4.FindMACbyIP(ip)
}
return s.srv6.FindMACbyIP(ip)
}
// AddStaticLease - add static v4 lease
func (s *Server) AddStaticLease(lease Lease) error {
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
}