]> Pileus Git - ~andy/linux/blobdiff - net/netfilter/ipvs/ip_vs_conn.c
Merge tag 'please-pull-pstore' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl...
[~andy/linux] / net / netfilter / ipvs / ip_vs_conn.c
index 704e514e02abec92b4676f3747468eb76c8c68a2..a083bda322b6058cf0ba6b65b604bb046c8a79fd 100644 (file)
@@ -79,51 +79,21 @@ static unsigned int ip_vs_conn_rnd __read_mostly;
 
 struct ip_vs_aligned_lock
 {
-       rwlock_t        l;
+       spinlock_t      l;
 } __attribute__((__aligned__(SMP_CACHE_BYTES)));
 
 /* lock array for conn table */
 static struct ip_vs_aligned_lock
 __ip_vs_conntbl_lock_array[CT_LOCKARRAY_SIZE] __cacheline_aligned;
 
-static inline void ct_read_lock(unsigned int key)
-{
-       read_lock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
-static inline void ct_read_unlock(unsigned int key)
-{
-       read_unlock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
-static inline void ct_write_lock(unsigned int key)
-{
-       write_lock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
-static inline void ct_write_unlock(unsigned int key)
-{
-       write_unlock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
-static inline void ct_read_lock_bh(unsigned int key)
-{
-       read_lock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
-static inline void ct_read_unlock_bh(unsigned int key)
-{
-       read_unlock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
-}
-
 static inline void ct_write_lock_bh(unsigned int key)
 {
-       write_lock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+       spin_lock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
 }
 
 static inline void ct_write_unlock_bh(unsigned int key)
 {
-       write_unlock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+       spin_unlock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
 }
 
 
@@ -197,13 +167,13 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
        /* Hash by protocol, client address and port */
        hash = ip_vs_conn_hashkey_conn(cp);
 
-       ct_write_lock(hash);
+       ct_write_lock_bh(hash);
        spin_lock(&cp->lock);
 
        if (!(cp->flags & IP_VS_CONN_F_HASHED)) {
-               hlist_add_head(&cp->c_list, &ip_vs_conn_tab[hash]);
                cp->flags |= IP_VS_CONN_F_HASHED;
                atomic_inc(&cp->refcnt);
+               hlist_add_head_rcu(&cp->c_list, &ip_vs_conn_tab[hash]);
                ret = 1;
        } else {
                pr_err("%s(): request for already hashed, called from %pF\n",
@@ -212,7 +182,7 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
        }
 
        spin_unlock(&cp->lock);
-       ct_write_unlock(hash);
+       ct_write_unlock_bh(hash);
 
        return ret;
 }
@@ -220,7 +190,7 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
 
 /*
  *     UNhashes ip_vs_conn from ip_vs_conn_tab.
- *     returns bool success.
+ *     returns bool success. Caller should hold conn reference.
  */
 static inline int ip_vs_conn_unhash(struct ip_vs_conn *cp)
 {
@@ -230,11 +200,11 @@ static inline int ip_vs_conn_unhash(struct ip_vs_conn *cp)
        /* unhash it and decrease its reference counter */
        hash = ip_vs_conn_hashkey_conn(cp);
 
-       ct_write_lock(hash);
+       ct_write_lock_bh(hash);
        spin_lock(&cp->lock);
 
        if (cp->flags & IP_VS_CONN_F_HASHED) {
-               hlist_del(&cp->c_list);
+               hlist_del_rcu(&cp->c_list);
                cp->flags &= ~IP_VS_CONN_F_HASHED;
                atomic_dec(&cp->refcnt);
                ret = 1;
@@ -242,7 +212,37 @@ static inline int ip_vs_conn_unhash(struct ip_vs_conn *cp)
                ret = 0;
 
        spin_unlock(&cp->lock);
-       ct_write_unlock(hash);
+       ct_write_unlock_bh(hash);
+
+       return ret;
+}
+
+/* Try to unlink ip_vs_conn from ip_vs_conn_tab.
+ * returns bool success.
+ */
+static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp)
+{
+       unsigned int hash;
+       bool ret;
+
+       hash = ip_vs_conn_hashkey_conn(cp);
+
+       ct_write_lock_bh(hash);
+       spin_lock(&cp->lock);
+
+       if (cp->flags & IP_VS_CONN_F_HASHED) {
+               ret = false;
+               /* Decrease refcnt and unlink conn only if we are last user */
+               if (atomic_cmpxchg(&cp->refcnt, 1, 0) == 1) {
+                       hlist_del_rcu(&cp->c_list);
+                       cp->flags &= ~IP_VS_CONN_F_HASHED;
+                       ret = true;
+               }
+       } else
+               ret = atomic_read(&cp->refcnt) ? false : true;
+
+       spin_unlock(&cp->lock);
+       ct_write_unlock_bh(hash);
 
        return ret;
 }
@@ -262,24 +262,25 @@ __ip_vs_conn_in_get(const struct ip_vs_conn_param *p)
 
        hash = ip_vs_conn_hashkey_param(p, false);
 
-       ct_read_lock(hash);
+       rcu_read_lock();
 
-       hlist_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
-               if (cp->af == p->af &&
-                   p->cport == cp->cport && p->vport == cp->vport &&
+       hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
+               if (p->cport == cp->cport && p->vport == cp->vport &&
+                   cp->af == p->af &&
                    ip_vs_addr_equal(p->af, p->caddr, &cp->caddr) &&
                    ip_vs_addr_equal(p->af, p->vaddr, &cp->vaddr) &&
                    ((!p->cport) ^ (!(cp->flags & IP_VS_CONN_F_NO_CPORT))) &&
                    p->protocol == cp->protocol &&
                    ip_vs_conn_net_eq(cp, p->net)) {
+                       if (!__ip_vs_conn_get(cp))
+                               continue;
                        /* HIT */
-                       atomic_inc(&cp->refcnt);
-                       ct_read_unlock(hash);
+                       rcu_read_unlock();
                        return cp;
                }
        }
 
-       ct_read_unlock(hash);
+       rcu_read_unlock();
 
        return NULL;
 }
@@ -346,14 +347,16 @@ struct ip_vs_conn *ip_vs_ct_in_get(const struct ip_vs_conn_param *p)
 
        hash = ip_vs_conn_hashkey_param(p, false);
 
-       ct_read_lock(hash);
+       rcu_read_lock();
 
-       hlist_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
-               if (!ip_vs_conn_net_eq(cp, p->net))
-                       continue;
-               if (p->pe_data && p->pe->ct_match) {
-                       if (p->pe == cp->pe && p->pe->ct_match(p, cp))
-                               goto out;
+       hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
+               if (unlikely(p->pe_data && p->pe->ct_match)) {
+                       if (!ip_vs_conn_net_eq(cp, p->net))
+                               continue;
+                       if (p->pe == cp->pe && p->pe->ct_match(p, cp)) {
+                               if (__ip_vs_conn_get(cp))
+                                       goto out;
+                       }
                        continue;
                }
 
@@ -363,17 +366,18 @@ struct ip_vs_conn *ip_vs_ct_in_get(const struct ip_vs_conn_param *p)
                     * p->vaddr is a fwmark */
                    ip_vs_addr_equal(p->protocol == IPPROTO_IP ? AF_UNSPEC :
                                     p->af, p->vaddr, &cp->vaddr) &&
-                   p->cport == cp->cport && p->vport == cp->vport &&
+                   p->vport == cp->vport && p->cport == cp->cport &&
                    cp->flags & IP_VS_CONN_F_TEMPLATE &&
-                   p->protocol == cp->protocol)
-                       goto out;
+                   p->protocol == cp->protocol &&
+                   ip_vs_conn_net_eq(cp, p->net)) {
+                       if (__ip_vs_conn_get(cp))
+                               goto out;
+               }
        }
        cp = NULL;
 
   out:
-       if (cp)
-               atomic_inc(&cp->refcnt);
-       ct_read_unlock(hash);
+       rcu_read_unlock();
 
        IP_VS_DBG_BUF(9, "template lookup/in %s %s:%d->%s:%d %s\n",
                      ip_vs_proto_name(p->protocol),
@@ -398,23 +402,24 @@ struct ip_vs_conn *ip_vs_conn_out_get(const struct ip_vs_conn_param *p)
         */
        hash = ip_vs_conn_hashkey_param(p, true);
 
-       ct_read_lock(hash);
+       rcu_read_lock();
 
-       hlist_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
-               if (cp->af == p->af &&
-                   p->vport == cp->cport && p->cport == cp->dport &&
+       hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
+               if (p->vport == cp->cport && p->cport == cp->dport &&
+                   cp->af == p->af &&
                    ip_vs_addr_equal(p->af, p->vaddr, &cp->caddr) &&
                    ip_vs_addr_equal(p->af, p->caddr, &cp->daddr) &&
                    p->protocol == cp->protocol &&
                    ip_vs_conn_net_eq(cp, p->net)) {
+                       if (!__ip_vs_conn_get(cp))
+                               continue;
                        /* HIT */
-                       atomic_inc(&cp->refcnt);
                        ret = cp;
                        break;
                }
        }
 
-       ct_read_unlock(hash);
+       rcu_read_unlock();
 
        IP_VS_DBG_BUF(9, "lookup/out %s %s:%d->%s:%d %s\n",
                      ip_vs_proto_name(p->protocol),
@@ -457,13 +462,13 @@ void ip_vs_conn_put(struct ip_vs_conn *cp)
 void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
 {
        if (ip_vs_conn_unhash(cp)) {
-               spin_lock(&cp->lock);
+               spin_lock_bh(&cp->lock);
                if (cp->flags & IP_VS_CONN_F_NO_CPORT) {
                        atomic_dec(&ip_vs_conn_no_cport_cnt);
                        cp->flags &= ~IP_VS_CONN_F_NO_CPORT;
                        cp->cport = cport;
                }
-               spin_unlock(&cp->lock);
+               spin_unlock_bh(&cp->lock);
 
                /* hash on new dport */
                ip_vs_conn_hash(cp);
@@ -549,7 +554,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
                return;
 
        /* Increase the refcnt counter of the dest */
-       atomic_inc(&dest->refcnt);
+       ip_vs_dest_hold(dest);
 
        conn_flags = atomic_read(&dest->conn_flags);
        if (cp->protocol != IPPROTO_UDP)
@@ -606,20 +611,22 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
  * Check if there is a destination for the connection, if so
  * bind the connection to the destination.
  */
-struct ip_vs_dest *ip_vs_try_bind_dest(struct ip_vs_conn *cp)
+void ip_vs_try_bind_dest(struct ip_vs_conn *cp)
 {
        struct ip_vs_dest *dest;
 
+       rcu_read_lock();
        dest = ip_vs_find_dest(ip_vs_conn_net(cp), cp->af, &cp->daddr,
                               cp->dport, &cp->vaddr, cp->vport,
                               cp->protocol, cp->fwmark, cp->flags);
        if (dest) {
                struct ip_vs_proto_data *pd;
 
-               spin_lock(&cp->lock);
+               spin_lock_bh(&cp->lock);
                if (cp->dest) {
-                       spin_unlock(&cp->lock);
-                       return dest;
+                       spin_unlock_bh(&cp->lock);
+                       rcu_read_unlock();
+                       return;
                }
 
                /* Applications work depending on the forwarding method
@@ -628,7 +635,7 @@ struct ip_vs_dest *ip_vs_try_bind_dest(struct ip_vs_conn *cp)
                        ip_vs_unbind_app(cp);
 
                ip_vs_bind_dest(cp, dest);
-               spin_unlock(&cp->lock);
+               spin_unlock_bh(&cp->lock);
 
                /* Update its packet transmitter */
                cp->packet_xmit = NULL;
@@ -643,7 +650,7 @@ struct ip_vs_dest *ip_vs_try_bind_dest(struct ip_vs_conn *cp)
                if (pd && atomic_read(&pd->appcnt))
                        ip_vs_bind_app(cp, pd->pp);
        }
-       return dest;
+       rcu_read_unlock();
 }
 
 
@@ -695,12 +702,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
                        dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
        }
 
-       /*
-        * Simply decrease the refcnt of the dest, because the
-        * dest will be either in service's destination list
-        * or in the trash.
-        */
-       atomic_dec(&dest->refcnt);
+       ip_vs_dest_put(dest);
 }
 
 static int expire_quiescent_template(struct netns_ipvs *ipvs,
@@ -757,41 +759,36 @@ int ip_vs_check_template(struct ip_vs_conn *ct)
                 * Simply decrease the refcnt of the template,
                 * don't restart its timer.
                 */
-               atomic_dec(&ct->refcnt);
+               __ip_vs_conn_put(ct);
                return 0;
        }
        return 1;
 }
 
+static void ip_vs_conn_rcu_free(struct rcu_head *head)
+{
+       struct ip_vs_conn *cp = container_of(head, struct ip_vs_conn,
+                                            rcu_head);
+
+       ip_vs_pe_put(cp->pe);
+       kfree(cp->pe_data);
+       kmem_cache_free(ip_vs_conn_cachep, cp);
+}
+
 static void ip_vs_conn_expire(unsigned long data)
 {
        struct ip_vs_conn *cp = (struct ip_vs_conn *)data;
        struct net *net = ip_vs_conn_net(cp);
        struct netns_ipvs *ipvs = net_ipvs(net);
 
-       cp->timeout = 60*HZ;
-
-       /*
-        *      hey, I'm using it
-        */
-       atomic_inc(&cp->refcnt);
-
        /*
         *      do I control anybody?
         */
        if (atomic_read(&cp->n_control))
                goto expire_later;
 
-       /*
-        *      unhash it if it is hashed in the conn table
-        */
-       if (!ip_vs_conn_unhash(cp) && !(cp->flags & IP_VS_CONN_F_ONE_PACKET))
-               goto expire_later;
-
-       /*
-        *      refcnt==1 implies I'm the only one referrer
-        */
-       if (likely(atomic_read(&cp->refcnt) == 1)) {
+       /* Unlink conn if not referenced anymore */
+       if (likely(ip_vs_conn_unlink(cp))) {
                /* delete the timer if it is activated by other users */
                del_timer(&cp->timer);
 
@@ -810,38 +807,41 @@ static void ip_vs_conn_expire(unsigned long data)
                                ip_vs_conn_drop_conntrack(cp);
                }
 
-               ip_vs_pe_put(cp->pe);
-               kfree(cp->pe_data);
                if (unlikely(cp->app != NULL))
                        ip_vs_unbind_app(cp);
                ip_vs_unbind_dest(cp);
                if (cp->flags & IP_VS_CONN_F_NO_CPORT)
                        atomic_dec(&ip_vs_conn_no_cport_cnt);
+               call_rcu(&cp->rcu_head, ip_vs_conn_rcu_free);
                atomic_dec(&ipvs->conn_count);
-
-               kmem_cache_free(ip_vs_conn_cachep, cp);
                return;
        }
 
-       /* hash it back to the table */
-       ip_vs_conn_hash(cp);
-
   expire_later:
-       IP_VS_DBG(7, "delayed: conn->refcnt-1=%d conn->n_control=%d\n",
-                 atomic_read(&cp->refcnt)-1,
+       IP_VS_DBG(7, "delayed: conn->refcnt=%d conn->n_control=%d\n",
+                 atomic_read(&cp->refcnt),
                  atomic_read(&cp->n_control));
 
+       atomic_inc(&cp->refcnt);
+       cp->timeout = 60*HZ;
+
        if (ipvs->sync_state & IP_VS_STATE_MASTER)
                ip_vs_sync_conn(net, cp, sysctl_sync_threshold(ipvs));
 
        ip_vs_conn_put(cp);
 }
 
-
+/* Modify timer, so that it expires as soon as possible.
+ * Can be called without reference only if under RCU lock.
+ */
 void ip_vs_conn_expire_now(struct ip_vs_conn *cp)
 {
-       if (del_timer(&cp->timer))
-               mod_timer(&cp->timer, jiffies);
+       /* Using mod_timer_pending will ensure the timer is not
+        * modified after the final del_timer in ip_vs_conn_expire.
+        */
+       if (timer_pending(&cp->timer) &&
+           time_after(cp->timer.expires, jiffies))
+               mod_timer_pending(&cp->timer, jiffies);
 }
 
 
@@ -858,7 +858,7 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
        struct ip_vs_proto_data *pd = ip_vs_proto_data_get(p->net,
                                                           p->protocol);
 
-       cp = kmem_cache_zalloc(ip_vs_conn_cachep, GFP_ATOMIC);
+       cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC);
        if (cp == NULL) {
                IP_VS_ERR_RL("%s(): no memory\n", __func__);
                return NULL;
@@ -869,13 +869,13 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
        ip_vs_conn_net_set(cp, p->net);
        cp->af             = p->af;
        cp->protocol       = p->protocol;
-       ip_vs_addr_copy(p->af, &cp->caddr, p->caddr);
+       ip_vs_addr_set(p->af, &cp->caddr, p->caddr);
        cp->cport          = p->cport;
-       ip_vs_addr_copy(p->af, &cp->vaddr, p->vaddr);
+       ip_vs_addr_set(p->af, &cp->vaddr, p->vaddr);
        cp->vport          = p->vport;
        /* proto should only be IPPROTO_IP if d_addr is a fwmark */
-       ip_vs_addr_copy(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af,
-                       &cp->daddr, daddr);
+       ip_vs_addr_set(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af,
+                      &cp->daddr, daddr);
        cp->dport          = dport;
        cp->flags          = flags;
        cp->fwmark         = fwmark;
@@ -884,6 +884,10 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
                cp->pe = p->pe;
                cp->pe_data = p->pe_data;
                cp->pe_data_len = p->pe_data_len;
+       } else {
+               cp->pe = NULL;
+               cp->pe_data = NULL;
+               cp->pe_data_len = 0;
        }
        spin_lock_init(&cp->lock);
 
@@ -894,18 +898,28 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
         */
        atomic_set(&cp->refcnt, 1);
 
+       cp->control = NULL;
        atomic_set(&cp->n_control, 0);
        atomic_set(&cp->in_pkts, 0);
 
+       cp->packet_xmit = NULL;
+       cp->app = NULL;
+       cp->app_data = NULL;
+       /* reset struct ip_vs_seq */
+       cp->in_seq.delta = 0;
+       cp->out_seq.delta = 0;
+
        atomic_inc(&ipvs->conn_count);
        if (flags & IP_VS_CONN_F_NO_CPORT)
                atomic_inc(&ip_vs_conn_no_cport_cnt);
 
        /* Bind the connection with a destination server */
+       cp->dest = NULL;
        ip_vs_bind_dest(cp, dest);
 
        /* Set its state and timeout */
        cp->state = 0;
+       cp->old_state = 0;
        cp->timeout = 3*HZ;
        cp->sync_endtime = jiffies & ~3UL;
 
@@ -952,24 +966,29 @@ static void *ip_vs_conn_array(struct seq_file *seq, loff_t pos)
        struct ip_vs_iter_state *iter = seq->private;
 
        for (idx = 0; idx < ip_vs_conn_tab_size; idx++) {
-               ct_read_lock_bh(idx);
-               hlist_for_each_entry(cp, &ip_vs_conn_tab[idx], c_list) {
+               hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[idx], c_list) {
+                       /* __ip_vs_conn_get() is not needed by
+                        * ip_vs_conn_seq_show and ip_vs_conn_sync_seq_show
+                        */
                        if (pos-- == 0) {
                                iter->l = &ip_vs_conn_tab[idx];
                                return cp;
                        }
                }
-               ct_read_unlock_bh(idx);
+               rcu_read_unlock();
+               rcu_read_lock();
        }
 
        return NULL;
 }
 
 static void *ip_vs_conn_seq_start(struct seq_file *seq, loff_t *pos)
+       __acquires(RCU)
 {
        struct ip_vs_iter_state *iter = seq->private;
 
        iter->l = NULL;
+       rcu_read_lock();
        return *pos ? ip_vs_conn_array(seq, *pos - 1) :SEQ_START_TOKEN;
 }
 
@@ -977,6 +996,7 @@ static void *ip_vs_conn_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
        struct ip_vs_conn *cp = v;
        struct ip_vs_iter_state *iter = seq->private;
+       struct hlist_node *e;
        struct hlist_head *l = iter->l;
        int idx;
 
@@ -985,31 +1005,27 @@ static void *ip_vs_conn_seq_next(struct seq_file *seq, void *v, loff_t *pos)
                return ip_vs_conn_array(seq, 0);
 
        /* more on same hash chain? */
-       if (cp->c_list.next)
-               return hlist_entry(cp->c_list.next, struct ip_vs_conn, c_list);
+       e = rcu_dereference(hlist_next_rcu(&cp->c_list));
+       if (e)
+               return hlist_entry(e, struct ip_vs_conn, c_list);
 
        idx = l - ip_vs_conn_tab;
-       ct_read_unlock_bh(idx);
-
        while (++idx < ip_vs_conn_tab_size) {
-               ct_read_lock_bh(idx);
-               hlist_for_each_entry(cp, &ip_vs_conn_tab[idx], c_list) {
+               hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[idx], c_list) {
                        iter->l = &ip_vs_conn_tab[idx];
                        return cp;
                }
-               ct_read_unlock_bh(idx);
+               rcu_read_unlock();
+               rcu_read_lock();
        }
        iter->l = NULL;
        return NULL;
 }
 
 static void ip_vs_conn_seq_stop(struct seq_file *seq, void *v)
+       __releases(RCU)
 {
-       struct ip_vs_iter_state *iter = seq->private;
-       struct hlist_head *l = iter->l;
-
-       if (l)
-               ct_read_unlock_bh(l - ip_vs_conn_tab);
+       rcu_read_unlock();
 }
 
 static int ip_vs_conn_seq_show(struct seq_file *seq, void *v)
@@ -1188,7 +1204,7 @@ static inline int todrop_entry(struct ip_vs_conn *cp)
 void ip_vs_random_dropentry(struct net *net)
 {
        int idx;
-       struct ip_vs_conn *cp;
+       struct ip_vs_conn *cp, *cp_c;
 
        /*
         * Randomly scan 1/32 of the whole table every second
@@ -1199,9 +1215,9 @@ void ip_vs_random_dropentry(struct net *net)
                /*
                 *  Lock is actually needed in this loop.
                 */
-               ct_write_lock_bh(hash);
+               rcu_read_lock();
 
-               hlist_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
+               hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
                        if (cp->flags & IP_VS_CONN_F_TEMPLATE)
                                /* connection template */
                                continue;
@@ -1228,12 +1244,15 @@ void ip_vs_random_dropentry(struct net *net)
 
                        IP_VS_DBG(4, "del connection\n");
                        ip_vs_conn_expire_now(cp);
-                       if (cp->control) {
+                       cp_c = cp->control;
+                       /* cp->control is valid only with reference to cp */
+                       if (cp_c && __ip_vs_conn_get(cp)) {
                                IP_VS_DBG(4, "del conn template\n");
-                               ip_vs_conn_expire_now(cp->control);
+                               ip_vs_conn_expire_now(cp_c);
+                               __ip_vs_conn_put(cp);
                        }
                }
-               ct_write_unlock_bh(hash);
+               rcu_read_unlock();
        }
 }
 
