in6: Modify address prefix lifetimes when updating address lifetimes

When one uses SIOCAIFADDR_IN6 to add a v6 address, it's possible to set
the preferred and valid lifetimes of the address.  If the address
already exists, this ioctl will recalculate and update the expiry times
based on the provided timestamps.

When adding a new address, the lifetimes are inherited by the prefix as
well, but only if we create a new prefix.  If the prefix already exists,
as it will in the case where an address is being updated rather than
being added, we do not touch the prefix lifetimes at all.  This means
that the original address lifetime still applies to the route associated
with that prefix, so when the prefix expires, the route goes away.

This behaviour doesn't make a lot of sense: if the admin updates an
address lifetime, we should ensure that the prefix lifetime is updated
too.  Make that change, ensuring that we do not shorten the prefix
lifetime, as the prefix might be shared among multiple interface
addresses.

Add a regression test.

Co-authored by:	Franco Fichtner <franco@opnsense.org>
Reviewed by:	pouria, zlei, ae
MFC after:	2 weeks
Sponsored by:	OPNsense
Sponsored by:	Klara, Inc.
Differential Revision:	https://reviews.freebsd.org/D54562
This commit is contained in:
Mark Johnston
2026-01-12 13:49:54 +00:00
parent fb08f80eaf
commit 74999aac5e
2 changed files with 98 additions and 0 deletions
+22
View File
@@ -1330,6 +1330,28 @@ in6_addifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *i
(*carp_detach_p)(&ia->ia_ifa, false);
goto out;
}
} else if (pr->ndpr_raf_onlink) {
time_t expiry;
/*
* If the prefix already exists, update lifetimes, but avoid
* shortening them.
*/
ND6_WLOCK();
expiry = in6_expire_time(pr0.ndpr_pltime);
if (pr->ndpr_preferred != 0 &&
(pr->ndpr_preferred < expiry || expiry == 0)) {
pr->ndpr_pltime = pr0.ndpr_pltime;
pr->ndpr_preferred = expiry;
}
expiry = in6_expire_time(pr0.ndpr_vltime);
if (pr->ndpr_expire != 0 &&
(pr->ndpr_expire < expiry || expiry == 0)) {
pr->ndpr_vltime = pr0.ndpr_vltime;
pr->ndpr_expire = expiry;
}
pr->ndpr_lastupdate = time_uptime;
ND6_WUNLOCK();
}
/* relate the address to the prefix */
+76
View File
@@ -287,6 +287,81 @@ ndp_prefix_lifetime_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_prefix_lifetime_extend"
ndp_prefix_lifetime_extend_head() {
atf_set descr 'Test prefix lifetime updates via ifconfig'
atf_set require.user root
atf_set require.progs jq
}
get_prefix_attr() {
local prefix=$1
local attr=$2
ndp -p --libxo json | \
jq -r '.ndp.["prefix-list"][] |
select(.prefix == "'${prefix}'") | .["'${attr}'"]'
}
# Given a prefix, return its expiry time in seconds.
prefix_expiry() {
get_prefix_attr $1 "expires_sec"
}
# Given a prefix, return its valid and preferred lifetimes.
prefix_lifetimes() {
local p v
v=$(get_prefix_attr $1 "valid-lifetime")
p=$(get_prefix_attr $1 "preferred-lifetime")
echo $v $p
}
ndp_prefix_lifetime_extend_body() {
local epair ex1 ex2 ex3 prefix pltime vltime
atf_check -o save:epair ifconfig epair create
epair=$(cat epair)
atf_check ifconfig ${epair} up
prefix="2001:db8:ffff:1000::"
atf_check ifconfig ${epair} inet6 ${prefix}1/64 pltime 5 vltime 10
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "10 5" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex1=$(prefix_expiry ${prefix}/64)
if [ "${ex1}" -gt 10 ]; then
atf_fail "Unexpected expiry time: ${ex1}"
fi
# Double the address lifetime and verify that the prefix is
# updated.
atf_check ifconfig ${epair} inet6 ${prefix}1/64 pltime 10 vltime 20
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "20 10" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex2=$(prefix_expiry ${prefix}/64)
if [ "${ex2}" -le "${ex1}" ]; then
atf_fail "Expiry time was not extended: ${ex1} <= ${ex2}"
fi
# Add a second address from the same prefix with a shorter
# lifetime, and make sure that the prefix lifetime is not
# shortened.
atf_check ifconfig ${epair} inet6 ${prefix}2/64 pltime 5 vltime 10
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "20 10" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex3=$(prefix_expiry ${prefix}/64)
if [ "${ex3}" -lt "${ex2}" ]; then
atf_fail "Expiry time was shortened: ${ex2} <= ${ex3}"
fi
}
atf_init_test_cases()
{
atf_add_test_case "ndp_add_gu_success"
@@ -294,4 +369,5 @@ atf_init_test_cases()
atf_add_test_case "ndp_slaac_default_route"
atf_add_test_case "ndp_prefix_len_mismatch"
atf_add_test_case "ndp_prefix_lifetime"
atf_add_test_case "ndp_prefix_lifetime_extend"
}