Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: rework user-space expectation helper support

This partially reworks bc01befdcf3e40979eb518085a075cbf0aacede0
which added userspace expectation support.

This patch removes the nf_ct_userspace_expect_list since now we
force to use the new iptables CT target feature to add the helper
extension for conntracks that have attached expectations from
userspace.

A new version of the proof-of-concept code to implement userspace
helpers from userspace is available at:

http://people.netfilter.org/pablo/userspace-conntrack-helpers/nf-ftp-helper-POC.tar.bz2

This patch also modifies the CT target to allow to set the
conntrack's userspace helper status flags. This flag is used
to tell the conntrack system to explicitly allocate the helper
extension.

This helper extension is useful to link the userspace expectations
with the master conntrack that is being tracked from one userspace
helper.

This feature fixes a problem in the current approach of the
userspace helper support. Basically, if the master conntrack that
has got a userspace expectation vanishes, the expectations point to
one invalid memory address. Thus, triggering an oops in the
expectation deletion event path.

I decided not to add a new revision of the CT target because
I only needed to add a new flag for it. I'll document in this
issue in the iptables manpage. I have also changed the return
value from EINVAL to EOPNOTSUPP if one flag not supported is
specified. Thus, in the future adding new features that only
require a new flag can be added without a new revision.

There is no official code using this in userspace (apart from
the proof-of-concept) that uses this infrastructure but there
will be some by beginning 2012.

Reported-by: Sam Roberts <vieuxtech@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

