net: Avoid extra wakeups of threads blocked in wait_for_packet()
[safe/jmp/linux-2.6] / net / dccp / proto.c
index c91d3c1..314a1b5 100644 (file)
@@ -27,7 +27,6 @@
 #include <net/xfrm.h>
 
 #include <asm/ioctls.h>
-#include <asm/semaphore.h>
 #include <linux/spinlock.h>
 #include <linux/timer.h>
 #include <linux/delay.h>
@@ -41,16 +40,10 @@ DEFINE_SNMP_STAT(struct dccp_mib, dccp_statistics) __read_mostly;
 
 EXPORT_SYMBOL_GPL(dccp_statistics);
 
-atomic_t dccp_orphan_count = ATOMIC_INIT(0);
-
+struct percpu_counter dccp_orphan_count;
 EXPORT_SYMBOL_GPL(dccp_orphan_count);
 
-struct inet_hashinfo __cacheline_aligned dccp_hashinfo = {
-       .lhash_lock     = RW_LOCK_UNLOCKED,
-       .lhash_users    = ATOMIC_INIT(0),
-       .lhash_wait = __WAIT_QUEUE_HEAD_INITIALIZER(dccp_hashinfo.lhash_wait),
-};
-
+struct inet_hashinfo dccp_hashinfo;
 EXPORT_SYMBOL_GPL(dccp_hashinfo);
 
 /* the maximum queue length for tx in packets. 0 is no limit */
@@ -68,6 +61,9 @@ void dccp_set_state(struct sock *sk, const int state)
        case DCCP_OPEN:
                if (oldstate != DCCP_OPEN)
                        DCCP_INC_STATS(DCCP_MIB_CURRESTAB);
+               /* Client retransmits all Confirm options until entering OPEN */
+               if (oldstate == DCCP_PARTOPEN)
+                       dccp_feat_list_purge(&dccp_sk(sk)->dccps_featneg);
                break;
 
        case DCCP_CLOSED:
