]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - net/ipv4/inet_hashtables.c
tproxy: fix hash locking issue when using port redirection in __inet_inherit_port()
[linux-2.6.git] / net / ipv4 / inet_hashtables.c
index fb7ad5a21ff3e2e86b2d9018ea29ccd2e3921848..1b344f30b463fab9ed70a8f19a19d348d7c626f7 100644 (file)
@@ -101,19 +101,43 @@ void inet_put_port(struct sock *sk)
 }
 EXPORT_SYMBOL(inet_put_port);
 
-void __inet_inherit_port(struct sock *sk, struct sock *child)
+int __inet_inherit_port(struct sock *sk, struct sock *child)
 {
        struct inet_hashinfo *table = sk->sk_prot->h.hashinfo;
-       const int bhash = inet_bhashfn(sock_net(sk), inet_sk(child)->inet_num,
+       unsigned short port = inet_sk(child)->inet_num;
+       const int bhash = inet_bhashfn(sock_net(sk), port,
                        table->bhash_size);
        struct inet_bind_hashbucket *head = &table->bhash[bhash];
        struct inet_bind_bucket *tb;
 
        spin_lock(&head->lock);
        tb = inet_csk(sk)->icsk_bind_hash;
+       if (tb->port != port) {
+               /* NOTE: using tproxy and redirecting skbs to a proxy
+                * on a different listener port breaks the assumption
+                * that the listener socket's icsk_bind_hash is the same
+                * as that of the child socket. We have to look up or
+                * create a new bind bucket for the child here. */
+               struct hlist_node *node;
+               inet_bind_bucket_for_each(tb, node, &head->chain) {
+                       if (net_eq(ib_net(tb), sock_net(sk)) &&
+                           tb->port == port)
+                               break;
+               }
+               if (!node) {
+                       tb = inet_bind_bucket_create(table->bind_bucket_cachep,
+                                                    sock_net(sk), head, port);
+                       if (!tb) {
+                               spin_unlock(&head->lock);
+                               return -ENOMEM;
+                       }
+               }
+       }
        sk_add_bind_node(child, &tb->owners);
        inet_csk(child)->icsk_bind_hash = tb;
        spin_unlock(&head->lock);
+
+       return 0;
 }
 EXPORT_SYMBOL_GPL(__inet_inherit_port);