diff mbox series

[RFC,net-next,1/4] net: ipv6: Make inet6addr_validator a blocking notifier

Message ID 1507653665-20540-2-git-send-email-dsahern@gmail.com
State RFC, archived
Delegated to: David Miller
Headers show
Series mlxsw: spectrum_router: Add extack messages for RIF and VRF overflow | expand

Commit Message

David Ahern Oct. 10, 2017, 4:41 p.m. UTC
inet6addr_validator chain was added by commit 3ad7d2468f79f ("Ipvlan
should return an error when an address is already in use") to allow
address validation before changes are committed and to be able to
fail the address change with an error back to the user. The address
validation is not done for addresses received from router
advertisements.

Handling RAs in softirq context is the only reason for the notifier
chain to be atomic versus blocking. Since the only current user, ipvlan,
of the validator chain ignores softirq context, the notifier can be made
blocking and simply not invoked for softirq path.

The blocking option is needed by spectrum for example to validate
resources for an adding an address to an interface.

Signed-off-by: David Ahern <dsahern@gmail.com>
---
 net/ipv6/addrconf.c      | 24 +++++++++++++++---------
 net/ipv6/addrconf_core.c |  9 +++++----
 2 files changed, 20 insertions(+), 13 deletions(-)

Comments

David Ahern Oct. 10, 2017, 7:32 p.m. UTC | #1
On 10/10/17 10:41 AM, David Ahern wrote:
> @@ -988,16 +987,23 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
>  		goto out2;
>  	}
>  
> -	i6vi.i6vi_addr = *addr;
> -	i6vi.i6vi_dev = idev;
> -	rcu_read_unlock_bh();
> +	/* validator notifier needs to be blocking;
> +	 * do not call in softirq context
> +	 */
> +	if (!in_softirq()) {
> +		struct in6_validator_info i6vi = {
> +			.i6vi_addr = *addr,
> +			.i6vi_dev = idev,
> +		};
>  
> -	err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
> +		rcu_read_unlock_bh();
> +		err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
> +		rcu_read_lock_bh();
>  
> -	rcu_read_lock_bh();
> -	err = notifier_to_errno(err);
> -	if (err)
> -		goto out2;
> +		err = notifier_to_errno(err);
> +		if (err)
> +			goto out2;
> +	}
>  
>  	spin_lock(&addrconf_hash_lock);
>  

The rcu_read_unlock_bh needs to be done before the in_softirq check.
With the change below I get the RIF overload with IPv6 addresses and I
verified the validator is skipped for RAs.

$ ip -batch vlan-ipv6-addr-batch
Error: spectrum: Exceeded number of supported router interfaces.
Command failed vlan-ipv6-addr-batch:683


diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 0bad4a800f73..d9c5b29a3b8b 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -988,6 +988,8 @@ ipv6_add_addr(struct inet6_dev *idev, const struct
in6_addr *addr,
                goto out2;
        }

+       rcu_read_unlock_bh();
+
        /* validator notifier needs to be blocking;
         * do not call in softirq context
         */
@@ -998,15 +1000,14 @@ ipv6_add_addr(struct inet6_dev *idev, const
struct in6_addr *addr,
                        .extack = extack,
                };

-               rcu_read_unlock_bh();
                err = inet6addr_validator_notifier_call_chain(NETDEV_UP,
&i6vi);
-               rcu_read_lock_bh();
-
                err = notifier_to_errno(err);
                if (err)
-                       goto out2;
+                       goto out1;
        }

+       rcu_read_lock_bh();
+
        spin_lock(&addrconf_hash_lock);

        /* Ignore adding duplicate addresses on an interface */
@@ -1079,7 +1080,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct
in6_addr *addr,
        write_unlock(&idev->lock);
 out2:
        rcu_read_unlock_bh();
