Hi Simon,

a `dhcp-host` reservation with `infinite` silently ends up with a finite lease as soon as the client sends option 51. calc_time() treats the configured lease
time as an upper bound, and `infinite` is an unbounded one, so whatever the
client asks for is shorter and wins. Apple devices request 7776000 seconds in every DHCPREQUEST, so every iPhone/iPad/Watch with a reservation is affected.

The attached patch honors `infinite` literally when it comes from `dhcp-host`:
for a reserved address there is nothing to be gained from expiring it early.
`dhcp-range` is left alone - there, granting a client's shorter request does
return pool addresses sooner.

Reproducer, with a client sending option 51 = 7776000 for its reserved address:

  dnsmasq -d --port=0 --dhcp-authoritative \
    --dhcp-range=10.99.0.100,10.99.0.200,255.255.255.0,1h \
    --dhcp-host=02:aa:bb:cc:dd:ee,10.99.0.9,testhost,infinite

The DHCPACK lease time is 7776000 before and 4294967295 with the patch. A
finite `dhcp-host` lease time and pool addresses behave exactly as before, in
both directions.

Downstream report: https://github.com/pi-hole/FTL/issues/3014

Best regards,
Dominik
From 63ced127b864459eb5d50651811c165151ca12df Mon Sep 17 00:00:00 2001
From: DL6ER <[email protected]>
Date: Sun, 9 Aug 2026 10:14:36 +0200
Subject: [PATCH] Do not shorten a host's infinite lease to the client's
 requested time

`calc_time()` treats the configured lease time as an upper bound and grants
whatever shorter time the client asks for in DHCP option 51. An infinite lease
time is unbounded, so *any* time the client requests wins and a host configured
as

    dhcp-host=<mac>,<address>,infinite

ends up with a finite lease. Apple clients run into this because they ask for
7776000 seconds (90 days) in every request.

A lease time given in `dhcp-host` is explicit and per-host, so `infinite` is
now honored literally - there is no pool hygiene to be gained from expiring a
reserved address earlier. `dhcp-range` is left unchanged: for pool addresses,
a client asking for less is still respected.

Signed-off-by: DL6ER <[email protected]>
---
 src/rfc2131.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/rfc2131.c b/src/rfc2131.c
index 7b58e69..4065c9c 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -1858,10 +1858,13 @@ unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
 
 static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt)
 {
-  unsigned int time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
-  
-  if (opt)
-    { 
+  int host_time = have_config(config, CONFIG_TIME);
+  unsigned int time = host_time ? config->lease_time : context->lease_time;
+
+  /* The configured lease time is an upper bound a client may ask to shorten,
+     but an infinite lease configured for this very host is meant literally. */
+  if (opt && !(host_time && time == 0xffffffff))
+    {
       unsigned int req_time = option_uint(opt, 0, 4);
       if (req_time < 120 )
 	req_time = 120; /* sanity */
-- 
2.43.0

_______________________________________________
Dnsmasq-discuss mailing list
[email protected]
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to