@@ -1244,7 +1263,7 @@ void ip_vs_random_dropentry(struct net *net)
 static void ip_vs_conn_flush(struct net *net)
 {
        int idx;
-       struct ip_vs_conn *cp;
+       struct ip_vs_conn *cp, *cp_c;
        struct netns_ipvs *ipvs = net_ipvs(net);
 
 flush_again:
@@ -1252,19 +1271,22 @@ flush_again:
                /*
                 *  Lock is actually needed in this loop.
                 */
-               ct_write_lock_bh(idx);
+               rcu_read_lock();
 
-               hlist_for_each_entry(cp, &ip_vs_conn_tab[idx], c_list) {
+               hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[idx], c_list) {
                        if (!ip_vs_conn_net_eq(cp, net))
                                continue;
                        IP_VS_DBG(4, "del connection\n");
                        ip_vs_conn_expire_now(cp);
-                       if (cp->control) {
+                       cp_c = cp->control;
+                       /* cp->control is valid only with reference to cp */
+                       if (cp_c && __ip_vs_conn_get(cp)) {
                                IP_VS_DBG(4, "del conn template\n");
-                               ip_vs_conn_expire_now(cp->control);
+                               ip_vs_conn_expire_now(cp_c);
+                               __ip_vs_conn_put(cp);
                        }
                }
-               ct_write_unlock_bh(idx);
+               rcu_read_unlock();
        }
 
        /* the counter may be not NULL, because maybe some conn entries
@@ -1331,7 +1353,7 @@ int __init ip_vs_conn_init(void)
                INIT_HLIST_HEAD(&ip_vs_conn_tab[idx]);
 
        for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++)  {
-               rwlock_init(&__ip_vs_conntbl_lock_array[idx].l);
+               spin_lock_init(&__ip_vs_conntbl_lock_array[idx].l);
        }
 
        /* calculate the random value for connection hash */
@@ -1342,6 +1364,8 @@ int __init ip_vs_conn_init(void)
 
 void ip_vs_conn_cleanup(void)
 {
+       /* Wait all ip_vs_conn_rcu_free() callbacks to complete */
+       rcu_barrier();
        /* Release the empty cache */
        kmem_cache_destroy(ip_vs_conn_cachep);
        vfree(ip_vs_conn_tab);