Index | Thread | Search

From:
Florian Obser <florian@narrans.de>
Subject:
Re: Allow rad(8) to advertise shorter lifetimes
To:
Ryan Vogt <rvogt.ca@gmail.com>
Cc:
tech@openbsd.org
Date:
Sun, 14 Sep 2025 13:05:33 +0200

Download raw body.

Thread
Apologies for my silence and very late response, life happened...

I fully agree that lifetimes should be limited and a excessively long
lifetimes handed out by a DHCPv6 server are a problem.

I don't think we need a config option for this. The minimum of what we
find on an interface and what we have in the config file (including
defaults) should win.

The following diff, inspired by your work, implements that.
Does this work for you?
Florian


diff --git frontend.c frontend.c
index 2a519dac0b1..97f4d0ba708 100644
--- frontend.c
+++ frontend.c
@@ -110,7 +110,7 @@ struct ra_iface {
 	int				 removed;
 	int				 link_state;
 	int				 prefix_count;
-	int				 ltime_decaying;
+	int				 has_autoconf_prefix;
 	size_t				 datalen;
 	uint8_t				 data[RA_MAX_SIZE];
 };
@@ -151,11 +151,12 @@ void			 unref_icmp6ev(struct ra_iface *);
 void			 set_icmp6sock(int, int);
 void			 add_new_prefix_to_ra_iface(struct ra_iface *r,
 			    struct in6_addr *, int, struct ra_prefix_conf *,
-			    uint32_t, uint32_t);
+			    int, uint32_t, uint32_t);
 void			 free_ra_iface(struct ra_iface *);
 int			 in6_mask2prefixlen(struct in6_addr *);
 void			 get_interface_prefixes(struct ra_iface *,
 			     struct ra_prefix_conf *, struct ifaddrs *);
+uint32_t		 calc_autoconf_ltime(time_t, uint32_t, uint32_t);
 int			 build_packet(struct ra_iface *);
 void			 build_leaving_packet(struct ra_iface *);
 void			 ra_output(struct ra_iface *, struct sockaddr_in6 *);
@@ -979,7 +980,7 @@ merge_ra_interfaces(void)
 		    entry) {
 			add_new_prefix_to_ra_iface(ra_iface,
 			    &ra_prefix_conf->prefix,
-			    ra_prefix_conf->prefixlen, ra_prefix_conf,
+			    ra_prefix_conf->prefixlen, ra_prefix_conf, 0,
 			    ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME);
 		}
 
@@ -1046,7 +1047,7 @@ get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
 	struct in6_ifreq	 ifr6;
 	struct ifaddrs		*ifa;
 	struct sockaddr_in6	*sin6;
-	uint32_t		 decaying_vltime, decaying_pltime;
+	uint32_t		 if_vltime, if_pltime;
 	int			 prefixlen;
 
 	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
@@ -1066,8 +1067,8 @@ get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
 		strlcpy(ifr6.ifr_name, ra_iface->name, sizeof(ifr6.ifr_name));
 		memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
 
-		decaying_vltime = ND6_INFINITE_LIFETIME;
-		decaying_pltime = ND6_INFINITE_LIFETIME;
+		if_vltime = ND6_INFINITE_LIFETIME;
+		if_pltime = ND6_INFINITE_LIFETIME;
 
 		if (ioctl(ioctlsock, SIOCGIFALIFETIME_IN6,
 		    (caddr_t)&ifr6) != -1) {
@@ -1075,9 +1076,9 @@ get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
 
 			lifetime = &ifr6.ifr_ifru.ifru_lifetime;
 			if (lifetime->ia6t_preferred)
-				decaying_pltime = lifetime->ia6t_preferred;
+				if_pltime = lifetime->ia6t_preferred;
 			if (lifetime->ia6t_expire)
-				decaying_vltime = lifetime->ia6t_expire;
+				if_vltime = lifetime->ia6t_expire;
 		}
 
 		memset(&ifr6, 0, sizeof(ifr6));
@@ -1096,8 +1097,7 @@ get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
 		mask_prefix(&sin6->sin6_addr, prefixlen);
 
 		add_new_prefix_to_ra_iface(ra_iface, &sin6->sin6_addr,
-		    prefixlen, autoprefix_conf, decaying_vltime,
-		    decaying_pltime);
+		    prefixlen, autoprefix_conf, 1, if_vltime, if_pltime);
 	}
 }
 
