223 lines
6.8 KiB
Go
223 lines
6.8 KiB
Go
package dhcpd
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
)
|
|
|
|
// ServerConfig is the configuration for the DHCP server. The order of YAML
|
|
// fields is important, since the YAML configuration file follows it.
|
|
type ServerConfig struct {
|
|
// Called when the configuration is changed by HTTP request
|
|
ConfigModified func() `yaml:"-"`
|
|
|
|
// Register an HTTP handler
|
|
HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
|
|
|
|
Enabled bool `yaml:"enabled"`
|
|
InterfaceName string `yaml:"interface_name"`
|
|
|
|
// LocalDomainName is the domain name used for DHCP hosts. For example,
|
|
// a DHCP client with the hostname "myhost" can be addressed as "myhost.lan"
|
|
// when LocalDomainName is "lan".
|
|
LocalDomainName string `yaml:"local_domain_name"`
|
|
|
|
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
|
Conf6 V6ServerConf `yaml:"dhcpv6"`
|
|
|
|
WorkDir string `yaml:"-"`
|
|
DBFilePath string `yaml:"-"`
|
|
}
|
|
|
|
// DHCPServer - DHCP server interface
|
|
type DHCPServer interface {
|
|
// ResetLeases resets leases.
|
|
ResetLeases(leases []*Lease) (err error)
|
|
// GetLeases returns deep clones of the current leases.
|
|
GetLeases(flags GetLeasesFlags) (leases []*Lease)
|
|
// AddStaticLease - add a static lease
|
|
AddStaticLease(l *Lease) (err error)
|
|
// RemoveStaticLease - remove a static lease
|
|
RemoveStaticLease(l *Lease) (err error)
|
|
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
|
FindMACbyIP(ip net.IP) net.HardwareAddr
|
|
|
|
// WriteDiskConfig4 - copy disk configuration
|
|
WriteDiskConfig4(c *V4ServerConf)
|
|
// WriteDiskConfig6 - copy disk configuration
|
|
WriteDiskConfig6(c *V6ServerConf)
|
|
|
|
// Start - start server
|
|
Start() (err error)
|
|
// Stop - stop server
|
|
Stop() (err error)
|
|
getLeasesRef() []*Lease
|
|
}
|
|
|
|
// V4ServerConf - server configuration
|
|
type V4ServerConf struct {
|
|
Enabled bool `yaml:"-" json:"-"`
|
|
InterfaceName string `yaml:"-" json:"-"`
|
|
|
|
GatewayIP netip.Addr `yaml:"gateway_ip" json:"gateway_ip"`
|
|
SubnetMask netip.Addr `yaml:"subnet_mask" json:"subnet_mask"`
|
|
// broadcastIP is the broadcasting address pre-calculated from the
|
|
// configured gateway IP and subnet mask.
|
|
broadcastIP netip.Addr
|
|
|
|
// The first & the last IP address for dynamic leases
|
|
// Bytes [0..2] of the last allowed IP address must match the first IP
|
|
RangeStart netip.Addr `yaml:"range_start" json:"range_start"`
|
|
RangeEnd netip.Addr `yaml:"range_end" json:"range_end"`
|
|
|
|
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
|
|
|
// IP conflict detector: time (ms) to wait for ICMP reply
|
|
// 0: disable
|
|
ICMPTimeout uint32 `yaml:"icmp_timeout_msec" json:"-"`
|
|
|
|
// 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" json:"-"`
|
|
|
|
ipRange *ipRange
|
|
|
|
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
|
dnsIPAddrs []netip.Addr // IPv4 addresses to return to DHCP clients as DNS server addresses
|
|
|
|
// subnet contains the DHCP server's subnet. The IP is the IP of the
|
|
// gateway.
|
|
subnet netip.Prefix
|
|
|
|
// notify is a way to signal to other components that leases have been
|
|
// changed. notify must be called outside of locked sections, since the
|
|
// clients might want to get the new data.
|
|
//
|
|
// TODO(a.garipov): This is utter madness and must be refactored. It just
|
|
// begs for deadlock bugs and other nastiness.
|
|
notify func(uint32)
|
|
}
|
|
|
|
// errNilConfig is an error returned by validation method if the config is nil.
|
|
const errNilConfig errors.Error = "nil config"
|
|
|
|
// ensureV4 returns an unmapped version of ip. An error is returned if the
|
|
// passed ip is not an IPv4.
|
|
func ensureV4(ip netip.Addr, kind string) (ip4 netip.Addr, err error) {
|
|
ip4 = ip.Unmap()
|
|
if !ip4.IsValid() || !ip4.Is4() {
|
|
return netip.Addr{}, fmt.Errorf("%v is not an IPv4 %s", ip, kind)
|
|
}
|
|
|
|
return ip4, nil
|
|
}
|
|
|
|
// Validate returns an error if c is not a valid configuration.
|
|
//
|
|
// TODO(e.burkov): Don't set the config fields when the server itself will stop
|
|
// containing the config.
|
|
func (c *V4ServerConf) Validate() (err error) {
|
|
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
|
|
|
if c == nil {
|
|
return errNilConfig
|
|
}
|
|
|
|
gatewayIP, err := ensureV4(c.GatewayIP, "address")
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is and there is
|
|
// an annotation deferred already.
|
|
return err
|
|
}
|
|
|
|
subnetMask, err := ensureV4(c.SubnetMask, "subnet mask")
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is and there is
|
|
// an annotation deferred already.
|
|
return err
|
|
}
|
|
maskLen, _ := net.IPMask(subnetMask.AsSlice()).Size()
|
|
|
|
c.subnet = netip.PrefixFrom(gatewayIP, maskLen)
|
|
c.broadcastIP = aghnet.BroadcastFromPref(c.subnet)
|
|
|
|
rangeStart, err := ensureV4(c.RangeStart, "address")
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is and there is
|
|
// an annotation deferred already.
|
|
return err
|
|
}
|
|
|
|
rangeEnd, err := ensureV4(c.RangeEnd, "address")
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is and there is
|
|
// an annotation deferred already.
|
|
return err
|
|
}
|
|
|
|
c.ipRange, err = newIPRange(rangeStart.AsSlice(), rangeEnd.AsSlice())
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is and there is
|
|
// an annotation deferred already.
|
|
return err
|
|
}
|
|
|
|
if c.ipRange.contains(gatewayIP.AsSlice()) {
|
|
return fmt.Errorf("gateway ip %v in the ip range: %v-%v",
|
|
gatewayIP,
|
|
c.RangeStart,
|
|
c.RangeEnd,
|
|
)
|
|
}
|
|
|
|
if !c.subnet.Contains(rangeStart) {
|
|
return fmt.Errorf("range start %v is outside network %v",
|
|
c.RangeStart,
|
|
c.subnet,
|
|
)
|
|
}
|
|
|
|
if !c.subnet.Contains(rangeEnd) {
|
|
return fmt.Errorf("range end %v is outside network %v",
|
|
c.RangeEnd,
|
|
c.subnet,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// V6ServerConf - server configuration
|
|
type V6ServerConf struct {
|
|
Enabled bool `yaml:"-" json:"-"`
|
|
InterfaceName string `yaml:"-" json:"-"`
|
|
|
|
// The first IP address for dynamic leases
|
|
// The last allowed IP address ends with 0xff byte
|
|
RangeStart net.IP `yaml:"range_start" json:"range_start"`
|
|
|
|
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
|
|
|
RASLAACOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags
|
|
RAAllowSLAAC bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags
|
|
|
|
ipStart net.IP // starting IP address for dynamic leases
|
|
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
|
dnsIPAddrs []net.IP // IPv6 addresses to return to DHCP clients as DNS server addresses
|
|
|
|
// Server calls this function when leases data changes
|
|
notify func(uint32)
|
|
}
|