dhcp: allow reserved leases outside scope pool range but within subnet#2032
Open
CoryCharlton wants to merge 1 commit into
Open
Conversation
Reserved DHCP leases whose IP is outside the scope's [startingAddress, endingAddress] pool range but still inside the scope's subnet were rejected on add and, when added via the API back door, failed unicast renewal. - Scope: validate reserved-lease addresses by subnet membership (IsAddressInNetwork) instead of pool range (IsAddressInRange), in both the ReservedLeases setter and TryAddReservedLease so the console form and API agree. Wrong-subnet addresses are still rejected. - DhcpServer.FindScope: in the no-relay unicast (RENEW/REBIND) branch, match the scope by the client's reserved lease (keyed on MAC) before the pool-range check, mirroring the broadcast and relay branches. Previously an out-of-pool ciaddr matched no scope, so the REQUEST was silently dropped and the client lost its lease at T1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #831.
Allows DHCP reserved leases whose IP is outside the scope's
[startingAddress, endingAddress]pool range but still within the scope's subnet — accepted on add and correctly served through renewal.Problem
Two defects combined to make out-of-pool reservations unusable:
Validation asymmetry. The
Scope.ReservedLeasessetter (used by the console form viadhcp/scopes/set) validated reserved addresses against the pool range withIsAddressInRange, rejecting anything outside[startingAddress, endingAddress]. The API pathaddReservedLease→Scope.TryAddReservedLeasedid no range check, so the API could create reservations the UI refused.Renewal drop (the real footgun). Reserved leases are keyed by MAC, so a
DISCOVER(ciaddr = 0) hits the broadcast branch ofFindScopeand an out-of-pool reservation is offered/assigned fine. But on renewal the client unicasts aREQUESTwithciaddr= its out-of-pool IP → the no-relay unicast branch ofFindScopeselected the scope only viaIsAddressInRange(ciaddr)→ no scope matched →FindScopereturnednull→ theREQUESTwas silently dropped (no NAK). The client held the address until T1, then lost the lease and re-DISCOVERed — a churn loop. So out-of-pool reservations "worked" until the first renewal.This was confirmed on a production fleet (v15.3): reserved leases inside the pool renewed normally, while reserved leases outside the pool had their
leaseExpiresfrozen at the last server restart and only recovered by re-DISCOVERing at expiry.Changes
Two files, ~3 hunks:
Scope.cs— validate reserved-lease addresses by subnet membership (IsAddressInNetwork) instead of pool range, in both theReservedLeasessetter andTryAddReservedLease, so the console form and the API agree. Wrong-subnet (and network/broadcast) addresses are still rejected.DhcpServer.cs— in the no-relay unicast (RENEW/REBIND) branch ofFindScope, match the scope by the client's reserved lease (keyed on MAC) before the pool-range check, mirroring what the broadcast and relay branches already do. Once the scope is found, the existingRENEWINGpath already ACKs correctly (the reserved offer is built without a range check).Reserved-first ordering also resolves the ambiguity where a reserved IP happens to fall inside a different scope's pool: the scope that actually reserved the address for that MAC wins.
Known limitation
If an out-of-pool reservation is deleted while a client still holds it, unicast renewal can no longer locate the scope (no reserved lease,
ciaddrout of range), so theREQUESTis dropped rather than NAK'd; the client re-DISCOVERs at timeout. Same net effect as today for that address. Fixing it would require matching by the existing dynamic-lease table inFindScope— a larger change I left out to keep this focused.Testing
This repo has no automated test suite, so the change was verified manually with a real, isolated DHCP exchange:
vethpair, on a subnet chosen not to collide with the host LAN. (A plaindummyinterface can't be used — it drops egress, so packets never reach the server.)DnsServerAppinstance in the server namespace; ascapy-based client craftingDISCOVER/REQUEST/ unicastRENEWat L2.…100-.250/24, reserved lease…20(out of pool) added viaaddReservedLease.DISCOVER→OFFERof.20,REQUEST(selecting) →ACK— regression check that allocation still works.RENEWwithciaddr = .20, no server-id, no requested-ip →ACKof.20(before the fix: silent drop).FindScopechange reproduces the silent drop on renewal, confirming the test exercises the bug.addReservedLeaseanddhcp/scopes/setwith a subnet-membership error.If you'd like, I'm happy to add unit coverage around
FindScopeand theReservedLeasessetter — I held off since the project has no test framework yet and that choice seemed better left to you.