2020-07-03 18:20:01 +03:00
|
|
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
|
|
|
|
|
|
package dhcpd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2021-03-05 19:20:36 +03:00
|
|
|
"strings"
|
2020-07-03 18:20:01 +03:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2020-11-20 13:44:21 +03:00
|
|
|
"github.com/go-ping/ping"
|
2020-07-03 18:20:01 +03:00
|
|
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
|
|
|
)
|
|
|
|
|
2020-11-27 14:39:43 +03:00
|
|
|
// v4Server is a DHCPv4 server.
|
|
|
|
//
|
|
|
|
// TODO(a.garipov): Think about unifying this and v6Server.
|
2020-07-03 18:20:01 +03:00
|
|
|
type v4Server struct {
|
|
|
|
srv *server4.Server
|
|
|
|
leasesLock sync.Mutex
|
|
|
|
leases []*Lease
|
2021-02-12 13:27:44 +03:00
|
|
|
// TODO(e.burkov): This field type should be a normal bitmap.
|
|
|
|
ipAddrs [256]byte
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
conf V4ServerConf
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteDiskConfig4 - write configuration
|
|
|
|
func (s *v4Server) WriteDiskConfig4(c *V4ServerConf) {
|
|
|
|
*c = s.conf
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteDiskConfig6 - write configuration
|
|
|
|
func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return TRUE if IP address is within range [start..stop]
|
2020-11-09 19:27:04 +03:00
|
|
|
func ip4InRange(start, stop, ip net.IP) bool {
|
2020-07-03 18:20:01 +03:00
|
|
|
if len(start) != 4 || len(stop) != 4 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
from := binary.BigEndian.Uint32(start)
|
|
|
|
to := binary.BigEndian.Uint32(stop)
|
|
|
|
check := binary.BigEndian.Uint32(ip)
|
|
|
|
return from <= check && check <= to
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResetLeases - reset leases
|
|
|
|
func (s *v4Server) ResetLeases(leases []*Lease) {
|
|
|
|
s.leases = nil
|
|
|
|
|
|
|
|
for _, l := range leases {
|
|
|
|
|
|
|
|
if l.Expiry.Unix() != leaseExpireStatic &&
|
|
|
|
!ip4InRange(s.conf.ipStart, s.conf.ipEnd, l.IP) {
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: skipping a lease with IP %v: not within current IP range", l.IP)
|
2020-07-03 18:20:01 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
s.addLease(l)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLeasesRef - get leases
|
|
|
|
func (s *v4Server) GetLeasesRef() []*Lease {
|
|
|
|
return s.leases
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return TRUE if this lease holds a blacklisted IP
|
|
|
|
func (s *v4Server) blacklisted(l *Lease) bool {
|
|
|
|
return l.HWAddr.String() == "00:00:00:00:00:00"
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLeases returns the list of current DHCP leases (thread-safe)
|
|
|
|
func (s *v4Server) GetLeases(flags int) []Lease {
|
2021-01-20 15:59:24 +03:00
|
|
|
// The function shouldn't return nil value because zero-length slice
|
|
|
|
// behaves differently in cases like marshalling. Our front-end also
|
|
|
|
// requires non-nil value in the response.
|
|
|
|
result := []Lease{}
|
2020-07-03 18:20:01 +03:00
|
|
|
now := time.Now().Unix()
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
for _, lease := range s.leases {
|
|
|
|
if ((flags&LeasesDynamic) != 0 && lease.Expiry.Unix() > now && !s.blacklisted(lease)) ||
|
|
|
|
((flags&LeasesStatic) != 0 && lease.Expiry.Unix() == leaseExpireStatic) {
|
|
|
|
result = append(result, *lease)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
|
|
|
func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
|
|
|
now := time.Now().Unix()
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
defer s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
ip4 := ip.To4()
|
|
|
|
if ip4 == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, l := range s.leases {
|
|
|
|
if l.IP.Equal(ip4) {
|
|
|
|
unix := l.Expiry.Unix()
|
|
|
|
if unix > now || unix == leaseExpireStatic {
|
|
|
|
return l.HWAddr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the specified IP to the black list for a time period
|
|
|
|
func (s *v4Server) blacklistLease(lease *Lease) {
|
|
|
|
hw := make(net.HardwareAddr, 6)
|
|
|
|
lease.HWAddr = hw
|
|
|
|
lease.Hostname = ""
|
|
|
|
lease.Expiry = time.Now().Add(s.conf.leaseTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove (swap) lease by index
|
|
|
|
func (s *v4Server) leaseRemoveSwapByIndex(i int) {
|
|
|
|
s.ipAddrs[s.leases[i].IP[3]] = 0
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: removed lease %s", s.leases[i].HWAddr)
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
n := len(s.leases)
|
|
|
|
if i != n-1 {
|
|
|
|
s.leases[i] = s.leases[n-1] // swap with the last element
|
|
|
|
}
|
|
|
|
s.leases = s.leases[:n-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a dynamic lease with the same properties
|
|
|
|
// Return error if a static lease is found
|
|
|
|
func (s *v4Server) rmDynamicLease(lease Lease) error {
|
|
|
|
for i := 0; i < len(s.leases); i++ {
|
|
|
|
l := s.leases[i]
|
|
|
|
|
|
|
|
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
|
|
|
|
|
|
|
if l.Expiry.Unix() == leaseExpireStatic {
|
|
|
|
return fmt.Errorf("static lease already exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
2020-08-25 13:38:52 +03:00
|
|
|
if i == len(s.leases) {
|
|
|
|
break
|
|
|
|
}
|
2020-07-03 18:20:01 +03:00
|
|
|
l = s.leases[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
if net.IP.Equal(l.IP, lease.IP) {
|
|
|
|
|
|
|
|
if l.Expiry.Unix() == leaseExpireStatic {
|
|
|
|
return fmt.Errorf("static lease already exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a lease
|
|
|
|
func (s *v4Server) addLease(l *Lease) {
|
|
|
|
s.leases = append(s.leases, l)
|
|
|
|
s.ipAddrs[l.IP[3]] = 1
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: added lease %s <-> %s", l.IP, l.HWAddr)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a lease with the same properties
|
|
|
|
func (s *v4Server) rmLease(lease Lease) error {
|
|
|
|
for i, l := range s.leases {
|
|
|
|
if net.IP.Equal(l.IP, lease.IP) {
|
|
|
|
|
|
|
|
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
|
|
|
l.Hostname != lease.Hostname {
|
2020-11-05 15:20:57 +03:00
|
|
|
return fmt.Errorf("lease not found")
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("lease not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddStaticLease adds a static lease (thread-safe)
|
|
|
|
func (s *v4Server) AddStaticLease(lease Lease) error {
|
|
|
|
if len(lease.IP) != 4 {
|
|
|
|
return fmt.Errorf("invalid IP")
|
|
|
|
}
|
|
|
|
if len(lease.HWAddr) != 6 {
|
|
|
|
return fmt.Errorf("invalid MAC")
|
|
|
|
}
|
|
|
|
lease.Expiry = time.Unix(leaseExpireStatic, 0)
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
err := s.rmDynamicLease(lease)
|
|
|
|
if err != nil {
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.addLease(&lease)
|
|
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
s.conf.notify(LeaseChangedAddedStatic)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveStaticLease removes a static lease (thread-safe)
|
|
|
|
func (s *v4Server) RemoveStaticLease(l Lease) error {
|
|
|
|
if len(l.IP) != 4 {
|
|
|
|
return fmt.Errorf("invalid IP")
|
|
|
|
}
|
|
|
|
if len(l.HWAddr) != 6 {
|
|
|
|
return fmt.Errorf("invalid MAC")
|
|
|
|
}
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
err := s.rmLease(l)
|
|
|
|
if err != nil {
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
s.conf.notify(LeaseChangedRemovedStatic)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send ICMP to the specified machine
|
|
|
|
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
|
|
|
func (s *v4Server) addrAvailable(target net.IP) bool {
|
|
|
|
if s.conf.ICMPTimeout == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
pinger, err := ping.NewPinger(target.String())
|
|
|
|
if err != nil {
|
|
|
|
log.Error("ping.NewPinger(): %v", err)
|
2020-11-20 13:44:21 +03:00
|
|
|
|
2020-07-03 18:20:01 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
pinger.SetPrivileged(true)
|
|
|
|
pinger.Timeout = time.Duration(s.conf.ICMPTimeout) * time.Millisecond
|
|
|
|
pinger.Count = 1
|
|
|
|
reply := false
|
|
|
|
pinger.OnRecv = func(pkt *ping.Packet) {
|
|
|
|
reply = true
|
|
|
|
}
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
2020-11-20 13:44:21 +03:00
|
|
|
|
|
|
|
err = pinger.Run()
|
|
|
|
if err != nil {
|
|
|
|
log.Error("pinger.Run(): %v", err)
|
|
|
|
return true
|
|
|
|
}
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
if reply {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Info("dhcpv4: IP conflict: %v is already used by another device", target)
|
2020-07-03 18:20:01 +03:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: ICMP procedure is complete: %v", target)
|
2020-07-03 18:20:01 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find lease by MAC
|
|
|
|
func (s *v4Server) findLease(mac net.HardwareAddr) *Lease {
|
|
|
|
for i := range s.leases {
|
|
|
|
if bytes.Equal(mac, s.leases[i].HWAddr) {
|
|
|
|
return s.leases[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get next free IP
|
|
|
|
func (s *v4Server) findFreeIP() net.IP {
|
|
|
|
for i := s.conf.ipStart[3]; ; i++ {
|
|
|
|
if s.ipAddrs[i] == 0 {
|
|
|
|
ip := make([]byte, 4)
|
|
|
|
copy(ip, s.conf.ipStart)
|
|
|
|
ip[3] = i
|
|
|
|
return ip
|
|
|
|
}
|
|
|
|
if i == s.conf.ipEnd[3] {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find an expired lease and return its index or -1
|
|
|
|
func (s *v4Server) findExpiredLease() int {
|
|
|
|
now := time.Now().Unix()
|
|
|
|
for i, lease := range s.leases {
|
|
|
|
if lease.Expiry.Unix() != leaseExpireStatic &&
|
|
|
|
lease.Expiry.Unix() <= now {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reserve lease for MAC
|
|
|
|
func (s *v4Server) reserveLease(mac net.HardwareAddr) *Lease {
|
|
|
|
l := Lease{}
|
|
|
|
l.HWAddr = make([]byte, 6)
|
|
|
|
copy(l.HWAddr, mac)
|
|
|
|
|
|
|
|
l.IP = s.findFreeIP()
|
|
|
|
if l.IP == nil {
|
|
|
|
i := s.findExpiredLease()
|
|
|
|
if i < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
copy(s.leases[i].HWAddr, mac)
|
|
|
|
return s.leases[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
s.addLease(&l)
|
|
|
|
return &l
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *v4Server) commitLease(l *Lease) {
|
|
|
|
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
s.conf.notify(LeaseChangedAdded)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process Discover request and return lease
|
2020-11-09 19:27:04 +03:00
|
|
|
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) *Lease {
|
2020-07-03 18:20:01 +03:00
|
|
|
mac := req.ClientHWAddr
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
defer s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
lease := s.findLease(mac)
|
|
|
|
if lease == nil {
|
|
|
|
toStore := false
|
|
|
|
for lease == nil {
|
|
|
|
lease = s.reserveLease(mac)
|
|
|
|
if lease == nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: No more IP addresses")
|
2020-07-03 18:20:01 +03:00
|
|
|
if toStore {
|
|
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
toStore = true
|
|
|
|
|
|
|
|
if !s.addrAvailable(lease.IP) {
|
|
|
|
s.blacklistLease(lease)
|
|
|
|
lease = nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress)
|
|
|
|
if len(reqIP) != 0 &&
|
|
|
|
!bytes.Equal(reqIP, lease.IP) {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: different RequestedIP: %v != %v", reqIP, lease.IP)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
|
|
|
|
return lease
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:01:55 +03:00
|
|
|
type optFQDN struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *optFQDN) String() string {
|
|
|
|
return "optFQDN"
|
|
|
|
}
|
|
|
|
|
|
|
|
// flags[1]
|
|
|
|
// A-RR[1]
|
|
|
|
// PTR-RR[1]
|
|
|
|
// name[]
|
|
|
|
func (o *optFQDN) ToBytes() []byte {
|
|
|
|
b := make([]byte, 3+len(o.name))
|
|
|
|
i := 0
|
|
|
|
|
|
|
|
b[i] = 0x03 // f_server_overrides | f_server
|
|
|
|
i++
|
|
|
|
|
|
|
|
b[i] = 255 // A-RR
|
|
|
|
i++
|
|
|
|
|
|
|
|
b[i] = 255 // PTR-RR
|
|
|
|
i++
|
|
|
|
|
|
|
|
copy(b[i:], []byte(o.name))
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2020-07-03 18:20:01 +03:00
|
|
|
// Process Request request and return lease
|
|
|
|
// Return false if we don't need to reply
|
2020-11-09 19:27:04 +03:00
|
|
|
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (*Lease, bool) {
|
2020-07-03 18:20:01 +03:00
|
|
|
var lease *Lease
|
|
|
|
mac := req.ClientHWAddr
|
|
|
|
hostname := req.Options.Get(dhcpv4.OptionHostName)
|
|
|
|
reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress)
|
|
|
|
if reqIP == nil {
|
|
|
|
reqIP = req.ClientIPAddr
|
|
|
|
}
|
|
|
|
|
|
|
|
sid := req.Options.Get(dhcpv4.OptionServerIdentifier)
|
|
|
|
if len(sid) != 0 &&
|
|
|
|
!bytes.Equal(sid, s.conf.dnsIPAddrs[0]) {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: Bad OptionServerIdentifier in Request message for %s", mac)
|
2020-07-03 18:20:01 +03:00
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(reqIP) != 4 {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: Bad OptionRequestedIPAddress in Request message for %s", mac)
|
2020-07-03 18:20:01 +03:00
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
s.leasesLock.Lock()
|
|
|
|
for _, l := range s.leases {
|
|
|
|
if bytes.Equal(l.HWAddr, mac) {
|
|
|
|
if !bytes.Equal(l.IP, reqIP) {
|
|
|
|
s.leasesLock.Unlock()
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: Mismatched OptionRequestedIPAddress in Request message for %s", mac)
|
2020-07-03 18:20:01 +03:00
|
|
|
return nil, true
|
|
|
|
}
|
|
|
|
|
|
|
|
lease = l
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.leasesLock.Unlock()
|
|
|
|
|
|
|
|
if lease == nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: No lease for %s", mac)
|
2020-07-03 18:20:01 +03:00
|
|
|
return nil, true
|
|
|
|
}
|
|
|
|
|
|
|
|
if lease.Expiry.Unix() != leaseExpireStatic {
|
2021-03-05 19:20:36 +03:00
|
|
|
// The trimming is required since some devices include trailing
|
|
|
|
// zero-byte in DHCP option length calculation.
|
|
|
|
//
|
|
|
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2582.
|
|
|
|
//
|
|
|
|
// TODO(e.burkov): Remove after the trimming for hostname option
|
|
|
|
// will be added into github.com/insomniacslk/dhcp module.
|
|
|
|
hostnameStr := strings.TrimRight(string(hostname), "\x00")
|
|
|
|
|
|
|
|
lease.Hostname = hostnameStr
|
2020-07-03 18:20:01 +03:00
|
|
|
s.commitLease(lease)
|
2020-08-24 13:01:55 +03:00
|
|
|
} else if len(lease.Hostname) != 0 {
|
|
|
|
o := &optFQDN{
|
2020-08-25 17:44:30 +03:00
|
|
|
name: lease.Hostname,
|
2020-08-24 13:01:55 +03:00
|
|
|
}
|
|
|
|
fqdn := dhcpv4.Option{
|
|
|
|
Code: dhcpv4.OptionFQDN,
|
|
|
|
Value: o,
|
|
|
|
}
|
|
|
|
resp.UpdateOption(fqdn)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
|
|
|
|
return lease, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find a lease associated with MAC and prepare response
|
|
|
|
// Return 1: OK
|
|
|
|
// Return 0: error; reply with Nak
|
|
|
|
// Return -1: error; don't reply
|
2020-11-09 19:27:04 +03:00
|
|
|
func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
2020-07-03 18:20:01 +03:00
|
|
|
var lease *Lease
|
|
|
|
|
|
|
|
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
|
|
|
|
|
|
|
switch req.MessageType() {
|
|
|
|
|
|
|
|
case dhcpv4.MessageTypeDiscover:
|
|
|
|
lease = s.processDiscover(req, resp)
|
|
|
|
if lease == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
case dhcpv4.MessageTypeRequest:
|
|
|
|
var toReply bool
|
|
|
|
lease, toReply = s.processRequest(req, resp)
|
|
|
|
if lease == nil {
|
|
|
|
if toReply {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return -1 // drop packet
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.YourIPAddr = make([]byte, 4)
|
|
|
|
copy(resp.YourIPAddr, lease.IP)
|
|
|
|
|
|
|
|
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
|
|
|
|
resp.UpdateOption(dhcpv4.OptRouter(s.conf.routerIP))
|
|
|
|
resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnetMask))
|
|
|
|
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
2020-08-25 14:07:11 +03:00
|
|
|
|
|
|
|
for _, opt := range s.conf.options {
|
|
|
|
resp.Options[opt.code] = opt.val
|
|
|
|
}
|
2020-07-03 18:20:01 +03:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// client(0.0.0.0:68) -> (Request:ClientMAC,Type=Discover,ClientID,ReqIP,HostName) -> server(255.255.255.255:67)
|
|
|
|
// client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,Type=Offer,ServerID,SubnetMask,LeaseTime) <- server(<IP>:67)
|
|
|
|
// client(0.0.0.0:68) -> (Request:ClientMAC,Type=Request,ClientID,ReqIP||ClientIP,HostName,ServerID,ParamReqList) -> server(255.255.255.255:67)
|
|
|
|
// client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,Type=ACK,ServerID,SubnetMask,LeaseTime) <- server(<IP>:67)
|
|
|
|
func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: received message: %s", req.Summary())
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
switch req.MessageType() {
|
|
|
|
case dhcpv4.MessageTypeDiscover,
|
|
|
|
dhcpv4.MessageTypeRequest:
|
|
|
|
//
|
|
|
|
|
|
|
|
default:
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: unsupported message type %d", req.MessageType())
|
2020-07-03 18:20:01 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := dhcpv4.NewReplyFromRequest(req)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: dhcpv4.New: %s", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(req.ClientHWAddr) != 6 {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: Invalid ClientHWAddr")
|
2020-07-03 18:20:01 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r := s.process(req, resp)
|
|
|
|
if r < 0 {
|
|
|
|
return
|
|
|
|
} else if r == 0 {
|
|
|
|
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: sending: %s", resp.Summary())
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
_, err = conn.WriteTo(resp.ToBytes(), peer)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
2020-07-03 18:20:01 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-09 19:27:04 +03:00
|
|
|
// Start starts the IPv4 DHCP server.
|
2020-07-03 18:20:01 +03:00
|
|
|
func (s *v4Server) Start() error {
|
|
|
|
if !s.conf.Enabled {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-09 19:27:04 +03:00
|
|
|
ifaceName := s.conf.InterfaceName
|
|
|
|
iface, err := net.InterfaceByName(ifaceName)
|
2020-07-03 18:20:01 +03:00
|
|
|
if err != nil {
|
2020-11-09 19:27:04 +03:00
|
|
|
return fmt.Errorf("dhcpv4: finding interface %s by name: %w", ifaceName, err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: starting...")
|
2020-11-09 19:27:04 +03:00
|
|
|
|
2020-11-27 14:39:43 +03:00
|
|
|
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
2020-11-09 19:27:04 +03:00
|
|
|
if err != nil {
|
2020-11-27 14:39:43 +03:00
|
|
|
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
2020-12-02 14:42:59 +03:00
|
|
|
if len(dnsIPAddrs) == 0 {
|
|
|
|
// No available IP addresses which may appear later.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-09 19:27:04 +03:00
|
|
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
|
|
|
|
2020-07-03 18:20:01 +03:00
|
|
|
laddr := &net.UDPAddr{
|
2021-01-13 16:56:05 +03:00
|
|
|
IP: net.IP{0, 0, 0, 0},
|
2020-07-03 18:20:01 +03:00
|
|
|
Port: dhcpv4.ServerPort,
|
|
|
|
}
|
|
|
|
s.srv, err = server4.NewServer(iface.Name, laddr, s.packetHandler, server4.WithDebugLogger())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Info("dhcpv4: listening")
|
2020-07-03 18:20:01 +03:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
err = s.srv.Serve()
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: srv.Serve: %s", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}()
|
2020-11-09 19:27:04 +03:00
|
|
|
|
2020-07-03 18:20:01 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop - stop server
|
|
|
|
func (s *v4Server) Stop() {
|
|
|
|
if s.srv == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: stopping")
|
2020-07-03 18:20:01 +03:00
|
|
|
err := s.srv.Close()
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Error("dhcpv4: srv.Close: %s", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
// now s.srv.Serve() will return
|
|
|
|
s.srv = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create DHCPv4 server
|
|
|
|
func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
|
|
|
s := &v4Server{}
|
|
|
|
s.conf = conf
|
|
|
|
|
|
|
|
if !conf.Enabled {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2021-01-13 16:56:05 +03:00
|
|
|
s.conf.routerIP, err = tryTo4(s.conf.GatewayIP)
|
2020-07-03 18:20:01 +03:00
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
2021-01-13 16:56:05 +03:00
|
|
|
if s.conf.SubnetMask == nil {
|
|
|
|
return s, fmt.Errorf("dhcpv4: invalid subnet mask: %v", s.conf.SubnetMask)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
s.conf.subnetMask = make([]byte, 4)
|
2021-01-13 16:56:05 +03:00
|
|
|
copy(s.conf.subnetMask, s.conf.SubnetMask.To4())
|
2020-07-03 18:20:01 +03:00
|
|
|
|
2021-01-13 16:56:05 +03:00
|
|
|
s.conf.ipStart, err = tryTo4(conf.RangeStart)
|
2020-07-03 18:20:01 +03:00
|
|
|
if s.conf.ipStart == nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
if s.conf.ipStart[0] == 0 {
|
2020-11-05 15:20:57 +03:00
|
|
|
return s, fmt.Errorf("dhcpv4: invalid range start IP")
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
2021-01-13 16:56:05 +03:00
|
|
|
s.conf.ipEnd, err = tryTo4(conf.RangeEnd)
|
2020-07-03 18:20:01 +03:00
|
|
|
if s.conf.ipEnd == nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
if !net.IP.Equal(s.conf.ipStart[:3], s.conf.ipEnd[:3]) ||
|
|
|
|
s.conf.ipStart[3] > s.conf.ipEnd[3] {
|
2020-11-05 15:20:57 +03:00
|
|
|
return s, fmt.Errorf("dhcpv4: range end IP should match range start IP")
|
2020-07-03 18:20:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if conf.LeaseDuration == 0 {
|
|
|
|
s.conf.leaseTime = time.Hour * 24
|
|
|
|
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
|
|
|
} else {
|
|
|
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
|
|
|
}
|
|
|
|
|
2020-08-25 14:07:11 +03:00
|
|
|
for _, o := range conf.Options {
|
|
|
|
code, val := parseOptionString(o)
|
|
|
|
if code == 0 {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("dhcpv4: bad option string: %s", o)
|
2020-08-25 14:07:11 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
opt := dhcpOption{
|
|
|
|
code: code,
|
|
|
|
val: val,
|
|
|
|
}
|
|
|
|
s.conf.options = append(s.conf.options, opt)
|
|
|
|
}
|
|
|
|
|
2020-07-03 18:20:01 +03:00
|
|
|
return s, nil
|
|
|
|
}
|