nf_conntrack_helper.c revision 6714cf5465d2803a21c6a46c1ea747795a8889fa
1/* Helper handling for netfilter. */ 2 3/* (C) 1999-2001 Paul `Rusty' Russell 4 * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org> 5 * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 */ 11 12#include <linux/types.h> 13#include <linux/netfilter.h> 14#include <linux/module.h> 15#include <linux/skbuff.h> 16#include <linux/vmalloc.h> 17#include <linux/stddef.h> 18#include <linux/random.h> 19#include <linux/err.h> 20#include <linux/kernel.h> 21#include <linux/netdevice.h> 22#include <linux/rculist.h> 23#include <linux/rtnetlink.h> 24 25#include <net/netfilter/nf_conntrack.h> 26#include <net/netfilter/nf_conntrack_l3proto.h> 27#include <net/netfilter/nf_conntrack_l4proto.h> 28#include <net/netfilter/nf_conntrack_helper.h> 29#include <net/netfilter/nf_conntrack_core.h> 30#include <net/netfilter/nf_conntrack_extend.h> 31 32static DEFINE_MUTEX(nf_ct_helper_mutex); 33static struct hlist_head *nf_ct_helper_hash __read_mostly; 34static unsigned int nf_ct_helper_hsize __read_mostly; 35static unsigned int nf_ct_helper_count __read_mostly; 36 37static bool nf_ct_auto_assign_helper __read_mostly = true; 38module_param_named(nf_conntrack_helper, nf_ct_auto_assign_helper, bool, 0644); 39MODULE_PARM_DESC(nf_conntrack_helper, 40 "Enable automatic conntrack helper assignment (default 1)"); 41 42#ifdef CONFIG_SYSCTL 43static struct ctl_table helper_sysctl_table[] = { 44 { 45 .procname = "nf_conntrack_helper", 46 .data = &init_net.ct.sysctl_auto_assign_helper, 47 .maxlen = sizeof(unsigned int), 48 .mode = 0644, 49 .proc_handler = proc_dointvec, 50 }, 51 {} 52}; 53 54static int nf_conntrack_helper_init_sysctl(struct net *net) 55{ 56 struct ctl_table *table; 57 58 table = kmemdup(helper_sysctl_table, sizeof(helper_sysctl_table), 59 GFP_KERNEL); 60 if (!table) 61 goto out; 62 63 table[0].data = &net->ct.sysctl_auto_assign_helper; 64 65 net->ct.helper_sysctl_header = 66 register_net_sysctl(net, "net/netfilter", table); 67 68 if (!net->ct.helper_sysctl_header) { 69 pr_err("nf_conntrack_helper: can't register to sysctl.\n"); 70 goto out_register; 71 } 72 return 0; 73 74out_register: 75 kfree(table); 76out: 77 return -ENOMEM; 78} 79 80static void nf_conntrack_helper_fini_sysctl(struct net *net) 81{ 82 struct ctl_table *table; 83 84 table = net->ct.helper_sysctl_header->ctl_table_arg; 85 unregister_net_sysctl_table(net->ct.helper_sysctl_header); 86 kfree(table); 87} 88#else 89static int nf_conntrack_helper_init_sysctl(struct net *net) 90{ 91 return 0; 92} 93 94static void nf_conntrack_helper_fini_sysctl(struct net *net) 95{ 96} 97#endif /* CONFIG_SYSCTL */ 98 99/* Stupid hash, but collision free for the default registrations of the 100 * helpers currently in the kernel. */ 101static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple) 102{ 103 return (((tuple->src.l3num << 8) | tuple->dst.protonum) ^ 104 (__force __u16)tuple->src.u.all) % nf_ct_helper_hsize; 105} 106 107static struct nf_conntrack_helper * 108__nf_ct_helper_find(const struct nf_conntrack_tuple *tuple) 109{ 110 struct nf_conntrack_helper *helper; 111 struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; 112 struct hlist_node *n; 113 unsigned int h; 114 115 if (!nf_ct_helper_count) 116 return NULL; 117 118 h = helper_hash(tuple); 119 hlist_for_each_entry_rcu(helper, n, &nf_ct_helper_hash[h], hnode) { 120 if (nf_ct_tuple_src_mask_cmp(tuple, &helper->tuple, &mask)) 121 return helper; 122 } 123 return NULL; 124} 125 126struct nf_conntrack_helper * 127__nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum) 128{ 129 struct nf_conntrack_helper *h; 130 struct hlist_node *n; 131 unsigned int i; 132 133 for (i = 0; i < nf_ct_helper_hsize; i++) { 134 hlist_for_each_entry_rcu(h, n, &nf_ct_helper_hash[i], hnode) { 135 if (!strcmp(h->name, name) && 136 h->tuple.src.l3num == l3num && 137 h->tuple.dst.protonum == protonum) 138 return h; 139 } 140 } 141 return NULL; 142} 143EXPORT_SYMBOL_GPL(__nf_conntrack_helper_find); 144 145struct nf_conntrack_helper * 146nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum) 147{ 148 struct nf_conntrack_helper *h; 149 150 h = __nf_conntrack_helper_find(name, l3num, protonum); 151#ifdef CONFIG_MODULES 152 if (h == NULL) { 153 if (request_module("nfct-helper-%s", name) == 0) 154 h = __nf_conntrack_helper_find(name, l3num, protonum); 155 } 156#endif 157 if (h != NULL && !try_module_get(h->me)) 158 h = NULL; 159 160 return h; 161} 162EXPORT_SYMBOL_GPL(nf_conntrack_helper_try_module_get); 163 164struct nf_conn_help *nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp) 165{ 166 struct nf_conn_help *help; 167 168 help = nf_ct_ext_add(ct, NF_CT_EXT_HELPER, gfp); 169 if (help) 170 INIT_HLIST_HEAD(&help->expectations); 171 else 172 pr_debug("failed to add helper extension area"); 173 return help; 174} 175EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add); 176 177int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl, 178 gfp_t flags) 179{ 180 struct nf_conntrack_helper *helper = NULL; 181 struct nf_conn_help *help; 182 struct net *net = nf_ct_net(ct); 183 int ret = 0; 184 185 /* We already got a helper explicitly attached. The function 186 * nf_conntrack_alter_reply - in case NAT is in use - asks for looking 187 * the helper up again. Since now the user is in full control of 188 * making consistent helper configurations, skip this automatic 189 * re-lookup, otherwise we'll lose the helper. 190 */ 191 if (test_bit(IPS_HELPER_BIT, &ct->status)) 192 return 0; 193 194 if (tmpl != NULL) { 195 help = nfct_help(tmpl); 196 if (help != NULL) { 197 helper = help->helper; 198 set_bit(IPS_HELPER_BIT, &ct->status); 199 } 200 } 201 202 help = nfct_help(ct); 203 if (net->ct.sysctl_auto_assign_helper && helper == NULL) { 204 helper = __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); 205 if (unlikely(!net->ct.auto_assign_helper_warned && helper)) { 206 pr_info("nf_conntrack: automatic helper " 207 "assignment is deprecated and it will " 208 "be removed soon. Use the iptables CT target " 209 "to attach helpers instead.\n"); 210 net->ct.auto_assign_helper_warned = true; 211 } 212 } 213 214 if (helper == NULL) { 215 if (help) 216 RCU_INIT_POINTER(help->helper, NULL); 217 goto out; 218 } 219 220 if (help == NULL) { 221 help = nf_ct_helper_ext_add(ct, flags); 222 if (help == NULL) { 223 ret = -ENOMEM; 224 goto out; 225 } 226 } else { 227 memset(&help->help, 0, sizeof(help->help)); 228 } 229 230 rcu_assign_pointer(help->helper, helper); 231out: 232 return ret; 233} 234EXPORT_SYMBOL_GPL(__nf_ct_try_assign_helper); 235 236static inline int unhelp(struct nf_conntrack_tuple_hash *i, 237 const struct nf_conntrack_helper *me) 238{ 239 struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(i); 240 struct nf_conn_help *help = nfct_help(ct); 241 242 if (help && rcu_dereference_protected( 243 help->helper, 244 lockdep_is_held(&nf_conntrack_lock) 245 ) == me) { 246 nf_conntrack_event(IPCT_HELPER, ct); 247 RCU_INIT_POINTER(help->helper, NULL); 248 } 249 return 0; 250} 251 252void nf_ct_helper_destroy(struct nf_conn *ct) 253{ 254 struct nf_conn_help *help = nfct_help(ct); 255 struct nf_conntrack_helper *helper; 256 257 if (help) { 258 rcu_read_lock(); 259 helper = rcu_dereference(help->helper); 260 if (helper && helper->destroy) 261 helper->destroy(ct); 262 rcu_read_unlock(); 263 } 264} 265 266static LIST_HEAD(nf_ct_helper_expectfn_list); 267 268void nf_ct_helper_expectfn_register(struct nf_ct_helper_expectfn *n) 269{ 270 spin_lock_bh(&nf_conntrack_lock); 271 list_add_rcu(&n->head, &nf_ct_helper_expectfn_list); 272 spin_unlock_bh(&nf_conntrack_lock); 273} 274EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_register); 275 276void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n) 277{ 278 spin_lock_bh(&nf_conntrack_lock); 279 list_del_rcu(&n->head); 280 spin_unlock_bh(&nf_conntrack_lock); 281} 282EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_unregister); 283 284struct nf_ct_helper_expectfn * 285nf_ct_helper_expectfn_find_by_name(const char *name) 286{ 287 struct nf_ct_helper_expectfn *cur; 288 bool found = false; 289 290 rcu_read_lock(); 291 list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) { 292 if (!strcmp(cur->name, name)) { 293 found = true; 294 break; 295 } 296 } 297 rcu_read_unlock(); 298 return found ? cur : NULL; 299} 300EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_name); 301 302struct nf_ct_helper_expectfn * 303nf_ct_helper_expectfn_find_by_symbol(const void *symbol) 304{ 305 struct nf_ct_helper_expectfn *cur; 306 bool found = false; 307 308 rcu_read_lock(); 309 list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) { 310 if (cur->expectfn == symbol) { 311 found = true; 312 break; 313 } 314 } 315 rcu_read_unlock(); 316 return found ? cur : NULL; 317} 318EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_symbol); 319 320int nf_conntrack_helper_register(struct nf_conntrack_helper *me) 321{ 322 unsigned int h = helper_hash(&me->tuple); 323 324 BUG_ON(me->expect_policy == NULL); 325 BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); 326 BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1); 327 328 mutex_lock(&nf_ct_helper_mutex); 329 hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); 330 nf_ct_helper_count++; 331 mutex_unlock(&nf_ct_helper_mutex); 332 333 return 0; 334} 335EXPORT_SYMBOL_GPL(nf_conntrack_helper_register); 336 337static void __nf_conntrack_helper_unregister(struct nf_conntrack_helper *me, 338 struct net *net) 339{ 340 struct nf_conntrack_tuple_hash *h; 341 struct nf_conntrack_expect *exp; 342 const struct hlist_node *n, *next; 343 const struct hlist_nulls_node *nn; 344 unsigned int i; 345 346 /* Get rid of expectations */ 347 for (i = 0; i < nf_ct_expect_hsize; i++) { 348 hlist_for_each_entry_safe(exp, n, next, 349 &net->ct.expect_hash[i], hnode) { 350 struct nf_conn_help *help = nfct_help(exp->master); 351 if ((rcu_dereference_protected( 352 help->helper, 353 lockdep_is_held(&nf_conntrack_lock) 354 ) == me || exp->helper == me) && 355 del_timer(&exp->timeout)) { 356 nf_ct_unlink_expect(exp); 357 nf_ct_expect_put(exp); 358 } 359 } 360 } 361 362 /* Get rid of expecteds, set helpers to NULL. */ 363 hlist_nulls_for_each_entry(h, nn, &net->ct.unconfirmed, hnnode) 364 unhelp(h, me); 365 for (i = 0; i < net->ct.htable_size; i++) { 366 hlist_nulls_for_each_entry(h, nn, &net->ct.hash[i], hnnode) 367 unhelp(h, me); 368 } 369} 370 371void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) 372{ 373 struct net *net; 374 375 mutex_lock(&nf_ct_helper_mutex); 376 hlist_del_rcu(&me->hnode); 377 nf_ct_helper_count--; 378 mutex_unlock(&nf_ct_helper_mutex); 379 380 /* Make sure every nothing is still using the helper unless its a 381 * connection in the hash. 382 */ 383 synchronize_rcu(); 384 385 rtnl_lock(); 386 spin_lock_bh(&nf_conntrack_lock); 387 for_each_net(net) 388 __nf_conntrack_helper_unregister(me, net); 389 spin_unlock_bh(&nf_conntrack_lock); 390 rtnl_unlock(); 391} 392EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); 393 394static struct nf_ct_ext_type helper_extend __read_mostly = { 395 .len = sizeof(struct nf_conn_help), 396 .align = __alignof__(struct nf_conn_help), 397 .id = NF_CT_EXT_HELPER, 398}; 399 400int nf_conntrack_helper_init(struct net *net) 401{ 402 int err; 403 404 net->ct.auto_assign_helper_warned = false; 405 net->ct.sysctl_auto_assign_helper = nf_ct_auto_assign_helper; 406 407 if (net_eq(net, &init_net)) { 408 nf_ct_helper_hsize = 1; /* gets rounded up to use one page */ 409 nf_ct_helper_hash = 410 nf_ct_alloc_hashtable(&nf_ct_helper_hsize, 0); 411 if (!nf_ct_helper_hash) 412 return -ENOMEM; 413 414 err = nf_ct_extend_register(&helper_extend); 415 if (err < 0) 416 goto err1; 417 } 418 419 err = nf_conntrack_helper_init_sysctl(net); 420 if (err < 0) 421 goto out_sysctl; 422 423 return 0; 424 425out_sysctl: 426 if (net_eq(net, &init_net)) 427 nf_ct_extend_unregister(&helper_extend); 428err1: 429 nf_ct_free_hashtable(nf_ct_helper_hash, nf_ct_helper_hsize); 430 return err; 431} 432 433void nf_conntrack_helper_fini(struct net *net) 434{ 435 nf_conntrack_helper_fini_sysctl(net); 436 if (net_eq(net, &init_net)) { 437 nf_ct_extend_unregister(&helper_extend); 438 nf_ct_free_hashtable(nf_ct_helper_hash, nf_ct_helper_hsize); 439 } 440} 441