Blame view

net/netfilter/nfnetlink_acct.c 10.9 KB
941390279   Pablo Neira Ayuso   netfilter: add ex...
1
2
3
4
5
6
7
8
9
10
11
12
  /*
   * (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>
6523cf9a4   Andrew Morton   net/netfilter/nfn...
13
  #include <linux/atomic.h>
941390279   Pablo Neira Ayuso   netfilter: add ex...
14
15
16
17
18
19
20
  #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>
941390279   Pablo Neira Ayuso   netfilter: add ex...
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  
  #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;
683399edd   Mathieu Poirier   netfilter: nfnetl...
35
  	unsigned long		flags;
941390279   Pablo Neira Ayuso   netfilter: add ex...
36
37
38
39
  	struct list_head	head;
  	atomic_t		refcnt;
  	char			name[NFACCT_NAME_MAX];
  	struct rcu_head		rcu_head;
683399edd   Mathieu Poirier   netfilter: nfnetl...
40
  	char			data[0];
941390279   Pablo Neira Ayuso   netfilter: add ex...
41
  };
683399edd   Mathieu Poirier   netfilter: nfnetl...
42
  #define NFACCT_F_QUOTA (NFACCT_F_QUOTA_PKTS | NFACCT_F_QUOTA_BYTES)
b6d046880   Alexey Perevalov   netfilter: nfnetl...
43
  #define NFACCT_OVERQUOTA_BIT	2	/* NFACCT_F_OVERQUOTA */
683399edd   Mathieu Poirier   netfilter: nfnetl...
44

941390279   Pablo Neira Ayuso   netfilter: add ex...
45
46
47
48
49
50
  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;
683399edd   Mathieu Poirier   netfilter: nfnetl...
51
52
  	unsigned int size = 0;
  	u32 flags = 0;
941390279   Pablo Neira Ayuso   netfilter: add ex...
53
54
55
56
57
  
  	if (!tb[NFACCT_NAME])
  		return -EINVAL;
  
  	acct_name = nla_data(tb[NFACCT_NAME]);
deadcfc33   Pablo Neira Ayuso   netfilter: nfnetl...
58
59
  	if (strlen(acct_name) == 0)
  		return -EINVAL;
941390279   Pablo Neira Ayuso   netfilter: add ex...
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  
  	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);
f9da455b9   Linus Torvalds   Merge git://git.k...
77
  			smp_mb__before_atomic();
683399edd   Mathieu Poirier   netfilter: nfnetl...
78
79
  			/* reset overquota flag if quota is enabled. */
  			if ((matching->flags & NFACCT_F_QUOTA))
b6d046880   Alexey Perevalov   netfilter: nfnetl...
80
81
  				clear_bit(NFACCT_OVERQUOTA_BIT,
  					  &matching->flags);
941390279   Pablo Neira Ayuso   netfilter: add ex...
82
83
84
85
  			return 0;
  		}
  		return -EBUSY;
  	}
683399edd   Mathieu Poirier   netfilter: nfnetl...
86
87
88
89
90
91
92
93
94
95
96
97
98
  	if (tb[NFACCT_FLAGS]) {
  		flags = ntohl(nla_get_be32(tb[NFACCT_FLAGS]));
  		if (flags & ~NFACCT_F_QUOTA)
  			return -EOPNOTSUPP;
  		if ((flags & NFACCT_F_QUOTA) == NFACCT_F_QUOTA)
  			return -EINVAL;
  		if (flags & NFACCT_F_OVERQUOTA)
  			return -EINVAL;
  
  		size += sizeof(u64);
  	}
  
  	nfacct = kzalloc(sizeof(struct nf_acct) + size, GFP_KERNEL);
941390279   Pablo Neira Ayuso   netfilter: add ex...
99
100
  	if (nfacct == NULL)
  		return -ENOMEM;
683399edd   Mathieu Poirier   netfilter: nfnetl...
101
102
103
104
105
106
  	if (flags & NFACCT_F_QUOTA) {
  		u64 *quota = (u64 *)nfacct->data;
  
  		*quota = be64_to_cpu(nla_get_be64(tb[NFACCT_QUOTA]));
  		nfacct->flags = flags;
  	}
941390279   Pablo Neira Ayuso   netfilter: add ex...
107
108
109
110
  	strncpy(nfacct->name, nla_data(tb[NFACCT_NAME]), NFACCT_NAME_MAX);
  
  	if (tb[NFACCT_BYTES]) {
  		atomic64_set(&nfacct->bytes,
fe31d1a86   Patrick McHardy   netfilter: sparse...
111
  			     be64_to_cpu(nla_get_be64(tb[NFACCT_BYTES])));
941390279   Pablo Neira Ayuso   netfilter: add ex...
112
113
114
  	}
  	if (tb[NFACCT_PKTS]) {
  		atomic64_set(&nfacct->pkts,
fe31d1a86   Patrick McHardy   netfilter: sparse...
115
  			     be64_to_cpu(nla_get_be64(tb[NFACCT_PKTS])));
941390279   Pablo Neira Ayuso   netfilter: add ex...
116
117
118
119
120
121
122
  	}
  	atomic_set(&nfacct->refcnt, 1);
  	list_add_tail_rcu(&nfacct->head, &nfnl_acct_list);
  	return 0;
  }
  
  static int
