diff mbox

ipv4: synchronize bind() with RTM_NEWADDR notifications

Message ID 1287655930-16879-1-git-send-email-timo.teras@iki.fi
State Rejected, archived
Delegated to: David Miller
Headers show

Commit Message

Timo Teras Oct. 21, 2010, 10:12 a.m. UTC
Otherwise we have race condition to user land:
 1. process A changes IP address
 2. kernel sends RTM_NEWADDR
 3. process B gets notification
 4. process B tries to bind() to new IP but that fails with
EADDRNOTAVAIL because FIB is not yet updated and inet_addr_type() in
inet_bind() does not recognize the IP as local
 5. kernel calls inetaddr_chain notifiers which updates FIB

IPv6 side seems to handle the notifications properly: bind()
immediately after RTM_NEWADDR succeeds as expected. This is because
ipv6_chk_addr() uses inet6_addr_lst which is updated before address
notification.

Signed-off-by: Timo Teräs <timo.teras@iki.fi>
---
 net/ipv4/af_inet.c  |    9 +++++++++
 net/ipv6/af_inet6.c |    4 +++-
 2 files changed, 12 insertions(+), 1 deletions(-)

Comments

Eric Dumazet Oct. 21, 2010, 10:25 a.m. UTC | #1
Le jeudi 21 octobre 2010 à 13:12 +0300, Timo Teräs a écrit :
> Otherwise we have race condition to user land:
>  1. process A changes IP address
>  2. kernel sends RTM_NEWADDR
>  3. process B gets notification
>  4. process B tries to bind() to new IP but that fails with
> EADDRNOTAVAIL because FIB is not yet updated and inet_addr_type() in
> inet_bind() does not recognize the IP as local
>  5. kernel calls inetaddr_chain notifiers which updates FIB
> 
> IPv6 side seems to handle the notifications properly: bind()
> immediately after RTM_NEWADDR succeeds as expected. This is because
> ipv6_chk_addr() uses inet6_addr_lst which is updated before address
> notification.
> 
> Signed-off-by: Timo Teräs <timo.teras@iki.fi>
> ---
>  net/ipv4/af_inet.c  |    9 +++++++++
>  net/ipv6/af_inet6.c |    4 +++-
>  2 files changed, 12 insertions(+), 1 deletions(-)
> 
> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
> index 6a1100c..21200e4 100644
> --- a/net/ipv4/af_inet.c
> +++ b/net/ipv4/af_inet.c
> @@ -466,6 +466,15 @@ int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
>  	if (addr_len < sizeof(struct sockaddr_in))
>  		goto out;
>  
> +	/* Acquire rtnl_lock to synchronize with possible simultaneous
> +	 * IP-address changes. This is needed because when RTM_NEWADDR
> +	 * is sent the new IP is not yet in FIB, but alas inet_addr_type
> +	 * checks the address type using FIB. Acquiring rtnl lock once
> +	 * makse sure that any address for which RTM_NEWADDR was sent
> +	 * earlier exists also in FIB. */
> +	rtnl_lock();
> +	rtnl_unlock();

You must be kidding ?

Really, this is a hot path...



--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Timo Teras Oct. 21, 2010, 10:41 a.m. UTC | #2
On 10/21/2010 01:25 PM, Eric Dumazet wrote:
> Le jeudi 21 octobre 2010 à 13:12 +0300, Timo Teräs a écrit :
>> Otherwise we have race condition to user land:
>>  1. process A changes IP address
>>  2. kernel sends RTM_NEWADDR
>>  3. process B gets notification
>>  4. process B tries to bind() to new IP but that fails with
>> EADDRNOTAVAIL because FIB is not yet updated and inet_addr_type() in
>> inet_bind() does not recognize the IP as local
>>  5. kernel calls inetaddr_chain notifiers which updates FIB
>>
>> IPv6 side seems to handle the notifications properly: bind()
>> immediately after RTM_NEWADDR succeeds as expected. This is because
>> ipv6_chk_addr() uses inet6_addr_lst which is updated before address
>> notification.
>>
>> Signed-off-by: Timo Teräs <timo.teras@iki.fi>
>> ---
>>  net/ipv4/af_inet.c  |    9 +++++++++
>>  net/ipv6/af_inet6.c |    4 +++-
>>  2 files changed, 12 insertions(+), 1 deletions(-)
>>
>> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
>> index 6a1100c..21200e4 100644
>> --- a/net/ipv4/af_inet.c
>> +++ b/net/ipv4/af_inet.c
>> @@ -466,6 +466,15 @@ int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
>>  	if (addr_len < sizeof(struct sockaddr_in))
>>  		goto out;
>>  
>> +	/* Acquire rtnl_lock to synchronize with possible simultaneous
>> +	 * IP-address changes. This is needed because when RTM_NEWADDR
>> +	 * is sent the new IP is not yet in FIB, but alas inet_addr_type
>> +	 * checks the address type using FIB. Acquiring rtnl lock once
>> +	 * makse sure that any address for which RTM_NEWADDR was sent
>> +	 * earlier exists also in FIB. */
>> +	rtnl_lock();
>> +	rtnl_unlock();
> 
> You must be kidding ?
> 
> Really, this is a hot path...

