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

View file

@ -550,14 +550,30 @@ func (s *v4Server) commitLease(l *Lease) {
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.
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
mac := req.ClientHWAddr
err = aghnet.ValidateHardwareAddress(mac)
if err != nil {
return nil, err
}
defer s.conf.notify(LeaseChangedDBStore)
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
@ -574,36 +590,15 @@ func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err erro
return l, nil
}
needsUpdate := false
defer func() {
if needsUpdate {
s.conf.notify(LeaseChangedDBStore)
}
}()
leaseReady := false
for !leaseReady {
l, err = s.reserveLease(mac)
l, err = s.allocateLease(mac)
if err != nil {
return nil, fmt.Errorf("reserving a lease: %w", err)
}
if l == nil {
return nil, err
} else if l == nil {
log.Debug("dhcpv4: no more ip addresses")
return nil, nil
}
needsUpdate = true
if s.addrAvailable(l.IP) {
leaseReady = true
} else {
s.blocklistLease(l)
l = nil
}
}
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
return l, nil
@ -641,11 +636,6 @@ func (o *optFQDN) ToBytes() []byte {
// processRequest is the handler for the DHCP Request request.
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
mac := req.ClientHWAddr
err := aghnet.ValidateHardwareAddress(mac)
if err != nil {
return nil, false
}
reqIP := req.RequestedIPAddress()
if reqIP == nil {
reqIP = req.ClientIPAddr
@ -711,7 +701,7 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs
}
s.commitLease(lease)
} else if len(lease.Hostname) != 0 {
} else if lease.Hostname != "" {
o := &optFQDN{
name: lease.Hostname,
}
@ -728,6 +718,107 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs
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
// Return 1: OK
// 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]))
// TODO(a.garipov): Refactor this into handlers.
var l *Lease
switch req.MessageType() {
case dhcpv4.MessageTypeDiscover:
@ -759,10 +851,26 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
}
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)
copy(resp.YourIPAddr, l.IP)
}
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
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() {
case dhcpv4.MessageTypeDiscover,
dhcpv4.MessageTypeRequest:
//
dhcpv4.MessageTypeRequest,
dhcpv4.MessageTypeDecline,
dhcpv4.MessageTypeRelease:
// Go on.
default:
log.Debug("dhcpv4: unsupported message type %d", req.MessageType())
return
}