[Dnsmasq-discuss] [PATCH] radv: explicit lease time as a floor for constructed-prefix RA lifetimes

Giuseppe Piscitelli ooonea at gmail.com
Sun Aug 9 21:56:45 UTC 2026


Simon,

one correction to my v2: the code was unchanged and fine, but the
man-page hunk had stale/incorrect context and would not apply cleanly
to current master.

Attached is v3, regenerated and checked against master
8d8ad76d896f8eff3bd59dcdb207062c7466a49f. The radv.c change is still
identical to v1/v2; only the man-page hunk context is corrected.

Verified here with:
- git diff --check
- git apply --check
- make -j2
- dnsmasq --test

All pass.

Patch inline below as well.

ooonea

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index c557553..2ec9b66 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1047,7 +1047,10 @@ options. If the lease time is given, then leases
 will be given for that length of time. The lease time is in seconds,
 or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or
"infinite". If not given,
 the default lease time is one hour for IPv4 and one day for IPv6. The
-minimum lease time is two minutes. For IPv6 ranges, the lease time
+minimum lease time is two minutes. An explicitly configured lease time also
+affects IPv6 router advertisements: it acts as a ceiling on the advertised
+prefix lifetimes. For constructed ranges it also acts as a floor, unless the
+prefix is being deprecated. For IPv6 ranges, the lease time
 maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
 lease or router advertisement to zero, which causes clients to use
 other addresses, if available, for new connections as a prelude to renumbering.