Is inet_bind() called from non-userland context? If yes, then this is a
bad idea. Otherwise I don't think it's that hot path...

The other idea of doing notifier calls before RTM_NEWADDR sending is
worse because it changes ordering of userland visible netlink notifications.

This looked like the easiest way out. If this is unacceptable, I guess
we are left with changing inet_addr_type() to not use FIB.

Or is there better ideas?


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller Oct. 21, 2010, 10:50 a.m. UTC | #3
From: Timo Teräs <timo.teras@iki.fi>
Date: Thu, 21 Oct 2010 13:41:37 +0300

> Is inet_bind() called from non-userland context? If yes, then this is a
> bad idea. Otherwise I don't think it's that hot path...

It is.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Timo Teras Oct. 21, 2010, 10:58 a.m. UTC | #4
On 10/21/2010 01:50 PM, David Miller wrote:
> From: Timo Teräs <timo.teras@iki.fi>
> Date: Thu, 21 Oct 2010 13:41:37 +0300
> 
>> Is inet_bind() called from non-userland context? If yes, then this is a
>> bad idea. Otherwise I don't think it's that hot path...
> 
> It is.

Yet, almost immediately after that there is lock_sock() which can also
sleep. How does that work then?

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller Oct. 21, 2010, 11:03 a.m. UTC | #5
From: Timo Teräs <timo.teras@iki.fi>
Date: Thu, 21 Oct 2010 13:58:08 +0300

> On 10/21/2010 01:50 PM, David Miller wrote:
>> From: Timo Teräs <timo.teras@iki.fi>
>> Date: Thu, 21 Oct 2010 13:41:37 +0300
>> 
>>> Is inet_bind() called from non-userland context? If yes, then this is a
>>> bad idea. Otherwise I don't think it's that hot path...
>> 
>> It is.
> 
> Yet, almost immediately after that there is lock_sock() which can also
> sleep. How does that work then?

RTNL interlocks globally with a heavy primitive, a mutex, lock_sock()
grabs a spinlcok which is local to the socket's context.

So if we have 100,000 sockets binding we'll suck with you're change
whereas the lock_sock() does not cause that problem.

Is this so difficult for you to comprehend?
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eric Dumazet Oct. 21, 2010, 11:12 a.m. UTC | #6
Le jeudi 21 octobre 2010 à 13:58 +0300, Timo Teräs a écrit :
> On 10/21/2010 01:50 PM, David Miller wrote:
> > From: Timo Teräs <timo.teras@iki.fi>
> > Date: Thu, 21 Oct 2010 13:41:37 +0300
> > 
> >> Is inet_bind() called from non-userland context? If yes, then this is a
> >> bad idea. Otherwise I don't think it's that hot path...
> > 
> > It is.
> 
> Yet, almost immediately after that there is lock_sock() which can also
> sleep. How does that work then?
> 

I am not sure I understand your question. Maybe my answer wont be clear.

rtnl_lock() can take ages on some setups, because its using one single
and shared mutex. Its use should be restricted to administrative
purposes.

bind() is a pretty hot path on many workloads, this is hardly what we
call an administrative task.

lock_sock() uses a per socket lock, and it is a _bit_ more scalable, you
can have 4096 cpus all using bind()/recv()/send()/... at the same time,
it just works.



