Add find_tree_node() helper that fetches a matching rbtree node. This is used by followup patch to optionally search the tree again while preventing concurrent updates via tree lock. Signed-off-by: Florian Westphal --- net/netfilter/nf_conncount.c | 96 ++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index d3866393b4f5..8d9c24b69dc9 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -488,23 +488,15 @@ insert_tree(struct net *net, return count; } -static unsigned int -count_tree(struct net *net, - const struct sk_buff *skb, - u16 l3num, - struct nf_conncount_data *data, - const u32 *key) +static struct nf_conncount_rb * +find_tree_node(struct nf_conncount_root *root, struct nf_conncount_data *data, + const u32 *key) { - struct nf_conncount_root *root; struct rb_node *parent; - struct nf_conncount_rb *rbconn; - unsigned int hash; - - hash = jhash2(key, data->keylen, data->initval) % CONNCOUNT_SLOTS; - root = &data->root[hash]; parent = rcu_dereference_raw(root->root.rb_node); while (parent) { + struct nf_conncount_rb *rbconn; int diff; rbconn = rb_entry(parent, struct nf_conncount_rb, node); @@ -515,40 +507,68 @@ count_tree(struct net *net, } else if (diff > 0) { parent = rcu_dereference_raw(parent->rb_right); } else { - int ret; + return rbconn; + } + } - if (!skb) { - nf_conncount_gc_list(net, &rbconn->list); - return rbconn->list.count; - } + return ERR_PTR(-ENOENT); +} - spin_lock_bh(&rbconn->list.list_lock); - /* Node might be about to be free'd. - * We need to defer to insert_tree() in this case. - */ - if (rbconn->list.count == 0) { - spin_unlock_bh(&rbconn->list.list_lock); - break; - } +static unsigned int +count_tree(struct net *net, + const struct sk_buff *skb, + u16 l3num, + struct nf_conncount_data *data, + const u32 *key) +{ + struct nf_conncount_root *root; + struct nf_conncount_rb *rbconn; + unsigned int hash; + int ret; - /* same source network -> be counted! */ - ret = __nf_conncount_add(net, skb, l3num, &rbconn->list); - spin_unlock_bh(&rbconn->list.list_lock); - if (ret && ret != -EEXIST) { - return 0; /* hotdrop */ - } else { - /* -EEXIST means add was skipped, update the list */ - if (ret == -EEXIST) - nf_conncount_gc_list(net, &rbconn->list); - return rbconn->list.count; - } + hash = jhash2(key, data->keylen, data->initval) % CONNCOUNT_SLOTS; + root = &data->root[hash]; + + rbconn = find_tree_node(root, data, key); + if (IS_ERR(rbconn)) { + if (PTR_ERR(rbconn) == -ENOENT) { + if (!skb) + return 0; + + return insert_tree(net, skb, l3num, data, hash, key); } + DEBUG_NET_WARN_ON_ONCE(IS_ERR(rbconn)); } - if (!skb) + DEBUG_NET_WARN_ON_ONCE(IS_ERR_OR_NULL(rbconn)); + if (IS_ERR_OR_NULL(rbconn)) return 0; - return insert_tree(net, skb, l3num, data, hash, key); + if (!skb) { + nf_conncount_gc_list(net, &rbconn->list); + return rbconn->list.count; + } + + spin_lock_bh(&rbconn->list.list_lock); + /* Node might be about to be free'd. + * We need to defer to insert_tree() in this case. + */ + if (rbconn->list.count == 0) { + spin_unlock_bh(&rbconn->list.list_lock); + return insert_tree(net, skb, l3num, data, hash, key); + } + + /* same source network -> be counted! */ + ret = __nf_conncount_add(net, skb, l3num, &rbconn->list); + spin_unlock_bh(&rbconn->list.list_lock); + + if (ret && ret != -EEXIST) + return 0; /* hotdrop */ + /* -EEXIST means add was skipped, update the list */ + if (ret == -EEXIST) + nf_conncount_gc_list(net, &rbconn->list); + + return rbconn->list.count; } static void tree_gc_worker(struct work_struct *work) -- 2.53.0