* [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation
@ 2026-05-07 13:28 Fernando Fernandez Mancera
2026-05-07 13:28 ` [PATCH 2/2 net v3] selftests: fib_tests: add temporary IPv6 address renewal test Fernando Fernandez Mancera
2026-05-10 14:55 ` [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Ido Schimmel
0 siblings, 2 replies; 4+ messages in thread
From: Fernando Fernandez Mancera @ 2026-05-07 13:28 UTC (permalink / raw)
To: netdev
Cc: linux-kselftest, horms, pabeni, kuba, edumazet, davem, idosch,
dsahern, Fernando Fernandez Mancera, Łukasz Stelmach
When a router temporarily deprecates an IPv6 prefix (either by sending a
Router Advertisement with Preferred Lifetime = 0 or by letting the
lifetime expire) and later restores it, the kernel permanently loses its
ability to generate temporary privacy addresses (RFC 8981) for that
prefix.
This happens because the address worker attempts to generate a
replacement temporary address when the current one nears expiration. As
the base prefix is deprecated already, the generation fails after
marking the temporary address already having spawned a replacement
(ifp->regen_count++).
When the router eventually restores the prefix, the temporary address
becomes active again. However, once it naturally expires, the address
worker sees this temporary address already tried to generate one and
skips the regeneration.
Fix this by verifying that the base prefix has sufficient preferred
lifetime remaining before attempting to generate a new temporary
address. In addition, make ipv6_create_tempaddr() return meaningful
error codes. This way, we can catch if a 0-lft RA arrived just after we
passed the verification mentioned above. If we don't have sufficient
preferred lifetime remaining, the worker will keep the next timer as it
is.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Łukasz Stelmach <steelman@post.pl>
Closes: https://lore.kernel.org/netdev/87340td30q.fsf%25steelman@post.pl/
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
v2: adjusted commit message, adjusted the implementation to cover all
race conditions
v3: regen now if ipv6_create_tempaddr failed due to timer to avoid an
infinite loop as we restart the loop and we need to check now against
prefered_lft again.
---
net/ipv6/addrconf.c | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 5476b6536eb7..d54737b5610d 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1379,7 +1379,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
write_unlock_bh(&idev->lock);
pr_info("%s: use_tempaddr is disabled\n", __func__);
in6_dev_put(idev);
- ret = -1;
+ ret = -EOPNOTSUPP;
goto out;
}
spin_lock_bh(&ifp->lock);
@@ -1390,7 +1390,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
pr_warn("%s: regeneration time exceeded - disabled temporary address support\n",
__func__);
in6_dev_put(idev);
- ret = -1;
+ ret = -EADDRNOTAVAIL;
goto out;
}
in6_ifa_hold(ifp);
@@ -1466,7 +1466,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
cfg.preferred_lft > if_public_preferred_lft) {
in6_ifa_put(ifp);
in6_dev_put(idev);
- ret = -1;
+ ret = -EINVAL;
goto out;
}
}
@@ -4655,8 +4655,17 @@ static void addrconf_verify_rtnl(struct net *net)
/* This is a non-regenerated temporary addr. */
unsigned long regen_advance = ipv6_get_regen_advance(ifp->idev);
+ unsigned long pub_tstamp = READ_ONCE(ifp->ifpub->tstamp);
+ unsigned long pub_age = 0;
+ bool pub_expired = false;
+
+ if (time_after(now, pub_tstamp))
+ pub_age = (now - pub_tstamp) / HZ;
- if (age + regen_advance >= ifp->prefered_lft) {
+ if (pub_age + regen_advance >= READ_ONCE(ifp->ifpub->prefered_lft))
+ pub_expired = true;
+
+ if (age + regen_advance >= ifp->prefered_lft && !pub_expired) {
struct inet6_ifaddr *ifpub = ifp->ifpub;
if (time_before(ifp->tstamp + ifp->prefered_lft * HZ, next))
next = ifp->tstamp + ifp->prefered_lft * HZ;
@@ -4670,12 +4679,19 @@ static void addrconf_verify_rtnl(struct net *net)
ifpub->regen_count = 0;
spin_unlock(&ifpub->lock);
rcu_read_unlock_bh();
- ipv6_create_tempaddr(ifpub, true);
+
+ if (ipv6_create_tempaddr(ifpub, true) == -EINVAL) {
+ spin_lock_bh(&ifp->lock);
+ ifp->regen_count = 0;
+ spin_unlock_bh(&ifp->lock);
+ now = jiffies;
+ }
in6_ifa_put(ifpub);
in6_ifa_put(ifp);
rcu_read_lock_bh();
goto restart;
- } else if (time_before(ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ, next))
+ } else if (time_before(ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ, next) &&
+ !pub_expired)
next = ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 2/2 net v3] selftests: fib_tests: add temporary IPv6 address renewal test
2026-05-07 13:28 [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Fernando Fernandez Mancera
@ 2026-05-07 13:28 ` Fernando Fernandez Mancera
2026-05-10 14:55 ` [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Ido Schimmel
1 sibling, 0 replies; 4+ messages in thread
From: Fernando Fernandez Mancera @ 2026-05-07 13:28 UTC (permalink / raw)
To: netdev
Cc: linux-kselftest, horms, pabeni, kuba, edumazet, davem, idosch,
dsahern, Fernando Fernandez Mancera
Add a test to check that temporary IPv6 address is regenerated properly
after the base prefix is deprecated and restored.
Fib6 temporary address renewal test
TEST: IPv6 temporary address cleanly deprecated and regenerated [ OK ]
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
v2: adjusted the sleep so there is enough time for the issue to trigger,
added cleanup at the end
v3: no changes
---
tools/testing/selftests/net/fib_tests.sh | 59 +++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/fib_tests.sh b/tools/testing/selftests/net/fib_tests.sh
index af64f93bb2e1..8f10de0eb985 100755
--- a/tools/testing/selftests/net/fib_tests.sh
+++ b/tools/testing/selftests/net/fib_tests.sh
@@ -12,7 +12,7 @@ TESTS="unregister down carrier nexthop suppress ipv6_notify ipv4_notify \
ipv4_route_metrics ipv4_route_v6_gw rp_filter ipv4_del_addr \
ipv6_del_addr ipv4_mangle ipv6_mangle ipv4_bcast_neigh fib6_gc_test \
ipv4_mpath_list ipv6_mpath_list ipv4_mpath_balance ipv6_mpath_balance \
- ipv4_mpath_balance_preferred fib6_ra_to_static"
+ ipv4_mpath_balance_preferred fib6_ra_to_static fib6_temp_addr_renewal"
VERBOSE=0
PAUSE_ON_FAIL=no
@@ -1611,6 +1611,62 @@ fib6_ra_to_static()
cleanup &> /dev/null
}
+fib6_temp_addr_renewal() {
+ setup
+
+ echo
+ echo "Fib6 temporary address renewal test"
+ set -e
+
+ # ra6 is required for the test. (ipv6toolkit)
+ if [ ! -x "$(command -v ra6)" ]; then
+ echo "SKIP: ra6 not found."
+ set +e
+ cleanup &> /dev/null
+ return
+ fi
+
+ # Create a pair of veth devices to send a RA message from one
+ # device to another.
+ $IP link add veth1 type veth peer name veth2
+ $IP link set dev veth1 up
+ $IP link set dev veth2 up
+
+ # Make veth1 ready to receive RA messages.
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.accept_ra=2
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.use_tempaddr=2
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.temp_prefered_lft=15
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.max_desync_factor=0
+
+ # Send a RA message with a prefix from veth2.
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#3600 -e
+ sleep 3
+
+ # Deprecate it
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#0 -e
+ sleep 3
+
+ # Restore it
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#3600 -e
+
+ ret=1
+ for i in $(seq 1 25); do
+ sleep 1
+ num_dep="$($IP -6 addr | grep -c "temporary deprecated" || true)"
+ num_tot="$($IP -6 addr | grep -c "temporary" || true)"
+
+ if [ "$num_dep" -eq 1 ] && [ "$num_tot" -ge 2 ]; then
+ ret=0
+ break
+ fi
+ done
+ log_test "$ret" 0 "IPv6 temporary address cleanly deprecated and regenerated"
+
+ set +e
+
+ cleanup &> /dev/null
+}
+
# add route for a prefix, flushing any existing routes first
# expected to be the first step of a test
add_route()
@@ -3002,6 +3058,7 @@ do
ipv6_mpath_balance) ipv6_mpath_balance_test;;
ipv4_mpath_balance_preferred) ipv4_mpath_balance_preferred_test;;
fib6_ra_to_static) fib6_ra_to_static;;
+ fib6_temp_addr_renewal) fib6_temp_addr_renewal;;
help) echo "Test names: $TESTS"; exit 0;;
esac
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation
2026-05-07 13:28 [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Fernando Fernandez Mancera
2026-05-07 13:28 ` [PATCH 2/2 net v3] selftests: fib_tests: add temporary IPv6 address renewal test Fernando Fernandez Mancera
@ 2026-05-10 14:55 ` Ido Schimmel
2026-05-10 15:43 ` Fernando Fernandez Mancera
1 sibling, 1 reply; 4+ messages in thread
From: Ido Schimmel @ 2026-05-10 14:55 UTC (permalink / raw)
To: Fernando Fernandez Mancera
Cc: netdev, linux-kselftest, horms, pabeni, kuba, edumazet, davem,
dsahern, Łukasz Stelmach
On Thu, May 07, 2026 at 03:28:27PM +0200, Fernando Fernandez Mancera wrote:
> When a router temporarily deprecates an IPv6 prefix (either by sending a
> Router Advertisement with Preferred Lifetime = 0 or by letting the
> lifetime expire) and later restores it, the kernel permanently loses its
> ability to generate temporary privacy addresses (RFC 8981) for that
> prefix.
>
> This happens because the address worker attempts to generate a
> replacement temporary address when the current one nears expiration. As
> the base prefix is deprecated already, the generation fails after
> marking the temporary address already having spawned a replacement
> (ifp->regen_count++).
>
> When the router eventually restores the prefix, the temporary address
> becomes active again. However, once it naturally expires, the address
> worker sees this temporary address already tried to generate one and
> skips the regeneration.
>
> Fix this by verifying that the base prefix has sufficient preferred
> lifetime remaining before attempting to generate a new temporary
> address. In addition, make ipv6_create_tempaddr() return meaningful
> error codes. This way, we can catch if a 0-lft RA arrived just after we
> passed the verification mentioned above. If we don't have sufficient
> preferred lifetime remaining, the worker will keep the next timer as it
> is.
I didn't go through all the Sashiko comments, but at least some of them
seem valid. I wonder if we can simplify this and do the following
instead:
When updating all the temporary addresses (f.e., because we received a
RA), instead of only creating a temporary address if none exist, also
create a temporary address if all the existing ones already regenerated
a temporary address, as otherwise no temporary address will ever be
created. Something like [1]. I didn't run it through Sashiko, but I did
confirm that your test fails without it and passes with it.
[1]
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 5476b6536eb7..3eaa583bb08d 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2597,6 +2597,7 @@ static void manage_tempaddrs(struct inet6_dev *idev,
{
u32 flags;
struct inet6_ifaddr *ift;
+ bool all_regen = true;
read_lock_bh(&idev->lock);
/* update all temporary addresses in the list */
@@ -2637,6 +2638,8 @@ static void manage_tempaddrs(struct inet6_dev *idev,
ift->tstamp = now;
if (prefered_lft > 0)
ift->flags &= ~IFA_F_DEPRECATED;
+ if (!ift->regen_count)
+ all_regen = false;
spin_unlock(&ift->lock);
if (!(flags&IFA_F_TENTATIVE))
@@ -2644,12 +2647,14 @@ static void manage_tempaddrs(struct inet6_dev *idev,
}
/* Also create a temporary address if it's enabled but no temporary
- * address currently exists.
+ * address currently exists or if all the temporary addresses already
+ * regenerated an address.
* However, we get called with valid_lft == 0, prefered_lft == 0, create == false
* as part of cleanup (ie. deleting the mngtmpaddr).
* We don't want that to result in creating a new temporary ip address.
*/
- if (list_empty(&idev->tempaddr_list) && (valid_lft || prefered_lft))
+ if ((list_empty(&idev->tempaddr_list) || all_regen)
+ && (valid_lft || prefered_lft))
create = true;
if (create && READ_ONCE(idev->cnf.use_tempaddr) > 0) {
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation
2026-05-10 14:55 ` [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Ido Schimmel
@ 2026-05-10 15:43 ` Fernando Fernandez Mancera
0 siblings, 0 replies; 4+ messages in thread
From: Fernando Fernandez Mancera @ 2026-05-10 15:43 UTC (permalink / raw)
To: Ido Schimmel
Cc: netdev, linux-kselftest, horms, pabeni, kuba, edumazet, davem,
dsahern, Łukasz Stelmach
On 5/10/26 4:55 PM, Ido Schimmel wrote:
> On Thu, May 07, 2026 at 03:28:27PM +0200, Fernando Fernandez Mancera wrote:
>> When a router temporarily deprecates an IPv6 prefix (either by sending a
>> Router Advertisement with Preferred Lifetime = 0 or by letting the
>> lifetime expire) and later restores it, the kernel permanently loses its
>> ability to generate temporary privacy addresses (RFC 8981) for that
>> prefix.
>>
>> This happens because the address worker attempts to generate a
>> replacement temporary address when the current one nears expiration. As
>> the base prefix is deprecated already, the generation fails after
>> marking the temporary address already having spawned a replacement
>> (ifp->regen_count++).
>>
>> When the router eventually restores the prefix, the temporary address
>> becomes active again. However, once it naturally expires, the address
>> worker sees this temporary address already tried to generate one and
>> skips the regeneration.
>>
>> Fix this by verifying that the base prefix has sufficient preferred
>> lifetime remaining before attempting to generate a new temporary
>> address. In addition, make ipv6_create_tempaddr() return meaningful
>> error codes. This way, we can catch if a 0-lft RA arrived just after we
>> passed the verification mentioned above. If we don't have sufficient
>> preferred lifetime remaining, the worker will keep the next timer as it
>> is.
>
> I didn't go through all the Sashiko comments, but at least some of them
> seem valid. I wonder if we can simplify this and do the following
> instead:
>
> When updating all the temporary addresses (f.e., because we received a
> RA), instead of only creating a temporary address if none exist, also
> create a temporary address if all the existing ones already regenerated
> a temporary address, as otherwise no temporary address will ever be
> created. Something like [1]. I didn't run it through Sashiko, but I did
> confirm that your test fails without it and passes with it.
>
Hi Ido,
Thanks for this suggestion. I was preparing a new iteration for the
patch and was trying to simplify it. This looks quite good indeed and
better than messing with the timers.
Let me test this properly and if it looks good I will send a v4 and add
a Suggested-by tag.
Thanks again Ido!
Fernando.
> [1]
> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
> index 5476b6536eb7..3eaa583bb08d 100644
> --- a/net/ipv6/addrconf.c
> +++ b/net/ipv6/addrconf.c
> @@ -2597,6 +2597,7 @@ static void manage_tempaddrs(struct inet6_dev *idev,
> {
> u32 flags;
> struct inet6_ifaddr *ift;
> + bool all_regen = true;
>
> read_lock_bh(&idev->lock);
> /* update all temporary addresses in the list */
> @@ -2637,6 +2638,8 @@ static void manage_tempaddrs(struct inet6_dev *idev,
> ift->tstamp = now;
> if (prefered_lft > 0)
> ift->flags &= ~IFA_F_DEPRECATED;
> + if (!ift->regen_count)
> + all_regen = false;
>
> spin_unlock(&ift->lock);
> if (!(flags&IFA_F_TENTATIVE))
> @@ -2644,12 +2647,14 @@ static void manage_tempaddrs(struct inet6_dev *idev,
> }
>
> /* Also create a temporary address if it's enabled but no temporary
> - * address currently exists.
> + * address currently exists or if all the temporary addresses already
> + * regenerated an address.
> * However, we get called with valid_lft == 0, prefered_lft == 0, create == false
> * as part of cleanup (ie. deleting the mngtmpaddr).
> * We don't want that to result in creating a new temporary ip address.
> */
> - if (list_empty(&idev->tempaddr_list) && (valid_lft || prefered_lft))
> + if ((list_empty(&idev->tempaddr_list) || all_regen)
> + && (valid_lft || prefered_lft))
> create = true;
>
> if (create && READ_ONCE(idev->cnf.use_tempaddr) > 0) {
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-10 15:43 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-07 13:28 [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Fernando Fernandez Mancera
2026-05-07 13:28 ` [PATCH 2/2 net v3] selftests: fib_tests: add temporary IPv6 address renewal test Fernando Fernandez Mancera
2026-05-10 14:55 ` [PATCH 1/2 net v3] ipv6: addrconf: fix temp address generation after prefix deprecation Ido Schimmel
2026-05-10 15:43 ` Fernando Fernandez Mancera
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox