diff mbox series

ipv6/addrconf: clamp preferred_lft to the minimum instead of erroring

Message ID 20230821011116.21931-1-alexhenrie24@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series ipv6/addrconf: clamp preferred_lft to the minimum instead of erroring | expand

Checks

Context Check Description
netdev/series_format warning Single patches do not need cover letters; Target tree name not specified in the subject
netdev/tree_selection success Guessed tree name to be net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1330 this patch: 1330
netdev/cc_maintainers warning 3 maintainers not CCed: kuba@kernel.org edumazet@google.com pabeni@redhat.com
netdev/build_clang success Errors and warnings before: 1353 this patch: 1353
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1353 this patch: 1353
netdev/checkpatch warning WARNING: Possible repeated word: 'that'
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Alex Henrie Aug. 21, 2023, 1:11 a.m. UTC
I tried setting /proc/sys/net/ipv6/conf/*/temp_prefered_lft to 1 so that
the address would roll over as frequently as possible, then spent hours
trying to understand why the preferred lifetime jumped to 4 billion
seconds. On my machine and network the shortest lifetime that avoids
underflow is 3 seconds.

After fixing the underflow, I ran into a second problem: The preferred
lifetime was less than the minimum required lifetime, so
ipv6_create_tempaddr would error out without creating any new address.
This error happened immediately with the preferred lifetime set to
1 second, after a few minutes with the preferred lifetime set to
4 seconds, and not at all with the preferred lifetime set to 5 seconds.
During my investigation, I found a Stack Exchange post from another
person who seems to have had the same problem: They stopped getting new
addresses if they lowered the preferred lifetime below 3 seconds, and
they didn't really know why.

The preferred lifetime is a preference, not a hard requirement. The
kernel does not strictly forbid new connections on a deprecated address,
nor does it guarantee that the address will be disposed of the instant
its total valid lifetime expires. So rather than disable IPv6 privacy
extensions altogether if the minimum required lifetime swells above the
preferred lifetime, it is more in keeping with the user's intent to
increase the temporary address's lifetime to the minimum necessary for
the current network conditions.

With these fixes, setting the preferred lifetime to 3 or 4 seconds "just
works" because the extra fraction of a second is practically
unnoticeable. It's even possible to reduce the time before deprecation
to 1 or 2 seconds by also disabling duplicate address detection (setting
/proc/sys/net/ipv6/conf/*/dad_transmits to 0). I realize that that is a
pretty niche use case, but I know at least one person who would gladly
sacrifice performance and convenience to be sure that they are getting
the maximum possible level of privacy.

Link: https://serverfault.com/a/1031168/310447
Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 net/ipv6/addrconf.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

Comments

Paolo Abeni Aug. 22, 2023, 9:54 a.m. UTC | #1
Hi,

On Sun, 2023-08-20 at 19:11 -0600, Alex Henrie wrote:
> I tried setting /proc/sys/net/ipv6/conf/*/temp_prefered_lft to 1 so that
> the address would roll over as frequently as possible, then spent hours
> trying to understand why the preferred lifetime jumped to 4 billion
> seconds. On my machine and network the shortest lifetime that avoids
> underflow is 3 seconds.
> 
> After fixing the underflow, I ran into a second problem: The preferred
> lifetime was less than the minimum required lifetime, so
> ipv6_create_tempaddr would error out without creating any new address.
> This error happened immediately with the preferred lifetime set to
> 1 second, after a few minutes with the preferred lifetime set to
> 4 seconds, and not at all with the preferred lifetime set to 5 seconds.
> During my investigation, I found a Stack Exchange post from another
> person who seems to have had the same problem: They stopped getting new
> addresses if they lowered the preferred lifetime below 3 seconds, and
> they didn't really know why.
> 
> The preferred lifetime is a preference, not a hard requirement. The
> kernel does not strictly forbid new connections on a deprecated address,
> nor does it guarantee that the address will be disposed of the instant
> its total valid lifetime expires. So rather than disable IPv6 privacy
> extensions altogether if the minimum required lifetime swells above the
> preferred lifetime, it is more in keeping with the user's intent to
> increase the temporary address's lifetime to the minimum necessary for
> the current network conditions.
> 
> With these fixes, setting the preferred lifetime to 3 or 4 seconds "just
> works" because the extra fraction of a second is practically
> unnoticeable. It's even possible to reduce the time before deprecation
> to 1 or 2 seconds by also disabling duplicate address detection (setting
> /proc/sys/net/ipv6/conf/*/dad_transmits to 0). I realize that that is a
> pretty niche use case, but I know at least one person who would gladly
> sacrifice performance and convenience to be sure that they are getting
> the maximum possible level of privacy.
> 
> Link: https://serverfault.com/a/1031168/310447
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>