--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Timo Teras Oct. 21, 2010, 11:29 a.m. UTC | #7
On 10/21/2010 02:03 PM, David Miller wrote:
> From: Timo Teräs <timo.teras@iki.fi>
> Date: Thu, 21 Oct 2010 13:58:08 +0300
> 
>> On 10/21/2010 01:50 PM, David Miller wrote:
>>> From: Timo Teräs <timo.teras@iki.fi>
>>> Date: Thu, 21 Oct 2010 13:41:37 +0300
>>>
>>>> Is inet_bind() called from non-userland context? If yes, then this is a
>>>> bad idea. Otherwise I don't think it's that hot path...
>>>
>>> It is.
>>
>> Yet, almost immediately after that there is lock_sock() which can also
>> sleep. How does that work then?
> 
> RTNL interlocks globally with a heavy primitive, a mutex, lock_sock()
> grabs a spinlcok which is local to the socket's context.
> 
> So if we have 100,000 sockets binding we'll suck with you're change
> whereas the lock_sock() does not cause that problem.
> 
> Is this so difficult for you to comprehend?

I was confused with Dave's original reply "It is." as referring to that
inet_bind() can get called from non-userland context. But apparently you
just meant that "It is (bad idea regardless)."

I thought the problem was possible sleeping, and not contention. Which
became very obvious from Eric's example. I didn't realize that many do
bind()/recv()/send() as general workload.

Sorry for not seeing the obvious.

This is the third time asking, what would be a good way to fix the
problem described in the original commit log?

Changing RTM_NEWADDR after FIB update would break Netlink event
ordering. And this breaks performance. I can't really use RTN_LOCAL
RTM_NEWROUTE events since (at least IPv6 side) has incorrect ifindex.

Should inet_addr_type() be rewritten to not use FIB lookups?
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller Oct. 21, 2010, 11:34 a.m. UTC | #8
From: Timo Teräs <timo.teras@iki.fi>
Date: Thu, 21 Oct 2010 14:29:22 +0300

> This is the third time asking, what would be a good way to fix the
> problem described in the original commit log?

I kept your report in my inbox backlog and intended to think about
it as time permitted.

As the merge window has just openned up, for me time will not be
"permitted" for some time.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Timo Teras Oct. 21, 2010, 11:57 a.m. UTC | #9
On 10/21/2010 02:34 PM, David Miller wrote:
> From: Timo Teräs <timo.teras@iki.fi>
> Date: Thu, 21 Oct 2010 14:29:22 +0300
> 
>> This is the third time asking, what would be a good way to fix the
>> problem described in the original commit log?
> 
> I kept your report in my inbox backlog and intended to think about
> it as time permitted.
> 
> As the merge window has just openned up, for me time will not be
> "permitted" for some time.

Fair enough. Just getting multiple "does not work" without single
mention of "will think about this later" felt frustrating.

I have one more alternative: Would it be acceptable if we did
rtnl_lock()/rtnl_unlock() only if inet_addr_type() earlier said the
address is unacceptable for binding. And then retry inet_addr_type(). Or
do you think that the error case EADDRNOTAVAIL is a hot path too?
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 6a1100c..21200e4 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -466,6 +466,15 @@  int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
 	if (addr_len < sizeof(struct sockaddr_in))
 		goto out;
 
+	/* Acquire rtnl_lock to synchronize with possible simultaneous
+	 * IP-address changes. This is needed because when RTM_NEWADDR
+	 * is sent the new IP is not yet in FIB, but alas inet_addr_type
+	 * checks the address type using FIB. Acquiring rtnl lock once
+	 * makse sure that any address for which RTM_NEWADDR was sent
+	 * earlier exists also in FIB. */
+	rtnl_lock();
+	rtnl_unlock();
+
 	chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
 
 	/* Not specified by any standard per-se, however it breaks too
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 56b9bf2..6fc37f4 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -300,7 +300,9 @@  int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
 			goto out;
 		}
 
-		/* Reproduce AF_INET checks to make the bindings consitant */
+		/* Reproduce AF_INET checks to make the bindings consistent */
+		rtnl_lock();
+		rtnl_unlock();
 		v4addr = addr->sin6_addr.s6_addr32[3];
 		chk_addr_ret = inet_addr_type(net, v4addr);
 		if (!sysctl_ip_nonlocal_bind &&