+48 -48
+4
include/linux/netfilter/nf_conntrack_common.h
··· 83 83 /* Conntrack is a fake untracked entry */ 84 84 IPS_UNTRACKED_BIT = 12, 85 85 IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT), 86 + 87 + /* Conntrack has a userspace helper. */ 88 + IPS_USERSPACE_HELPER_BIT = 13, 89 + IPS_USERSPACE_HELPER = (1 << IPS_USERSPACE_HELPER_BIT), 86 90 }; 87 91 88 92 /* Connection tracking event types */
+2 -1
include/linux/netfilter/xt_CT.h
··· 3 3 4 4 #include <linux/types.h> 5 5 6 - #define XT_CT_NOTRACK 0x1 6 + #define XT_CT_NOTRACK 0x1 7 + #define XT_CT_USERSPACE_HELPER 0x2 7 8 8 9 struct xt_ct_target_info { 9 10 __u16 flags;
-1
include/net/netfilter/nf_conntrack_expect.h
··· 91 91 92 92 void nf_ct_remove_expectations(struct nf_conn *ct); 93 93 void nf_ct_unexpect_related(struct nf_conntrack_expect *exp); 94 - void nf_ct_remove_userspace_expectations(void); 95 94 96 95 /* Allocate space for an expectation: this is mandatory before calling 97 96 nf_ct_expect_related. You will have to call put afterwards. */
+21 -42
net/netfilter/nf_conntrack_expect.c
··· 38 38 39 39 static struct kmem_cache *nf_ct_expect_cachep __read_mostly; 40 40 41 - static HLIST_HEAD(nf_ct_userspace_expect_list); 42 - 43 41 /* nf_conntrack_expect helper functions */ 44 42 void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, 45 43 u32 pid, int report) ··· 45 47 struct nf_conn_help *master_help = nfct_help(exp->master); 46 48 struct net *net = nf_ct_exp_net(exp); 47 49 50 + NF_CT_ASSERT(master_help); 48 51 NF_CT_ASSERT(!timer_pending(&exp->timeout)); 49 52 50 53 hlist_del_rcu(&exp->hnode); 51 54 net->ct.expect_count--; 52 55 53 56 hlist_del(&exp->lnode); 54 - if (!(exp->flags & NF_CT_EXPECT_USERSPACE)) 55 - master_help->expecting[exp->class]--; 57 + master_help->expecting[exp->class]--; 56 58 57 59 nf_ct_expect_event_report(IPEXP_DESTROY, exp, pid, report); 58 60 nf_ct_expect_put(exp); ··· 312 314 } 313 315 EXPORT_SYMBOL_GPL(nf_ct_expect_put); 314 316 315 - static void nf_ct_expect_insert(struct nf_conntrack_expect *exp) 317 + static int nf_ct_expect_insert(struct nf_conntrack_expect *exp) 316 318 { 317 319 struct nf_conn_help *master_help = nfct_help(exp->master); 320 + struct nf_conntrack_helper *helper; 318 321 struct net *net = nf_ct_exp_net(exp); 319 - const struct nf_conntrack_expect_policy *p; 320 322 unsigned int h = nf_ct_expect_dst_hash(&exp->tuple); 321 323 322 324 /* two references : one for hash insert, one for the timer */ 323 325 atomic_add(2, &exp->use); 324 326 325 - if (master_help) { 326 - hlist_add_head(&exp->lnode, &master_help->expectations); 327 - master_help->expecting[exp->class]++; 328 - } else if (exp->flags & NF_CT_EXPECT_USERSPACE) 329 - hlist_add_head(&exp->lnode, &nf_ct_userspace_expect_list); 327 + hlist_add_head(&exp->lnode, &master_help->expectations); 328 + master_help->expecting[exp->class]++; 330 329 331 330 hlist_add_head_rcu(&exp->hnode, &net->ct.expect_hash[h]); 332 331 net->ct.expect_count++; 333 332 334 333 setup_timer(&exp->timeout, nf_ct_expectation_timed_out, 335 334 (unsigned long)exp); 336 - if (master_help) { 337 - p = &rcu_dereference_protected( 338 - master_help->helper, 339 - lockdep_is_held(&nf_conntrack_lock) 340 - )->expect_policy[exp->class]; 341 - exp->timeout.expires = jiffies + p->timeout * HZ; 335 + helper = rcu_dereference_protected(master_help->helper, 336 + lockdep_is_held(&nf_conntrack_lock)); 337 + if (helper) { 338 + exp->timeout.expires = jiffies + 339 + helper->expect_policy[exp->class].timeout * HZ; 342 340 } 343 341 add_timer(&exp->timeout); 344 342 345 343 NF_CT_STAT_INC(net, expect_create); 344 + return 0; 346 345 } 347 346 348 347 /* Race with expectations being used means we could have none to find; OK. */ ··· 384 389 struct nf_conntrack_expect *i; 385 390 struct nf_conn *master = expect->master; 386 391 struct nf_conn_help *master_help = nfct_help(master); 392 + struct nf_conntrack_helper *helper; 387 393 struct net *net = nf_ct_exp_net(expect); 388 394 struct hlist_node *n; 389 395 unsigned int h; 390 396 int ret = 1; 391 397 392 - /* Don't allow expectations created from kernel-space with no helper */ 393 - if (!(expect->flags & NF_CT_EXPECT_USERSPACE) && 394 - (!master_help || (master_help && !master_help->helper))) { 398 + if (!master_help) { 395 399 ret = -ESHUTDOWN; 396 400 goto out; 397 401 } ··· 408 414 } 409 415 } 410 416 /* Will be over limit? */ 411 - if (master_help) { 412 - p = &rcu_dereference_protected( 413 - master_help->helper, 414 - lockdep_is_held(&nf_conntrack_lock) 415 - )->expect_policy[expect->class]; 417 + helper = rcu_dereference_protected(master_help->helper, 418 + lockdep_is_held(&nf_conntrack_lock)); 419 + if (helper) { 420 + p = &helper->expect_policy[expect->class]; 416 421 if (p->max_expected && 417 422 master_help->expecting[expect->class] >= p->max_expected) { 418 423 evict_oldest_expect(master, expect); ··· 443 450 if (ret <= 0) 444 451 goto out; 445 452 446 - ret = 0; 447 - nf_ct_expect_insert(expect); 453 + ret = nf_ct_expect_insert(expect); 454 + if (ret < 0) 455 + goto out; 448 456 spin_unlock_bh(&nf_conntrack_lock); 449 457 nf_ct_expect_event_report(IPEXP_NEW, expect, pid, report); 450 458 return ret; ··· 454 460 return ret; 455 461 } 456 462 EXPORT_SYMBOL_GPL(nf_ct_expect_related_report); 457 - 458 - void nf_ct_remove_userspace_expectations(void) 459 - { 460 - struct nf_conntrack_expect *exp; 461 - struct hlist_node *n, *next; 462 - 463 - hlist_for_each_entry_safe(exp, n, next, 464 - &nf_ct_userspace_expect_list, lnode) { 465 - if (del_timer(&exp->timeout)) { 466 - nf_ct_unlink_expect(exp); 467 - nf_ct_expect_put(exp); 468 - } 469 - } 470 - } 471 - EXPORT_SYMBOL_GPL(nf_ct_remove_userspace_expectations); 472 463 473 464 #ifdef CONFIG_PROC_FS 474 465 struct ct_expect_iter_state {
+12
net/netfilter/nf_conntrack_helper.c
··· 121 121 int ret = 0; 122 122 123 123 if (tmpl != NULL) { 124 + /* we've got a userspace helper. */ 125 + if (tmpl->status & IPS_USERSPACE_HELPER) { 126 + help = nf_ct_helper_ext_add(ct, flags); 127 + if (help == NULL) { 128 + ret = -ENOMEM; 129 + goto out; 130 + } 131 + rcu_assign_pointer(help->helper, NULL); 132 + __set_bit(IPS_USERSPACE_HELPER_BIT, &ct->status); 133 + ret = 0; 134 + goto out; 135 + } 124 136 help = nfct_help(tmpl); 125 137 if (help != NULL) 126 138 helper = help->helper;
+4 -1
net/netfilter/nf_conntrack_netlink.c
··· 2040 2040 } 2041 2041 help = nfct_help(ct); 2042 2042 if (!help) { 2043 + err = -EOPNOTSUPP; 2044 + goto out; 2045 + } 2046 + if (test_bit(IPS_USERSPACE_HELPER_BIT, &ct->status)) { 2043 2047 if (!cda[CTA_EXPECT_TIMEOUT]) { 2044 2048 err = -EINVAL; 2045 2049 goto out; ··· 2268 2264 { 2269 2265 pr_info("ctnetlink: unregistering from nfnetlink.\n"); 2270 2266 2271 - nf_ct_remove_userspace_expectations(); 2272 2267 unregister_pernet_subsys(&ctnetlink_net_ops); 2273 2268 nfnetlink_subsys_unregister(&ctnl_exp_subsys); 2274 2269 nfnetlink_subsys_unregister(&ctnl_subsys);
+5 -3
net/netfilter/xt_CT.c
··· 62 62 int ret = 0; 63 63 u8 proto; 64 64 65 - if (info->flags & ~XT_CT_NOTRACK) 66 - return -EINVAL; 65 + if (info->flags & ~(XT_CT_NOTRACK | XT_CT_USERSPACE_HELPER)) 66 + return -EOPNOTSUPP; 67 67 68 68 if (info->flags & XT_CT_NOTRACK) { 69 69 ct = nf_ct_untracked_get(); ··· 92 92 GFP_KERNEL)) 93 93 goto err3; 94 94 95 - if (info->helper[0]) { 95 + if (info->flags & XT_CT_USERSPACE_HELPER) { 96 + __set_bit(IPS_USERSPACE_HELPER_BIT, &ct->status); 97 + } else if (info->helper[0]) { 96 98 ret = -ENOENT; 97 99 proto = xt_ct_find_proto(par); 98 100 if (!proto) {