-
+out1:
        if (likely(err == 0))
                inet6addr_notifier_call_chain(NETDEV_UP, ifa);
        else {
Ido Schimmel Oct. 11, 2017, 1:08 p.m. UTC | #2
On Tue, Oct 10, 2017 at 09:41:02AM -0700, David Ahern wrote:
> inet6addr_validator chain was added by commit 3ad7d2468f79f ("Ipvlan
> should return an error when an address is already in use") to allow
> address validation before changes are committed and to be able to
> fail the address change with an error back to the user. The address
> validation is not done for addresses received from router
> advertisements.
> 
> Handling RAs in softirq context is the only reason for the notifier
> chain to be atomic versus blocking. Since the only current user, ipvlan,
> of the validator chain ignores softirq context, the notifier can be made
> blocking and simply not invoked for softirq path.
> 
> The blocking option is needed by spectrum for example to validate
> resources for an adding an address to an interface.
> 
> Signed-off-by: David Ahern <dsahern@gmail.com>

With the fixup posted later:

Reviewed-by: Ido Schimmel <idosch@mellanox.com>

BTW, the in_softirq() check in ipvlan_addr6_validator_event() can be
removed after this patch.
David Miller Oct. 11, 2017, 9:13 p.m. UTC | #3
From: David Ahern <dsahern@gmail.com>
Date: Tue, 10 Oct 2017 09:41:02 -0700

> +	/* validator notifier needs to be blocking;
> +	 * do not call in softirq context
> +	 */
> +	if (!in_softirq()) {

I think we can test this better.

You should be able to audit the call sites and for each one set the
value of a new boolean argument properly, and this way you can also
give the boolean argument a descriptive name.

Furthermore, we can also then pull the inet6_addr allocation out of
the locking paths and thus use GFP_KERNEL when possible.
David Ahern Oct. 11, 2017, 9:56 p.m. UTC | #4
On 10/11/17 3:13 PM, David Miller wrote:
> From: David Ahern <dsahern@gmail.com>
> Date: Tue, 10 Oct 2017 09:41:02 -0700
> 
>> +	/* validator notifier needs to be blocking;
>> +	 * do not call in softirq context
>> +	 */
>> +	if (!in_softirq()) {
> 
> I think we can test this better.

The callchain we are protecting against is
            7fff8149d0dd ipv6_add_addr ([kernel.kallsyms])
            7fff814a161b addrconf_prefix_rcv ([kernel.kallsyms])
            7fff814afb8a ndisc_router_discovery ([kernel.kallsyms])
            7fff814b0310 ndisc_rcv ([kernel.kallsyms])
            7fff814b62da icmpv6_rcv ([kernel.kallsyms])
            7fff81499c37 ip6_input_finish ([kernel.kallsyms])
            7fff81499e96 ip6_input ([kernel.kallsyms])
            7fff8149a519 ip6_mc_input ([kernel.kallsyms])
            7fff81499f9d ip6_rcv_finish ([kernel.kallsyms])
            7fff8149a349 ipv6_rcv ([kernel.kallsyms])
            7fff813fbe12 __netif_receive_skb_core ([kernel.kallsyms])
            7fff813fc04c __netif_receive_skb ([kernel.kallsyms])
            7fff813ff97c netif_receive_skb_internal ([kernel.kallsyms])

> 
> You should be able to audit the call sites and for each one set the
> value of a new boolean argument properly, and this way you can also
> give the boolean argument a descriptive name.

The safest is an in_atomic() check, but to your point I'll see if the
caller can pass in atomic vs blocking option as a bool.

> 
> Furthermore, we can also then pull the inet6_addr allocation out of
> the locking paths and thus use GFP_KERNEL when possible.
> 

Yes, I was thinking about that as a follow on -- how far down can the
rcu_read_lock_bh be pushed.
diff mbox series

Patch

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index d9f6226694eb..632cf4b26277 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -963,7 +963,6 @@  ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
 	struct net *net = dev_net(idev->dev);
 	struct inet6_ifaddr *ifa = NULL;
 	struct rt6_info *rt;
-	struct in6_validator_info i6vi;
 	unsigned int hash;
 	int err = 0;
 	int addr_type = ipv6_addr_type(addr);
@@ -988,16 +987,23 @@  ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
 		goto out2;
 	}
 
-	i6vi.i6vi_addr = *addr;
-	i6vi.i6vi_dev = idev;
-	rcu_read_unlock_bh();
+	/* validator notifier needs to be blocking;
+	 * do not call in softirq context
+	 */
+	if (!in_softirq()) {
+		struct in6_validator_info i6vi = {
+			.i6vi_addr = *addr,
+			.i6vi_dev = idev,
+		};
 
-	err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
+		rcu_read_unlock_bh();
+		err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
+		rcu_read_lock_bh();
 
-	rcu_read_lock_bh();
-	err = notifier_to_errno(err);
-	if (err)
-		goto out2;
+		err = notifier_to_errno(err);
+		if (err)
+			goto out2;
+	}
 
 	spin_lock(&addrconf_hash_lock);
 
diff --git a/net/ipv6/addrconf_core.c b/net/ipv6/addrconf_core.c
index 9e3488d50b15..32b564dfd02a 100644
--- a/net/ipv6/addrconf_core.c
+++ b/net/ipv6/addrconf_core.c
@@ -88,7 +88,7 @@  int __ipv6_addr_type(const struct in6_addr *addr)
 EXPORT_SYMBOL(__ipv6_addr_type);
 
 static ATOMIC_NOTIFIER_HEAD(inet6addr_chain);
-static ATOMIC_NOTIFIER_HEAD(inet6addr_validator_chain);
+static BLOCKING_NOTIFIER_HEAD(inet6addr_validator_chain);
 
 int register_inet6addr_notifier(struct notifier_block *nb)
 {
@@ -110,19 +110,20 @@  EXPORT_SYMBOL(inet6addr_notifier_call_chain);
 
 int register_inet6addr_validator_notifier(struct notifier_block *nb)
 {
-	return atomic_notifier_chain_register(&inet6addr_validator_chain, nb);
+	return blocking_notifier_chain_register(&inet6addr_validator_chain, nb);
 }
 EXPORT_SYMBOL(register_inet6addr_validator_notifier);
 
 int unregister_inet6addr_validator_notifier(struct notifier_block *nb)
 {
-	return atomic_notifier_chain_unregister(&inet6addr_validator_chain, nb);
+	return blocking_notifier_chain_unregister(&inet6addr_validator_chain,
+						  nb);
 }
 EXPORT_SYMBOL(unregister_inet6addr_validator_notifier);
 
 int inet6addr_validator_notifier_call_chain(unsigned long val, void *v)
 {
-	return atomic_notifier_call_chain(&inet6addr_validator_chain, val, v);
+	return blocking_notifier_call_chain(&inet6addr_validator_chain, val, v);
 }
 EXPORT_SYMBOL(inet6addr_validator_notifier_call_chain);