nf_conntrack_helper.c 11.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* Helper handling for netfilter. */

/* (C) 1999-2001 Paul `Rusty' Russell
 * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org>
 * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/types.h>
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/vmalloc.h>
#include <linux/stddef.h>
#include <linux/random.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
22
#include <linux/rculist.h>
23
#include <linux/rtnetlink.h>
24 25 26

#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_l3proto.h>
27
#include <net/netfilter/nf_conntrack_l4proto.h>
28 29
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_core.h>
30
#include <net/netfilter/nf_conntrack_extend.h>
31

32
static DEFINE_MUTEX(nf_ct_helper_mutex);
33 34 35 36
static struct hlist_head *nf_ct_helper_hash __read_mostly;
static unsigned int nf_ct_helper_hsize __read_mostly;
static unsigned int nf_ct_helper_count __read_mostly;

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
static bool nf_ct_auto_assign_helper __read_mostly = true;
module_param_named(nf_conntrack_helper, nf_ct_auto_assign_helper, bool, 0644);
MODULE_PARM_DESC(nf_conntrack_helper,
		 "Enable automatic conntrack helper assignment (default 1)");

#ifdef CONFIG_SYSCTL
static struct ctl_table helper_sysctl_table[] = {
	{
		.procname	= "nf_conntrack_helper",
		.data		= &init_net.ct.sysctl_auto_assign_helper,
		.maxlen		= sizeof(unsigned int),
		.mode		= 0644,
		.proc_handler	= proc_dointvec,
	},
	{}
};

static int nf_conntrack_helper_init_sysctl(struct net *net)
{
	struct ctl_table *table;

	table = kmemdup(helper_sysctl_table, sizeof(helper_sysctl_table),
			GFP_KERNEL);
	if (!table)
		goto out;

	table[0].data = &net->ct.sysctl_auto_assign_helper;

	net->ct.helper_sysctl_header =
		register_net_sysctl(net, "net/netfilter", table);

	if (!net->ct.helper_sysctl_header) {
		pr_err("nf_conntrack_helper: can't register to sysctl.\n");
		goto out_register;
	}
	return 0;

out_register:
	kfree(table);
out:
	return -ENOMEM;
}

static void nf_conntrack_helper_fini_sysctl(struct net *net)
{
	struct ctl_table *table;

	table = net->ct.helper_sysctl_header->ctl_table_arg;
	unregister_net_sysctl_table(net->ct.helper_sysctl_header);
	kfree(table);
}
#else
static int nf_conntrack_helper_init_sysctl(struct net *net)
{
	return 0;
}

static void nf_conntrack_helper_fini_sysctl(struct net *net)
{
}
#endif /* CONFIG_SYSCTL */
98 99 100 101 102 103

/* Stupid hash, but collision free for the default registrations of the
 * helpers currently in the kernel. */
static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple)
{
	return (((tuple->src.l3num << 8) | tuple->dst.protonum) ^
A
Al Viro 已提交
104
		(__force __u16)tuple->src.u.all) % nf_ct_helper_hsize;
105
}
106

107
static struct nf_conntrack_helper *
108 109
__nf_ct_helper_find(const struct nf_conntrack_tuple *tuple)
{
110
	struct nf_conntrack_helper *helper;
111
	struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) };
112 113
	struct hlist_node *n;
	unsigned int h;
114

115 116 117 118
	if (!nf_ct_helper_count)
		return NULL;

	h = helper_hash(tuple);
119
	hlist_for_each_entry_rcu(helper, n, &nf_ct_helper_hash[h], hnode) {
120 121
		if (nf_ct_tuple_src_mask_cmp(tuple, &helper->tuple, &mask))
			return helper;
122 123 124 125 126
	}
	return NULL;
}

struct nf_conntrack_helper *
127
__nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum)
128 129
{
	struct nf_conntrack_helper *h;
130 131
	struct hlist_node *n;
	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 136 137
			if (!strcmp(h->name, name) &&
			    h->tuple.src.l3num == l3num &&
			    h->tuple.dst.protonum == protonum)
138 139
				return h;
		}
140 141 142
	}
	return NULL;
}
143
EXPORT_SYMBOL_GPL(__nf_conntrack_helper_find);
144

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
struct nf_conntrack_helper *
nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum)
{
	struct nf_conntrack_helper *h;

	h = __nf_conntrack_helper_find(name, l3num, protonum);
#ifdef CONFIG_MODULES
	if (h == NULL) {
		if (request_module("nfct-helper-%s", name) == 0)
			h = __nf_conntrack_helper_find(name, l3num, protonum);
	}
#endif
	if (h != NULL && !try_module_get(h->me))
		h = NULL;

	return h;
}
EXPORT_SYMBOL_GPL(nf_conntrack_helper_try_module_get);