@@ -176,11 +172,8 @@ EXPORT_SYMBOL_GPL(dccp_state_name);
 int dccp_init_sock(struct sock *sk, const __u8 ctl_sock_initialized)
 {
        struct dccp_sock *dp = dccp_sk(sk);
-       struct dccp_minisock *dmsk = dccp_msk(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
 
-       dccp_minisock_init(&dp->dccps_minisock);
-
        icsk->icsk_rto          = DCCP_TIMEOUT_INIT;
        icsk->icsk_syn_retries  = sysctl_dccp_request_retries;
        sk->sk_state            = DCCP_CLOSED;
@@ -194,54 +187,18 @@ int dccp_init_sock(struct sock *sk, const __u8 ctl_sock_initialized)
 
        dccp_init_xmit_timers(sk);
 
-       /*
-        * FIXME: We're hardcoding the CCID, and doing this at this point makes
-        * the listening (master) sock get CCID control blocks, which is not
-        * necessary, but for now, to not mess with the test userspace apps,
-        * lets leave it here, later the real solution is to do this in a
-        * setsockopt(CCIDs-I-want/accept). -acme
-        */
-       if (likely(ctl_sock_initialized)) {
-               int rc = dccp_feat_init(dmsk);
-
-               if (rc)
-                       return rc;
-
-               if (dmsk->dccpms_send_ack_vector) {
-                       dp->dccps_hc_rx_ackvec = dccp_ackvec_alloc(GFP_KERNEL);
-                       if (dp->dccps_hc_rx_ackvec == NULL)
-                               return -ENOMEM;
-               }
-               dp->dccps_hc_rx_ccid = ccid_hc_rx_new(dmsk->dccpms_rx_ccid,
-                                                     sk, GFP_KERNEL);
-               dp->dccps_hc_tx_ccid = ccid_hc_tx_new(dmsk->dccpms_tx_ccid,
-                                                     sk, GFP_KERNEL);
-               if (unlikely(dp->dccps_hc_rx_ccid == NULL ||
-                            dp->dccps_hc_tx_ccid == NULL)) {
-                       ccid_hc_rx_delete(dp->dccps_hc_rx_ccid, sk);
-                       ccid_hc_tx_delete(dp->dccps_hc_tx_ccid, sk);
-                       if (dmsk->dccpms_send_ack_vector) {
-                               dccp_ackvec_free(dp->dccps_hc_rx_ackvec);
-                               dp->dccps_hc_rx_ackvec = NULL;
-                       }
-                       dp->dccps_hc_rx_ccid = dp->dccps_hc_tx_ccid = NULL;
-                       return -ENOMEM;
-               }
-       } else {
-               /* control socket doesn't need feat nego */
-               INIT_LIST_HEAD(&dmsk->dccpms_pending);
-               INIT_LIST_HEAD(&dmsk->dccpms_conf);
-       }
-
+       INIT_LIST_HEAD(&dp->dccps_featneg);
+       /* control socket doesn't need feat nego */
+       if (likely(ctl_sock_initialized))
+               return dccp_feat_init(sk);
        return 0;
 }
 
 EXPORT_SYMBOL_GPL(dccp_init_sock);
 
-int dccp_destroy_sock(struct sock *sk)
+void dccp_destroy_sock(struct sock *sk)
 {
        struct dccp_sock *dp = dccp_sk(sk);
-       struct dccp_minisock *dmsk = dccp_msk(sk);
 
        /*
         * DCCP doesn't use sk_write_queue, just sk_send_head
@@ -259,7 +216,7 @@ int dccp_destroy_sock(struct sock *sk)
        kfree(dp->dccps_service_list);
        dp->dccps_service_list = NULL;
 
-       if (dmsk->dccpms_send_ack_vector) {
+       if (dp->dccps_hc_rx_ackvec != NULL) {
                dccp_ackvec_free(dp->dccps_hc_rx_ackvec);
                dp->dccps_hc_rx_ackvec = NULL;
        }
@@ -268,9 +225,7 @@ int dccp_destroy_sock(struct sock *sk)
        dp->dccps_hc_rx_ccid = dp->dccps_hc_tx_ccid = NULL;
 
        /* clean up feature negotiation state */
-       dccp_feat_clean(dmsk);
-
-       return 0;
+       dccp_feat_list_purge(&dp->dccps_featneg);
 }
 
 EXPORT_SYMBOL_GPL(dccp_destroy_sock);
@@ -280,6 +235,9 @@ static inline int dccp_listen_start(struct sock *sk, int backlog)
        struct dccp_sock *dp = dccp_sk(sk);
 
        dp->dccps_role = DCCP_ROLE_LISTEN;
+       /* do not start to listen if feature negotiation setup fails */
+       if (dccp_feat_finalise_settings(dp))
+               return -EPROTO;
        return inet_csk_listen_start(sk, backlog);
 }
 
@@ -312,7 +270,9 @@ int dccp_disconnect(struct sock *sk, int flags)
                sk->sk_err = ECONNRESET;
 
        dccp_clear_xmit_timers(sk);
+
        __skb_queue_purge(&sk->sk_receive_queue);
