Download raw body.
Allow rad(8) to advertise shorter lifetimes
Hi tech@,
I wanted to touch base with the list, to see if there's any thoughts
about the proposed "configured lifetimes (yes|no)" feature for
rad.conf(5) / rad(8).
Since I sent my original message a few weeks back, I've had a couple
of additional brainwaves, which I've inlined below. Apologies for any
confusion I'm causing with a self-reply.
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.
I'd very much so appreciate insight from someone who has more
knowledge about IPv6 than I do. I can't convince myself it's
necessarily an issue to send year-long lifetimes (or even much
larger!) into an internal network by default, but I equally can't
convince myself there isn't an issue. I know IPv6 is designed for hosts
to have many different IP addresses without issue, but it still feels
like there's an unnecessary opening for some sort of denial of service
if I'm unknowningly (or unthinkingly) sending lifetimes like that into
my network.
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.
> There was a related discussion [1] back in May 2024 on tech@, in
> which florian@ and I had talked about making rad announce lifetimes
> bounded by both their static rad.conf(5) setting and any discovered
> configured lifetimes.
> [1] https://marc.info/?l=openbsd-tech&m=171717272517654
>
> This patch isn't exactly what was discussed back then, but I think
> it accomplishes the goal. It introduces a new "configured lifetimes"
> option for a prefix in rad.conf, and it defaults to "yes". That
> option, when set to "yes", instructs rad: please use any configured
> lifetimes (valid/preferred) you find for this prefix instead of the
> statically declared lifetimes in rad.conf. If it's set to "no", the
> statically declared lifetimes will be used instead, *unless* they're
> longer than the configured lifetimes for the prefix (in which case the
> shorter configured lifetimes are still used).
>
> Phrased slightly differently: "configured lifetimes yes" instructs
> rad to use longer configured lifetimes than the statically declared
> lifetimes, if longer ones are there.
>
> After the patch is applied, rad.conf can look like this:
>
> interface em1
> {
> auto prefix {
> preferred lifetime 300
> valid lifetime 600
> configured lifetimes no
> }
> }
>
> In this case, rad will send advertisements with a preferred lifetime
> of 5 minutes and a valid lifetime of 10 minutes, unless there are
> shorter lifetimes configured for the prefix (then, rad will use a
> shorter preferred lifetime and/or a shorter valid lifetime). Longer
> lifetimes configured on em1 will discarded.
>
> As a final note for clarity: existing rad.conf configuration files
> will be unaffected -- the default setting of "yes" for
> "configured lifetimes" matches the current behaviour of rad.
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.
(I haven't attached a changed patch, but I'd be happy to write it.)
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 */
> };
Allow rad(8) to advertise shorter lifetimes