diff --git a/src/radv.c b/src/radv.c
index 00c2aa7..e31bc25 100644
--- a/src/radv.c
+++ b/src/radv.c
@@ -608,6 +608,7 @@ static int add_prefixes(struct in6_addr *local,  int prefix,
 	  int do_slaac = 0;
 	  int deprecate  = 0;
 	  int constructed = 0;
+	  int have_lease = 0;
 	  int adv_router = 0;
 	  int off_link = 0;
 	  unsigned int time = 0xffffffff;
@@ -660,6 +661,8 @@ static int add_prefixes(struct in6_addr *local,  int prefix,
 		    if (time < ((unsigned int)(3 * param->adv_interval)))
 		      time = 3 * param->adv_interval;
 		  }
+		if (context->flags & CONTEXT_SETLEASE)
+		  have_lease = 1;

 		if (context->flags & CONTEXT_DEPRECATE)
 		  deprecate = 1;
@@ -697,7 +700,19 @@ static int add_prefixes(struct in6_addr *local,
int prefix,
 	  /* configured time is ceiling */
 	  if (!constructed || valid > time)
 	    valid = time;
-	
+
+	  /* For CONSTRUCTED prefixes an explicitly configured lease time is
+	     also a floor: an upstream delegating with very short lifetimes
+	     (mobile links) would otherwise churn the addresses of any client
+	     that sleeps through a few RAs. Deprecation still wins below. */
+	  if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
+	    {
+	      if (valid < time)
+		valid = time;
+	      if (preferred < time)
+		preferred = time;
+	    }
+
 	  if (flags & IFACE_DEPRECATED)
 	    preferred = 0;
 	

On Sun, 9 Aug 2026 14:44:32 -0700, Giuseppe Piscitelli <ooonea at gmail.com> wrote:
> Thanks Simon.
>
> Sure — attached is v2 with the man-page update. It documents both the
> existing ceiling behaviour for explicitly configured lease times in
> router advertisements and the new floor behaviour for constructed
> ranges. No code changes from v1.
>
> Patch inline below as well.
>
> ooonea
>
> radv: let an explicit lease time floor constructed-prefix RA lifetimes
>
> For constructor: ranges dnsmasq advertises the residual kernel lifetimes
> of the interface address, and an explicitly configured lease time only
> caps them. Behind an upstream that delegates with very short lifetimes
> (Starlink PD: ~300s valid / 150s preferred) every client inherits the
> churn verbatim, and any device that sleeps through a few RAs (every
> phone) loses its GUA mid-flow, killing established connections.
>
> With this patch an explicitly configured lease time on a constructed
> range acts as a floor as well. Prefix deprecation is untouched: a
> deprecated context or kernel-deprecated address still advertises
> preferred=0, and a vanished prefix still goes through the CONTEXT_OLD
> path, so renumbering behaves exactly as stock.
>
> Document both the existing RA lifetime ceiling and the new floor
> behaviour for constructed ranges in dnsmasq.8.
>
> diff --git a/src/radv.c b/src/radv.c
> --- a/src/radv.c
> +++ b/src/radv.c
> @@ -608,6 +608,7 @@
> int do_slaac = 0;
> int deprecate = 0;
> int constructed = 0;
> + int have_lease = 0;
> int adv_router = 0;
> int off_link = 0;
> unsigned int time = 0xffffffff;
> @@ -660,6 +661,8 @@
> if (time < ((unsigned int)(3 * param->adv_interval)))
> time = 3 * param->adv_interval;
> }
> + if (context->flags & CONTEXT_SETLEASE)
> + have_lease = 1;
>
> if (context->flags & CONTEXT_DEPRECATE)
> deprecate = 1;
> @@ -697,7 +700,19 @@
> /* configured time is ceiling */
> if (!constructed || valid > time)
> valid = time;
> -
> +
> + /* For CONSTRUCTED prefixes an explicitly configured lease time is
> + also a floor: an upstream delegating with very short lifetimes
> + (mobile links) would otherwise churn the addresses of any client
> + that sleeps through a few RAs. Deprecation still wins below. */
> + if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
> + {
> + if (valid < time)
> + valid = time;
> + if (preferred < time)
> + preferred = time;
> + }
> +
> if (flags & IFACE_DEPRECATED)
> preferred = 0;
>
> diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
> --- a/man/dnsmasq.8
> +++ b/man/dnsmasq.8
> @@ -1061,1 +1061,5 @@
> The minimum lease time is two minutes.
> +An explicitly configured lease time also affects IPv6 router advertisements:
> +it acts as a ceiling on the advertised prefix lifetimes. For constructed
> +ranges it also acts as a floor, unless the prefix is being deprecated.
> +
>
> On Sun, 9 Aug 2026 21:47:07 +0100, Simon Kelley <simon at thekelleys.org.uk> wrote:
> > This looks sensible to me.
> >
> > Can I ask for one more contribution? It would really save me time if the
> > patch included an update to the man page, describing an explicitly set
> > lease time affects RA, both the existing ceiling behaviour, which is
> > inexplicably missing :) and the new floor behaviour for constructed ranges.
> >
> > This is posted to the list partly because it's generally applicable.
> > Your patch is more likely to get accepted if you don't make me write the
> > documentation, people.
> >
> > Cheers,
> >
> > Simon.
> >
> > On 07.08.2026 01:16, Giuseppe Piscitelli wrote:
> > > Hi,
> > >
> > > on constructed prefixes (--dhcp-range=::,constructor:eth0,...) the RA
> > > carries the residual lifetimes of the interface address, and an
> > > explicitly configured lease time only caps them ("configured time is
> > > ceiling" in radv.c).
> > >
> > > Behind an upstream that delegates with very short lifetimes this
> > > propagates the churn to every client. Starlink delegates a /56 with
> > > about 300s valid / 150s preferred: the router refreshes forever, but a
> > > client that sleeps through a few RAs -- every phone -- loses its
> > > global address, and its established connections die with it. Seen live
> > > on my LAN: phones answering on v4 and link-local while their GUA sat
> > > FAILED in the router's neighbour cache, apps stalling mid-stream on
> > > WiFi only.
> > >
> > > With this patch an explicitly configured lease time on a constructed
> > > range acts as a floor as well. Ranges without an explicit lease time
> > > are unchanged. Deprecation still wins: a kernel-deprecated address or
> > > a deprecated context still advertises preferred=0, and a vanished
> > > prefix still goes through the old-prefix path, so renumbering behaves
> > > as before.
> > >
> > > Running in production here on 2.93 with a 12h lease on the constructed
> > > range: clients hold 12h instead of the 300s residue and the
> > > sleep-induced address loss is gone. The patch applies cleanly to
> > > current master.
> > >
> > > If changing the meaning of the existing lease-time field on
> > > constructed ranges is not wanted, I can rework this behind a dedicated
> > > keyword instead.
> > >
> > > Patch inline below and attached too, in case my mailer mangles the
> > > tabs.
> > >
> > > ooonea
> > >
> > > --- a/src/radv.c
> > > +++ b/src/radv.c
> > > @@ -608,6 +608,7 @@
> > > int do_slaac = 0;
> > > int deprecate = 0;
> > > int constructed = 0;
> > > + int have_lease = 0;
> > > int adv_router = 0;
> > > int off_link = 0;
> > > unsigned int time = 0xffffffff;
> > > @@ -660,6 +661,8 @@
> > > if (time < ((unsigned int)(3 * param->adv_interval)))
> > > time = 3 * param->adv_interval;
> > > }
> > > + if (context->flags & CONTEXT_SETLEASE)
> > > + have_lease = 1;
> > >
> > > if (context->flags & CONTEXT_DEPRECATE)
> > > deprecate = 1;
> > > @@ -697,7 +700,19 @@
> > > /* configured time is ceiling */
> > > if (!constructed || valid > time)
> > > valid = time;
> > > -
> > > +
> > > + /* For CONSTRUCTED prefixes an explicitly configured lease time is
> > > + also a floor: an upstream delegating with very short lifetimes
> > > + (mobile links) would otherwise churn the addresses of any client
> > > + that sleeps through a few RAs. Deprecation still wins below. */
> > > + if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
> > > + {
> > > + if (valid < time)
> > > + valid = time;
> > > + if (preferred < time)
> > > + preferred = time;
> > > + }
> > > +
> > > if (flags & IFACE_DEPRECATED)
> > > preferred = 0;
> > >
> > >
> > > _______________________________________________
> > > Dnsmasq-discuss mailing list
> > > Dnsmasq-discuss at lists.thekelleys.org.uk
> > > https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dnsmasq-constructed-ra-lifetime-floor-v3.patch
Type: text/x-diff
Size: 2395 bytes
Desc: not available
URL: <http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20260809/632c866d/attachment.patch>


More information about the Dnsmasq-discuss mailing list