tcp: bind() fix when many ports are bound
authorEric Dumazet <eric.dumazet@gmail.com>
Wed, 21 Apr 2010 09:26:15 +0000 (09:26 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 23 Apr 2010 02:06:06 +0000 (19:06 -0700)
Port autoselection done by kernel only works when number of bound
sockets is under a threshold (typically 30000).

When this threshold is over, we must check if there is a conflict before
exiting first loop in inet_csk_get_port()

Change inet_csk_bind_conflict() to forbid two reuse-enabled sockets to
bind on same (address,port) tuple (with a non ANY address)

Same change for inet6_csk_bind_conflict()

Reported-by: Gaspar Chilingarov <gasparch@gmail.com>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Acked-by: Evgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv4/inet_connection_sock.c
net/ipv6/inet6_connection_sock.c

index 8da6429..14825eb 100644 (file)
@@ -70,13 +70,17 @@ int inet_csk_bind_conflict(const struct sock *sk,
                    (!sk->sk_bound_dev_if ||
                     !sk2->sk_bound_dev_if ||
                     sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {
+                       const __be32 sk2_rcv_saddr = inet_rcv_saddr(sk2);
+
                        if (!reuse || !sk2->sk_reuse ||
                            sk2->sk_state == TCP_LISTEN) {
-                               const __be32 sk2_rcv_saddr = inet_rcv_saddr(sk2);
                                if (!sk2_rcv_saddr || !sk_rcv_saddr ||
                                    sk2_rcv_saddr == sk_rcv_saddr)
                                        break;
-                       }
+                       } else if (reuse && sk2->sk_reuse &&
+                                  sk2_rcv_saddr &&
+                                  sk2_rcv_saddr == sk_rcv_saddr)
+                               break;
                }
        }
        return node != NULL;
@@ -120,9 +124,11 @@ again:
                                                smallest_size = tb->num_owners;
                                                smallest_rover = rover;
                                                if (atomic_read(&hashinfo->bsockets) > (high - low) + 1) {
-                                                       spin_unlock(&head->lock);
-                                                       snum = smallest_rover;
-                                                       goto have_snum;
+                                                       if (!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb)) {
+                                                               spin_unlock(&head->lock);
+                                                               snum = smallest_rover;
+                                                               goto have_snum;
+                                                       }
                                                }
                                        }
                                        goto next;
index 628db24..b4b7d40 100644 (file)
@@ -42,11 +42,16 @@ int inet6_csk_bind_conflict(const struct sock *sk,
                if (sk != sk2 &&
                    (!sk->sk_bound_dev_if ||
                     !sk2->sk_bound_dev_if ||
-                    sk->sk_bound_dev_if == sk2->sk_bound_dev_if) &&
-                   (!sk->sk_reuse || !sk2->sk_reuse ||
-                    sk2->sk_state == TCP_LISTEN) &&
-                    ipv6_rcv_saddr_equal(sk, sk2))
-                       break;
+                    sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {
+                       if ((!sk->sk_reuse || !sk2->sk_reuse ||
+                            sk2->sk_state == TCP_LISTEN) &&
+                            ipv6_rcv_saddr_equal(sk, sk2))
+                               break;
+                       else if (sk->sk_reuse && sk2->sk_reuse &&
+                               !ipv6_addr_any(inet6_rcv_saddr(sk2)) &&
+                               ipv6_rcv_saddr_equal(sk, sk2))
+                               break;
+               }
        }
 
        return node != NULL;