diff options
author | Florian Westphal <fw@strlen.de> | 2012-05-07 06:51:45 -0400 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2012-05-09 07:04:57 -0400 |
commit | 0197dee7d3182bb6b6a21955860dfa14fa022d84 (patch) | |
tree | 121237ff7991a5fea8d8ac6202ad6c92834e744e | |
parent | 817e076f61bca3d0270af60632d1fe07cd4919f1 (diff) |
netfilter: hashlimit: byte-based limit mode
can be used e.g. for ingress traffic policing or
to detect when a host/port consumes more bandwidth than expected.
This is done by optionally making cost to mean
"cost per 16-byte-chunk-of-data" instead of "cost per packet".
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
-rw-r--r-- | include/linux/netfilter/xt_hashlimit.h | 10 | ||||
-rw-r--r-- | net/netfilter/xt_hashlimit.c | 116 |
2 files changed, 106 insertions, 20 deletions
diff --git a/include/linux/netfilter/xt_hashlimit.h b/include/linux/netfilter/xt_hashlimit.h index b1925b5925e9..05fe7993dd76 100644 --- a/include/linux/netfilter/xt_hashlimit.h +++ b/include/linux/netfilter/xt_hashlimit.h | |||
@@ -6,7 +6,11 @@ | |||
6 | /* timings are in milliseconds. */ | 6 | /* timings are in milliseconds. */ |
7 | #define XT_HASHLIMIT_SCALE 10000 | 7 | #define XT_HASHLIMIT_SCALE 10000 |
8 | /* 1/10,000 sec period => max of 10,000/sec. Min rate is then 429490 | 8 | /* 1/10,000 sec period => max of 10,000/sec. Min rate is then 429490 |
9 | seconds, or one every 59 hours. */ | 9 | * seconds, or one packet every 59 hours. |
10 | */ | ||
11 | |||
12 | /* packet length accounting is done in 16-byte steps */ | ||
13 | #define XT_HASHLIMIT_BYTE_SHIFT 4 | ||
10 | 14 | ||
11 | /* details of this structure hidden by the implementation */ | 15 | /* details of this structure hidden by the implementation */ |
12 | struct xt_hashlimit_htable; | 16 | struct xt_hashlimit_htable; |
@@ -17,6 +21,10 @@ enum { | |||
17 | XT_HASHLIMIT_HASH_SIP = 1 << 2, | 21 | XT_HASHLIMIT_HASH_SIP = 1 << 2, |
18 | XT_HASHLIMIT_HASH_SPT = 1 << 3, | 22 | XT_HASHLIMIT_HASH_SPT = 1 << 3, |
19 | XT_HASHLIMIT_INVERT = 1 << 4, | 23 | XT_HASHLIMIT_INVERT = 1 << 4, |
24 | XT_HASHLIMIT_BYTES = 1 << 5, | ||
25 | #ifdef __KERNEL__ | ||
26 | XT_HASHLIMIT_MAX = 1 << 6, | ||
27 | #endif | ||
20 | }; | 28 | }; |
21 | 29 | ||
22 | struct hashlimit_cfg { | 30 | struct hashlimit_cfg { |
diff --git a/net/netfilter/xt_hashlimit.c b/net/netfilter/xt_hashlimit.c index b6bbd0630e5f..d0424f9621f2 100644 --- a/net/netfilter/xt_hashlimit.c +++ b/net/netfilter/xt_hashlimit.c | |||
@@ -388,6 +388,18 @@ static void htable_put(struct xt_hashlimit_htable *hinfo) | |||
388 | 388 | ||
389 | #define CREDITS_PER_JIFFY POW2_BELOW32(MAX_CPJ) | 389 | #define CREDITS_PER_JIFFY POW2_BELOW32(MAX_CPJ) |
390 | 390 | ||
391 | /* in byte mode, the lowest possible rate is one packet/second. | ||
392 | * credit_cap is used as a counter that tells us how many times we can | ||
393 | * refill the "credits available" counter when it becomes empty. | ||
394 | */ | ||
395 | #define MAX_CPJ_BYTES (0xFFFFFFFF / HZ) | ||
396 | #define CREDITS_PER_JIFFY_BYTES POW2_BELOW32(MAX_CPJ_BYTES) | ||
397 | |||
398 | static u32 xt_hashlimit_len_to_chunks(u32 len) | ||
399 | { | ||
400 | return (len >> XT_HASHLIMIT_BYTE_SHIFT) + 1; | ||
401 | } | ||
402 | |||
391 | /* Precision saver. */ | 403 | /* Precision saver. */ |
392 | static u32 user2credits(u32 user) | 404 | static u32 user2credits(u32 user) |
393 | { | 405 | { |
@@ -399,21 +411,53 @@ static u32 user2credits(u32 user) | |||
399 | return (user * HZ * CREDITS_PER_JIFFY) / XT_HASHLIMIT_SCALE; | 411 | return (user * HZ * CREDITS_PER_JIFFY) / XT_HASHLIMIT_SCALE; |
400 | } | 412 | } |
401 | 413 | ||
402 | static void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now) | 414 | static u32 user2credits_byte(u32 user) |
403 | { | 415 | { |
404 | dh->rateinfo.credit += (now - dh->rateinfo.prev) * CREDITS_PER_JIFFY; | 416 | u64 us = user; |
405 | if (dh->rateinfo.credit > dh->rateinfo.credit_cap) | 417 | us *= HZ * CREDITS_PER_JIFFY_BYTES; |
406 | dh->rateinfo.credit = dh->rateinfo.credit_cap; | 418 | return (u32) (us >> 32); |
419 | } | ||
420 | |||
421 | static void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now, u32 mode) | ||
422 | { | ||
423 | unsigned long delta = now - dh->rateinfo.prev; | ||
424 | u32 cap; | ||
425 | |||
426 | if (delta == 0) | ||
427 | return; | ||
428 | |||
407 | dh->rateinfo.prev = now; | 429 | dh->rateinfo.prev = now; |
430 | |||
431 | if (mode & XT_HASHLIMIT_BYTES) { | ||
432 | u32 tmp = dh->rateinfo.credit; | ||
433 | dh->rateinfo.credit += CREDITS_PER_JIFFY_BYTES * delta; | ||
434 | cap = CREDITS_PER_JIFFY_BYTES * HZ; | ||
435 | if (tmp >= dh->rateinfo.credit) {/* overflow */ | ||
436 | dh->rateinfo.credit = cap; | ||
437 | return; | ||
438 | } | ||
439 | } else { | ||
440 | dh->rateinfo.credit += delta * CREDITS_PER_JIFFY; | ||
441 | cap = dh->rateinfo.credit_cap; | ||
442 | } | ||
443 | if (dh->rateinfo.credit > cap) | ||
444 | dh->rateinfo.credit = cap; | ||
408 | } | 445 | } |
409 | 446 | ||
410 | static void rateinfo_init(struct dsthash_ent *dh, | 447 | static void rateinfo_init(struct dsthash_ent *dh, |
411 | struct xt_hashlimit_htable *hinfo) | 448 | struct xt_hashlimit_htable *hinfo) |
412 | { | 449 | { |
413 | dh->rateinfo.prev = jiffies; | 450 | dh->rateinfo.prev = jiffies; |
414 | dh->rateinfo.credit = user2credits(hinfo->cfg.avg * hinfo->cfg.burst); | 451 | if (hinfo->cfg.mode & XT_HASHLIMIT_BYTES) { |
415 | dh->rateinfo.cost = user2credits(hinfo->cfg.avg); | 452 | dh->rateinfo.credit = CREDITS_PER_JIFFY_BYTES * HZ; |
416 | dh->rateinfo.credit_cap = dh->rateinfo.credit; | 453 | dh->rateinfo.cost = user2credits_byte(hinfo->cfg.avg); |
454 | dh->rateinfo.credit_cap = hinfo->cfg.burst; | ||
455 | } else { | ||
456 | dh->rateinfo.credit = user2credits(hinfo->cfg.avg * | ||
457 | hinfo->cfg.burst); | ||
458 | dh->rateinfo.cost = user2credits(hinfo->cfg.avg); | ||
459 | dh->rateinfo.credit_cap = dh->rateinfo.credit; | ||
460 | } | ||
417 | } | 461 | } |
418 | 462 | ||
419 | static inline __be32 maskl(__be32 a, unsigned int l) | 463 | static inline __be32 maskl(__be32 a, unsigned int l) |
@@ -519,6 +563,21 @@ hashlimit_init_dst(const struct xt_hashlimit_htable *hinfo, | |||
519 | return 0; | 563 | return 0; |
520 | } | 564 | } |
521 | 565 | ||
566 | static u32 hashlimit_byte_cost(unsigned int len, struct dsthash_ent *dh) | ||
567 | { | ||
568 | u64 tmp = xt_hashlimit_len_to_chunks(len); | ||
569 | tmp = tmp * dh->rateinfo.cost; | ||
570 | |||
571 | if (unlikely(tmp > CREDITS_PER_JIFFY_BYTES * HZ)) | ||
572 | tmp = CREDITS_PER_JIFFY_BYTES * HZ; | ||
573 | |||
574 | if (dh->rateinfo.credit < tmp && dh->rateinfo.credit_cap) { | ||
575 | dh->rateinfo.credit_cap--; | ||
576 | dh->rateinfo.credit = CREDITS_PER_JIFFY_BYTES * HZ; | ||
577 | } | ||
578 | return (u32) tmp; | ||
579 | } | ||
580 | |||
522 | static bool | 581 | static bool |
523 | hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) | 582 | hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) |
524 | { | 583 | { |
@@ -527,6 +586,7 @@ hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) | |||
527 | unsigned long now = jiffies; | 586 | unsigned long now = jiffies; |
528 | struct dsthash_ent *dh; | 587 | struct dsthash_ent *dh; |
529 | struct dsthash_dst dst; | 588 | struct dsthash_dst dst; |
589 | u32 cost; | ||
530 | 590 | ||
531 | if (hashlimit_init_dst(hinfo, &dst, skb, par->thoff) < 0) | 591 | if (hashlimit_init_dst(hinfo, &dst, skb, par->thoff) < 0) |
532 | goto hotdrop; | 592 | goto hotdrop; |
@@ -544,12 +604,17 @@ hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) | |||
544 | } else { | 604 | } else { |
545 | /* update expiration timeout */ | 605 | /* update expiration timeout */ |
546 | dh->expires = now + msecs_to_jiffies(hinfo->cfg.expire); | 606 | dh->expires = now + msecs_to_jiffies(hinfo->cfg.expire); |
547 | rateinfo_recalc(dh, now); | 607 | rateinfo_recalc(dh, now, hinfo->cfg.mode); |
548 | } | 608 | } |
549 | 609 | ||
550 | if (dh->rateinfo.credit >= dh->rateinfo.cost) { | 610 | if (info->cfg.mode & XT_HASHLIMIT_BYTES) |
611 | cost = hashlimit_byte_cost(skb->len, dh); | ||
612 | else | ||
613 | cost = dh->rateinfo.cost; | ||
614 | |||
615 | if (dh->rateinfo.credit >= cost) { | ||
551 | /* below the limit */ | 616 | /* below the limit */ |
552 | dh->rateinfo.credit -= dh->rateinfo.cost; | 617 | dh->rateinfo.credit -= cost; |
553 | spin_unlock(&dh->lock); | 618 | spin_unlock(&dh->lock); |
554 | rcu_read_unlock_bh(); | 619 | rcu_read_unlock_bh(); |
555 | return !(info->cfg.mode & XT_HASHLIMIT_INVERT); | 620 | return !(info->cfg.mode & XT_HASHLIMIT_INVERT); |
@@ -571,14 +636,6 @@ static int hashlimit_mt_check(const struct xt_mtchk_param *par) | |||
571 | struct xt_hashlimit_mtinfo1 *info = par->matchinfo; | 636 | struct xt_hashlimit_mtinfo1 *info = par->matchinfo; |
572 | int ret; | 637 | int ret; |
573 | 638 | ||
574 | /* Check for overflow. */ | ||
575 | if (info->cfg.burst == 0 || | ||
576 | user2credits(info->cfg.avg * info->cfg.burst) < | ||
577 | user2credits(info->cfg.avg)) { | ||
578 | pr_info("overflow, try lower: %u/%u\n", | ||
579 | info->cfg.avg, info->cfg.burst); | ||
580 | return -ERANGE; | ||
581 | } | ||
582 | if (info->cfg.gc_interval == 0 || info->cfg.expire == 0) | 639 | if (info->cfg.gc_interval == 0 || info->cfg.expire == 0) |
583 | return -EINVAL; | 640 | return -EINVAL; |
584 | if (info->name[sizeof(info->name)-1] != '\0') | 641 | if (info->name[sizeof(info->name)-1] != '\0') |
@@ -591,6 +648,26 @@ static int hashlimit_mt_check(const struct xt_mtchk_param *par) | |||
591 | return -EINVAL; | 648 | return -EINVAL; |
592 | } | 649 | } |
593 | 650 | ||
651 | if (info->cfg.mode >= XT_HASHLIMIT_MAX) { | ||
652 | pr_info("Unknown mode mask %X, kernel too old?\n", | ||
653 | info->cfg.mode); | ||
654 | return -EINVAL; | ||
655 | } | ||
656 | |||
657 | /* Check for overflow. */ | ||
658 | if (info->cfg.mode & XT_HASHLIMIT_BYTES) { | ||
659 | if (user2credits_byte(info->cfg.avg) == 0) { | ||
660 | pr_info("overflow, rate too high: %u\n", info->cfg.avg); | ||
661 | return -EINVAL; | ||
662 | } | ||
663 | } else if (info->cfg.burst == 0 || | ||
664 | user2credits(info->cfg.avg * info->cfg.burst) < | ||
665 | user2credits(info->cfg.avg)) { | ||
666 | pr_info("overflow, try lower: %u/%u\n", | ||
667 | info->cfg.avg, info->cfg.burst); | ||
668 | return -ERANGE; | ||
669 | } | ||
670 | |||
594 | mutex_lock(&hashlimit_mutex); | 671 | mutex_lock(&hashlimit_mutex); |
595 | info->hinfo = htable_find_get(net, info->name, par->family); | 672 | info->hinfo = htable_find_get(net, info->name, par->family); |
596 | if (info->hinfo == NULL) { | 673 | if (info->hinfo == NULL) { |
@@ -683,10 +760,11 @@ static int dl_seq_real_show(struct dsthash_ent *ent, u_int8_t family, | |||
683 | struct seq_file *s) | 760 | struct seq_file *s) |
684 | { | 761 | { |
685 | int res; | 762 | int res; |
763 | const struct xt_hashlimit_htable *ht = s->private; | ||
686 | 764 | ||
687 | spin_lock(&ent->lock); | 765 | spin_lock(&ent->lock); |
688 | /* recalculate to show accurate numbers */ | 766 | /* recalculate to show accurate numbers */ |
689 | rateinfo_recalc(ent, jiffies); | 767 | rateinfo_recalc(ent, jiffies, ht->cfg.mode); |
690 | 768 | ||
691 | switch (family) { | 769 | switch (family) { |
692 | case NFPROTO_IPV4: | 770 | case NFPROTO_IPV4: |