15e473046   Eric W. Biederman   netlink: Rename p...
123
  nfnl_acct_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type,
941390279   Pablo Neira Ayuso   netfilter: add ex...
124
125
126
127
  		   int event, struct nf_acct *acct)
  {
  	struct nlmsghdr *nlh;
  	struct nfgenmsg *nfmsg;
15e473046   Eric W. Biederman   netlink: Rename p...
128
  	unsigned int flags = portid ? NLM_F_MULTI : 0;
941390279   Pablo Neira Ayuso   netfilter: add ex...
129
  	u64 pkts, bytes;
d24675cb1   Alexey Perevalov   netfilter: nfnetl...
130
  	u32 old_flags;
941390279   Pablo Neira Ayuso   netfilter: add ex...
131
132
  
  	event |= NFNL_SUBSYS_ACCT << 8;
15e473046   Eric W. Biederman   netlink: Rename p...
133
  	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nfmsg), flags);
941390279   Pablo Neira Ayuso   netfilter: add ex...
134
135
136
137
138
139
140
  	if (nlh == NULL)
  		goto nlmsg_failure;
  
  	nfmsg = nlmsg_data(nlh);
  	nfmsg->nfgen_family = AF_UNSPEC;
  	nfmsg->version = NFNETLINK_V0;
  	nfmsg->res_id = 0;
7c8011895   David S. Miller   nfnetlink_acct: S...
141
142
  	if (nla_put_string(skb, NFACCT_NAME, acct->name))
  		goto nla_put_failure;
941390279   Pablo Neira Ayuso   netfilter: add ex...
143

d24675cb1   Alexey Perevalov   netfilter: nfnetl...
144
  	old_flags = acct->flags;
941390279   Pablo Neira Ayuso   netfilter: add ex...
145
146
147
  	if (type == NFNL_MSG_ACCT_GET_CTRZERO) {
  		pkts = atomic64_xchg(&acct->pkts, 0);
  		bytes = atomic64_xchg(&acct->bytes, 0);
f9da455b9   Linus Torvalds   Merge git://git.k...
148
  		smp_mb__before_atomic();
683399edd   Mathieu Poirier   netfilter: nfnetl...
149
  		if (acct->flags & NFACCT_F_QUOTA)
b6d046880   Alexey Perevalov   netfilter: nfnetl...
150
  			clear_bit(NFACCT_OVERQUOTA_BIT, &acct->flags);
941390279   Pablo Neira Ayuso   netfilter: add ex...
151
152
153
154
  	} else {
  		pkts = atomic64_read(&acct->pkts);
  		bytes = atomic64_read(&acct->bytes);
  	}
7c8011895   David S. Miller   nfnetlink_acct: S...
155
156
157
158
  	if (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))))
  		goto nla_put_failure;
683399edd   Mathieu Poirier   netfilter: nfnetl...
159
160
  	if (acct->flags & NFACCT_F_QUOTA) {
  		u64 *quota = (u64 *)acct->data;
941390279   Pablo Neira Ayuso   netfilter: add ex...
161

d24675cb1   Alexey Perevalov   netfilter: nfnetl...
162
  		if (nla_put_be32(skb, NFACCT_FLAGS, htonl(old_flags)) ||
683399edd   Mathieu Poirier   netfilter: nfnetl...
163
164
165
  		    nla_put_be64(skb, NFACCT_QUOTA, cpu_to_be64(*quota)))
  			goto nla_put_failure;
  	}