+       __skb_queue_purge(&sk->sk_write_queue);
        if (sk->sk_send_head != NULL) {
                __kfree_skb(sk->sk_send_head);
                sk->sk_send_head = NULL;
@@ -330,7 +290,7 @@ int dccp_disconnect(struct sock *sk, int flags)
        inet_csk_delack_init(sk);
        __sk_dst_reset(sk);
 
-       BUG_TRAP(!inet->num || icsk->icsk_bind_hash);
+       WARN_ON(inet->num && !icsk->icsk_bind_hash);
 
        sk->sk_error_report(sk);
        return err;
@@ -467,37 +427,70 @@ static int dccp_setsockopt_service(struct sock *sk, const __be32 service,
        return 0;
 }
 
-/* byte 1 is feature.  the rest is the preference list */
-static int dccp_setsockopt_change(struct sock *sk, int type,
-                                 struct dccp_so_feat __user *optval)
+static int dccp_setsockopt_cscov(struct sock *sk, int cscov, bool rx)
+{
+       u8 *list, len;
+       int i, rc;
+
+       if (cscov < 0 || cscov > 15)
+               return -EINVAL;
+       /*
+        * Populate a list of permissible values, in the range cscov...15. This
+        * is necessary since feature negotiation of single values only works if
+        * both sides incidentally choose the same value. Since the list starts
+        * lowest-value first, negotiation will pick the smallest shared value.
+        */
+       if (cscov == 0)
+               return 0;
+       len = 16 - cscov;
+
+       list = kmalloc(len, GFP_KERNEL);
+       if (list == NULL)
+               return -ENOBUFS;
+
+       for (i = 0; i < len; i++)
+               list[i] = cscov++;
+
+       rc = dccp_feat_register_sp(sk, DCCPF_MIN_CSUM_COVER, rx, list, len);
+
+       if (rc == 0) {
+               if (rx)
+                       dccp_sk(sk)->dccps_pcrlen = cscov;
+               else
+                       dccp_sk(sk)->dccps_pcslen = cscov;
+       }
+       kfree(list);
+       return rc;
+}
+
+static int dccp_setsockopt_ccid(struct sock *sk, int type,
+                               char __user *optval, int optlen)
 {
-       struct dccp_so_feat opt;
        u8 *val;
-       int rc;
+       int rc = 0;
 
-       if (copy_from_user(&opt, optval, sizeof(opt)))
-               return -EFAULT;
+       if (optlen < 1 || optlen > DCCP_FEAT_MAX_SP_VALS)
+               return -EINVAL;
 
-       val = kmalloc(opt.dccpsf_len, GFP_KERNEL);
-       if (!val)
+       val = kmalloc(optlen, GFP_KERNEL);
+       if (val == NULL)
                return -ENOMEM;
 
-       if (copy_from_user(val, opt.dccpsf_val, opt.dccpsf_len)) {
-               rc = -EFAULT;
-               goto out_free_val;
+       if (copy_from_user(val, optval, optlen)) {
+               kfree(val);
+               return -EFAULT;
        }
 
-       rc = dccp_feat_change(dccp_msk(sk), type, opt.dccpsf_feat,
-                             val, opt.dccpsf_len, GFP_KERNEL);
-       if (rc)
-               goto out_free_val;
+       lock_sock(sk);
+       if (type == DCCP_SOCKOPT_TX_CCID || type == DCCP_SOCKOPT_CCID)
+               rc = dccp_feat_register_sp(sk, DCCPF_CCID, 1, val, optlen);
 
-out:
-       return rc;
+       if (!rc && (type == DCCP_SOCKOPT_RX_CCID || type == DCCP_SOCKOPT_CCID))
+               rc = dccp_feat_register_sp(sk, DCCPF_CCID, 0, val, optlen);
+       release_sock(sk);
 
-out_free_val:
        kfree(val);
-       goto out;
+       return rc;
 }
 
 static int do_dccp_setsockopt(struct sock *sk, int level, int optname,
@@ -506,7 +499,21 @@ static int do_dccp_setsockopt(struct sock *sk, int level, int optname,
        struct dccp_sock *dp = dccp_sk(sk);
        int val, err = 0;
 
-       if (optlen < sizeof(int))
+       switch (optname) {
+       case DCCP_SOCKOPT_PACKET_SIZE:
+               DCCP_WARN("sockopt(PACKET_SIZE) is deprecated: fix your app\n");
+               return 0;
+       case DCCP_SOCKOPT_CHANGE_L:
+       case DCCP_SOCKOPT_CHANGE_R:
+               DCCP_WARN("sockopt(CHANGE_L/R) is deprecated: fix your app\n");
+               return 0;
+       case DCCP_SOCKOPT_CCID:
+       case DCCP_SOCKOPT_RX_CCID:
+       case DCCP_SOCKOPT_TX_CCID:
+               return dccp_setsockopt_ccid(sk, optname, optval, optlen);
+       }
+
+       if (optlen < (int)sizeof(int))
                return -EINVAL;
 
        if (get_user(val, (int __user *)optval))
@@ -517,53 +524,24 @@ static int do_dccp_setsockopt(struct sock *sk, int level, int optname,
 
        lock_sock(sk);
        switch (optname) {
-       case DCCP_SOCKOPT_PACKET_SIZE:
-               DCCP_WARN("sockopt(PACKET_SIZE) is deprecated: fix your app\n");
-               err = 0;
-               break;
-       case DCCP_SOCKOPT_CHANGE_L:
-               if (optlen != sizeof(struct dccp_so_feat))
-                       err = -EINVAL;
-               else
-                       err = dccp_setsockopt_change(sk, DCCPO_CHANGE_L,
-                                                    (struct dccp_so_feat __user *)
-                                                    optval);
-               break;
-       case DCCP_SOCKOPT_CHANGE_R:
-               if (optlen != sizeof(struct dccp_so_feat))
-                       err = -EINVAL;
-               else
-                       err = dccp_setsockopt_change(sk, DCCPO_CHANGE_R,
-                                                    (struct dccp_so_feat __user *)
-                                                    optval);
-               break;
        case DCCP_SOCKOPT_SERVER_TIMEWAIT:
                if (dp->dccps_role != DCCP_ROLE_SERVER)
                        err = -EOPNOTSUPP;
                else
                        dp->dccps_server_timewait = (val != 0);
                break;
-       case DCCP_SOCKOPT_SEND_CSCOV:   /* sender side, RFC 4340, sec. 9.2 */
-               if (val < 0 || val > 15)
-                       err = -EINVAL;
-               else
-                       dp->dccps_pcslen = val;
+       case DCCP_SOCKOPT_SEND_CSCOV:
+               err = dccp_setsockopt_cscov(sk, val, false);
                break;
-       case DCCP_SOCKOPT_RECV_CSCOV:   /* receiver side, RFC 4340 sec. 9.2.1 */
-               if (val < 0 || val > 15)
-                       err = -EINVAL;
-               else {
-                       dp->dccps_pcrlen = val;
-                       /* FIXME: add feature negotiation,
-                        * ChangeL(MinimumChecksumCoverage, val) */
-               }
+       case DCCP_SOCKOPT_RECV_CSCOV:
+               err = dccp_setsockopt_cscov(sk, val, true);
                break;
        default:
                err = -ENOPROTOOPT;
                break;
        }
-
        release_sock(sk);
+
        return err;
 }
 
@@ -644,6 +622,18 @@ static int do_dccp_getsockopt(struct sock *sk, int level, int optname,
        case DCCP_SOCKOPT_GET_CUR_MPS:
                val = dp->dccps_mss_cache;
                break;
+       case DCCP_SOCKOPT_AVAILABLE_CCIDS:
+               return ccid_getsockopt_builtin_ccids(sk, len, optval, optlen);
+       case DCCP_SOCKOPT_TX_CCID:
+               val = ccid_get_current_tx_ccid(dp);
+               if (val < 0)
+                       return -ENOPROTOOPT;
+               break;
+       case DCCP_SOCKOPT_RX_CCID:
+               val = ccid_get_current_rx_ccid(dp);
+               if (val < 0)
+                       return -ENOPROTOOPT;
+               break;
        case DCCP_SOCKOPT_SERVER_TIMEWAIT:
                val = dp->dccps_server_timewait;
                break;
@@ -972,7 +962,6 @@ adjudge_to_death:
        state = sk->sk_state;
        sock_hold(sk);
        sock_orphan(sk);
-       atomic_inc(sk->sk_prot->orphan_count);
 
        /*
         * It is the last release_sock in its life. It will remove backlog.
@@ -984,7 +973,9 @@ adjudge_to_death:
         */
        local_bh_disable();
        bh_lock_sock(sk);
-       BUG_TRAP(!sock_owned_by_user(sk));
+       WARN_ON(sock_owned_by_user(sk));
+
+       percpu_counter_inc(sk->sk_prot->orphan_count);
 
        /* Have we already been destroyed by a softirq or backlog? */
        if (state != DCCP_CLOSED && sk->sk_state == DCCP_CLOSED)
@@ -1010,33 +1001,14 @@ void dccp_shutdown(struct sock *sk, int how)
 
 EXPORT_SYMBOL_GPL(dccp_shutdown);
 
-static int __init dccp_mib_init(void)
+static inline int dccp_mib_init(void)
 {
-       int rc = -ENOMEM;
-
-       dccp_statistics[0] = alloc_percpu(struct dccp_mib);
-       if (dccp_statistics[0] == NULL)
-               goto out;
-
-       dccp_statistics[1] = alloc_percpu(struct dccp_mib);
-       if (dccp_statistics[1] == NULL)
-               goto out_free_one;
-
-       rc = 0;
-out:
-       return rc;
-out_free_one:
-       free_percpu(dccp_statistics[0]);
-       dccp_statistics[0] = NULL;
-       goto out;
-
+       return snmp_mib_init((void**)dccp_statistics, sizeof(struct dccp_mib));
 }
 
-static void dccp_mib_exit(void)
+static inline void dccp_mib_exit(void)
 {
-       free_percpu(dccp_statistics[0]);
-       free_percpu(dccp_statistics[1]);
-       dccp_statistics[0] = dccp_statistics[1] = NULL;
+       snmp_mib_free((void**)dccp_statistics);
 }
 
 static int thash_entries;
@@ -1045,7 +1017,7 @@ MODULE_PARM_DESC(thash_entries, "Number of ehash buckets");
 
 #ifdef CONFIG_IP_DCCP_DEBUG
 int dccp_debug;
-module_param(dccp_debug, bool, 0444);
+module_param(dccp_debug, bool, 0644);
 MODULE_PARM_DESC(dccp_debug, "Enable debug messages");
 
 EXPORT_SYMBOL_GPL(dccp_debug);
@@ -1055,17 +1027,21 @@ static int __init dccp_init(void)
 {
        unsigned long goal;
        int ehash_order, bhash_order, i;
-       int rc = -ENOBUFS;
+       int rc;
 
        BUILD_BUG_ON(sizeof(struct dccp_skb_cb) >
                     FIELD_SIZEOF(struct sk_buff, cb));
-
+       rc = percpu_counter_init(&dccp_orphan_count, 0);
+       if (rc)
+               goto out;
+       rc = -ENOBUFS;
+       inet_hashinfo_init(&dccp_hashinfo);
        dccp_hashinfo.bind_bucket_cachep =
                kmem_cache_create("dccp_bind_bucket",
                                  sizeof(struct inet_bind_bucket), 0,
                                  SLAB_HWCACHE_ALIGN, NULL);
        if (!dccp_hashinfo.bind_bucket_cachep)
-               goto out;
+               goto out_free_percpu;
 
        /*
         * Size and allocate the main established and bind bucket
@@ -1099,8 +1075,8 @@ static int __init dccp_init(void)
        }
 
        for (i = 0; i < dccp_hashinfo.ehash_size; i++) {
-               INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].chain);
-               INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].twchain);
+               INIT_HLIST_NULLS_HEAD(&dccp_hashinfo.ehash[i].chain, i);
+               INIT_HLIST_NULLS_HEAD(&dccp_hashinfo.ehash[i].twchain, i);
        }
 
        if (inet_ehash_locks_alloc(&dccp_hashinfo))
@@ -1140,9 +1116,15 @@ static int __init dccp_init(void)
        if (rc)
                goto out_ackvec_exit;
 
+       rc = ccid_initialize_builtins();
+       if (rc)
+               goto out_sysctl_exit;
+
        dccp_timestamping_init();
 out:
        return rc;
+out_sysctl_exit:
+       dccp_sysctl_exit();
 out_ackvec_exit:
        dccp_ackvec_exit();
 out_free_dccp_mib:
@@ -1158,11 +1140,14 @@ out_free_dccp_ehash:
 out_free_bind_bucket_cachep:
        kmem_cache_destroy(dccp_hashinfo.bind_bucket_cachep);
        dccp_hashinfo.bind_bucket_cachep = NULL;
+out_free_percpu:
+       percpu_counter_destroy(&dccp_orphan_count);
        goto out;
 }
 
 static void __exit dccp_fini(void)
 {
+       ccid_cleanup_builtins();
        dccp_mib_exit();
        free_pages((unsigned long)dccp_hashinfo.bhash,
                   get_order(dccp_hashinfo.bhash_size *