Blame view
net/netfilter/nfnetlink_acct.c
12.7 KB
e97150df8
|
1 |
// SPDX-License-Identifier: GPL-2.0-or-later |
941390279
|
2 3 4 |
/* * (C) 2011 Pablo Neira Ayuso <pablo@netfilter.org> * (C) 2011 Intra2net AG <http://www.intra2net.com> |
941390279
|
5 6 7 8 9 |
*/ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> |
6523cf9a4
|
10 |
#include <linux/atomic.h> |
b54ab92b8
|
11 |
#include <linux/refcount.h> |
941390279
|
12 13 14 15 16 17 18 |
#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
|
19 20 21 22 23 24 25 26 |
#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"); |
941390279
|
27 28 29 |
struct nf_acct { atomic64_t pkts; atomic64_t bytes; |
683399edd
|
30 |
unsigned long flags; |
941390279
|
31 |
struct list_head head; |
b54ab92b8
|
32 |
refcount_t refcnt; |
941390279
|
33 34 |
char name[NFACCT_NAME_MAX]; struct rcu_head rcu_head; |
683399edd
|
35 |
char data[0]; |
941390279
|
36 |
}; |
f111f780a
|
37 38 39 40 |
struct nfacct_filter { u32 value; u32 mask; }; |
683399edd
|
41 |
#define NFACCT_F_QUOTA (NFACCT_F_QUOTA_PKTS | NFACCT_F_QUOTA_BYTES) |
b6d046880
|
42 |
#define NFACCT_OVERQUOTA_BIT 2 /* NFACCT_F_OVERQUOTA */ |
683399edd
|
43 |
|
7b8002a15
|
44 45 |
static int nfnl_acct_new(struct net *net, struct sock *nfnl, struct sk_buff *skb, const struct nlmsghdr *nlh, |
04ba724b6
|
46 47 |
const struct nlattr * const tb[], struct netlink_ext_ack *extack) |
941390279
|
48 49 50 |
{ struct nf_acct *nfacct, *matching = NULL; char *acct_name; |
683399edd
|
51 52 |
unsigned int size = 0; u32 flags = 0; |
941390279
|
53 54 55 56 57 |
if (!tb[NFACCT_NAME]) return -EINVAL; acct_name = nla_data(tb[NFACCT_NAME]); |
deadcfc33
|
58 59 |
if (strlen(acct_name) == 0) return -EINVAL; |
941390279
|
60 |
|
3499abb24
|
61 |
list_for_each_entry(nfacct, &net->nfnl_acct_list, head) { |
941390279
|
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
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
|
77 |
smp_mb__before_atomic(); |
683399edd
|
78 79 |
/* reset overquota flag if quota is enabled. */ if ((matching->flags & NFACCT_F_QUOTA)) |
b6d046880
|
80 81 |
clear_bit(NFACCT_OVERQUOTA_BIT, &matching->flags); |
941390279
|
82 83 84 85 |
return 0; } return -EBUSY; } |
683399edd
|
86 87 88 89 90 91 92 93 |
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; |
eda3fc50d
|
94 95 |
if ((flags & NFACCT_F_QUOTA) && !tb[NFACCT_QUOTA]) return -EINVAL; |
683399edd
|
96 97 98 99 100 |
size += sizeof(u64); } nfacct = kzalloc(sizeof(struct nf_acct) + size, GFP_KERNEL); |
941390279
|
101 102 |
if (nfacct == NULL) return -ENOMEM; |
683399edd
|
103 104 105 106 107 108 |
if (flags & NFACCT_F_QUOTA) { u64 *quota = (u64 *)nfacct->data; *quota = be64_to_cpu(nla_get_be64(tb[NFACCT_QUOTA])); nfacct->flags = flags; } |
4b83a9049
|
109 |
nla_strlcpy(nfacct->name, tb[NFACCT_NAME], NFACCT_NAME_MAX); |
941390279
|
110 111 112 |
if (tb[NFACCT_BYTES]) { atomic64_set(&nfacct->bytes, |
fe31d1a86
|
113 |
be64_to_cpu(nla_get_be64(tb[NFACCT_BYTES]))); |
941390279
|
114 115 116 |
} if (tb[NFACCT_PKTS]) { atomic64_set(&nfacct->pkts, |
fe31d1a86
|
117 |
be64_to_cpu(nla_get_be64(tb[NFACCT_PKTS]))); |
941390279
|
118 |
} |
b54ab92b8
|
119 |
refcount_set(&nfacct->refcnt, 1); |
3499abb24
|
120 |
list_add_tail_rcu(&nfacct->head, &net->nfnl_acct_list); |
941390279
|
121 122 123 124 |
return 0; } static int |
15e473046
|
125 |
nfnl_acct_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, |
941390279
|
126 127 128 129 |
int event, struct nf_acct *acct) { struct nlmsghdr *nlh; struct nfgenmsg *nfmsg; |
15e473046
|
130 |
unsigned int flags = portid ? NLM_F_MULTI : 0; |
941390279
|
131 |
u64 pkts, bytes; |
d24675cb1
|
132 |
u32 old_flags; |
941390279
|
133 |
|
dedb67c4b
|
134 |
event = nfnl_msg_type(NFNL_SUBSYS_ACCT, event); |
15e473046
|
135 |
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nfmsg), flags); |
941390279
|
136 137 138 139 140 141 142 |
if (nlh == NULL) goto nlmsg_failure; nfmsg = nlmsg_data(nlh); nfmsg->nfgen_family = AF_UNSPEC; nfmsg->version = NFNETLINK_V0; nfmsg->res_id = 0; |
7c8011895
|
143 144 |
if (nla_put_string(skb, NFACCT_NAME, acct->name)) goto nla_put_failure; |
941390279
|
145 |
|
d24675cb1
|
146 |
old_flags = acct->flags; |
941390279
|
147 148 149 |
if (type == NFNL_MSG_ACCT_GET_CTRZERO) { pkts = atomic64_xchg(&acct->pkts, 0); bytes = atomic64_xchg(&acct->bytes, 0); |
f9da455b9
|
150 |
smp_mb__before_atomic(); |
683399edd
|
151 |
if (acct->flags & NFACCT_F_QUOTA) |
b6d046880
|
152 |
clear_bit(NFACCT_OVERQUOTA_BIT, &acct->flags); |
941390279
|
153 154 155 156 |
} else { pkts = atomic64_read(&acct->pkts); bytes = atomic64_read(&acct->bytes); } |
b46f6ded9
|
157 158 159 160 |
if (nla_put_be64(skb, NFACCT_PKTS, cpu_to_be64(pkts), NFACCT_PAD) || nla_put_be64(skb, NFACCT_BYTES, cpu_to_be64(bytes), NFACCT_PAD) || |
b54ab92b8
|
161 |
nla_put_be32(skb, NFACCT_USE, htonl(refcount_read(&acct->refcnt)))) |
7c8011895
|
162 |
goto nla_put_failure; |
683399edd
|
163 164 |
if (acct->flags & NFACCT_F_QUOTA) { u64 *quota = (u64 *)acct->data; |
941390279
|
165 |
|
d24675cb1
|
166 |
if (nla_put_be32(skb, NFACCT_FLAGS, htonl(old_flags)) || |
b46f6ded9
|
167 168 |
nla_put_be64(skb, NFACCT_QUOTA, cpu_to_be64(*quota), NFACCT_PAD)) |
683399edd
|
169 170 |
goto nla_put_failure; } |
941390279
|
171 172 173 174 175 176 177 178 179 180 181 182 |
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) { |
3499abb24
|
183 |
struct net *net = sock_net(skb->sk); |
941390279
|
184 |
struct nf_acct *cur, *last; |
f111f780a
|
185 |
const struct nfacct_filter *filter = cb->data; |
941390279
|
186 187 188 189 190 191 192 193 194 |
if (cb->args[2]) return 0; last = (struct nf_acct *)cb->args[1]; if (cb->args[1]) cb->args[1] = 0; rcu_read_lock(); |
3499abb24
|
195 |
list_for_each_entry_rcu(cur, &net->nfnl_acct_list, head) { |
991a6b735
|
196 197 198 |
if (last) { if (cur != last) continue; |
941390279
|
199 |
|
991a6b735
|
200 201 |
last = NULL; } |
f111f780a
|
202 203 204 |
if (filter && (cur->flags & filter->mask) != filter->value) continue; |
15e473046
|
205 |
if (nfnl_acct_fill_info(skb, NETLINK_CB(cb->skb).portid, |
941390279
|
206 207 208 209 210 211 212 213 214 215 216 217 |
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; } |
f111f780a
|
218 219 220 221 222 223 224 225 226 227 |
static int nfnl_acct_done(struct netlink_callback *cb) { kfree(cb->data); return 0; } static const struct nla_policy filter_policy[NFACCT_FILTER_MAX + 1] = { [NFACCT_FILTER_MASK] = { .type = NLA_U32 }, [NFACCT_FILTER_VALUE] = { .type = NLA_U32 }, }; |
3e673b23b
|
228 |
static int nfnl_acct_start(struct netlink_callback *cb) |
f111f780a
|
229 |
{ |
3e673b23b
|
230 |
const struct nlattr *const attr = cb->data; |
f111f780a
|
231 |
struct nlattr *tb[NFACCT_FILTER_MAX + 1]; |
3e673b23b
|
232 |
struct nfacct_filter *filter; |
f111f780a
|
233 |
int err; |
3e673b23b
|
234 235 |
if (!attr) return 0; |
8cb081746
|
236 237 |
err = nla_parse_nested_deprecated(tb, NFACCT_FILTER_MAX, attr, filter_policy, NULL); |
f111f780a
|
238 |
if (err < 0) |
3e673b23b
|
239 |
return err; |
f111f780a
|
240 |
|
017b1b6d2
|
241 |
if (!tb[NFACCT_FILTER_MASK] || !tb[NFACCT_FILTER_VALUE]) |
3e673b23b
|
242 |
return -EINVAL; |
017b1b6d2
|
243 |
|
f111f780a
|
244 245 |
filter = kzalloc(sizeof(struct nfacct_filter), GFP_KERNEL); if (!filter) |
3e673b23b
|
246 |
return -ENOMEM; |
f111f780a
|
247 248 249 |
filter->mask = ntohl(nla_get_be32(tb[NFACCT_FILTER_MASK])); filter->value = ntohl(nla_get_be32(tb[NFACCT_FILTER_VALUE])); |
3e673b23b
|
250 |
cb->data = filter; |
f111f780a
|
251 |
|
3e673b23b
|
252 |
return 0; |
f111f780a
|
253 |
} |
7b8002a15
|
254 255 |
static int nfnl_acct_get(struct net *net, struct sock *nfnl, struct sk_buff *skb, const struct nlmsghdr *nlh, |
04ba724b6
|
256 257 |
const struct nlattr * const tb[], struct netlink_ext_ack *extack) |
941390279
|
258 |
{ |
3ab0b245a
|
259 |
int ret = -ENOENT; |
941390279
|
260 261 262 263 |
struct nf_acct *cur; char *acct_name; if (nlh->nlmsg_flags & NLM_F_DUMP) { |
80d326fab
|
264 265 |
struct netlink_dump_control c = { .dump = nfnl_acct_dump, |
3e673b23b
|
266 |
.start = nfnl_acct_start, |
f111f780a
|
267 |
.done = nfnl_acct_done, |
3e673b23b
|
268 |
.data = (void *)tb[NFACCT_FILTER], |
80d326fab
|
269 |
}; |
f111f780a
|
270 |
|
80d326fab
|
271 |
return netlink_dump_start(nfnl, skb, nlh, &c); |
941390279
|
272 273 274 275 276 |
} if (!tb[NFACCT_NAME]) return -EINVAL; acct_name = nla_data(tb[NFACCT_NAME]); |
3499abb24
|
277 |
list_for_each_entry(cur, &net->nfnl_acct_list, head) { |
941390279
|
278 279 280 281 282 283 |
struct sk_buff *skb2; if (strncmp(cur->name, acct_name, NFACCT_NAME_MAX)!= 0) continue; skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
3ab0b245a
|
284 285 |
if (skb2 == NULL) { ret = -ENOMEM; |
941390279
|
286 |
break; |
3ab0b245a
|
287 |
} |
941390279
|
288 |
|
15e473046
|
289 |
ret = nfnl_acct_fill_info(skb2, NETLINK_CB(skb).portid, |
941390279
|
290 291 292 |
nlh->nlmsg_seq, NFNL_MSG_TYPE(nlh->nlmsg_type), NFNL_MSG_ACCT_NEW, cur); |
3ab0b245a
|
293 |
if (ret <= 0) { |
941390279
|
294 |
kfree_skb(skb2); |
3ab0b245a
|
295 296 |
break; } |
15e473046
|
297 |
ret = netlink_unicast(nfnl, skb2, NETLINK_CB(skb).portid, |
3ab0b245a
|
298 299 300 |
MSG_DONTWAIT); if (ret > 0) ret = 0; |
941390279
|
301 |
|
3ab0b245a
|
302 303 |
/* this avoids a loop in nfnetlink. */ return ret == -EAGAIN ? -ENOBUFS : ret; |
941390279
|
304 305 306 307 308 309 310 311 |
} 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; |
12be15dd5
|
312 313 314 |
/* We want to avoid races with nfnl_acct_put. So only when the current * refcnt is 1, we decrease it to 0. */ |
b54ab92b8
|
315 |
if (refcount_dec_if_one(&cur->refcnt)) { |
941390279
|
316 317 318 319 |
/* We are protected by nfnl mutex. */ list_del_rcu(&cur->head); kfree_rcu(cur, rcu_head); } else { |
941390279
|
320 321 322 323 |
ret = -EBUSY; } return ret; } |
7b8002a15
|
324 325 |
static int nfnl_acct_del(struct net *net, struct sock *nfnl, struct sk_buff *skb, const struct nlmsghdr *nlh, |
04ba724b6
|
326 327 |
const struct nlattr * const tb[], struct netlink_ext_ack *extack) |
941390279
|
328 |
{ |
93fac10b9
|
329 |
struct nf_acct *cur, *tmp; |
941390279
|
330 |
int ret = -ENOENT; |
93fac10b9
|
331 |
char *acct_name; |
941390279
|
332 333 |
if (!tb[NFACCT_NAME]) { |
93fac10b9
|
334 |
list_for_each_entry_safe(cur, tmp, &net->nfnl_acct_list, head) |
941390279
|
335 336 337 338 339 |
nfnl_acct_try_del(cur); return 0; } acct_name = nla_data(tb[NFACCT_NAME]); |
3499abb24
|
340 |
list_for_each_entry(cur, &net->nfnl_acct_list, head) { |
941390279
|
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
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
|
357 358 |
[NFACCT_FLAGS] = { .type = NLA_U32 }, [NFACCT_QUOTA] = { .type = NLA_U64 }, |
f111f780a
|
359 |
[NFACCT_FILTER] = {.type = NLA_NESTED }, |
941390279
|
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
}; 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); |
3499abb24
|
385 |
struct nf_acct *nfnl_acct_find_get(struct net *net, const char *acct_name) |
941390279
|
386 387 388 389 |
{ struct nf_acct *cur, *acct = NULL; rcu_read_lock(); |
3499abb24
|
390 |
list_for_each_entry_rcu(cur, &net->nfnl_acct_list, head) { |
941390279
|
391 392 393 394 395 |
if (strncmp(cur->name, acct_name, NFACCT_NAME_MAX)!= 0) continue; if (!try_module_get(THIS_MODULE)) goto err; |
b54ab92b8
|
396 |
if (!refcount_inc_not_zero(&cur->refcnt)) { |
941390279
|
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
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) { |
b54ab92b8
|
412 |
if (refcount_dec_and_test(&acct->refcnt)) |
3499abb24
|
413 |
kfree_rcu(acct, rcu_head); |
941390279
|
414 415 416 417 418 419 420 421 422 423 |
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); |
aca300183
|
424 |
static void nfnl_overquota_report(struct net *net, struct nf_acct *nfacct) |
683399edd
|
425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
{ 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; } |
aca300183
|
439 |
netlink_broadcast(net->nfnl, skb, 0, NFNLGRP_ACCT_QUOTA, |
683399edd
|
440 441 |
GFP_ATOMIC); } |
cceae76ef
|
442 |
int nfnl_acct_overquota(struct net *net, struct nf_acct *nfacct) |
683399edd
|
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
{ 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
|
459 |
!test_and_set_bit(NFACCT_OVERQUOTA_BIT, &nfacct->flags)) { |
aca300183
|
460 |
nfnl_overquota_report(net, nfacct); |
683399edd
|
461 462 463 464 465 |
} return ret; } EXPORT_SYMBOL_GPL(nfnl_acct_overquota); |
3499abb24
|
466 467 468 469 470 471 472 473 474 475 476 477 478 |
static int __net_init nfnl_acct_net_init(struct net *net) { INIT_LIST_HEAD(&net->nfnl_acct_list); return 0; } static void __net_exit nfnl_acct_net_exit(struct net *net) { struct nf_acct *cur, *tmp; list_for_each_entry_safe(cur, tmp, &net->nfnl_acct_list, head) { list_del_rcu(&cur->head); |
b54ab92b8
|
479 |
if (refcount_dec_and_test(&cur->refcnt)) |
3499abb24
|
480 481 482 483 484 485 486 487 |
kfree_rcu(cur, rcu_head); } } static struct pernet_operations nfnl_acct_ops = { .init = nfnl_acct_net_init, .exit = nfnl_acct_net_exit, }; |
941390279
|
488 489 490 |
static int __init nfnl_acct_init(void) { int ret; |
3499abb24
|
491 492 493 494 495 496 |
ret = register_pernet_subsys(&nfnl_acct_ops); if (ret < 0) { pr_err("nfnl_acct_init: failed to register pernet ops "); goto err_out; } |
941390279
|
497 498 499 500 |
ret = nfnetlink_subsys_register(&nfnl_acct_subsys); if (ret < 0) { pr_err("nfnl_acct_init: cannot register with nfnetlink. "); |
3499abb24
|
501 |
goto cleanup_pernet; |
941390279
|
502 503 |
} return 0; |
3499abb24
|
504 505 506 |
cleanup_pernet: unregister_pernet_subsys(&nfnl_acct_ops); |
941390279
|
507 508 509 510 511 512 |
err_out: return ret; } static void __exit nfnl_acct_exit(void) { |
941390279
|
513 |
nfnetlink_subsys_unregister(&nfnl_acct_subsys); |
3499abb24
|
514 |
unregister_pernet_subsys(&nfnl_acct_ops); |
941390279
|
515 516 517 518 |
} module_init(nfnl_acct_init); module_exit(nfnl_acct_exit); |