941390279   Pablo Neira Ayuso   netfilter: add ex...
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  	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) {
991a6b735   Pablo Neira Ayuso   netfilter: nfnetl...
189
190
191
  		if (last) {
  			if (cur != last)
  				continue;
941390279   Pablo Neira Ayuso   netfilter: add ex...
192

991a6b735   Pablo Neira Ayuso   netfilter: nfnetl...
193
194
  			last = NULL;
  		}
15e473046   Eric W. Biederman   netlink: Rename p...
195
  		if (nfnl_acct_fill_info(skb, NETLINK_CB(cb->skb).portid,
941390279   Pablo Neira Ayuso   netfilter: add ex...
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
  				       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[])
  {
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
213
  	int ret = -ENOENT;
941390279   Pablo Neira Ayuso   netfilter: add ex...
214
215
216
217
  	struct nf_acct *cur;
  	char *acct_name;
  
  	if (nlh->nlmsg_flags & NLM_F_DUMP) {
80d326fab   Pablo Neira Ayuso   netlink: add netl...
218
219
220
221
  		struct netlink_dump_control c = {
  			.dump = nfnl_acct_dump,
  		};
  		return netlink_dump_start(nfnl, skb, nlh, &c);
941390279   Pablo Neira Ayuso   netfilter: add ex...
222
223
224
225
226
227
228
229
230
231
232
233
234
  	}
  
  	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);
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
235
236
  		if (skb2 == NULL) {
  			ret = -ENOMEM;
941390279   Pablo Neira Ayuso   netfilter: add ex...
237
  			break;
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
238
  		}
941390279   Pablo Neira Ayuso   netfilter: add ex...
239

15e473046   Eric W. Biederman   netlink: Rename p...
240
  		ret = nfnl_acct_fill_info(skb2, NETLINK_CB(skb).portid,
941390279   Pablo Neira Ayuso   netfilter: add ex...
241
242
243
  					 nlh->nlmsg_seq,
  					 NFNL_MSG_TYPE(nlh->nlmsg_type),
  					 NFNL_MSG_ACCT_NEW, cur);
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
244
  		if (ret <= 0) {
941390279   Pablo Neira Ayuso   netfilter: add ex...
245
  			kfree_skb(skb2);
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
246
247
  			break;
  		}
15e473046   Eric W. Biederman   netlink: Rename p...
248
  		ret = netlink_unicast(nfnl, skb2, NETLINK_CB(skb).portid,
3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
249
250
251
  					MSG_DONTWAIT);
  		if (ret > 0)
  			ret = 0;
941390279   Pablo Neira Ayuso   netfilter: add ex...
252

3ab0b245a   Pablo Neira Ayuso   netfilter: nfnetl...
253
254
  		/* this avoids a loop in nfnetlink. */
  		return ret == -EAGAIN ? -ENOBUFS : ret;
941390279   Pablo Neira Ayuso   netfilter: add ex...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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
  	}
  	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 },
683399edd   Mathieu Poirier   netfilter: nfnetl...
310
311
  	[NFACCT_FLAGS] = { .type = NLA_U32 },
  	[NFACCT_QUOTA] = { .type = NLA_U64 },
941390279   Pablo Neira Ayuso   netfilter: add ex...
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
  };
  
  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);
683399edd   Mathieu Poirier   netfilter: nfnetl...
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
  static void nfnl_overquota_report(struct nf_acct *nfacct)
  {
  	int ret;
  	struct sk_buff *skb;
  
  	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
  	if (skb == NULL)
  		return;
  
  	ret = nfnl_acct_fill_info(skb, 0, 0, NFNL_MSG_ACCT_OVERQUOTA, 0,
  				  nfacct);
  	if (ret <= 0) {
  		kfree_skb(skb);
  		return;
  	}
  	netlink_broadcast(init_net.nfnl, skb, 0, NFNLGRP_ACCT_QUOTA,
  			  GFP_ATOMIC);
  }
  
  int nfnl_acct_overquota(const struct sk_buff *skb, struct nf_acct *nfacct)
  {
  	u64 now;
  	u64 *quota;
  	int ret = NFACCT_UNDERQUOTA;
  
  	/* no place here if we don't have a quota */
  	if (!(nfacct->flags & NFACCT_F_QUOTA))
  		return NFACCT_NO_QUOTA;
  
  	quota = (u64 *)nfacct->data;
  	now = (nfacct->flags & NFACCT_F_QUOTA_PKTS) ?
  	       atomic64_read(&nfacct->pkts) : atomic64_read(&nfacct->bytes);
  
  	ret = now > *quota;
  
  	if (now >= *quota &&
b6d046880   Alexey Perevalov   netfilter: nfnetl...
413
  	    !test_and_set_bit(NFACCT_OVERQUOTA_BIT, &nfacct->flags)) {
683399edd   Mathieu Poirier   netfilter: nfnetl...
414
415
416
417
418
419
  		nfnl_overquota_report(nfacct);
  	}
  
  	return ret;
  }
  EXPORT_SYMBOL_GPL(nfnl_acct_overquota);
941390279   Pablo Neira Ayuso   netfilter: add ex...
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
  static int __init nfnl_acct_init(void)
  {
  	int ret;
  
  	pr_info("nfnl_acct: registering with nfnetlink.
  ");
  	ret = nfnetlink_subsys_register(&nfnl_acct_subsys);
  	if (ret < 0) {
  		pr_err("nfnl_acct_init: cannot register with nfnetlink.
  ");
  		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.
  ");
  	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);