It looks like you are fixing 2 separate bugs, so 2 separate patches
would be better.

You should explicitly state the target tree (in this case 'net') into
the patch subj.

You should add a suitable fixes tag to each patch.

> ---
>  net/ipv6/addrconf.c | 10 +++-------
>  1 file changed, 3 insertions(+), 7 deletions(-)
> 
> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
> index 94cec2075eee..4008d4a5e58d 100644
> --- a/net/ipv6/addrconf.c
> +++ b/net/ipv6/addrconf.c
> @@ -1368,7 +1368,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
>  	 * idev->desync_factor if it's larger
>  	 */
>  	cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
> -	max_desync_factor = min_t(__u32,
> +	max_desync_factor = min_t(__s64,
>  				  idev->cnf.max_desync_factor,
>  				  cnf_temp_preferred_lft - regen_advance);

It would be better if you describe in the commit message your above
fix.

Also possibly using 'long' as the target type (same as
'max_desync_factor') would be more clear.
>  
> @@ -1402,12 +1402,8 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
>  	 * temporary addresses being generated.
>  	 */
>  	age = (now - tmp_tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
> -	if (cfg.preferred_lft <= regen_advance + age) {
> -		in6_ifa_put(ifp);
> -		in6_dev_put(idev);
> -		ret = -1;
> -		goto out;
> -	}
> +	if (cfg.preferred_lft <= regen_advance + age)
> +		cfg.preferred_lft = regen_advance + age + 1;

This change obsoletes the comment pairing the code. At very least you
should update that and the sysctl knob description in
Documentation/networking/ip-sysctl.rst.

But I'm unsure we can raise the preferred lifetime so easily. e.g. what
if preferred_lft becomes greater then valid_lft?

I think a fairly safer alternative option would be documenting the
current behavior in ip-sysctl.rst

Cheers,

Paolo
Alex Henrie Aug. 23, 2023, 3:41 a.m. UTC | #2
Hi Paolo, thanks for the review.

On Tue, Aug 22, 2023 at 3:54 AM Paolo Abeni <pabeni@redhat.com> wrote:

> It looks like you are fixing 2 separate bugs, so 2 separate patches
> would be better.

The two problems are closely related, and in the same function. But I
will split the patch into two patches to your preference.

> You should explicitly state the target tree (in this case 'net') into
> the patch subj.

Will fix in v2, thanks.

> You should add a suitable fixes tag to each patch.

That would be "Fixes: 76506a986dc31394fd1f2741db037d29c7e57843" and
"Fixes: eac55bf97094f6b64116426864cf4666ef7587bc", correct?

> On Sun, 2023-08-20 at 19:11 -0600, Alex Henrie wrote:

> > @@ -1368,7 +1368,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
> >        * idev->desync_factor if it's larger
> >        */
> >       cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
> > -     max_desync_factor = min_t(__u32,
> > +     max_desync_factor = min_t(__s64,
> >                                 idev->cnf.max_desync_factor,
> >                                 cnf_temp_preferred_lft - regen_advance);
>
> It would be better if you describe in the commit message your above
> fix.

I did mention the underflow problem in the commit message. When I
split the patch into two patches, it will be even more prominent. What
more would you like the commit message to say?

> Also possibly using 'long' as the target type (same as
> 'max_desync_factor') would be more clear.

OK, will change in v2.

> > @@ -1402,12 +1402,8 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
> >        * temporary addresses being generated.
> >        */
> >       age = (now - tmp_tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
> > -     if (cfg.preferred_lft <= regen_advance + age) {
> > -             in6_ifa_put(ifp);
> > -             in6_dev_put(idev);
> > -             ret = -1;
> > -             goto out;
> > -     }
> > +     if (cfg.preferred_lft <= regen_advance + age)
> > +             cfg.preferred_lft = regen_advance + age + 1;
>
> This change obsoletes the comment pairing the code. At very least you
> should update that and the sysctl knob description in
> Documentation/networking/ip-sysctl.rst.

The general idea is still valid: The preferred lifetime must be
greater than regen_advance. I will rephrase the comment to be more
clear in v2.

> But I'm unsure we can raise the preferred lifetime so easily. e.g. what
> if preferred_lft becomes greater then valid_lft?

Excellent point. We really should clamp preferred_lft to valid_lft as
well. I can make that change in v2.

By the way, if valid_lft is less than regen_advance, temporary
addresses still won't work. However, that is much more understandable
because valid_lft has to be at least the length of the longest needed
connection, so in practice it's always going to be much longer than 5
seconds.

> I think a fairly safer alternative option would be documenting the
> current behavior in ip-sysctl.rst

I feel strongly that the current behavior, which can appear to be
working fine for a few minutes before breaking, is very undesirable. I
could, nonetheless, add some explanation to ip-sysctl.rst about what
happens if preferred_lft or valid_lft is too small.

Thanks for caring about doing IPv6 right,

-Alex
David Ahern Aug. 23, 2023, 3:45 a.m. UTC | #3
On 8/22/23 9:41 PM, Alex Henrie wrote:
> Hi Paolo, thanks for the review.
> 
> On Tue, Aug 22, 2023 at 3:54 AM Paolo Abeni <pabeni@redhat.com> wrote:
> 
>> It looks like you are fixing 2 separate bugs, so 2 separate patches
>> would be better.
> 
> The two problems are closely related, and in the same function. But I
> will split the patch into two patches to your preference.
> 
>> You should explicitly state the target tree (in this case 'net') into
>> the patch subj.
> 
> Will fix in v2, thanks.
> 
>> You should add a suitable fixes tag to each patch.
> 
> That would be "Fixes: 76506a986dc31394fd1f2741db037d29c7e57843" and
> "Fixes: eac55bf97094f6b64116426864cf4666ef7587bc", correct?

See `git log` and search for Fixes to see examples. e.g.,

Fixes: eac55bf97094 ("IPv6: do not create temporary adresses with too
short preferred lifetime")
Jiri Bohac Aug. 23, 2023, 8:36 a.m. UTC | #4
On Tue, Aug 22, 2023 at 09:41:37PM -0600, Alex Henrie wrote:
> "Fixes: eac55bf97094f6b64116426864cf4666ef7587bc", correct?
> 
> > On Sun, 2023-08-20 at 19:11 -0600, Alex Henrie wrote:
> 
> > > @@ -1368,7 +1368,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
> > >        * idev->desync_factor if it's larger
> > >        */
> > >       cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
> > > -     max_desync_factor = min_t(__u32,
> > > +     max_desync_factor = min_t(__s64,
> > >                                 idev->cnf.max_desync_factor,
> > >                                 cnf_temp_preferred_lft - regen_advance);
> >
> > It would be better if you describe in the commit message your above
> > fix.
> 
> I did mention the underflow problem in the commit message. When I
> split the patch into two patches, it will be even more prominent. What
> more would you like the commit message to say?
> 
> > Also possibly using 'long' as the target type (same as
> > 'max_desync_factor') would be more clear.
> 
> OK, will change in v2.

This part looks good to me. Sorry for introducing the bug and
thanks for finding it!
Paolo Abeni Aug. 23, 2023, 11 a.m. UTC | #5
On Tue, 2023-08-22 at 21:41 -0600, Alex Henrie wrote:
> On Tue, Aug 22, 2023 at 3:54 AM Paolo Abeni <pabeni@redhat.com> wrote:
> > On Sun, 2023-08-20 at 19:11 -0600, Alex Henrie wrote:
> 
> > > @@ -1368,7 +1368,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
> > >        * idev->desync_factor if it's larger
> > >        */
> > >       cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
> > > -     max_desync_factor = min_t(__u32,
> > > +     max_desync_factor = min_t(__s64,
> > >                                 idev->cnf.max_desync_factor,
> > >                                 cnf_temp_preferred_lft - regen_advance);
> > 
> > It would be better if you describe in the commit message your above
> > fix.
> 
> I did mention the underflow problem in the commit message. When I
> split the patch into two patches, it will be even more prominent. What
> more would you like the commit message to say?

I think explicitly mentioning that the existing code incorrectly casted
a negative value to an unsigned one should suffice. 

> 
> > Also possibly using 'long' as the target type (same as
> > 'max_desync_factor') would be more clear.
> 
> OK, will change in v2.
> 
> > > @@ -1402,12 +1402,8 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
> > >        * temporary addresses being generated.
> > >        */
> > >       age = (now - tmp_tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
> > > -     if (cfg.preferred_lft <= regen_advance + age) {
> > > -             in6_ifa_put(ifp);
> > > -             in6_dev_put(idev);
> > > -             ret = -1;
> > > -             goto out;
> > > -     }
> > > +     if (cfg.preferred_lft <= regen_advance + age)
> > > +             cfg.preferred_lft = regen_advance + age + 1;
> > 
> > This change obsoletes the comment pairing the code. At very least you
> > should update that and the sysctl knob description in
> > Documentation/networking/ip-sysctl.rst.
> 
> The general idea is still valid: The preferred lifetime must be
> greater than regen_advance. I will rephrase the comment to be more
> clear in v2.
> 
> > But I'm unsure we can raise the preferred lifetime so easily. e.g. what
> > if preferred_lft becomes greater then valid_lft?
> 
> Excellent point. We really should clamp preferred_lft to valid_lft as
> well. I can make that change in v2.
> 
> By the way, if valid_lft is less than regen_advance, temporary
> addresses still won't work. However, that is much more understandable
> because valid_lft has to be at least the length of the longest needed
> connection, so in practice it's always going to be much longer than 5
> seconds.
> 
> > I think a fairly safer alternative option would be documenting the
> > current behavior in ip-sysctl.rst
> 
> I feel strongly that the current behavior, which can appear to be
> working fine for a few minutes before breaking, is very undesirable.
> I
> could, nonetheless, add some explanation to ip-sysctl.rst about what
> happens if preferred_lft or valid_lft is too small.

I think that we could accept the general idea that setting some
"extreme"/edge values on system settings will lead to unexpected
results/limited functionality.

IDK how much relevant is the 'preferred_lft < 5' use-case.

I fear that changing "under-the-hood" the preferred lifetime value in
use could have unexpected side effects for other scenarios. e.g. we can
hit the 'increase preferred lifetime' condition even when:

cfg.preferred_lft == <some largish, more common, value>
age == ~cfg.preferred_lft

@David A.: I would love to hear your opinion here.

Thank,

Paolo
diff mbox series

Patch

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 94cec2075eee..4008d4a5e58d 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1368,7 +1368,7 @@  static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
 	 * idev->desync_factor if it's larger
 	 */
 	cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
-	max_desync_factor = min_t(__u32,
+	max_desync_factor = min_t(__s64,
 				  idev->cnf.max_desync_factor,
 				  cnf_temp_preferred_lft - regen_advance);
 
@@ -1402,12 +1402,8 @@  static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
 	 * temporary addresses being generated.
 	 */
 	age = (now - tmp_tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
-	if (cfg.preferred_lft <= regen_advance + age) {
-		in6_ifa_put(ifp);
-		in6_dev_put(idev);
-		ret = -1;
-		goto out;
-	}
+	if (cfg.preferred_lft <= regen_advance + age)
+		cfg.preferred_lft = regen_advance + age + 1;
 
 	cfg.ifa_flags = IFA_F_TEMPORARY;
 	/* set in addrconf_prefix_rcv() */