Pull request: dhcpd: support decline and release msgs

Updates #3053.

Squashed commit of the following:

commit eb68a21436536d67b6d23873e6b6f291912bf3a1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri May 14 18:58:15 2021 +0300

    dhcpd: do not panic

commit 423678934e736e842fc90042ee0037d1efa912ee
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri May 14 18:18:49 2021 +0300

    dhcpd: support decline and release msgs
This commit is contained in:
Ainar Garipov 2021-05-14 19:16:07 +03:00
parent c890f5c152
commit 9d788a2983
2 changed files with 157 additions and 42 deletions

View file

@ -17,6 +17,10 @@ and this project adheres to
## [v0.106.3] - 2021-05-17 (APPROX.) ## [v0.106.3] - 2021-05-17 (APPROX.)
--> -->
### Added
- Support for DHCP `DECLINE` and `RELEASE` message types ([#3053]).
### Fixed ### Fixed
- DHCP leases validation ([#3107], [#3127]). - DHCP leases validation ([#3107], [#3127]).
@ -26,6 +30,7 @@ and this project adheres to
- Disallowed domains are now case-insensitive ([#3115]). - Disallowed domains are now case-insensitive ([#3115]).
[#2875]: https://github.com/AdguardTeam/AdGuardHome/issues/2875 [#2875]: https://github.com/AdguardTeam/AdGuardHome/issues/2875
[#3053]: https://github.com/AdguardTeam/AdGuardHome/issues/3053
[#3064]: https://github.com/AdguardTeam/AdGuardHome/issues/3064 [#3064]: https://github.com/AdguardTeam/AdGuardHome/issues/3064
[#3107]: https://github.com/AdguardTeam/AdGuardHome/issues/3107 [#3107]: https://github.com/AdguardTeam/AdGuardHome/issues/3107
[#3115]: https://github.com/AdguardTeam/AdGuardHome/issues/3115 [#3115]: https://github.com/AdguardTeam/AdGuardHome/issues/3115

View file

@ -550,14 +550,30 @@ func (s *v4Server) commitLease(l *Lease) {
s.conf.notify(LeaseChangedAdded) s.conf.notify(LeaseChangedAdded)
} }
// allocateLease allocates a new lease for the MAC address. If there are no IP
// addresses left, both l and err are nil.
func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) {
for {
l, err = s.reserveLease(mac)
if err != nil {
return nil, fmt.Errorf("reserving a lease: %w", err)
} else if l == nil {
return nil, nil
}
if s.addrAvailable(l.IP) {
return l, nil
}
s.blocklistLease(l)
}
}
// processDiscover is the handler for the DHCP Discover request. // processDiscover is the handler for the DHCP Discover request.
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) { func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
mac := req.ClientHWAddr mac := req.ClientHWAddr
err = aghnet.ValidateHardwareAddress(mac) defer s.conf.notify(LeaseChangedDBStore)
if err != nil {
return nil, err
}
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
@ -574,36 +590,15 @@ func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err erro
return l, nil return l, nil
} }
needsUpdate := false l, err = s.allocateLease(mac)
defer func() {
if needsUpdate {
s.conf.notify(LeaseChangedDBStore)
}
}()
leaseReady := false
for !leaseReady {
l, err = s.reserveLease(mac)
if err != nil { if err != nil {
return nil, fmt.Errorf("reserving a lease: %w", err) return nil, err
} } else if l == nil {
if l == nil {
log.Debug("dhcpv4: no more ip addresses") log.Debug("dhcpv4: no more ip addresses")
return nil, nil return nil, nil
} }
needsUpdate = true
if s.addrAvailable(l.IP) {
leaseReady = true
} else {
s.blocklistLease(l)
l = nil
}
}
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer)) resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
return l, nil return l, nil
@ -641,11 +636,6 @@ func (o *optFQDN) ToBytes() []byte {
// processRequest is the handler for the DHCP Request request. // processRequest is the handler for the DHCP Request request.
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) { func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
mac := req.ClientHWAddr mac := req.ClientHWAddr
err := aghnet.ValidateHardwareAddress(mac)
if err != nil {
return nil, false
}
reqIP := req.RequestedIPAddress() reqIP := req.RequestedIPAddress()
if reqIP == nil { if reqIP == nil {
reqIP = req.ClientIPAddr reqIP = req.ClientIPAddr
@ -711,7 +701,7 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs
} }
s.commitLease(lease) s.commitLease(lease)
} else if len(lease.Hostname) != 0 { } else if lease.Hostname != "" {
o := &optFQDN{ o := &optFQDN{
name: lease.Hostname, name: lease.Hostname,
} }
@ -728,6 +718,107 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs
return lease, true return lease, true
} }
// processRequest is the handler for the DHCP Decline request.
func (s *v4Server) processDecline(req, resp *dhcpv4.DHCPv4) (err error) {
s.conf.notify(LeaseChangedDBStore)
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
mac := req.ClientHWAddr
reqIP := req.RequestedIPAddress()
if reqIP == nil {
reqIP = req.ClientIPAddr
}
var oldLease *Lease
for _, l := range s.leases {
if bytes.Equal(l.HWAddr, mac) && l.IP.Equal(reqIP) {
oldLease = l
break
}
}
if oldLease == nil {
log.Info("dhcpv4: lease with ip %s for %s not found", reqIP, mac)
return nil
}
err = s.rmDynamicLease(oldLease)
if err != nil {
return fmt.Errorf("removing old lease for %s: %w", mac, err)
}
newLease, err := s.allocateLease(mac)
if err != nil {
return fmt.Errorf("allocating new lease for %s: %w", mac, err)
} else if newLease == nil {
log.Info("dhcpv4: allocating new lease for %s: no more ip addresses", mac)
resp.YourIPAddr = make([]byte, 4)
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
return nil
}
newLease.Hostname = oldLease.Hostname
newLease.Expiry = time.Now().Add(s.conf.leaseTime)
err = s.addLease(newLease)
if err != nil {
return fmt.Errorf("adding new lease for %s: %w", mac, err)
}
log.Info("dhcpv4: changed ip from %s to %s for %s", reqIP, newLease.IP, mac)
resp.YourIPAddr = make([]byte, 4)
copy(resp.YourIPAddr, newLease.IP)
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
return nil
}
// processRelease is the handler for the DHCP Release request.
func (s *v4Server) processRelease(req, resp *dhcpv4.DHCPv4) (err error) {
mac := req.ClientHWAddr
reqIP := req.RequestedIPAddress()
if reqIP == nil {
reqIP = req.ClientIPAddr
}
// TODO(a.garipov): Add a separate notification type for dynamic lease
// removal?
defer s.conf.notify(LeaseChangedDBStore)
n := 0
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
for _, l := range s.leases {
if !bytes.Equal(l.HWAddr, mac) || !l.IP.Equal(reqIP) {
continue
}
err = s.rmDynamicLease(l)
if err != nil {
err = fmt.Errorf("removing dynamic lease for %s: %w", mac, err)
return
}
n++
}
log.Info("dhcpv4: released %d dynamic leases for %s", n, mac)
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
return nil
}
// Find a lease associated with MAC and prepare response // Find a lease associated with MAC and prepare response
// Return 1: OK // Return 1: OK
// Return 0: error; reply with Nak // Return 0: error; reply with Nak
@ -737,6 +828,7 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0])) resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
// TODO(a.garipov): Refactor this into handlers.
var l *Lease var l *Lease
switch req.MessageType() { switch req.MessageType() {
case dhcpv4.MessageTypeDiscover: case dhcpv4.MessageTypeDiscover:
@ -759,10 +851,26 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
} }
return -1 // drop packet return -1 // drop packet
} }
case dhcpv4.MessageTypeDecline:
err = s.processDecline(req, resp)
if err != nil {
log.Error("dhcpv4: processing decline: %s", err)
return 0
}
case dhcpv4.MessageTypeRelease:
err = s.processRelease(req, resp)
if err != nil {
log.Error("dhcpv4: processing release: %s", err)
return 0
}
} }
if l != nil {
resp.YourIPAddr = make([]byte, 4) resp.YourIPAddr = make([]byte, 4)
copy(resp.YourIPAddr, l.IP) copy(resp.YourIPAddr, l.IP)
}
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime)) resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
resp.UpdateOption(dhcpv4.OptRouter(s.conf.subnet.IP)) resp.UpdateOption(dhcpv4.OptRouter(s.conf.subnet.IP))
@ -785,11 +893,13 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
switch req.MessageType() { switch req.MessageType() {
case dhcpv4.MessageTypeDiscover, case dhcpv4.MessageTypeDiscover,
dhcpv4.MessageTypeRequest: dhcpv4.MessageTypeRequest,
// dhcpv4.MessageTypeDecline,
dhcpv4.MessageTypeRelease:
// Go on.
default: default:
log.Debug("dhcpv4: unsupported message type %d", req.MessageType()) log.Debug("dhcpv4: unsupported message type %d", req.MessageType())
return return
} }