]> err.no Git - linux-2.6/blobdiff - net/ipv4/inet_hashtables.c
[PATCH] NTP: ntp-helper functions
[linux-2.6] / net / ipv4 / inet_hashtables.c
index 33d6cbe32cdc5180c9e681374b62f60b8e926bc5..e8d29fe736d29ade84886500ec61bab103a54ceb 100644 (file)
 
 #include <linux/config.h>
 #include <linux/module.h>
+#include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/wait.h>
 
+#include <net/inet_connection_sock.h>
 #include <net/inet_hashtables.h>
 
 /*
@@ -54,10 +57,9 @@ void inet_bind_bucket_destroy(kmem_cache_t *cachep, struct inet_bind_bucket *tb)
 void inet_bind_hash(struct sock *sk, struct inet_bind_bucket *tb,
                    const unsigned short snum)
 {
-       struct inet_sock *inet = inet_sk(sk);
-       inet->num       = snum;
+       inet_sk(sk)->num = snum;
        sk_add_bind_node(sk, &tb->owners);
-       inet->bind_hash = tb;
+       inet_csk(sk)->icsk_bind_hash = tb;
 }
 
 EXPORT_SYMBOL(inet_bind_hash);
@@ -67,16 +69,15 @@ EXPORT_SYMBOL(inet_bind_hash);
  */
 static void __inet_put_port(struct inet_hashinfo *hashinfo, struct sock *sk)
 {
-       struct inet_sock *inet = inet_sk(sk);
-       const int bhash = inet_bhashfn(inet->num, hashinfo->bhash_size);
+       const int bhash = inet_bhashfn(inet_sk(sk)->num, hashinfo->bhash_size);
        struct inet_bind_hashbucket *head = &hashinfo->bhash[bhash];
        struct inet_bind_bucket *tb;
 
        spin_lock(&head->lock);
-       tb = inet->bind_hash;
+       tb = inet_csk(sk)->icsk_bind_hash;
        __sk_del_bind_node(sk);
-       inet->bind_hash = NULL;
-       inet->num = 0;
+       inet_csk(sk)->icsk_bind_hash = NULL;
+       inet_sk(sk)->num = 0;
        inet_bind_bucket_destroy(hashinfo->bind_bucket_cachep, tb);
        spin_unlock(&head->lock);
 }
@@ -89,3 +90,76 @@ void inet_put_port(struct inet_hashinfo *hashinfo, struct sock *sk)
 }
 
 EXPORT_SYMBOL(inet_put_port);
+
+/*
+ * This lock without WQ_FLAG_EXCLUSIVE is good on UP and it can be very bad on SMP.
+ * Look, when several writers sleep and reader wakes them up, all but one
+ * immediately hit write lock and grab all the cpus. Exclusive sleep solves
+ * this, _but_ remember, it adds useless work on UP machines (wake up each
+ * exclusive lock release). It should be ifdefed really.
+ */
+void inet_listen_wlock(struct inet_hashinfo *hashinfo)
+{
+       write_lock(&hashinfo->lhash_lock);
+
+       if (atomic_read(&hashinfo->lhash_users)) {
+               DEFINE_WAIT(wait);
+
+               for (;;) {
+                       prepare_to_wait_exclusive(&hashinfo->lhash_wait,
+                                                 &wait, TASK_UNINTERRUPTIBLE);
+                       if (!atomic_read(&hashinfo->lhash_users))
+                               break;
+                       write_unlock_bh(&hashinfo->lhash_lock);
+                       schedule();
+                       write_lock_bh(&hashinfo->lhash_lock);
+               }
+
+               finish_wait(&hashinfo->lhash_wait, &wait);
+       }
+}
+
+EXPORT_SYMBOL(inet_listen_wlock);
+
+/*
+ * Don't inline this cruft. Here are some nice properties to exploit here. The
+ * BSD API does not allow a listening sock to specify the remote port nor the
+ * remote address for the connection. So always assume those are both
+ * wildcarded during the search since they can never be otherwise.
+ */
+struct sock *__inet_lookup_listener(const struct hlist_head *head, const u32 daddr,
+                                   const unsigned short hnum, const int dif)
+{
+       struct sock *result = NULL, *sk;
+       const struct hlist_node *node;
+       int hiscore = -1;
+
+       sk_for_each(sk, node, head) {
+               const struct inet_sock *inet = inet_sk(sk);
+
+               if (inet->num == hnum && !ipv6_only_sock(sk)) {
+                       const __u32 rcv_saddr = inet->rcv_saddr;
+                       int score = sk->sk_family == PF_INET ? 1 : 0;
+
+                       if (rcv_saddr) {
+                               if (rcv_saddr != daddr)
+                                       continue;
+                               score += 2;
+                       }
+                       if (sk->sk_bound_dev_if) {
+                               if (sk->sk_bound_dev_if != dif)
+                                       continue;
+                               score += 2;
+                       }
+                       if (score == 5)
+                               return sk;
+                       if (score > hiscore) {
+                               hiscore = score;
+                               result  = sk;
+                       }
+               }
+       }
+       return result;
+}
+
+EXPORT_SYMBOL_GPL(__inet_lookup_listener);