2018-12-28 14:17:51 +00:00
package dhcpd
import (
"bytes"
"fmt"
"net"
2019-10-11 16:56:18 +00:00
"net/http"
2019-08-19 11:27:02 +00:00
"path/filepath"
2019-03-06 12:13:27 +00:00
"strings"
2018-12-29 16:13:00 +00:00
"sync"
2018-12-28 14:17:51 +00:00
"time"
2019-02-25 13:44:22 +00:00
"github.com/AdguardTeam/golibs/log"
2018-12-28 14:17:51 +00:00
"github.com/krolaw/dhcp4"
2019-03-22 07:36:48 +00:00
ping "github.com/sparrc/go-ping"
2018-12-28 14:17:51 +00:00
)
const defaultDiscoverTime = time . Second * 3
2019-05-14 10:02:04 +00:00
const leaseExpireStatic = 1
2018-12-28 14:17:51 +00:00
2020-02-18 16:27:09 +00:00
var webHandlersRegistered = false
2019-01-24 17:11:01 +00:00
// Lease contains the necessary information about a DHCP lease
2018-12-28 14:17:51 +00:00
// field ordering is important -- yaml fields will mirror ordering from here
type Lease struct {
2018-12-29 13:44:07 +00:00
HWAddr net . HardwareAddr ` json:"mac" yaml:"hwaddr" `
IP net . IP ` json:"ip" `
Hostname string ` json:"hostname" `
2019-05-14 10:02:04 +00:00
// Lease expiration time
// 1: static lease
Expiry time . Time ` json:"expires" `
2018-12-28 14:17:51 +00:00
}
2019-01-24 17:11:01 +00:00
// ServerConfig - DHCP server configuration
2018-12-28 14:17:51 +00:00
// field ordering is important -- yaml fields will mirror ordering from here
type ServerConfig struct {
Enabled bool ` json:"enabled" yaml:"enabled" `
InterfaceName string ` json:"interface_name" yaml:"interface_name" ` // eth0, en0 and so on
GatewayIP string ` json:"gateway_ip" yaml:"gateway_ip" `
SubnetMask string ` json:"subnet_mask" yaml:"subnet_mask" `
RangeStart string ` json:"range_start" yaml:"range_start" `
RangeEnd string ` json:"range_end" yaml:"range_end" `
2019-10-14 09:12:06 +00:00
LeaseDuration uint32 ` json:"lease_duration" yaml:"lease_duration" ` // in seconds
2019-03-07 13:48:55 +00:00
// IP conflict detector: time (ms) to wait for ICMP reply.
// 0: disable
2019-10-14 09:12:06 +00:00
ICMPTimeout uint32 ` json:"icmp_timeout_msec" yaml:"icmp_timeout_msec" `
WorkDir string ` json:"-" yaml:"-" `
DBFilePath string ` json:"-" yaml:"-" ` // path to DB file
2019-10-11 16:56:18 +00:00
// Called when the configuration is changed by HTTP request
ConfigModified func ( ) ` json:"-" yaml:"-" `
// Register an HTTP handler
HTTPRegister func ( string , string , func ( http . ResponseWriter , * http . Request ) ) ` json:"-" yaml:"-" `
2018-12-28 14:17:51 +00:00
}
2020-01-30 07:25:02 +00:00
type onLeaseChangedT func ( flags int )
// flags for onLeaseChanged()
const (
LeaseChangedAdded = iota
LeaseChangedAddedStatic
LeaseChangedRemovedStatic
LeaseChangedBlacklisted
)
2019-01-24 17:11:01 +00:00
// Server - the current state of the DHCP server
2018-12-28 14:17:51 +00:00
type Server struct {
conn * filterConn // listening UDP socket
ipnet * net . IPNet // if interface name changes, this needs to be reset
2019-03-05 16:14:35 +00:00
cond * sync . Cond // Synchronize worker thread with main thread
mutex sync . Mutex // Mutex for 'cond'
running bool // Set if the worker thread is running
stopping bool // Set if the worker thread should be stopped
2018-12-28 14:17:51 +00:00
// leases
leases [ ] * Lease
2019-05-14 14:49:45 +00:00
leasesLock sync . RWMutex
2018-12-28 14:17:51 +00:00
leaseStart net . IP // parsed from config RangeStart
leaseStop net . IP // parsed from config RangeEnd
leaseTime time . Duration // parsed from config LeaseDuration
leaseOptions dhcp4 . Options // parsed from config GatewayIP and SubnetMask
// IP address pool -- if entry is in the pool, then it's attached to a lease
IPpool map [ [ 4 ] byte ] net . HardwareAddr
2019-05-14 14:49:45 +00:00
conf ServerConfig
2020-01-30 07:25:02 +00:00
// Called when the leases DB is modified
2020-06-23 09:13:13 +00:00
onLeaseChanged [ ] onLeaseChangedT
2018-12-28 14:17:51 +00:00
}
2019-03-06 12:13:27 +00:00
// Print information about the available network interfaces
func printInterfaces ( ) {
ifaces , _ := net . Interfaces ( )
var buf strings . Builder
for i := range ifaces {
buf . WriteString ( fmt . Sprintf ( "\"%s\", " , ifaces [ i ] . Name ) )
}
log . Info ( "Available network interfaces: %s" , buf . String ( ) )
}
2019-05-14 12:50:07 +00:00
// CheckConfig checks the configuration
func ( s * Server ) CheckConfig ( config ServerConfig ) error {
tmpServer := Server { }
return tmpServer . setConfig ( config )
}
2019-11-22 11:21:08 +00:00
// Create - create object
func Create ( config ServerConfig ) * Server {
s := Server { }
s . conf = config
2019-11-06 10:04:01 +00:00
s . conf . DBFilePath = filepath . Join ( config . WorkDir , dbFilename )
2020-01-30 07:25:02 +00:00
if s . conf . Enabled {
err := s . setConfig ( config )
if err != nil {
log . Error ( "DHCP: %s" , err )
return nil
}
}
2019-11-06 10:04:01 +00:00
2020-04-07 08:48:03 +00:00
if ! webHandlersRegistered && s . conf . HTTPRegister != nil {
webHandlersRegistered = true
s . registerHandlers ( )
}
2019-11-06 10:04:01 +00:00
// we can't delay database loading until DHCP server is started,
// because we need static leases functionality available beforehand
s . dbLoad ( )
2019-11-22 11:21:08 +00:00
return & s
}
2019-05-14 12:49:53 +00:00
// Init checks the configuration and initializes the server
func ( s * Server ) Init ( config ServerConfig ) error {
err := s . setConfig ( config )
if err != nil {
return err
2018-12-28 14:17:51 +00:00
}
2019-05-14 12:49:53 +00:00
return nil
}
2020-01-30 07:25:02 +00:00
// SetOnLeaseChanged - set callback
func ( s * Server ) SetOnLeaseChanged ( onLeaseChanged onLeaseChangedT ) {
2020-06-23 09:13:13 +00:00
s . onLeaseChanged = append ( s . onLeaseChanged , onLeaseChanged )
2020-01-30 07:25:02 +00:00
}
func ( s * Server ) notify ( flags int ) {
2020-06-23 09:13:13 +00:00
if len ( s . onLeaseChanged ) == 0 {
2020-01-30 07:25:02 +00:00
return
}
2020-06-23 09:13:13 +00:00
for _ , f := range s . onLeaseChanged {
f ( flags )
}
2020-01-30 07:25:02 +00:00
}
2019-11-22 11:21:08 +00:00
// WriteDiskConfig - write configuration
func ( s * Server ) WriteDiskConfig ( c * ServerConfig ) {
* c = s . conf
}
2019-05-14 12:49:53 +00:00
func ( s * Server ) setConfig ( config ServerConfig ) error {
2019-05-14 14:49:45 +00:00
iface , err := net . InterfaceByName ( config . InterfaceName )
2018-12-28 14:17:51 +00:00
if err != nil {
2019-03-06 12:13:27 +00:00
printInterfaces ( )
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Couldn't find interface by name %s" , config . InterfaceName )
2018-12-28 14:17:51 +00:00
}
// get ipv4 address of an interface
s . ipnet = getIfaceIPv4 ( iface )
if s . ipnet == nil {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Couldn't find IPv4 address of interface %s %+v" , config . InterfaceName , iface )
2018-12-28 14:17:51 +00:00
}
2019-05-14 14:49:45 +00:00
if config . LeaseDuration == 0 {
2018-12-28 14:17:51 +00:00
s . leaseTime = time . Hour * 2
} else {
2019-05-14 14:49:45 +00:00
s . leaseTime = time . Second * time . Duration ( config . LeaseDuration )
2018-12-28 14:17:51 +00:00
}
2019-05-14 14:49:45 +00:00
s . leaseStart , err = parseIPv4 ( config . RangeStart )
2018-12-28 14:17:51 +00:00
if err != nil {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Failed to parse range start address %s" , config . RangeStart )
2018-12-28 14:17:51 +00:00
}
2019-05-14 14:49:45 +00:00
s . leaseStop , err = parseIPv4 ( config . RangeEnd )
2018-12-28 14:17:51 +00:00
if err != nil {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Failed to parse range end address %s" , config . RangeEnd )
2018-12-28 14:17:51 +00:00
}
2019-08-23 11:44:23 +00:00
if dhcp4 . IPRange ( s . leaseStart , s . leaseStop ) <= 0 {
return wrapErrPrint ( err , "DHCP: Incorrect range_start/range_end values" )
}
2018-12-28 14:17:51 +00:00
2019-05-14 14:49:45 +00:00
subnet , err := parseIPv4 ( config . SubnetMask )
2019-07-17 08:55:21 +00:00
if err != nil || ! isValidSubnetMask ( subnet ) {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Failed to parse subnet mask %s" , config . SubnetMask )
2018-12-28 14:17:51 +00:00
}
// if !bytes.Equal(subnet, s.ipnet.Mask) {
// return wrapErrPrint(err, "specified subnet mask %s does not meatch interface %s subnet mask %s", s.SubnetMask, s.InterfaceName, s.ipnet.Mask)
// }
2019-05-14 14:49:45 +00:00
router , err := parseIPv4 ( config . GatewayIP )
2018-12-28 14:17:51 +00:00
if err != nil {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Failed to parse gateway IP %s" , config . GatewayIP )
2018-12-28 14:17:51 +00:00
}
s . leaseOptions = dhcp4 . Options {
dhcp4 . OptionSubnetMask : subnet ,
dhcp4 . OptionRouter : router ,
dhcp4 . OptionDomainNameServer : s . ipnet . IP ,
}
2019-10-11 16:56:18 +00:00
oldconf := s . conf
s . conf = config
s . conf . WorkDir = oldconf . WorkDir
s . conf . HTTPRegister = oldconf . HTTPRegister
s . conf . ConfigModified = oldconf . ConfigModified
2019-11-06 10:04:01 +00:00
s . conf . DBFilePath = oldconf . DBFilePath
2019-05-14 12:49:53 +00:00
return nil
}
// Start will listen on port 67 and serve DHCP requests.
func ( s * Server ) Start ( ) error {
2018-12-28 14:17:51 +00:00
// TODO: don't close if interface and addresses are the same
if s . conn != nil {
2020-04-05 15:34:43 +00:00
_ = s . closeConn ( )
2018-12-28 14:17:51 +00:00
}
2019-05-14 14:49:45 +00:00
iface , err := net . InterfaceByName ( s . conf . InterfaceName )
2019-05-14 12:49:53 +00:00
if err != nil {
2019-05-14 14:49:45 +00:00
return wrapErrPrint ( err , "Couldn't find interface by name %s" , s . conf . InterfaceName )
2019-05-14 12:49:53 +00:00
}
2019-03-05 14:15:38 +00:00
2018-12-28 14:17:51 +00:00
c , err := newFilterConn ( * iface , ":67" ) // it has to be bound to 0.0.0.0:67, otherwise it won't see DHCP discover/request packets
if err != nil {
return wrapErrPrint ( err , "Couldn't start listening socket on 0.0.0.0:67" )
}
2019-02-28 15:24:01 +00:00
log . Info ( "DHCP: listening on 0.0.0.0:67" )
2018-12-28 14:17:51 +00:00
s . conn = c
2019-03-05 16:14:35 +00:00
s . cond = sync . NewCond ( & s . mutex )
2018-12-28 14:17:51 +00:00
2019-03-05 16:14:35 +00:00
s . running = true
2018-12-28 14:17:51 +00:00
go func ( ) {
// operate on c instead of c.conn because c.conn can change over time
err := dhcp4 . Serve ( c , s )
2019-03-05 16:14:35 +00:00
if err != nil && ! s . stopping {
2018-12-28 14:17:51 +00:00
log . Printf ( "dhcp4.Serve() returned with error: %s" , err )
}
2020-04-05 15:34:43 +00:00
_ = c . Close ( ) // in case Serve() exits for other reason than listening socket closure
2019-03-05 16:14:35 +00:00
s . running = false
s . cond . Signal ( )
2018-12-28 14:17:51 +00:00
} ( )
return nil
}
2019-01-24 17:11:01 +00:00
// Stop closes the listening UDP socket
2018-12-28 14:17:51 +00:00
func ( s * Server ) Stop ( ) error {
if s . conn == nil {
// nothing to do, return silently
return nil
}
2019-03-05 16:14:35 +00:00
s . stopping = true
2018-12-28 14:17:51 +00:00
err := s . closeConn ( )
if err != nil {
return wrapErrPrint ( err , "Couldn't close UDP listening socket" )
}
2019-03-05 16:14:35 +00:00
// We've just closed the listening socket.
// Worker thread should exit right after it tries to read from the socket.
s . mutex . Lock ( )
for s . running {
s . cond . Wait ( )
}
s . mutex . Unlock ( )
2018-12-28 14:17:51 +00:00
return nil
}
// closeConn will close the connection and set it to zero
func ( s * Server ) closeConn ( ) error {
if s . conn == nil {
return nil
}
err := s . conn . Close ( )
s . conn = nil
return err
}
2019-03-06 12:13:27 +00:00
// Reserve a lease for the client
2018-12-28 14:17:51 +00:00
func ( s * Server ) reserveLease ( p dhcp4 . Packet ) ( * Lease , error ) {
// WARNING: do not remove copy()
// the given hwaddr by p.CHAddr() in the packet survives only during ServeDHCP() call
// since we need to retain it we need to make our own copy
hwaddrCOW := p . CHAddr ( )
hwaddr := make ( net . HardwareAddr , len ( hwaddrCOW ) )
copy ( hwaddr , hwaddrCOW )
// not assigned a lease, create new one, find IP from LRU
2019-03-11 12:11:48 +00:00
hostname := p . ParseOptions ( ) [ dhcp4 . OptionHostName ]
lease := & Lease { HWAddr : hwaddr , Hostname : string ( hostname ) }
2018-12-29 14:23:42 +00:00
log . Tracef ( "Lease not found for %s: creating new one" , hwaddr )
2019-06-26 11:01:59 +00:00
s . leasesLock . Lock ( )
defer s . leasesLock . Unlock ( )
2019-01-25 13:01:27 +00:00
ip , err := s . findFreeIP ( hwaddr )
2018-12-28 14:17:51 +00:00
if err != nil {
2019-03-11 12:11:48 +00:00
i := s . findExpiredLease ( )
if i < 0 {
return nil , wrapErrPrint ( err , "Couldn't find free IP for the lease %s" , hwaddr . String ( ) )
}
log . Tracef ( "Assigning IP address %s to %s (lease for %s expired at %s)" ,
s . leases [ i ] . IP , hwaddr , s . leases [ i ] . HWAddr , s . leases [ i ] . Expiry )
lease . IP = s . leases [ i ] . IP
s . leases [ i ] = lease
s . reserveIP ( lease . IP , hwaddr )
return lease , nil
2018-12-28 14:17:51 +00:00
}
2019-03-11 12:11:48 +00:00
2018-12-29 14:23:42 +00:00
log . Tracef ( "Assigning to %s IP address %s" , hwaddr , ip . String ( ) )
2019-03-11 12:11:48 +00:00
lease . IP = ip
2018-12-28 14:17:51 +00:00
s . leases = append ( s . leases , lease )
return lease , nil
}
2019-03-06 12:13:27 +00:00
// Find a lease for the client
func ( s * Server ) findLease ( p dhcp4 . Packet ) * Lease {
2018-12-28 14:17:51 +00:00
hwaddr := p . CHAddr ( )
for i := range s . leases {
2018-12-28 17:50:24 +00:00
if bytes . Equal ( [ ] byte ( hwaddr ) , [ ] byte ( s . leases [ i ] . HWAddr ) ) {
2018-12-29 14:23:42 +00:00
// log.Tracef("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr)
2018-12-28 14:17:51 +00:00
return s . leases [ i ]
}
}
return nil
}
2019-03-11 12:11:48 +00:00
// Find an expired lease and return its index or -1
func ( s * Server ) findExpiredLease ( ) int {
now := time . Now ( ) . Unix ( )
for i , lease := range s . leases {
2019-05-14 10:02:04 +00:00
if lease . Expiry . Unix ( ) <= now && lease . Expiry . Unix ( ) != leaseExpireStatic {
2019-03-11 12:11:48 +00:00
return i
}
}
return - 1
}
2019-01-25 13:01:27 +00:00
func ( s * Server ) findFreeIP ( hwaddr net . HardwareAddr ) ( net . IP , error ) {
2018-12-28 14:17:51 +00:00
// go from start to end, find unreserved IP
var foundIP net . IP
for i := 0 ; i < dhcp4 . IPRange ( s . leaseStart , s . leaseStop ) ; i ++ {
newIP := dhcp4 . IPAdd ( s . leaseStart , i )
2019-02-28 15:24:01 +00:00
foundHWaddr := s . findReservedHWaddr ( newIP )
2018-12-29 14:23:42 +00:00
log . Tracef ( "tried IP %v, got hwaddr %v" , newIP , foundHWaddr )
2018-12-28 14:17:51 +00:00
if foundHWaddr != nil && len ( foundHWaddr ) != 0 {
// if !bytes.Equal(foundHWaddr, hwaddr) {
2018-12-29 14:23:42 +00:00
// log.Tracef("SHOULD NOT HAPPEN: hwaddr in IP pool %s is not equal to hwaddr in lease %s", foundHWaddr, hwaddr)
2018-12-28 14:17:51 +00:00
// }
continue
}
foundIP = newIP
break
}
if foundIP == nil {
// TODO: LRU
2019-01-25 13:01:27 +00:00
return nil , fmt . Errorf ( "couldn't find free entry in IP pool" )
2018-12-28 14:17:51 +00:00
}
s . reserveIP ( foundIP , hwaddr )
return foundIP , nil
}
2019-02-28 15:24:01 +00:00
func ( s * Server ) findReservedHWaddr ( ip net . IP ) net . HardwareAddr {
2018-12-28 14:17:51 +00:00
rawIP := [ ] byte ( ip )
IP4 := [ 4 ] byte { rawIP [ 0 ] , rawIP [ 1 ] , rawIP [ 2 ] , rawIP [ 3 ] }
return s . IPpool [ IP4 ]
}
func ( s * Server ) reserveIP ( ip net . IP , hwaddr net . HardwareAddr ) {
rawIP := [ ] byte ( ip )
IP4 := [ 4 ] byte { rawIP [ 0 ] , rawIP [ 1 ] , rawIP [ 2 ] , rawIP [ 3 ] }
s . IPpool [ IP4 ] = hwaddr
}
func ( s * Server ) unreserveIP ( ip net . IP ) {
rawIP := [ ] byte ( ip )
IP4 := [ 4 ] byte { rawIP [ 0 ] , rawIP [ 1 ] , rawIP [ 2 ] , rawIP [ 3 ] }
delete ( s . IPpool , IP4 )
}
2019-01-24 17:11:01 +00:00
// ServeDHCP handles an incoming DHCP request
2018-12-28 14:17:51 +00:00
func ( s * Server ) ServeDHCP ( p dhcp4 . Packet , msgType dhcp4 . MessageType , options dhcp4 . Options ) dhcp4 . Packet {
2019-03-06 12:13:27 +00:00
s . printLeases ( )
2018-12-28 14:17:51 +00:00
switch msgType {
case dhcp4 . Discover : // Broadcast Packet From Client - Can I have an IP?
2019-02-28 15:24:01 +00:00
return s . handleDiscover ( p , options )
2018-12-28 14:17:51 +00:00
case dhcp4 . Request : // Broadcast From Client - I'll take that IP (Also start for renewals)
// start/renew a lease -- update lease time
// some clients (OSX) just go right ahead and do Request first from previously known IP, if they get NAK, they restart full cycle with Discover then Request
2019-01-25 13:01:27 +00:00
return s . handleDHCP4Request ( p , options )
2019-02-28 15:24:01 +00:00
2018-12-28 14:17:51 +00:00
case dhcp4 . Decline : // Broadcast From Client - Sorry I can't use that IP
2019-03-06 12:13:27 +00:00
return s . handleDecline ( p , options )
2018-12-28 14:17:51 +00:00
case dhcp4 . Release : // From Client, I don't need that IP anymore
2019-03-06 12:13:27 +00:00
return s . handleRelease ( p , options )
2018-12-28 14:17:51 +00:00
case dhcp4 . Inform : // From Client, I have this IP and there's nothing you can do about it
2019-03-06 12:13:27 +00:00
return s . handleInform ( p , options )
2018-12-28 14:17:51 +00:00
// from server -- ignore those but enumerate just in case
case dhcp4 . Offer : // Broadcast From Server - Here's an IP
2019-03-06 12:13:27 +00:00
log . Printf ( "DHCP: received message from %s: Offer" , p . CHAddr ( ) )
2019-02-28 15:24:01 +00:00
2018-12-28 14:17:51 +00:00
case dhcp4 . ACK : // From Server, Yes you can have that IP
2019-03-06 12:13:27 +00:00
log . Printf ( "DHCP: received message from %s: ACK" , p . CHAddr ( ) )
2019-02-28 15:24:01 +00:00
2018-12-28 14:17:51 +00:00
case dhcp4 . NAK : // From Server, No you cannot have that IP
2019-03-06 12:13:27 +00:00
log . Printf ( "DHCP: received message from %s: NAK" , p . CHAddr ( ) )
2019-02-28 15:24:01 +00:00
2018-12-28 14:17:51 +00:00
default :
2019-03-06 12:13:27 +00:00
log . Printf ( "DHCP: unknown packet %v from %s" , msgType , p . CHAddr ( ) )
2018-12-28 14:17:51 +00:00
return nil
}
return nil
}
2019-03-07 13:48:55 +00:00
// Send ICMP to the specified machine
// Return TRUE if it doesn't reply, which probably means that the IP is available
func ( s * Server ) addrAvailable ( target net . IP ) bool {
2019-05-14 14:49:45 +00:00
if s . conf . ICMPTimeout == 0 {
2019-03-07 13:48:55 +00:00
return true
2019-01-24 17:11:01 +00:00
}
2019-03-07 13:48:55 +00:00
pinger , err := ping . NewPinger ( target . String ( ) )
if err != nil {
log . Error ( "ping.NewPinger(): %v" , err )
return true
2019-01-24 17:11:01 +00:00
}
2019-03-07 13:48:55 +00:00
pinger . SetPrivileged ( true )
2019-05-14 14:49:45 +00:00
pinger . Timeout = time . Duration ( s . conf . ICMPTimeout ) * time . Millisecond
2019-03-07 13:48:55 +00:00
pinger . Count = 1
reply := false
pinger . OnRecv = func ( pkt * ping . Packet ) {
// log.Tracef("Received ICMP Reply from %v", target)
reply = true
2019-01-24 17:11:01 +00:00
}
2019-03-07 13:48:55 +00:00
log . Tracef ( "Sending ICMP Echo to %v" , target )
pinger . Run ( )
2019-01-24 17:11:01 +00:00
2019-03-07 13:48:55 +00:00
if reply {
log . Info ( "DHCP: IP conflict: %v is already used by another device" , target )
return false
2019-01-24 17:11:01 +00:00
}
2019-03-07 13:48:55 +00:00
log . Tracef ( "ICMP procedure is complete: %v" , target )
return true
}
// Add the specified IP to the black list for a time period
func ( s * Server ) blacklistLease ( lease * Lease ) {
hw := make ( net . HardwareAddr , 6 )
2019-05-14 14:49:45 +00:00
s . leasesLock . Lock ( )
2019-06-26 11:01:59 +00:00
s . reserveIP ( lease . IP , hw )
2019-03-07 13:48:55 +00:00
lease . HWAddr = hw
lease . Hostname = ""
lease . Expiry = time . Now ( ) . Add ( s . leaseTime )
2019-06-26 11:02:41 +00:00
s . dbStore ( )
2019-05-14 14:49:45 +00:00
s . leasesLock . Unlock ( )
2020-01-30 07:25:02 +00:00
s . notify ( LeaseChangedBlacklisted )
2019-03-07 13:48:55 +00:00
}
2019-03-07 11:06:35 +00:00
// Return TRUE if DHCP packet is correct
func isValidPacket ( p dhcp4 . Packet ) bool {
hw := p . CHAddr ( )
zeroes := make ( [ ] byte , len ( hw ) )
if bytes . Equal ( hw , zeroes ) {
log . Tracef ( "Packet has empty CHAddr" )
return false
}
return true
}
2019-02-28 15:24:01 +00:00
func ( s * Server ) handleDiscover ( p dhcp4 . Packet , options dhcp4 . Options ) dhcp4 . Packet {
// find a lease, but don't update lease time
2019-03-06 12:13:27 +00:00
var lease * Lease
var err error
2019-02-28 15:24:01 +00:00
2019-03-06 12:13:27 +00:00
reqIP := net . IP ( options [ dhcp4 . OptionRequestedIPAddress ] )
hostname := p . ParseOptions ( ) [ dhcp4 . OptionHostName ]
log . Tracef ( "Message from client: Discover. ReqIP: %s HW: %s Hostname: %s" ,
reqIP , p . CHAddr ( ) , hostname )
2019-03-07 11:06:35 +00:00
if ! isValidPacket ( p ) {
2019-01-24 17:11:01 +00:00
return nil
}
2019-03-06 12:13:27 +00:00
lease = s . findLease ( p )
for lease == nil {
lease , err = s . reserveLease ( p )
if err != nil {
log . Error ( "Couldn't find free lease: %s" , err )
return nil
}
2019-03-07 13:48:55 +00:00
if ! s . addrAvailable ( lease . IP ) {
s . blacklistLease ( lease )
lease = nil
continue
}
2019-03-06 12:13:27 +00:00
break
2019-01-24 17:11:01 +00:00
}
2019-02-28 15:24:01 +00:00
opt := s . leaseOptions . SelectOrderOrAll ( options [ dhcp4 . OptionParameterRequestList ] )
reply := dhcp4 . ReplyPacket ( p , dhcp4 . Offer , s . ipnet . IP , lease . IP , s . leaseTime , opt )
log . Tracef ( "Replying with offer: offered IP %v for %v with options %+v" , lease . IP , s . leaseTime , reply . ParseOptions ( ) )
return reply
}
2019-01-25 13:01:27 +00:00
func ( s * Server ) handleDHCP4Request ( p dhcp4 . Packet , options dhcp4 . Options ) dhcp4 . Packet {
2019-03-06 12:13:27 +00:00
var lease * Lease
2019-01-24 17:11:01 +00:00
2019-03-06 12:13:27 +00:00
reqIP := net . IP ( options [ dhcp4 . OptionRequestedIPAddress ] )
log . Tracef ( "Message from client: Request. IP: %s ReqIP: %s HW: %s" ,
p . CIAddr ( ) , reqIP , p . CHAddr ( ) )
2019-01-24 17:11:01 +00:00
2019-03-07 11:06:35 +00:00
if ! isValidPacket ( p ) {
return nil
}
2019-02-28 15:24:01 +00:00
server := options [ dhcp4 . OptionServerIdentifier ]
if server != nil && ! net . IP ( server ) . Equal ( s . ipnet . IP ) {
2019-01-24 17:11:01 +00:00
log . Tracef ( "Request message not for this DHCP server (%v vs %v)" , server , s . ipnet . IP )
return nil // Message not for this dhcp server
}
if reqIP == nil {
2019-01-25 13:01:27 +00:00
reqIP = p . CIAddr ( )
2019-01-24 17:11:01 +00:00
2019-03-06 12:13:27 +00:00
} else if reqIP == nil || reqIP . To4 ( ) == nil {
log . Tracef ( "Requested IP isn't a valid IPv4: %s" , reqIP )
2019-01-24 17:11:01 +00:00
return dhcp4 . ReplyPacket ( p , dhcp4 . NAK , s . ipnet . IP , nil , 0 , nil )
}
2019-03-06 12:13:27 +00:00
lease = s . findLease ( p )
if lease == nil {
2019-03-07 13:57:20 +00:00
log . Tracef ( "Lease for %s isn't found" , p . CHAddr ( ) )
2019-01-24 17:11:01 +00:00
return dhcp4 . ReplyPacket ( p , dhcp4 . NAK , s . ipnet . IP , nil , 0 , nil )
}
2019-03-06 12:13:27 +00:00
if ! lease . IP . Equal ( reqIP ) {
log . Tracef ( "Lease for %s doesn't match requested/client IP: %s vs %s" ,
lease . HWAddr , lease . IP , reqIP )
return dhcp4 . ReplyPacket ( p , dhcp4 . NAK , s . ipnet . IP , nil , 0 , nil )
2019-01-24 17:11:01 +00:00
}
2019-09-23 15:47:13 +00:00
if lease . Expiry . Unix ( ) != leaseExpireStatic {
lease . Expiry = time . Now ( ) . Add ( s . leaseTime )
2020-01-30 07:25:02 +00:00
s . leasesLock . Lock ( )
s . dbStore ( )
s . leasesLock . Unlock ( )
s . notify ( LeaseChangedAdded ) // Note: maybe we shouldn't call this function if only expiration time is updated
2019-09-23 15:47:13 +00:00
}
2019-03-06 12:13:27 +00:00
log . Tracef ( "Replying with ACK. IP: %s HW: %s Expire: %s" ,
lease . IP , lease . HWAddr , lease . Expiry )
opt := s . leaseOptions . SelectOrderOrAll ( options [ dhcp4 . OptionParameterRequestList ] )
return dhcp4 . ReplyPacket ( p , dhcp4 . ACK , s . ipnet . IP , lease . IP , s . leaseTime , opt )
}
2019-01-24 17:11:01 +00:00
2019-03-06 12:13:27 +00:00
func ( s * Server ) handleInform ( p dhcp4 . Packet , options dhcp4 . Options ) dhcp4 . Packet {
log . Tracef ( "Message from client: Inform. IP: %s HW: %s" ,
p . CIAddr ( ) , p . CHAddr ( ) )
2019-01-24 17:11:01 +00:00
2019-03-06 12:13:27 +00:00
return nil
}
func ( s * Server ) handleRelease ( p dhcp4 . Packet , options dhcp4 . Options ) dhcp4 . Packet {
log . Tracef ( "Message from client: Release. IP: %s HW: %s" ,
p . CIAddr ( ) , p . CHAddr ( ) )
return nil
2019-01-24 17:11:01 +00:00
}
2019-03-06 12:13:27 +00:00
func ( s * Server ) handleDecline ( p dhcp4 . Packet , options dhcp4 . Options ) dhcp4 . Packet {
reqIP := net . IP ( options [ dhcp4 . OptionRequestedIPAddress ] )
log . Tracef ( "Message from client: Decline. IP: %s HW: %s" ,
reqIP , p . CHAddr ( ) )
return nil
2019-01-24 17:11:01 +00:00
}
2019-05-14 10:02:04 +00:00
// AddStaticLease adds a static lease (thread-safe)
func ( s * Server ) AddStaticLease ( l Lease ) error {
if len ( l . IP ) != 4 {
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "invalid IP" )
2019-05-14 10:02:04 +00:00
}
if len ( l . HWAddr ) != 6 {
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "invalid MAC" )
2019-05-14 10:02:04 +00:00
}
l . Expiry = time . Unix ( leaseExpireStatic , 0 )
s . leasesLock . Lock ( )
if s . findReservedHWaddr ( l . IP ) != nil {
2019-08-23 13:14:00 +00:00
err := s . rmDynamicLeaseWithIP ( l . IP )
if err != nil {
2020-01-30 07:25:02 +00:00
s . leasesLock . Unlock ( )
2019-08-23 13:14:00 +00:00
return err
}
2020-04-08 08:55:58 +00:00
} else {
err := s . rmDynamicLeaseWithMAC ( l . HWAddr )
if err != nil {
s . leasesLock . Unlock ( )
return err
}
2019-05-14 10:02:04 +00:00
}
s . leases = append ( s . leases , & l )
s . reserveIP ( l . IP , l . HWAddr )
s . dbStore ( )
2020-01-30 07:25:02 +00:00
s . leasesLock . Unlock ( )
s . notify ( LeaseChangedAddedStatic )
2019-05-14 10:02:04 +00:00
return nil
}
2019-08-23 13:14:00 +00:00
// Remove a dynamic lease by IP address
func ( s * Server ) rmDynamicLeaseWithIP ( ip net . IP ) error {
var newLeases [ ] * Lease
for _ , lease := range s . leases {
2020-04-05 15:34:43 +00:00
if net . IP . Equal ( lease . IP . To4 ( ) , ip ) {
2019-08-23 13:14:00 +00:00
if lease . Expiry . Unix ( ) == leaseExpireStatic {
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "static lease with the same IP already exists" )
2019-08-23 13:14:00 +00:00
}
continue
}
newLeases = append ( newLeases , lease )
}
s . leases = newLeases
s . unreserveIP ( ip )
return nil
}
2020-04-08 08:55:58 +00:00
// Remove a dynamic lease by IP address
func ( s * Server ) rmDynamicLeaseWithMAC ( mac net . HardwareAddr ) error {
var newLeases [ ] * Lease
for _ , lease := range s . leases {
if bytes . Equal ( lease . HWAddr , mac ) {
if lease . Expiry . Unix ( ) == leaseExpireStatic {
return fmt . Errorf ( "static lease with the same IP already exists" )
}
s . unreserveIP ( lease . IP )
continue
}
newLeases = append ( newLeases , lease )
}
s . leases = newLeases
return nil
}
2019-08-23 13:14:00 +00:00
// Remove a lease
func ( s * Server ) rmLease ( l Lease ) error {
var newLeases [ ] * Lease
for _ , lease := range s . leases {
2020-04-05 15:34:43 +00:00
if net . IP . Equal ( lease . IP . To4 ( ) , l . IP ) {
2019-08-23 13:14:00 +00:00
if ! bytes . Equal ( lease . HWAddr , l . HWAddr ) ||
lease . Hostname != l . Hostname {
return fmt . Errorf ( "Lease not found" )
}
continue
}
newLeases = append ( newLeases , lease )
}
s . leases = newLeases
s . unreserveIP ( l . IP )
return nil
}
2019-05-14 10:02:04 +00:00
// RemoveStaticLease removes a static lease (thread-safe)
func ( s * Server ) RemoveStaticLease ( l Lease ) error {
if len ( l . IP ) != 4 {
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "invalid IP" )
2019-05-14 10:02:04 +00:00
}
if len ( l . HWAddr ) != 6 {
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "invalid MAC" )
2019-05-14 10:02:04 +00:00
}
s . leasesLock . Lock ( )
if s . findReservedHWaddr ( l . IP ) == nil {
2020-01-30 07:25:02 +00:00
s . leasesLock . Unlock ( )
2020-04-05 15:34:43 +00:00
return fmt . Errorf ( "lease not found" )
2019-05-14 10:02:04 +00:00
}
2019-08-23 13:14:00 +00:00
err := s . rmLease ( l )
if err != nil {
2020-01-30 07:25:02 +00:00
s . leasesLock . Unlock ( )
2019-08-23 13:14:00 +00:00
return err
2019-05-14 10:02:04 +00:00
}
s . dbStore ( )
2020-01-30 07:25:02 +00:00
s . leasesLock . Unlock ( )
s . notify ( LeaseChangedRemovedStatic )
2019-05-14 10:02:04 +00:00
return nil
}
2020-01-30 07:25:02 +00:00
// flags for Leases() function
const (
LeasesDynamic = 1
LeasesStatic = 2
LeasesAll = LeasesDynamic | LeasesStatic
)
2019-02-28 15:24:01 +00:00
// Leases returns the list of current DHCP leases (thread-safe)
2020-01-30 07:25:02 +00:00
func ( s * Server ) Leases ( flags int ) [ ] Lease {
2019-03-19 12:54:35 +00:00
var result [ ] Lease
now := time . Now ( ) . Unix ( )
2019-05-14 14:49:45 +00:00
s . leasesLock . RLock ( )
2019-03-19 12:54:35 +00:00
for _ , lease := range s . leases {
2020-01-30 07:25:02 +00:00
if ( ( flags & LeasesDynamic ) != 0 && lease . Expiry . Unix ( ) > now ) ||
( ( flags & LeasesStatic ) != 0 && lease . Expiry . Unix ( ) == leaseExpireStatic ) {
2019-03-19 12:54:35 +00:00
result = append ( result , * lease )
}
}
2019-05-14 14:49:45 +00:00
s . leasesLock . RUnlock ( )
2019-03-19 12:54:35 +00:00
2018-12-29 16:13:00 +00:00
return result
2018-12-28 14:17:51 +00:00
}
2019-03-01 17:03:22 +00:00
2019-03-06 12:13:27 +00:00
// Print information about the current leases
func ( s * Server ) printLeases ( ) {
log . Tracef ( "Leases:" )
for i , lease := range s . leases {
log . Tracef ( "Lease #%d: hwaddr %s, ip %s, expiry %s" ,
i , lease . HWAddr , lease . IP , lease . Expiry )
}
}
2019-05-28 11:11:47 +00:00
// FindIPbyMAC finds an IP address by MAC address in the currently active DHCP leases
func ( s * Server ) FindIPbyMAC ( mac net . HardwareAddr ) net . IP {
now := time . Now ( ) . Unix ( )
2019-05-14 14:49:45 +00:00
s . leasesLock . RLock ( )
defer s . leasesLock . RUnlock ( )
2019-05-28 11:11:47 +00:00
for _ , l := range s . leases {
if l . Expiry . Unix ( ) > now && bytes . Equal ( mac , l . HWAddr ) {
return l . IP
}
}
return nil
}
2019-09-26 13:40:52 +00:00
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
func ( s * Server ) FindMACbyIP ( ip net . IP ) net . HardwareAddr {
now := time . Now ( ) . Unix ( )
s . leasesLock . RLock ( )
defer s . leasesLock . RUnlock ( )
2019-12-23 13:59:02 +00:00
ip4 := ip . To4 ( )
if ip4 == nil {
return nil
}
2019-09-26 13:40:52 +00:00
for _ , l := range s . leases {
2019-12-23 13:59:02 +00:00
if l . IP . Equal ( ip4 ) {
unix := l . Expiry . Unix ( )
if unix > now || unix == leaseExpireStatic {
return l . HWAddr
}
2019-09-26 13:40:52 +00:00
}
}
return nil
}
2019-03-01 17:03:22 +00:00
// Reset internal state
func ( s * Server ) reset ( ) {
2019-05-14 14:49:45 +00:00
s . leasesLock . Lock ( )
2019-03-01 17:03:22 +00:00
s . leases = nil
s . IPpool = make ( map [ [ 4 ] byte ] net . HardwareAddr )
2019-06-26 11:01:59 +00:00
s . leasesLock . Unlock ( )
2019-03-01 17:03:22 +00:00
}