1/* 2 * (C) 2012 Pablo Neira Ayuso <pablo@netfilter.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 as 6 * published by the Free Software Foundation (or any later at your option). 7 * 8 * This software has been sponsored by Vyatta Inc. <http://www.vyatta.com> 9 */ 10#include <linux/init.h> 11#include <linux/module.h> 12#include <linux/kernel.h> 13#include <linux/skbuff.h> 14#include <linux/netlink.h> 15#include <linux/rculist.h> 16#include <linux/slab.h> 17#include <linux/types.h> 18#include <linux/list.h> 19#include <linux/errno.h> 20#include <net/netlink.h> 21#include <net/sock.h> 22 23#include <net/netfilter/nf_conntrack_helper.h> 24#include <net/netfilter/nf_conntrack_expect.h> 25#include <net/netfilter/nf_conntrack_ecache.h> 26 27#include <linux/netfilter/nfnetlink.h> 28#include <linux/netfilter/nfnetlink_conntrack.h> 29#include <linux/netfilter/nfnetlink_cthelper.h> 30 31MODULE_LICENSE("GPL"); 32MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>"); 33MODULE_DESCRIPTION("nfnl_cthelper: User-space connection tracking helpers"); 34 35static int 36nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, 37 struct nf_conn *ct, enum ip_conntrack_info ctinfo) 38{ 39 const struct nf_conn_help *help; 40 struct nf_conntrack_helper *helper; 41 42 help = nfct_help(ct); 43 if (help == NULL) 44 return NF_DROP; 45 46 /* rcu_read_lock()ed by nf_hook_slow */ 47 helper = rcu_dereference(help->helper); 48 if (helper == NULL) 49 return NF_DROP; 50 51 /* This is an user-space helper not yet configured, skip. */ 52 if ((helper->flags & 53 (NF_CT_HELPER_F_USERSPACE | NF_CT_HELPER_F_CONFIGURED)) == 54 NF_CT_HELPER_F_USERSPACE) 55 return NF_ACCEPT; 56 57 /* If the user-space helper is not available, don't block traffic. */ 58 return NF_QUEUE_NR(helper->queue_num) | NF_VERDICT_FLAG_QUEUE_BYPASS; 59} 60 61static const struct nla_policy nfnl_cthelper_tuple_pol[NFCTH_TUPLE_MAX+1] = { 62 [NFCTH_TUPLE_L3PROTONUM] = { .type = NLA_U16, }, 63 [NFCTH_TUPLE_L4PROTONUM] = { .type = NLA_U8, }, 64}; 65 66static int 67nfnl_cthelper_parse_tuple(struct nf_conntrack_tuple *tuple, 68 const struct nlattr *attr) 69{ 70 int err; 71 struct nlattr *tb[NFCTH_TUPLE_MAX+1]; 72 73 err = nla_parse_nested(tb, NFCTH_TUPLE_MAX, attr, nfnl_cthelper_tuple_pol); 74 if (err < 0) 75 return err; 76 77 if (!tb[NFCTH_TUPLE_L3PROTONUM] || !tb[NFCTH_TUPLE_L4PROTONUM]) 78 return -EINVAL; 79 80 tuple->src.l3num = ntohs(nla_get_be16(tb[NFCTH_TUPLE_L3PROTONUM])); 81 tuple->dst.protonum = nla_get_u8(tb[NFCTH_TUPLE_L4PROTONUM]); 82 83 return 0; 84} 85 86static int 87nfnl_cthelper_from_nlattr(struct nlattr *attr, struct nf_conn *ct) 88{ 89 const struct nf_conn_help *help = nfct_help(ct); 90 91 if (attr == NULL) 92 return -EINVAL; 93 94 if (help->helper->data_len == 0) 95 return -EINVAL; 96 97 memcpy(&help->data, nla_data(attr), help->helper->data_len); 98 return 0; 99} 100 101static int 102nfnl_cthelper_to_nlattr(struct sk_buff *skb, const struct nf_conn *ct) 103{ 104 const struct nf_conn_help *help = nfct_help(ct); 105 106 if (help->helper->data_len && 107 nla_put(skb, CTA_HELP_INFO, help->helper->data_len, &help->data)) 108 goto nla_put_failure; 109 110 return 0; 111 112nla_put_failure: 113 return -ENOSPC; 114} 115 116static const struct nla_policy nfnl_cthelper_expect_pol[NFCTH_POLICY_MAX+1] = { 117 [NFCTH_POLICY_NAME] = { .type = NLA_NUL_STRING, 118 .len = NF_CT_HELPER_NAME_LEN-1 }, 119 [NFCTH_POLICY_EXPECT_MAX] = { .type = NLA_U32, }, 120 [NFCTH_POLICY_EXPECT_TIMEOUT] = { .type = NLA_U32, }, 121}; 122 123static int 124nfnl_cthelper_expect_policy(struct nf_conntrack_expect_policy *expect_policy, 125 const struct nlattr *attr) 126{ 127 int err; 128 struct nlattr *tb[NFCTH_POLICY_MAX+1]; 129 130 err = nla_parse_nested(tb, NFCTH_POLICY_MAX, attr, nfnl_cthelper_expect_pol); 131 if (err < 0) 132 return err; 133 134 if (!tb[NFCTH_POLICY_NAME] || 135 !tb[NFCTH_POLICY_EXPECT_MAX] || 136 !tb[NFCTH_POLICY_EXPECT_TIMEOUT]) 137 return -EINVAL; 138 139 strncpy(expect_policy->name, 140 nla_data(tb[NFCTH_POLICY_NAME]), NF_CT_HELPER_NAME_LEN); 141 expect_policy->max_expected = 142 ntohl(nla_get_be32(tb[NFCTH_POLICY_EXPECT_MAX])); 143 expect_policy->timeout = 144 ntohl(nla_get_be32(tb[NFCTH_POLICY_EXPECT_TIMEOUT])); 145 146 return 0; 147} 148 149static const struct nla_policy 150nfnl_cthelper_expect_policy_set[NFCTH_POLICY_SET_MAX+1] = { 151 [NFCTH_POLICY_SET_NUM] = { .type = NLA_U32, }, 152}; 153 154static int 155nfnl_cthelper_parse_expect_policy(struct nf_conntrack_helper *helper, 156 const struct nlattr *attr) 157{ 158 int i, ret; 159 struct nf_conntrack_expect_policy *expect_policy; 160 struct nlattr *tb[NFCTH_POLICY_SET_MAX+1]; 161 162 ret = nla_parse_nested(tb, NFCTH_POLICY_SET_MAX, attr, 163 nfnl_cthelper_expect_policy_set); 164 if (ret < 0) 165 return ret; 166 167 if (!tb[NFCTH_POLICY_SET_NUM]) 168 return -EINVAL; 169 170 helper->expect_class_max = 171 ntohl(nla_get_be32(tb[NFCTH_POLICY_SET_NUM])); 172 173 if (helper->expect_class_max != 0 && 174 helper->expect_class_max > NF_CT_MAX_EXPECT_CLASSES) 175 return -EOVERFLOW; 176 177 expect_policy = kzalloc(sizeof(struct nf_conntrack_expect_policy) * 178 helper->expect_class_max, GFP_KERNEL); 179 if (expect_policy == NULL) 180 return -ENOMEM; 181 182 for (i=0; i<helper->expect_class_max; i++) { 183 if (!tb[NFCTH_POLICY_SET+i]) 184 goto err; 185 186 ret = nfnl_cthelper_expect_policy(&expect_policy[i], 187 tb[NFCTH_POLICY_SET+i]); 188 if (ret < 0) 189 goto err; 190 } 191 helper->expect_policy = expect_policy; 192 return 0; 193err: 194 kfree(expect_policy); 195 return -EINVAL; 196} 197 198static int 199nfnl_cthelper_create(const struct nlattr * const tb[], 200 struct nf_conntrack_tuple *tuple) 201{ 202 struct nf_conntrack_helper *helper; 203 int ret; 204 205 if (!tb[NFCTH_TUPLE] || !tb[NFCTH_POLICY] || !tb[NFCTH_PRIV_DATA_LEN]) 206 return -EINVAL; 207 208 helper = kzalloc(sizeof(struct nf_conntrack_helper), GFP_KERNEL); 209 if (helper == NULL) 210 return -ENOMEM; 211 212 ret = nfnl_cthelper_parse_expect_policy(helper, tb[NFCTH_POLICY]); 213 if (ret < 0) 214 goto err; 215 216 strncpy(helper->name, nla_data(tb[NFCTH_NAME]), NF_CT_HELPER_NAME_LEN); 217 helper->data_len = ntohl(nla_get_be32(tb[NFCTH_PRIV_DATA_LEN])); 218 helper->flags |= NF_CT_HELPER_F_USERSPACE; 219 memcpy(&helper->tuple, tuple, sizeof(struct nf_conntrack_tuple)); 220 221 helper->me = THIS_MODULE; 222 helper->help = nfnl_userspace_cthelper; 223 helper->from_nlattr = nfnl_cthelper_from_nlattr; 224 helper->to_nlattr = nfnl_cthelper_to_nlattr; 225 226 /* Default to queue number zero, this can be updated at any time. */ 227 if (tb[NFCTH_QUEUE_NUM]) 228 helper->queue_num = ntohl(nla_get_be32(tb[NFCTH_QUEUE_NUM])); 229 230 if (tb[NFCTH_STATUS]) { 231 int status = ntohl(nla_get_be32(tb[NFCTH_STATUS])); 232 233 switch(status) { 234 case NFCT_HELPER_STATUS_ENABLED: 235 helper->flags |= NF_CT_HELPER_F_CONFIGURED; 236 break; 237 case NFCT_HELPER_STATUS_DISABLED: 238 helper->flags &= ~NF_CT_HELPER_F_CONFIGURED; 239 break; 240 } 241 } 242 243 ret = nf_conntrack_helper_register(helper); 244 if (ret < 0) 245 goto err; 246 247 return 0; 248err: 249 kfree(helper); 250 return ret; 251} 252 253static int 254nfnl_cthelper_update(const struct nlattr * const tb[], 255 struct nf_conntrack_helper *helper) 256{ 257 int ret; 258 259 if (tb[NFCTH_PRIV_DATA_LEN]) 260 return -EBUSY; 261 262 if (tb[NFCTH_POLICY]) { 263 ret = nfnl_cthelper_parse_expect_policy(helper, 264 tb[NFCTH_POLICY]); 265 if (ret < 0) 266 return ret; 267 } 268 if (tb[NFCTH_QUEUE_NUM]) 269 helper->queue_num = ntohl(nla_get_be32(tb[NFCTH_QUEUE_NUM])); 270 271 if (tb[NFCTH_STATUS]) { 272 int status = ntohl(nla_get_be32(tb[NFCTH_STATUS])); 273 274 switch(status) { 275 case NFCT_HELPER_STATUS_ENABLED: 276 helper->flags |= NF_CT_HELPER_F_CONFIGURED; 277 break; 278 case NFCT_HELPER_STATUS_DISABLED: 279 helper->flags &= ~NF_CT_HELPER_F_CONFIGURED; 280 break; 281 } 282 } 283 return 0; 284} 285 286static int 287nfnl_cthelper_new(struct sock *nfnl, struct sk_buff *skb, 288 const struct nlmsghdr *nlh, const struct nlattr * const tb[]) 289{ 290 const char *helper_name; 291 struct nf_conntrack_helper *cur, *helper = NULL; 292 struct nf_conntrack_tuple tuple; 293 int ret = 0, i; 294 295 if (!tb[NFCTH_NAME] || !tb[NFCTH_TUPLE]) 296 return -EINVAL; 297 298 helper_name = nla_data(tb[NFCTH_NAME]); 299 300 ret = nfnl_cthelper_parse_tuple(&tuple, tb[NFCTH_TUPLE]); 301 if (ret < 0) 302 return ret; 303 304 rcu_read_lock(); 305 for (i = 0; i < nf_ct_helper_hsize && !helper; i++) { 306 hlist_for_each_entry_rcu(cur, &nf_ct_helper_hash[i], hnode) { 307 308 /* skip non-userspace conntrack helpers. */ 309 if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) 310 continue; 311 312 if (strncmp(cur->name, helper_name, 313 NF_CT_HELPER_NAME_LEN) != 0) 314 continue; 315 316 if ((tuple.src.l3num != cur->tuple.src.l3num || 317 tuple.dst.protonum != cur->tuple.dst.protonum)) 318 continue; 319 320 if (nlh->nlmsg_flags & NLM_F_EXCL) { 321 ret = -EEXIST; 322 goto err; 323 } 324 helper = cur; 325 break; 326 } 327 } 328 rcu_read_unlock(); 329 330 if (helper == NULL) 331 ret = nfnl_cthelper_create(tb, &tuple); 332 else 333 ret = nfnl_cthelper_update(tb, helper); 334 335 return ret; 336err: 337 rcu_read_unlock(); 338 return ret; 339} 340 341static int 342nfnl_cthelper_dump_tuple(struct sk_buff *skb, 343 struct nf_conntrack_helper *helper) 344{ 345 struct nlattr *nest_parms; 346 347 nest_parms = nla_nest_start(skb, NFCTH_TUPLE | NLA_F_NESTED); 348 if (nest_parms == NULL) 349 goto nla_put_failure; 350 351 if (nla_put_be16(skb, NFCTH_TUPLE_L3PROTONUM, 352 htons(helper->tuple.src.l3num))) 353 goto nla_put_failure; 354 355 if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->tuple.dst.protonum)) 356 goto nla_put_failure; 357 358 nla_nest_end(skb, nest_parms); 359 return 0; 360 361nla_put_failure: 362 return -1; 363} 364 365static int 366nfnl_cthelper_dump_policy(struct sk_buff *skb, 367 struct nf_conntrack_helper *helper) 368{ 369 int i; 370 struct nlattr *nest_parms1, *nest_parms2; 371 372 nest_parms1 = nla_nest_start(skb, NFCTH_POLICY | NLA_F_NESTED); 373 if (nest_parms1 == NULL) 374 goto nla_put_failure; 375 376 if (nla_put_be32(skb, NFCTH_POLICY_SET_NUM, 377 htonl(helper->expect_class_max))) 378 goto nla_put_failure; 379 380 for (i=0; i<helper->expect_class_max; i++) { 381 nest_parms2 = nla_nest_start(skb, 382 (NFCTH_POLICY_SET+i) | NLA_F_NESTED); 383 if (nest_parms2 == NULL) 384 goto nla_put_failure; 385 386 if (nla_put_string(skb, NFCTH_POLICY_NAME, 387 helper->expect_policy[i].name)) 388 goto nla_put_failure; 389 390 if (nla_put_be32(skb, NFCTH_POLICY_EXPECT_MAX, 391 htonl(helper->expect_policy[i].max_expected))) 392 goto nla_put_failure; 393 394 if (nla_put_be32(skb, NFCTH_POLICY_EXPECT_TIMEOUT, 395 htonl(helper->expect_policy[i].timeout))) 396 goto nla_put_failure; 397 398 nla_nest_end(skb, nest_parms2); 399 } 400 nla_nest_end(skb, nest_parms1); 401 return 0; 402 403nla_put_failure: 404 return -1; 405} 406 407static int 408nfnl_cthelper_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, 409 int event, struct nf_conntrack_helper *helper) 410{ 411 struct nlmsghdr *nlh; 412 struct nfgenmsg *nfmsg; 413 unsigned int flags = portid ? NLM_F_MULTI : 0; 414 int status; 415 416 event |= NFNL_SUBSYS_CTHELPER << 8; 417 nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nfmsg), flags); 418 if (nlh == NULL) 419 goto nlmsg_failure; 420 421 nfmsg = nlmsg_data(nlh); 422 nfmsg->nfgen_family = AF_UNSPEC; 423 nfmsg->version = NFNETLINK_V0; 424 nfmsg->res_id = 0; 425 426 if (nla_put_string(skb, NFCTH_NAME, helper->name)) 427 goto nla_put_failure; 428 429 if (nla_put_be32(skb, NFCTH_QUEUE_NUM, htonl(helper->queue_num))) 430 goto nla_put_failure; 431 432 if (nfnl_cthelper_dump_tuple(skb, helper) < 0) 433 goto nla_put_failure; 434 435 if (nfnl_cthelper_dump_policy(skb, helper) < 0) 436 goto nla_put_failure; 437 438 if (nla_put_be32(skb, NFCTH_PRIV_DATA_LEN, htonl(helper->data_len))) 439 goto nla_put_failure; 440 441 if (helper->flags & NF_CT_HELPER_F_CONFIGURED) 442 status = NFCT_HELPER_STATUS_ENABLED; 443 else 444 status = NFCT_HELPER_STATUS_DISABLED; 445 446 if (nla_put_be32(skb, NFCTH_STATUS, htonl(status))) 447 goto nla_put_failure; 448 449 nlmsg_end(skb, nlh); 450 return skb->len; 451 452nlmsg_failure: 453nla_put_failure: 454 nlmsg_cancel(skb, nlh); 455 return -1; 456} 457 458static int 459nfnl_cthelper_dump_table(struct sk_buff *skb, struct netlink_callback *cb) 460{ 461 struct nf_conntrack_helper *cur, *last; 462 463 rcu_read_lock(); 464 last = (struct nf_conntrack_helper *)cb->args[1]; 465 for (; cb->args[0] < nf_ct_helper_hsize; cb->args[0]++) { 466restart: 467 hlist_for_each_entry_rcu(cur, 468 &nf_ct_helper_hash[cb->args[0]], hnode) { 469 470 /* skip non-userspace conntrack helpers. */ 471 if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) 472 continue; 473 474 if (cb->args[1]) { 475 if (cur != last) 476 continue; 477 cb->args[1] = 0; 478 } 479 if (nfnl_cthelper_fill_info(skb, 480 NETLINK_CB(cb->skb).portid, 481 cb->nlh->nlmsg_seq, 482 NFNL_MSG_TYPE(cb->nlh->nlmsg_type), 483 NFNL_MSG_CTHELPER_NEW, cur) < 0) { 484 cb->args[1] = (unsigned long)cur; 485 goto out; 486 } 487 } 488 } 489 if (cb->args[1]) { 490 cb->args[1] = 0; 491 goto restart; 492 } 493out: 494 rcu_read_unlock(); 495 return skb->len; 496} 497 498static int 499nfnl_cthelper_get(struct sock *nfnl, struct sk_buff *skb, 500 const struct nlmsghdr *nlh, const struct nlattr * const tb[]) 501{ 502 int ret = -ENOENT, i; 503 struct nf_conntrack_helper *cur; 504 struct sk_buff *skb2; 505 char *helper_name = NULL; 506 struct nf_conntrack_tuple tuple; 507 bool tuple_set = false; 508 509 if (nlh->nlmsg_flags & NLM_F_DUMP) { 510 struct netlink_dump_control c = { 511 .dump = nfnl_cthelper_dump_table, 512 }; 513 return netlink_dump_start(nfnl, skb, nlh, &c); 514 } 515 516 if (tb[NFCTH_NAME]) 517 helper_name = nla_data(tb[NFCTH_NAME]); 518 519 if (tb[NFCTH_TUPLE]) { 520 ret = nfnl_cthelper_parse_tuple(&tuple, tb[NFCTH_TUPLE]); 521 if (ret < 0) 522 return ret; 523 524 tuple_set = true; 525 } 526 527 for (i = 0; i < nf_ct_helper_hsize; i++) { 528 hlist_for_each_entry_rcu(cur, &nf_ct_helper_hash[i], hnode) { 529 530 /* skip non-userspace conntrack helpers. */ 531 if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) 532 continue; 533 534 if (helper_name && strncmp(cur->name, helper_name, 535 NF_CT_HELPER_NAME_LEN) != 0) { 536 continue; 537 } 538 if (tuple_set && 539 (tuple.src.l3num != cur->tuple.src.l3num || 540 tuple.dst.protonum != cur->tuple.dst.protonum)) 541 continue; 542 543 skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 544 if (skb2 == NULL) { 545 ret = -ENOMEM; 546 break; 547 } 548 549 ret = nfnl_cthelper_fill_info(skb2, NETLINK_CB(skb).portid, 550 nlh->nlmsg_seq, 551 NFNL_MSG_TYPE(nlh->nlmsg_type), 552 NFNL_MSG_CTHELPER_NEW, cur); 553 if (ret <= 0) { 554 kfree_skb(skb2); 555 break; 556 } 557 558 ret = netlink_unicast(nfnl, skb2, NETLINK_CB(skb).portid, 559 MSG_DONTWAIT); 560 if (ret > 0) 561 ret = 0; 562 563 /* this avoids a loop in nfnetlink. */ 564 return ret == -EAGAIN ? -ENOBUFS : ret; 565 } 566 } 567 return ret; 568} 569 570static int 571nfnl_cthelper_del(struct sock *nfnl, struct sk_buff *skb, 572 const struct nlmsghdr *nlh, const struct nlattr * const tb[]) 573{ 574 char *helper_name = NULL; 575 struct nf_conntrack_helper *cur; 576 struct hlist_node *tmp; 577 struct nf_conntrack_tuple tuple; 578 bool tuple_set = false, found = false; 579 int i, j = 0, ret; 580 581 if (tb[NFCTH_NAME]) 582 helper_name = nla_data(tb[NFCTH_NAME]); 583 584 if (tb[NFCTH_TUPLE]) { 585 ret = nfnl_cthelper_parse_tuple(&tuple, tb[NFCTH_TUPLE]); 586 if (ret < 0) 587 return ret; 588 589 tuple_set = true; 590 } 591 592 for (i = 0; i < nf_ct_helper_hsize; i++) { 593 hlist_for_each_entry_safe(cur, tmp, &nf_ct_helper_hash[i], 594 hnode) { 595 /* skip non-userspace conntrack helpers. */ 596 if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) 597 continue; 598 599 j++; 600 601 if (helper_name && strncmp(cur->name, helper_name, 602 NF_CT_HELPER_NAME_LEN) != 0) { 603 continue; 604 } 605 if (tuple_set && 606 (tuple.src.l3num != cur->tuple.src.l3num || 607 tuple.dst.protonum != cur->tuple.dst.protonum)) 608 continue; 609 610 found = true; 611 nf_conntrack_helper_unregister(cur); 612 } 613 } 614 /* Make sure we return success if we flush and there is no helpers */ 615 return (found || j == 0) ? 0 : -ENOENT; 616} 617 618static const struct nla_policy nfnl_cthelper_policy[NFCTH_MAX+1] = { 619 [NFCTH_NAME] = { .type = NLA_NUL_STRING, 620 .len = NF_CT_HELPER_NAME_LEN-1 }, 621 [NFCTH_QUEUE_NUM] = { .type = NLA_U32, }, 622}; 623 624static const struct nfnl_callback nfnl_cthelper_cb[NFNL_MSG_CTHELPER_MAX] = { 625 [NFNL_MSG_CTHELPER_NEW] = { .call = nfnl_cthelper_new, 626 .attr_count = NFCTH_MAX, 627 .policy = nfnl_cthelper_policy }, 628 [NFNL_MSG_CTHELPER_GET] = { .call = nfnl_cthelper_get, 629 .attr_count = NFCTH_MAX, 630 .policy = nfnl_cthelper_policy }, 631 [NFNL_MSG_CTHELPER_DEL] = { .call = nfnl_cthelper_del, 632 .attr_count = NFCTH_MAX, 633 .policy = nfnl_cthelper_policy }, 634}; 635 636static const struct nfnetlink_subsystem nfnl_cthelper_subsys = { 637 .name = "cthelper", 638 .subsys_id = NFNL_SUBSYS_CTHELPER, 639 .cb_count = NFNL_MSG_CTHELPER_MAX, 640 .cb = nfnl_cthelper_cb, 641}; 642 643MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_CTHELPER); 644 645static int __init nfnl_cthelper_init(void) 646{ 647 int ret; 648 649 ret = nfnetlink_subsys_register(&nfnl_cthelper_subsys); 650 if (ret < 0) { 651 pr_err("nfnl_cthelper: cannot register with nfnetlink.\n"); 652 goto err_out; 653 } 654 return 0; 655err_out: 656 return ret; 657} 658 659static void __exit nfnl_cthelper_exit(void) 660{ 661 struct nf_conntrack_helper *cur; 662 struct hlist_node *tmp; 663 int i; 664 665 nfnetlink_subsys_unregister(&nfnl_cthelper_subsys); 666 667 for (i=0; i<nf_ct_helper_hsize; i++) { 668 hlist_for_each_entry_safe(cur, tmp, &nf_ct_helper_hash[i], 669 hnode) { 670 /* skip non-userspace conntrack helpers. */ 671 if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) 672 continue; 673 674 nf_conntrack_helper_unregister(cur); 675 } 676 } 677} 678 679module_init(nfnl_cthelper_init); 680module_exit(nfnl_cthelper_exit); 681