aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Westphal <fw@strlen.de>2018-12-27 19:24:44 -0500
committerPablo Neira Ayuso <pablo@netfilter.org>2018-12-28 20:45:19 -0500
commitf7fcc98dfc2d136722007fec0debbed761679b94 (patch)
treeabac7ea27b0b8414e427da2ed4995d1c14a85b80
parent4cd273bb91b3001f623f516ec726c49754571b1a (diff)
netfilter: nf_conncount: split gc in two phases
The lockless workqueue garbage collector can race with packet path garbage collector to delete list nodes, as it calls tree_nodes_free() with the addresses of nodes that might have been free'd already from another cpu. To fix this, split gc into two phases. One phase to perform gc on the connections: From a locking perspective, this is the same as count_tree(): we hold rcu lock, but we do not change the tree, we only change the nodes' contents. The second phase acquires the tree lock and reaps empty nodes. This avoids a race condition of the garbage collection vs. packet path: If a node has been free'd already, the second phase won't find it anymore. This second phase is, from locking perspective, same as insert_tree(). The former only modifies nodes (list content, count), latter modifies the tree itself (rb_erase or rb_insert). Fixes: 5c789e131cbb9 ("netfilter: nf_conncount: Add list lock and gc worker, and RCU for init tree search") Reviewed-by: Shawn Bohrer <sbohrer@cloudflare.com> Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
-rw-r--r--net/netfilter/nf_conncount.c22
1 files changed, 19 insertions, 3 deletions
diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c
index 8bb4ed85c262..753132e4afa8 100644
--- a/net/netfilter/nf_conncount.c
+++ b/net/netfilter/nf_conncount.c
@@ -500,16 +500,32 @@ static void tree_gc_worker(struct work_struct *work)
500 for (node = rb_first(root); node != NULL; node = rb_next(node)) { 500 for (node = rb_first(root); node != NULL; node = rb_next(node)) {
501 rbconn = rb_entry(node, struct nf_conncount_rb, node); 501 rbconn = rb_entry(node, struct nf_conncount_rb, node);
502 if (nf_conncount_gc_list(data->net, &rbconn->list)) 502 if (nf_conncount_gc_list(data->net, &rbconn->list))
503 gc_nodes[gc_count++] = rbconn; 503 gc_count++;
504 } 504 }
505 rcu_read_unlock(); 505 rcu_read_unlock();
506 506
507 spin_lock_bh(&nf_conncount_locks[tree]); 507 spin_lock_bh(&nf_conncount_locks[tree]);
508 if (gc_count < ARRAY_SIZE(gc_nodes))
509 goto next; /* do not bother */
508 510
509 if (gc_count) { 511 gc_count = 0;
510 tree_nodes_free(root, gc_nodes, gc_count); 512 node = rb_first(root);
513 while (node != NULL) {
514 rbconn = rb_entry(node, struct nf_conncount_rb, node);
515 node = rb_next(node);
516
517 if (rbconn->list.count > 0)
518 continue;
519
520 gc_nodes[gc_count++] = rbconn;
521 if (gc_count >= ARRAY_SIZE(gc_nodes)) {
522 tree_nodes_free(root, gc_nodes, gc_count);
523 gc_count = 0;
524 }
511 } 525 }
512 526
527 tree_nodes_free(root, gc_nodes, gc_count);
528next:
513 clear_bit(tree, data->pending_trees); 529 clear_bit(tree, data->pending_trees);
514 530
515 next_tree = (tree + 1) % CONNCOUNT_SLOTS; 531 next_tree = (tree + 1) % CONNCOUNT_SLOTS;