diff options
Diffstat (limited to 'net/netfilter/nfnetlink_acct.c')
| -rw-r--r-- | net/netfilter/nfnetlink_acct.c | 361 | 
1 files changed, 361 insertions, 0 deletions
| diff --git a/net/netfilter/nfnetlink_acct.c b/net/netfilter/nfnetlink_acct.c new file mode 100644 index 00000000000..11ba013e47f --- /dev/null +++ b/net/netfilter/nfnetlink_acct.c @@ -0,0 +1,361 @@ +/* + * (C) 2011 Pablo Neira Ayuso <pablo@netfilter.org> + * (C) 2011 Intra2net AG <http://www.intra2net.com> + * + * 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 (or any later at your option). + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/rculist.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <net/netlink.h> +#include <net/sock.h> +#include <asm/atomic.h> + +#include <linux/netfilter.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nfnetlink_acct.h> + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>"); +MODULE_DESCRIPTION("nfacct: Extended Netfilter accounting infrastructure"); + +static LIST_HEAD(nfnl_acct_list); + +struct nf_acct { +	atomic64_t		pkts; +	atomic64_t		bytes; +	struct list_head	head; +	atomic_t		refcnt; +	char			name[NFACCT_NAME_MAX]; +	struct rcu_head		rcu_head; +}; + +static int +nfnl_acct_new(struct sock *nfnl, struct sk_buff *skb, +	     const struct nlmsghdr *nlh, const struct nlattr * const tb[]) +{ +	struct nf_acct *nfacct, *matching = NULL; +	char *acct_name; + +	if (!tb[NFACCT_NAME]) +		return -EINVAL; + +	acct_name = nla_data(tb[NFACCT_NAME]); + +	list_for_each_entry(nfacct, &nfnl_acct_list, head) { +		if (strncmp(nfacct->name, acct_name, NFACCT_NAME_MAX) != 0) +			continue; + +                if (nlh->nlmsg_flags & NLM_F_EXCL) +			return -EEXIST; + +		matching = nfacct; +		break; +        } + +	if (matching) { +		if (nlh->nlmsg_flags & NLM_F_REPLACE) { +			/* reset counters if you request a replacement. */ +			atomic64_set(&matching->pkts, 0); +			atomic64_set(&matching->bytes, 0); +			return 0; +		} +		return -EBUSY; +	} + +	nfacct = kzalloc(sizeof(struct nf_acct), GFP_KERNEL); +	if (nfacct == NULL) +		return -ENOMEM; + +	strncpy(nfacct->name, nla_data(tb[NFACCT_NAME]), NFACCT_NAME_MAX); + +	if (tb[NFACCT_BYTES]) { +		atomic64_set(&nfacct->bytes, +			     be64_to_cpu(nla_get_u64(tb[NFACCT_BYTES]))); +	} +	if (tb[NFACCT_PKTS]) { +		atomic64_set(&nfacct->pkts, +			     be64_to_cpu(nla_get_u64(tb[NFACCT_PKTS]))); +	} +	atomic_set(&nfacct->refcnt, 1); +	list_add_tail_rcu(&nfacct->head, &nfnl_acct_list); +	return 0; +} + +static int +nfnl_acct_fill_info(struct sk_buff *skb, u32 pid, u32 seq, u32 type, +		   int event, struct nf_acct *acct) +{ +	struct nlmsghdr *nlh; +	struct nfgenmsg *nfmsg; +	unsigned int flags = pid ? NLM_F_MULTI : 0; +	u64 pkts, bytes; + +	event |= NFNL_SUBSYS_ACCT << 8; +	nlh = nlmsg_put(skb, pid, seq, event, sizeof(*nfmsg), flags); +	if (nlh == NULL) +		goto nlmsg_failure; + +	nfmsg = nlmsg_data(nlh); +	nfmsg->nfgen_family = AF_UNSPEC; +	nfmsg->version = NFNETLINK_V0; +	nfmsg->res_id = 0; + +	NLA_PUT_STRING(skb, NFACCT_NAME, acct->name); + +	if (type == NFNL_MSG_ACCT_GET_CTRZERO) { +		pkts = atomic64_xchg(&acct->pkts, 0); +		bytes = atomic64_xchg(&acct->bytes, 0); +	} else { +		pkts = atomic64_read(&acct->pkts); +		bytes = atomic64_read(&acct->bytes); +	} +	NLA_PUT_BE64(skb, NFACCT_PKTS, cpu_to_be64(pkts)); +	NLA_PUT_BE64(skb, NFACCT_BYTES, cpu_to_be64(bytes)); +	NLA_PUT_BE32(skb, NFACCT_USE, htonl(atomic_read(&acct->refcnt))); + +	nlmsg_end(skb, nlh); +	return skb->len; + +nlmsg_failure: +nla_put_failure: +	nlmsg_cancel(skb, nlh); +	return -1; +} + +static int +nfnl_acct_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ +	struct nf_acct *cur, *last; + +	if (cb->args[2]) +		return 0; + +	last = (struct nf_acct *)cb->args[1]; +	if (cb->args[1]) +		cb->args[1] = 0; + +	rcu_read_lock(); +	list_for_each_entry_rcu(cur, &nfnl_acct_list, head) { +		if (last && cur != last) +			continue; + +		if (nfnl_acct_fill_info(skb, NETLINK_CB(cb->skb).pid, +				       cb->nlh->nlmsg_seq, +				       NFNL_MSG_TYPE(cb->nlh->nlmsg_type), +				       NFNL_MSG_ACCT_NEW, cur) < 0) { +			cb->args[1] = (unsigned long)cur; +			break; +		} +	} +	if (!cb->args[1]) +		cb->args[2] = 1; +	rcu_read_unlock(); +	return skb->len; +} + +static int +nfnl_acct_get(struct sock *nfnl, struct sk_buff *skb, +	     const struct nlmsghdr *nlh, const struct nlattr * const tb[]) +{ +	int ret = -ENOENT; +	struct nf_acct *cur; +	char *acct_name; + +	if (nlh->nlmsg_flags & NLM_F_DUMP) { +		return netlink_dump_start(nfnl, skb, nlh, nfnl_acct_dump, +					  NULL, 0); +	} + +	if (!tb[NFACCT_NAME]) +		return -EINVAL; +	acct_name = nla_data(tb[NFACCT_NAME]); + +	list_for_each_entry(cur, &nfnl_acct_list, head) { +		struct sk_buff *skb2; + +		if (strncmp(cur->name, acct_name, NFACCT_NAME_MAX)!= 0) +			continue; + +		skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +		if (skb2 == NULL) { +			ret = -ENOMEM; +			break; +		} + +		ret = nfnl_acct_fill_info(skb2, NETLINK_CB(skb).pid, +					 nlh->nlmsg_seq, +					 NFNL_MSG_TYPE(nlh->nlmsg_type), +					 NFNL_MSG_ACCT_NEW, cur); +		if (ret <= 0) { +			kfree_skb(skb2); +			break; +		} +		ret = netlink_unicast(nfnl, skb2, NETLINK_CB(skb).pid, +					MSG_DONTWAIT); +		if (ret > 0) +			ret = 0; + +		/* this avoids a loop in nfnetlink. */ +		return ret == -EAGAIN ? -ENOBUFS : ret; +	} +	return ret; +} + +/* try to delete object, fail if it is still in use. */ +static int nfnl_acct_try_del(struct nf_acct *cur) +{ +	int ret = 0; + +	/* we want to avoid races with nfnl_acct_find_get. */ +	if (atomic_dec_and_test(&cur->refcnt)) { +		/* We are protected by nfnl mutex. */ +		list_del_rcu(&cur->head); +		kfree_rcu(cur, rcu_head); +	} else { +		/* still in use, restore reference counter. */ +		atomic_inc(&cur->refcnt); +		ret = -EBUSY; +	} +	return ret; +} + +static int +nfnl_acct_del(struct sock *nfnl, struct sk_buff *skb, +	     const struct nlmsghdr *nlh, const struct nlattr * const tb[]) +{ +	char *acct_name; +	struct nf_acct *cur; +	int ret = -ENOENT; + +	if (!tb[NFACCT_NAME]) { +		list_for_each_entry(cur, &nfnl_acct_list, head) +			nfnl_acct_try_del(cur); + +		return 0; +	} +	acct_name = nla_data(tb[NFACCT_NAME]); + +	list_for_each_entry(cur, &nfnl_acct_list, head) { +		if (strncmp(cur->name, acct_name, NFACCT_NAME_MAX) != 0) +			continue; + +		ret = nfnl_acct_try_del(cur); +		if (ret < 0) +			return ret; + +		break; +	} +	return ret; +} + +static const struct nla_policy nfnl_acct_policy[NFACCT_MAX+1] = { +	[NFACCT_NAME] = { .type = NLA_NUL_STRING, .len = NFACCT_NAME_MAX-1 }, +	[NFACCT_BYTES] = { .type = NLA_U64 }, +	[NFACCT_PKTS] = { .type = NLA_U64 }, +}; + +static const struct nfnl_callback nfnl_acct_cb[NFNL_MSG_ACCT_MAX] = { +	[NFNL_MSG_ACCT_NEW]		= { .call = nfnl_acct_new, +					    .attr_count = NFACCT_MAX, +					    .policy = nfnl_acct_policy }, +	[NFNL_MSG_ACCT_GET] 		= { .call = nfnl_acct_get, +					    .attr_count = NFACCT_MAX, +					    .policy = nfnl_acct_policy }, +	[NFNL_MSG_ACCT_GET_CTRZERO] 	= { .call = nfnl_acct_get, +					    .attr_count = NFACCT_MAX, +					    .policy = nfnl_acct_policy }, +	[NFNL_MSG_ACCT_DEL]		= { .call = nfnl_acct_del, +					    .attr_count = NFACCT_MAX, +					    .policy = nfnl_acct_policy }, +}; + +static const struct nfnetlink_subsystem nfnl_acct_subsys = { +	.name				= "acct", +	.subsys_id			= NFNL_SUBSYS_ACCT, +	.cb_count			= NFNL_MSG_ACCT_MAX, +	.cb				= nfnl_acct_cb, +}; + +MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_ACCT); + +struct nf_acct *nfnl_acct_find_get(const char *acct_name) +{ +	struct nf_acct *cur, *acct = NULL; + +	rcu_read_lock(); +	list_for_each_entry_rcu(cur, &nfnl_acct_list, head) { +		if (strncmp(cur->name, acct_name, NFACCT_NAME_MAX)!= 0) +			continue; + +		if (!try_module_get(THIS_MODULE)) +			goto err; + +		if (!atomic_inc_not_zero(&cur->refcnt)) { +			module_put(THIS_MODULE); +			goto err; +		} + +		acct = cur; +		break; +	} +err: +	rcu_read_unlock(); +	return acct; +} +EXPORT_SYMBOL_GPL(nfnl_acct_find_get); + +void nfnl_acct_put(struct nf_acct *acct) +{ +	atomic_dec(&acct->refcnt); +	module_put(THIS_MODULE); +} +EXPORT_SYMBOL_GPL(nfnl_acct_put); + +void nfnl_acct_update(const struct sk_buff *skb, struct nf_acct *nfacct) +{ +	atomic64_inc(&nfacct->pkts); +	atomic64_add(skb->len, &nfacct->bytes); +} +EXPORT_SYMBOL_GPL(nfnl_acct_update); + +static int __init nfnl_acct_init(void) +{ +	int ret; + +	pr_info("nfnl_acct: registering with nfnetlink.\n"); +	ret = nfnetlink_subsys_register(&nfnl_acct_subsys); +	if (ret < 0) { +		pr_err("nfnl_acct_init: cannot register with nfnetlink.\n"); +		goto err_out; +	} +	return 0; +err_out: +	return ret; +} + +static void __exit nfnl_acct_exit(void) +{ +	struct nf_acct *cur, *tmp; + +	pr_info("nfnl_acct: unregistering from nfnetlink.\n"); +	nfnetlink_subsys_unregister(&nfnl_acct_subsys); + +	list_for_each_entry_safe(cur, tmp, &nfnl_acct_list, head) { +		list_del_rcu(&cur->head); +		/* We are sure that our objects have no clients at this point, +		 * it's safe to release them all without checking refcnt. */ +		kfree_rcu(cur, rcu_head); +	} +} + +module_init(nfnl_acct_init); +module_exit(nfnl_acct_exit); | 