164 165 166
struct nf_conn_help *
nf_ct_helper_ext_add(struct nf_conn *ct,
		     struct nf_conntrack_helper *helper, gfp_t gfp)
167 168 169
{
	struct nf_conn_help *help;

170 171
	help = nf_ct_ext_add_length(ct, NF_CT_EXT_HELPER,
				    helper->data_len, gfp);
172 173 174 175 176 177 178 179
	if (help)
		INIT_HLIST_HEAD(&help->expectations);
	else
		pr_debug("failed to add helper extension area");
	return help;
}
EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add);

180 181
int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl,
			      gfp_t flags)
182
{
183 184
	struct nf_conntrack_helper *helper = NULL;
	struct nf_conn_help *help;
185
	struct net *net = nf_ct_net(ct);
186 187
	int ret = 0;

188 189 190 191 192 193 194 195 196
	/* We already got a helper explicitly attached. The function
	 * nf_conntrack_alter_reply - in case NAT is in use - asks for looking
	 * the helper up again. Since now the user is in full control of
	 * making consistent helper configurations, skip this automatic
	 * re-lookup, otherwise we'll lose the helper.
	 */
	if (test_bit(IPS_HELPER_BIT, &ct->status))
		return 0;

197 198
	if (tmpl != NULL) {
		help = nfct_help(tmpl);
199
		if (help != NULL) {
200
			helper = help->helper;
201 202
			set_bit(IPS_HELPER_BIT, &ct->status);
		}
203 204 205
	}

	help = nfct_help(ct);
206
	if (net->ct.sysctl_auto_assign_helper && helper == NULL) {
207
		helper = __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
208 209 210 211 212 213 214 215 216
		if (unlikely(!net->ct.auto_assign_helper_warned && helper)) {
			pr_info("nf_conntrack: automatic helper "
				"assignment is deprecated and it will "
				"be removed soon. Use the iptables CT target "
				"to attach helpers instead.\n");
			net->ct.auto_assign_helper_warned = true;
		}
	}

217 218
	if (helper == NULL) {
		if (help)
219
			RCU_INIT_POINTER(help->helper, NULL);
220 221 222 223
		goto out;
	}

	if (help == NULL) {
224
		help = nf_ct_helper_ext_add(ct, helper, flags);
225 226 227 228 229
		if (help == NULL) {
			ret = -ENOMEM;
			goto out;
		}
	} else {
230
		memset(help->data, 0, helper->data_len);
231 232
	}

233
	rcu_assign_pointer(help->helper, helper);
234 235 236 237 238
out:
	return ret;
}
EXPORT_SYMBOL_GPL(__nf_ct_try_assign_helper);

239 240 241 242 243 244
static inline int unhelp(struct nf_conntrack_tuple_hash *i,
			 const struct nf_conntrack_helper *me)
{
	struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(i);
	struct nf_conn_help *help = nfct_help(ct);

E
Eric Dumazet 已提交
245 246 247 248
	if (help && rcu_dereference_protected(
			help->helper,
			lockdep_is_held(&nf_conntrack_lock)
			) == me) {
249
		nf_conntrack_event(IPCT_HELPER, ct);
250
		RCU_INIT_POINTER(help->helper, NULL);
251 252 253 254
	}
	return 0;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268
void nf_ct_helper_destroy(struct nf_conn *ct)
{
	struct nf_conn_help *help = nfct_help(ct);
	struct nf_conntrack_helper *helper;

	if (help) {
		rcu_read_lock();
		helper = rcu_dereference(help->helper);
		if (helper && helper->destroy)
			helper->destroy(ct);
		rcu_read_unlock();
	}
}

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
static LIST_HEAD(nf_ct_helper_expectfn_list);

void nf_ct_helper_expectfn_register(struct nf_ct_helper_expectfn *n)
{
	spin_lock_bh(&nf_conntrack_lock);
	list_add_rcu(&n->head, &nf_ct_helper_expectfn_list);
	spin_unlock_bh(&nf_conntrack_lock);
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_register);

void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n)
{
	spin_lock_bh(&nf_conntrack_lock);
	list_del_rcu(&n->head);
	spin_unlock_bh(&nf_conntrack_lock);
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_unregister);

struct nf_ct_helper_expectfn *
nf_ct_helper_expectfn_find_by_name(const char *name)
{
	struct nf_ct_helper_expectfn *cur;
	bool found = false;

	rcu_read_lock();
	list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) {
		if (!strcmp(cur->name, name)) {
			found = true;
			break;
		}
	}
	rcu_read_unlock();
	return found ? cur : NULL;
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_name);