@@ -1118,41 +1118,30 @@ find_ra_prefix_conf(struct ra_prefix_conf_head* head, struct in6_addr *prefix,
 
 void
 add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
-    int prefixlen, struct ra_prefix_conf *ra_prefix_conf,
-    uint32_t decaying_vltime, uint32_t decaying_pltime)
+    int prefixlen, struct ra_prefix_conf *ra_prefix_conf, int autoconf,
+    uint32_t if_vltime, uint32_t if_pltime)
 {
 	struct ra_prefix_conf	*new_ra_prefix_conf;
 
 	if ((new_ra_prefix_conf = find_ra_prefix_conf(&ra_iface->prefixes, addr,
 	    prefixlen)) != NULL) {
-		if (decaying_vltime != ND6_INFINITE_LIFETIME ||
-		    decaying_pltime != ND6_INFINITE_LIFETIME) {
-			ra_iface->ltime_decaying = 1;
-			new_ra_prefix_conf->ltime_decaying = 0;
-			if (decaying_vltime != ND6_INFINITE_LIFETIME) {
-				new_ra_prefix_conf->vltime = decaying_vltime;
-				new_ra_prefix_conf->ltime_decaying |=
-				    VLTIME_DECAYING;
-			}
-			if (decaying_pltime != ND6_INFINITE_LIFETIME) {
-				new_ra_prefix_conf->pltime = decaying_pltime;
-				new_ra_prefix_conf->ltime_decaying |=
-				    PLTIME_DECAYING;
-			}
-		} else if (new_ra_prefix_conf->ltime_decaying) {
+		int changed = new_ra_prefix_conf->autoconf != autoconf;
+
+		new_ra_prefix_conf->autoconf = autoconf;
+		new_ra_prefix_conf->if_vltime = if_vltime;
+		new_ra_prefix_conf->if_pltime = if_pltime;
+
+		if (changed) {
 			struct ra_prefix_conf *pc;
 
-			new_ra_prefix_conf->ltime_decaying = 0;
-			ra_iface->ltime_decaying = 0;
+			ra_iface->has_autoconf_prefix = 0;
 			SIMPLEQ_FOREACH(pc, &ra_iface->prefixes, entry) {
-				if (pc->ltime_decaying) {
-					ra_iface->ltime_decaying = 1;
+				if (pc->autoconf) {
+					ra_iface->has_autoconf_prefix = 1;
 					break;
 				}
 			}
-		} else
-			log_debug("ignoring duplicate %s/%d prefix",
-			    in6_to_str(addr), prefixlen);
+		}
 		return;
 	}
 
@@ -1164,24 +1153,36 @@ add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
 	new_ra_prefix_conf->prefixlen = prefixlen;
 	new_ra_prefix_conf->vltime = ra_prefix_conf->vltime;
 	new_ra_prefix_conf->pltime = ra_prefix_conf->pltime;
-	if (decaying_vltime != ND6_INFINITE_LIFETIME ||
-	    decaying_pltime != ND6_INFINITE_LIFETIME) {
-		ra_iface->ltime_decaying = 1;
-		if (decaying_vltime != ND6_INFINITE_LIFETIME) {
-			new_ra_prefix_conf->vltime = decaying_vltime;
-			new_ra_prefix_conf->ltime_decaying |= VLTIME_DECAYING;
-		}
-		if (decaying_pltime != ND6_INFINITE_LIFETIME) {
-			new_ra_prefix_conf->pltime = decaying_pltime;
-			new_ra_prefix_conf->ltime_decaying |= PLTIME_DECAYING;
-		}
-	}
+	new_ra_prefix_conf->autoconf = autoconf;
+	if (autoconf)
+		ra_iface->has_autoconf_prefix = 1;
+	new_ra_prefix_conf->if_vltime = if_vltime;
+	new_ra_prefix_conf->if_pltime = if_pltime;
 	new_ra_prefix_conf->aflag = ra_prefix_conf->aflag;
 	new_ra_prefix_conf->lflag = ra_prefix_conf->lflag;
 	SIMPLEQ_INSERT_TAIL(&ra_iface->prefixes, new_ra_prefix_conf, entry);
 	ra_iface->prefix_count++;
 }
 
+uint32_t
+calc_autoconf_ltime(time_t t, uint32_t conf_ltime, uint32_t if_ltime)
+{
+	uint32_t ltime;
+
+	if (if_ltime == ND6_INFINITE_LIFETIME)
+		return conf_ltime;
+
+	if (if_ltime > t)
+		ltime = if_ltime - t;
+	else
+		ltime = 0;
+
+	if (ltime < conf_ltime)
+		return ltime;
+	else
+		return conf_ltime;
+}
+
 int
 build_packet(struct ra_iface *ra_iface)
 {
@@ -1294,16 +1295,15 @@ build_packet(struct ra_iface *ra_iface)
 			ndopt_pi->nd_opt_pi_flags_reserved |=
 			    ND_OPT_PI_FLAG_AUTO;
 
-		if (ra_prefix_conf->ltime_decaying & VLTIME_DECAYING)
-			vltime = ra_prefix_conf->vltime < t ? 0 :
-			    ra_prefix_conf->vltime - t;
-		else
-			vltime = ra_prefix_conf->vltime;
-		if (ra_prefix_conf->ltime_decaying & PLTIME_DECAYING)
-			pltime = ra_prefix_conf->pltime < t ? 0 :
-			    ra_prefix_conf->pltime - t;
-		else
+		if (ra_prefix_conf->autoconf) {
+			pltime = calc_autoconf_ltime(t,
+			    ra_prefix_conf->pltime, ra_prefix_conf->if_pltime);
+			vltime = calc_autoconf_ltime(t,
+			    ra_prefix_conf->vltime, ra_prefix_conf->if_vltime);
+		} else {
 			pltime = ra_prefix_conf->pltime;
+			vltime = ra_prefix_conf->vltime;
+		}
 
 		ndopt_pi->nd_opt_pi_valid_time = htonl(vltime);
 		ndopt_pi->nd_opt_pi_preferred_time = htonl(pltime);
@@ -1430,7 +1430,7 @@ ra_output(struct ra_iface *ra_iface, struct sockaddr_in6 *to)
 	if (!LINK_STATE_IS_UP(ra_iface->link_state))
 		return;
 
-	if (ra_iface->ltime_decaying)
+	if (ra_iface->has_autoconf_prefix)
 		/* update vltime & pltime */
 		build_packet(ra_iface);
 
diff --git rad.conf.5 rad.conf.5
index d03bcc139aa..7211c48d0a1 100644
--- rad.conf.5
+++ rad.conf.5
@@ -207,13 +207,13 @@ The preferred lifetime (pltime) in seconds for addresses generated from this
 prefix.
 The default is 2700.
 This option is ignored if the prefix is discovered from a network interface
-and it has a preferred lifetime configured.
+and it has a lower preferred lifetime.
 .It Cm valid lifetime Ar seconds
 The valid lifetime (vltime) in seconds for addresses generated from this
 prefix.
 The default is 5400.
 This option is ignored if the prefix is discovered from a network interface
-and it has a valid lifetime configured.
+and it has a lower valid lifetime.
 .El
 .El
 .El
diff --git rad.h rad.h
index 7775ac4a0aa..00b4f1bfeb0 100644
--- rad.h
+++ rad.h
@@ -35,8 +35,6 @@
 #define	MIN_DELAY_BETWEEN_RAS	3	/* 3 seconds */
 #define	MAX_SEARCH		1025	/* MAXDNAME in arpa/nameser.h */
 #define	DEFAULT_RDNS_LIFETIME	3 * MAX_RTR_ADV_INTERVAL
-#define	PLTIME_DECAYING		1
-#define	VLTIME_DECAYING		2
 
 #define	IMSG_DATA_SIZE(imsg)	((imsg).hdr.len - IMSG_HEADER_SIZE)
 
@@ -116,7 +114,9 @@ struct ra_prefix_conf {
 	int				 prefixlen;	/* prefix length */
 	uint32_t			 vltime;	/* valid lifetime */
 	uint32_t			 pltime;	/* preferred lifetime */
-	int				 ltime_decaying;
+	uint32_t			 if_vltime;	/* valid lifetime */
+	uint32_t			 if_pltime;	/* preferred lifetime */
+	int				 autoconf;
 	int				 lflag;		/* on-link flag*/
 	int				 aflag;		/* autonom. addr flag */
 };



On 2025-08-07 17:09 -07, Ryan Vogt <rvogt.ca@gmail.com> wrote:
> Hi tech@,
>
> I'd like to ping one final time, to see if there's any feedback about
> this proposed patch.
>
> I also wanted to elaborate on one comment that I made in my previous
> message (inline below), which I explained very poorly.
>
> On Sun, Jul 20, 2025 at 01:43:06PM -0700, Ryan Vogt wrote:
>> On Tue, Jul 01, 2025 at 04:11:48PM -0700, Ryan Vogt wrote:
>> > Hi tech@,
>> > 
>> > I've attached a proposed patch for rad(8). It allows rad to send
>> > advertisements with shorter valid and preferred lifetimes than the
>> > lifetimes otherwise already configured for a prefix discovered on an
>> > interface.
>> > 
>> > The motivation for the patch is for debugging network setups.
>> > Currently, let's say you have dhcp6leased(8) getting an upstream
>> > prefix-delegation lease, good for 7 days. When rad starts advertising
>> > the prefix, it'll advertise it as having a lifetime of 7 days. In
>> > general, this is good behaviour. But, for debugging, I'd like to be
>> > able to tell rad to advertise a lifetime of *no longer* than
>> > 10 minutes, even if the upstream lease is good for more than 10 min.
>> 
>> I'm less confident in this statement now than I was a few weeks ago:
>> that the current default behaviour of rad(8) is ideal (providing de
>> facto unbounded lifetimes where they exist).
>> 
>> Someone sent me a message off-list where they commented about their
>> situation with Google Fiber/Webpass. From what I understood, this
>> Google service gives 1-year valid lifetimes and 1/2-year preferred
>> lifetimes on DHCPv6 prefix delegations. Discussing extra-big numbers
>> like that made me think more about the "ecosystem" (for lack of a
>> better word) formed by dhcp6leased(8) and rad(8).
>> 
>> As it stands right now, in -current with the default behaviour of both
>> rad(8) and dhcpd6leased(6), firing up those two programs on an OpenBSD
>> gateway would send 1-year valid / half-year preferred lifetimes into
>> the network behind the gateway. For comparison, the default lifetimes
>> rad uses in a static-address situation are 90-minutes valid,
>> 45-minutes preferred.
>
> In my previous message, I did a really poor job explaining what it was
> that made me uncomfortable with this situation. Please allow me a
> second attempt.
>
> Let's say that dhcp6leased gets a really long prefix delegation --
> 1 year, 20 years, it doesn't matter. Let's call that prefix
>     XXXX:XXXX:XXXX:XXXX::/64
> Now, in the situation I described above, rad will echo that prefix
> into the internal network, inside the gateway. All the clients on that
> side will now have autoconfigured IPv6 addresses in the
> XXXX:XXXX:XXXX:XXXX::/64 block with really long lifetimes.
>
> My concern is what would happen, now, if dhcp6leased were to get a new
> prefix delegation early. As for how this could happen, maybe the
> original DHCPv6 reply was malicious? Maybe there was a change to the
> DHCPv6 server immediately upstream? I'm admittedly hand-waving the
> specifics here, because my concern is only that it's a possibility.
>
> So, let's say that dhcp6leased gets another really long prefix
> delegation -- again, some number of years, this time for prefix
>     YYYY:YYYY:YYYY:YYYY::/64
> Once again, rad will echo that prefix into the internal network. So,
> all the hosts on the internal network will now have autoconfigured
> addresses in the YYYY:YYYY:YYYY:YYYY::/64 range and the
> XXXX:XXXX:XXXX:XXXX::/64 range, both with really long lifetimes. It
> doesn't matter if rad isn't advertising XXXX:XXXX:XXXX:XXXX::/64
> anymore, because the hosts will remember it for its lifetime.
>
> Repeat by giving dhcp6leased a ZZZZ:ZZZZ:ZZZZ:ZZZZ::/64 delegation,
> a WWWW:WWWW:WWWW:WWWW::/64 delegation, etc., and all the hosts inside
> the network could have a lot of IPv6 addresses, all with really long
> lifetimes.
>
> While IPv6 is designed to allow hosts to have lots of different
> addresses, what concerns me is whether there's some risk of resource
> exhaustion to the hosts inside the network, if rad keeps advertising
> different prefixes with very long lifetimes.
>
> Bounding rad's advertised lifetimes doesn't make this potential
> problem go away (and there are a lot of question marks after
> "potential problem", because I'm not sure how much of one it is). But
> my thinking, in my previous message, was that bounding rad's
> advertised lifetimes could limit how long any issue persists.
>
> As always, thank you very much for your time.
>
> Cheers,
> Ryan
>
>> All of that to say, is the current default behaviour of rad(8) what
>> the default behaviour should be? I've added some example configuration
>> files, inline below, showing what would happen if we changed that
>> behaviour.
>>
>> [...] 
>> 
>> As a thought exercise, here's what would happen if I were to change
>> the patch slightly, making "configured lifetimes no" the default,
>> instead of matching rad's current behaviour with
>> "configured lifetimes yes" being the default.
>> 
>> [...]
>> 
>> This would change the default behaviour of rad only in situations with
>> configured lifetimes, such as where a lifetime has been placed onto
>> the interface by dhcp6leased(8).
>> 
>> Here are some example rad.conf(5) configuration files and how they
>> would behave with this modification:
>> 
>> Configuration file 1 - rad would send lifetimes of at most
>> 5400 seconds (90 minutes) valid, 2700 seconds (45 minutes) preferred.
>> If the configured lifetimes on the interface are shorter, rad would
>> send those smaller values:
>> 
>>     interface em1
>> 
>> Configuration file 2 - rad would send lifetimes of at most 600 seconds
>> (10 mintues) valid, 300 seconds (5 minutes) preferred. Again, if the
>> configurated lifetimes are less, rad would send those smaller values:
>> 
>>     interface em1 {
>>         auto prefix {
>>             valid lifetime 600
>>             preferred lifetime 300
>>         }
>>     }
>> 
>> Configuration file 3 - we explicitly tell rad to go ahead and send
>> whatever lifetimes dhcp6leased places onto em1, even if they're huge:
>> 
>>     interface em1 {
>>         auto prefix {
>>             configured lifetimes yes
>>         }
>>     }
>> 
>> (For clarity: the behaviour of rad in -current with the first two
>> configuration files is to send the unbounded configured lifetimes.
>> There's no "configured lifetimes" option in -current, so the third
>> configuration file has no meaning in -current.)
>> 
>> Changing the patch like this to bound rad's behaviour by default,
>> though, would change the behaviour of rad for what I imagine are a lot
>> of people's current rad.conf files. And, it would be over something
>> that I can't actually convince myself is a real problem. Thoughts?
>> 
>> Again, thank you very much for taking the time to look through this
>> wall of text I've created, and for sharing any insight.
>> 
>> Cheers,
>> Ryan
>> 
>> > Thank you very much for taking the time to consider this patch, and
>> > for any feedback provided.
>> > 
>> > Cheers,
>> > Ryan
>> > 
>> > diff --git usr.sbin/rad/frontend.c usr.sbin/rad/frontend.c
>> > index 2a519dac0b1..cb955043576 100644
>> > --- usr.sbin/rad/frontend.c
>> > +++ usr.sbin/rad/frontend.c
>> > @@ -89,6 +89,9 @@
>> >  #define	RA_MAX_SIZE		1500
>> >  #define	ROUTE_SOCKET_BUF_SIZE	16384
>> >  
>> > +#define LT_IS_DECAYING(vltime, pltime) \
>> > +    ((vltime) != ND6_INFINITE_LIFETIME || (pltime) != ND6_INFINITE_LIFETIME)
>> > +
>> >  struct icmp6_ev {
>> >  	struct event		 ev;
>> >  	uint8_t			 answer[1500];
>> > @@ -1122,37 +1125,35 @@ add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
>> >      uint32_t decaying_vltime, uint32_t decaying_pltime)
>> >  {
>> >  	struct ra_prefix_conf	*new_ra_prefix_conf;
>> > +	struct ra_prefix_conf	*pc;
>> > +	int			 was_decaying;
>> >  
>> >  	if ((new_ra_prefix_conf = find_ra_prefix_conf(&ra_iface->prefixes, addr,
>> >  	    prefixlen)) != NULL) {
>> > -		if (decaying_vltime != ND6_INFINITE_LIFETIME ||
>> > -		    decaying_pltime != ND6_INFINITE_LIFETIME) {
>> > +		if (new_ra_prefix_conf->decaying_vltime == decaying_vltime &&
>> > +		    new_ra_prefix_conf->decaying_pltime == decaying_pltime) {
>> > +			log_debug("ignoring duplicate %s/%d prefix",
>> > +			    in6_to_str(addr), prefixlen);
>> > +			return;
>> > +		}
>> > +		was_decaying = LT_IS_DECAYING(
>> > +		    new_ra_prefix_conf->decaying_vltime,
>> > +		    new_ra_prefix_conf->decaying_pltime);
>> > +		new_ra_prefix_conf->decaying_vltime = decaying_vltime;
>> > +		new_ra_prefix_conf->decaying_pltime = decaying_pltime;
>> > +		if (LT_IS_DECAYING(decaying_vltime, decaying_pltime))
>> >  			ra_iface->ltime_decaying = 1;
>> > -			new_ra_prefix_conf->ltime_decaying = 0;
>> > -			if (decaying_vltime != ND6_INFINITE_LIFETIME) {
>> > -				new_ra_prefix_conf->vltime = decaying_vltime;
>> > -				new_ra_prefix_conf->ltime_decaying |=
>> > -				    VLTIME_DECAYING;
>> > -			}
>> > -			if (decaying_pltime != ND6_INFINITE_LIFETIME) {
>> > -				new_ra_prefix_conf->pltime = decaying_pltime;
>> > -				new_ra_prefix_conf->ltime_decaying |=
>> > -				    PLTIME_DECAYING;
>> > -			}
>> > -		} else if (new_ra_prefix_conf->ltime_decaying) {
>> > -			struct ra_prefix_conf *pc;
>> > -
>> > -			new_ra_prefix_conf->ltime_decaying = 0;
>> > +		else if (was_decaying) {
>> >  			ra_iface->ltime_decaying = 0;
>> >  			SIMPLEQ_FOREACH(pc, &ra_iface->prefixes, entry) {
>> > -				if (pc->ltime_decaying) {
>> > +				if (LT_IS_DECAYING(pc->vltime, pc->pltime)) {
>> >  					ra_iface->ltime_decaying = 1;
>> >  					break;
>> >  				}
>> >  			}
>> > -		} else
>> > -			log_debug("ignoring duplicate %s/%d prefix",
>> > -			    in6_to_str(addr), prefixlen);
>> > +		}
>> > +		log_debug("updating %s/%d prefix lifetimes", in6_to_str(addr),
>> > +		    prefixlen);
>> >  		return;
>> >  	}
>> >  
>> > @@ -1164,18 +1165,11 @@ add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
>> >  	new_ra_prefix_conf->prefixlen = prefixlen;
>> >  	new_ra_prefix_conf->vltime = ra_prefix_conf->vltime;
>> >  	new_ra_prefix_conf->pltime = ra_prefix_conf->pltime;
>> > -	if (decaying_vltime != ND6_INFINITE_LIFETIME ||
>> > -	    decaying_pltime != ND6_INFINITE_LIFETIME) {
>> > +	new_ra_prefix_conf->decaying_vltime = decaying_vltime;
>> > +	new_ra_prefix_conf->decaying_pltime = decaying_pltime;
>> > +	if (LT_IS_DECAYING(decaying_vltime, decaying_pltime))
>> >  		ra_iface->ltime_decaying = 1;
>> > -		if (decaying_vltime != ND6_INFINITE_LIFETIME) {
>> > -			new_ra_prefix_conf->vltime = decaying_vltime;
>> > -			new_ra_prefix_conf->ltime_decaying |= VLTIME_DECAYING;
>> > -		}
>> > -		if (decaying_pltime != ND6_INFINITE_LIFETIME) {
>> > -			new_ra_prefix_conf->pltime = decaying_pltime;
>> > -			new_ra_prefix_conf->ltime_decaying |= PLTIME_DECAYING;
>> > -		}
>> > -	}
>> > +	new_ra_prefix_conf->cfgltimes = ra_prefix_conf->cfgltimes;
>> >  	new_ra_prefix_conf->aflag = ra_prefix_conf->aflag;
>> >  	new_ra_prefix_conf->lflag = ra_prefix_conf->lflag;
>> >  	SIMPLEQ_INSERT_TAIL(&ra_iface->prefixes, new_ra_prefix_conf, entry);
>> > @@ -1294,14 +1288,22 @@ build_packet(struct ra_iface *ra_iface)
>> >  			ndopt_pi->nd_opt_pi_flags_reserved |=
>> >  			    ND_OPT_PI_FLAG_AUTO;
>> >  
>> > -		if (ra_prefix_conf->ltime_decaying & VLTIME_DECAYING)
>> > -			vltime = ra_prefix_conf->vltime < t ? 0 :
>> > -			    ra_prefix_conf->vltime - t;
>> > +		if (ra_prefix_conf->decaying_vltime != ND6_INFINITE_LIFETIME) {
>> > +			vltime = ra_prefix_conf->decaying_vltime < t ? 0 :
>> > +			    ra_prefix_conf->decaying_vltime - t;
>> > +			if (vltime > ra_prefix_conf->vltime &&
>> > +			    !ra_prefix_conf->cfgltimes)
>> > +			    vltime = ra_prefix_conf->vltime;
>> > +		}
>> >  		else
>> >  			vltime = ra_prefix_conf->vltime;
>> > -		if (ra_prefix_conf->ltime_decaying & PLTIME_DECAYING)
>> > -			pltime = ra_prefix_conf->pltime < t ? 0 :
>> > -			    ra_prefix_conf->pltime - t;
>> > +		if (ra_prefix_conf->decaying_pltime != ND6_INFINITE_LIFETIME) {
>> > +			pltime = ra_prefix_conf->decaying_pltime < t ? 0 :
>> > +			    ra_prefix_conf->decaying_pltime - t;
>> > +			if (pltime > ra_prefix_conf->pltime &&
>> > +			    !ra_prefix_conf->cfgltimes)
>> > +			    pltime = ra_prefix_conf->pltime;
>> > +		}
>> >  		else
>> >  			pltime = ra_prefix_conf->pltime;
>> >  
>> > diff --git usr.sbin/rad/parse.y usr.sbin/rad/parse.y
>> > index bf6b6e6b86d..d4d058491dd 100644
>> > --- usr.sbin/rad/parse.y
>> > +++ usr.sbin/rad/parse.y
>> > @@ -122,7 +122,7 @@ typedef struct {
>> >  %token	CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER
>> >  %token	AUTO PREFIX VALID PREFERENCE PREFERRED LIFETIME ONLINK AUTONOMOUS
>> >  %token	ADDRESS_CONFIGURATION DNS NAMESERVER SEARCH MTU NAT64 HIGH MEDIUM LOW
>> > -%token	SOURCE LINK_LAYER
>> > +%token	SOURCE LINK_LAYER CONFIGURED LIFETIMES
>> >  
>> >  %token	<v.string>	STRING
>> >  %token	<v.number>	NUMBER
>> > @@ -368,6 +368,9 @@ ra_prefixoptsl	: VALID LIFETIME NUMBER {
>> >  		| PREFERRED LIFETIME NUMBER {
>> >  			ra_prefix_conf->pltime = $3;
>> >  		}
>> > +		| CONFIGURED LIFETIMES yesno {
>> > +			ra_prefix_conf->cfgltimes = $3;
>> > +		}
>> >  		| ONLINK yesno {
>> >  			ra_prefix_conf->lflag = $2;
>> >  		}
>> > @@ -519,6 +522,7 @@ lookup(char *s)
>> >  		{"auto",		AUTO},
>> >  		{"autonomous",		AUTONOMOUS},
>> >  		{"configuration",	CONFIGURATION},
>> > +		{"configured",		CONFIGURED},
>> >  		{"default",		DEFAULT},
>> >  		{"dns",			DNS},
>> >  		{"high",		HIGH},
>> > @@ -526,6 +530,7 @@ lookup(char *s)
>> >  		{"include",		INCLUDE},
>> >  		{"interface",		RA_IFACE},
>> >  		{"lifetime",		LIFETIME},
>> > +		{"lifetimes",		LIFETIMES},
>> >  		{"limit",		LIMIT},
>> >  		{"link-layer",		LINK_LAYER},
>> >  		{"low",			LOW},
>> > @@ -1087,6 +1092,7 @@ conf_get_ra_prefix(struct in6_addr *addr, int prefixlen)
>> >  	prefix->prefixlen = prefixlen;
>> >  	prefix->vltime = ADV_VALID_LIFETIME;
>> >  	prefix->pltime = ADV_PREFERRED_LIFETIME;
>> > +	prefix->cfgltimes = 1;
>> >  	prefix->lflag = 1;
>> >  	prefix->aflag = 1;
>> >  
>> > diff --git usr.sbin/rad/rad.conf.5 usr.sbin/rad/rad.conf.5
>> > index bf684759e11..292070c7c60 100644
>> > --- usr.sbin/rad/rad.conf.5
>> > +++ usr.sbin/rad/rad.conf.5
>> > @@ -168,6 +168,20 @@ options are as follows:
>> >  This prefix can be used to generate IPv6 addresses.
>> >  The default is
>> >  .Cm yes .
>> > +.It Ic configured lifetimes Pq Cm yes Ns | Ns Cm no
>> > +If the prefix is discovered from a network interface and it has configured
>> > +valid or preferred lifetimes, use the configured lifetimes instead of those
>> > +specified in the static
>> > +.Ic preferred lifetime
>> > +and
>> > +.Ic valid lifetime
>> > +options.
>> > +Even if set to
>> > +.Cm no ,
>> > +static lifetimes will never be used when they are longer than the
>> > +prefix's configured lifetimes.
>> > +The default is
>> > +.Cm yes .
>> >  .It Ic on-link Pq Cm yes Ns | Ns Cm no
>> >  This prefix is considered on-link.
>> >  The default is
>> > @@ -176,14 +190,10 @@ The default is
>> >  The preferred lifetime (pltime) in seconds for addresses generated from this
>> >  prefix.
>> >  The default is 2700.
>> > -This option is ignored if the prefix is discovered from a network interface
>> > -and it has a preferred lifetime configured.
>> >  .It Ic valid lifetime Ar seconds
>> >  The valid lifetime (vltime) in seconds for addresses generated from this
>> >  prefix.
>> >  The default is 5400.
>> > -This option is ignored if the prefix is discovered from a network interface
>> > -and it has a valid lifetime configured.
>> >  .El
>> >  .El
>> >  .El
>> > diff --git usr.sbin/rad/rad.h usr.sbin/rad/rad.h
>> > index 7775ac4a0aa..5f7b79fb38b 100644
>> > --- usr.sbin/rad/rad.h
>> > +++ usr.sbin/rad/rad.h
>> > @@ -35,8 +35,6 @@
>> >  #define	MIN_DELAY_BETWEEN_RAS	3	/* 3 seconds */
>> >  #define	MAX_SEARCH		1025	/* MAXDNAME in arpa/nameser.h */
>> >  #define	DEFAULT_RDNS_LIFETIME	3 * MAX_RTR_ADV_INTERVAL
>> > -#define	PLTIME_DECAYING		1
>> > -#define	VLTIME_DECAYING		2
>> >  
>> >  #define	IMSG_DATA_SIZE(imsg)	((imsg).hdr.len - IMSG_HEADER_SIZE)
>> >  
>> > @@ -116,7 +114,9 @@ struct ra_prefix_conf {
>> >  	int				 prefixlen;	/* prefix length */
>> >  	uint32_t			 vltime;	/* valid lifetime */
>> >  	uint32_t			 pltime;	/* preferred lifetime */
>> > -	int				 ltime_decaying;
>> > +	uint32_t			 decaying_vltime;
>> > +	uint32_t			 decaying_pltime;
>> > +	int				 cfgltimes;	/* config'd lifetimes */
>> >  	int				 lflag;		/* on-link flag*/
>> >  	int				 aflag;		/* autonom. addr flag */
>> >  };
>

-- 
In my defence, I have been left unsupervised.