struct nf_ct_helper_expectfn *
nf_ct_helper_expectfn_find_by_symbol(const void *symbol)
{
	struct nf_ct_helper_expectfn *cur;
	bool found = false;

	rcu_read_lock();
	list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) {
		if (cur->expectfn == symbol) {
			found = true;
			break;
		}
	}
	rcu_read_unlock();
	return found ? cur : NULL;
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_symbol);

323 324
int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
{
325 326
	unsigned int h = helper_hash(&me->tuple);

327 328
	BUG_ON(me->expect_policy == NULL);
	BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES);
329
	BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1);
330

331 332
	mutex_lock(&nf_ct_helper_mutex);
	hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
333
	nf_ct_helper_count++;
334
	mutex_unlock(&nf_ct_helper_mutex);
335 336 337

	return 0;
}
338
EXPORT_SYMBOL_GPL(nf_conntrack_helper_register);
339

340 341
static void __nf_conntrack_helper_unregister(struct nf_conntrack_helper *me,
					     struct net *net)
342 343
{
	struct nf_conntrack_tuple_hash *h;
344
	struct nf_conntrack_expect *exp;
345
	const struct hlist_node *n, *next;
346
	const struct hlist_nulls_node *nn;
347
	unsigned int i;
348 349

	/* Get rid of expectations */
350 351
	for (i = 0; i < nf_ct_expect_hsize; i++) {
		hlist_for_each_entry_safe(exp, n, next,
352
					  &net->ct.expect_hash[i], hnode) {
353
			struct nf_conn_help *help = nfct_help(exp->master);
E
Eric Dumazet 已提交
354 355 356 357
			if ((rcu_dereference_protected(
					help->helper,
					lockdep_is_held(&nf_conntrack_lock)
					) == me || exp->helper == me) &&
358 359 360 361
			    del_timer(&exp->timeout)) {
				nf_ct_unlink_expect(exp);
				nf_ct_expect_put(exp);
			}
362 363 364 365
		}
	}

	/* Get rid of expecteds, set helpers to NULL. */
366
	hlist_nulls_for_each_entry(h, nn, &net->ct.unconfirmed, hnnode)
367
		unhelp(h, me);
368
	for (i = 0; i < net->ct.htable_size; i++) {
369
		hlist_nulls_for_each_entry(h, nn, &net->ct.hash[i], hnnode)
370 371
			unhelp(h, me);
	}
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
}

void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me)
{
	struct net *net;

	mutex_lock(&nf_ct_helper_mutex);
	hlist_del_rcu(&me->hnode);
	nf_ct_helper_count--;
	mutex_unlock(&nf_ct_helper_mutex);

	/* Make sure every nothing is still using the helper unless its a
	 * connection in the hash.
	 */
	synchronize_rcu();

388
	rtnl_lock();
389 390 391
	spin_lock_bh(&nf_conntrack_lock);
	for_each_net(net)
		__nf_conntrack_helper_unregister(me, net);
392
	spin_unlock_bh(&nf_conntrack_lock);
393
	rtnl_unlock();
394
}
395
EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister);
396

397
static struct nf_ct_ext_type helper_extend __read_mostly = {
398 399 400 401 402
	.len	= sizeof(struct nf_conn_help),
	.align	= __alignof__(struct nf_conn_help),
	.id	= NF_CT_EXT_HELPER,
};

403
int nf_conntrack_helper_init(struct net *net)
404
{
405 406
	int err;

407 408
	net->ct.auto_assign_helper_warned = false;
	net->ct.sysctl_auto_assign_helper = nf_ct_auto_assign_helper;
409

410 411 412 413 414 415 416 417 418 419 420 421 422
	if (net_eq(net, &init_net)) {
		nf_ct_helper_hsize = 1; /* gets rounded up to use one page */
		nf_ct_helper_hash =
			nf_ct_alloc_hashtable(&nf_ct_helper_hsize, 0);
		if (!nf_ct_helper_hash)
			return -ENOMEM;

		err = nf_ct_extend_register(&helper_extend);
		if (err < 0)
			goto err1;
	}

	err = nf_conntrack_helper_init_sysctl(net);
423
	if (err < 0)
424
		goto out_sysctl;
425 426 427

	return 0;

428 429 430
out_sysctl:
	if (net_eq(net, &init_net))
		nf_ct_extend_unregister(&helper_extend);
431
err1:
432
	nf_ct_free_hashtable(nf_ct_helper_hash, nf_ct_helper_hsize);
433
	return err;
434 435
}

436
void nf_conntrack_helper_fini(struct net *net)
437
{
438 439 440 441 442
	nf_conntrack_helper_fini_sysctl(net);
	if (net_eq(net, &init_net)) {
		nf_ct_extend_unregister(&helper_extend);
		nf_ct_free_hashtable(nf_ct_helper_hash, nf_ct_helper_hsize);
	}
443
}