Date: Sat, 16 Jul 2005 18:02:19 +0300 From: "Chris Dionissopoulos" <dionch@freemail.gr> To: <freebsd-ipfw@freebsd.org>, <freebsd-net@freebsd.org> Subject: Traffic quota features in IPFW Message-ID: <001c01c58a17$5dbe4a40$0100000a@R3B>
next in thread | raw e-mail | index | archive | help
[-- Attachment #1 --] Hi ppl, ( and sorry for cross posting) I review Andrey's Elsukov patch for adding "bound" support in ipfw, and i decide to push a little forward this feature. You can see the whole picture in there: http://www.freebsd.org/cgi/query-pr.cgi?pr=80642 and there: http://butcher.heavennet.ru/ In my patch, 3 new options are added: 1. "below <VALUE>" (which is the same option as Andrey's "bound" option, I just rename it) 2. "above <VALUE>" which is the oposite option of "below". Match rules when the counter is above <value> 3. "check-quota" (which is the same option as Andrey's "check-bound" , but now applies to both "above" and "below" options). Notes: 1. Patch is against releng_6. 2. I also include a more compicated example which is (IMHO) a complete traffic quota+shaping solution for a small (or not so small) ISP. 3. For installation, follow the instructions Adrey publish in his webspace: http://butcher.heavennet.ru/ 4. Patch doesn't breaks ipfw ABI (today) , because adds new options at the end of list. If you apply this patch in a month or so, I cannot guarantee success. 5. Please test, and send me your feedbacks. I 'll be happy if you find usefull these features and if any developer commits this patch in current or releng_6 branch. Chris. ____________________________________________________________________ http://www.freemail.gr - δωρεάν υπηρεσία ηλεκτρονικού ταχυδρομείου. http://www.freemail.gr - free email service for the Greek-speaking. [-- Attachment #2 --] --- sys/netinet/ip_fw.h.orig Sat Jul 16 14:55:58 2005 +++ sys/netinet/ip_fw.h Sat Jul 16 15:08:37 2005 @@ -154,6 +154,13 @@ O_NGTEE, /* copy to ng_ipfw */ O_IP4, + + /* + * Traffic quota options + */ + O_QBELOW, /* u64 = uplimit in bytes */ + O_QABOVE, /* u64 = downlimit in bytes */ + O_CHECK_QUOTA, /* u16 = rule number */ O_LAST_OPCODE /* not an opcode! */ }; @@ -230,6 +237,14 @@ } ipfw_insn_u32; /* + * This is used to store 64-bit quota value. + */ +typedef struct _ipfw_insn_u64 { + ipfw_insn o; + u_int64_t quota; +} ipfw_insn_u64; + +/* * This is used to store IP addr-mask pairs. */ typedef struct _ipfw_insn_ip { @@ -351,12 +366,17 @@ * * When assembling instruction, remember the following: * + * + if a rule has a "quota" option, then the first instruction + * (at r->cmd) MUST BE an O_QBELOW|O_QABOVE * + if a rule has a "keep-state" (or "limit") option, then the * first instruction (at r->cmd) MUST BE an O_PROBE_STATE * + if a rule has a "log" option, then the first action * (at ACTION_PTR(r)) MUST be O_LOG * + if a rule has an "altq" option, it comes after "log" * + * + * NOTE: actually, O_PROB instruction may be first too. But O_QBELOW|O_QABOVE + * MUST BE always first (at r->cmd). * NOTE: we use a simple linked list of rules because we never need * to delete a rule without scanning the list. We do not use * queue(3) macros for portability and readability. --- sys/netinet/ip_fw2.c.orig Sat Jul 16 14:55:58 2005 +++ sys/netinet/ip_fw2.c Sat Jul 16 17:06:19 2005 @@ -2251,6 +2251,36 @@ * logic to deal with F_NOT and F_OR flags associated * with the opcode. */ + case O_QBELOW: + match = (f->bcnt < ((ipfw_insn_u64 *)cmd)->quota); + break; + + case O_QABOVE: + match = (f->bcnt > ((ipfw_insn_u64 *)cmd)->quota); + break; + + case O_CHECK_QUOTA: + { + struct ip_fw* rule; + for (rule = f->next; + rule && cmd->arg1 >= rule->rulenum; + rule = rule->next) + if (rule->rulenum == cmd->arg1) + switch (rule->cmd->opcode) { + case O_QBELOW: + match = (rule->bcnt < + ((ipfw_insn_u64 *)(rule->cmd))->quota); + break; + case O_QABOVE: + match = (rule->bcnt > + ((ipfw_insn_u64 *)(rule->cmd))->quota); + break; + default: + break; + } + } + break; + case O_NOP: match = 1; break; @@ -3373,6 +3403,7 @@ case O_EXT_HDR: case O_IP6: case O_IP4: + case O_CHECK_QUOTA: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; break; @@ -3388,6 +3419,17 @@ case O_ICMPTYPE: if (cmdlen != F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; + break; + + case O_QBELOW: + case O_QABOVE: + if (cmdlen != F_INSN_SIZE(ipfw_insn_u64)) + goto bad_size; + if (cmd != rule->cmd) { + printf("ipfw: bogus rule, opcode %d must be first\n", + cmd->opcode); + return EINVAL; + } break; case O_LIMIT: --- sbin/ipfw/ipfw2.c.orig Sat Jul 16 15:21:06 2005 +++ sbin/ipfw/ipfw2.c Sat Jul 16 17:11:42 2005 @@ -73,6 +73,8 @@ show_sets, /* display rule sets */ test_only, /* only check syntax */ comment_only, /* only print action and comment */ + not_humanval, /* don't use human-readable unit suffixes + when show boundary values */ verbose; #define IP_MASK_ALL 0xffffffff @@ -277,6 +279,10 @@ TOK_SRCIP6, TOK_IPV4, + + TOK_QBELOW, + TOK_QABOVE, + TOK_CHECK_QUOTA, }; struct _s_x dummynet_params[] = { @@ -404,6 +410,9 @@ { "src-ipv6", TOK_SRCIP6}, { "src-ip6", TOK_SRCIP6}, { "//", TOK_COMMENT }, + { "below", TOK_QBELOW}, + { "above", TOK_QABOVE}, + { "check-quota", TOK_CHECK_QUOTA}, { "not", TOK_NOT }, /* pseudo option */ { "!", /* escape ? */ TOK_NOT }, /* pseudo option */ @@ -1636,6 +1645,10 @@ flags |= HAVE_PROTO; break; + case O_QBELOW: + case O_QABOVE: + break; + default: /*options ... */ if (!(cmd->len & (F_OR|F_NOT))) if (((cmd->opcode == O_IP6) && @@ -1857,6 +1870,10 @@ case O_EXT_HDR: print_ext6hdr( (ipfw_insn *) cmd ); break; + + case O_CHECK_QUOTA: + printf(" check-quota %d", cmd->arg1); + break; default: printf(" [opcode %d len %d]", @@ -1872,6 +1889,28 @@ } } show_prerequisites(&flags, HAVE_IP, 0); + + if (rule->cmd->opcode == O_QBELOW || rule->cmd->opcode == O_QABOVE) { + uint64_t bound = ((ipfw_insn_u64 *)(rule->cmd))->quota; + if (rule->cmd->opcode == O_QBELOW) + printf(" below "); + else + printf(" above "); + if (!not_humanval) { + if ((bound >> 10) && !(bound & 0x2FF)) { + if ((bound >> 20) && !(bound & 0xFFFFF)) { + if ((bound >> 30) && !(bound & 0x3FFFFFFF)) + printf("%uGB", bound >> 30); + else + printf("%uMB", bound >> 20); + } else + printf("%uKB", bound >> 10); + } else + printf("%uB", bound); + } else + printf("%u", bound); + } + if (comment) printf(" // %s", comment); printf("\n"); @@ -2515,6 +2554,9 @@ " icmp6types LIST | ext6hdr LIST | flow-id N[,N] |\n" " mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n" " setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n" +" tcpdatalen LIST | below VALUE | above VALUE | check-quota NUM |\n" +" verrevpath | versrcreach | antispoof\n" + " tcpdatalen LIST | verrevpath | versrcreach | antispoof\n" ); exit(0); @@ -3677,7 +3719,7 @@ * various flags used to record that we entered some fields. */ ipfw_insn *have_state = NULL; /* check-state or keep-state */ - ipfw_insn *have_log = NULL, *have_altq = NULL; + ipfw_insn *have_log = NULL, *have_altq = NULL, *have_quota = NULL; size_t len; int i; @@ -4494,6 +4536,66 @@ ac = 0; break; + case TOK_QBELOW: + NEED1("below requires numeric value"); + if (open_par) + errx(EX_USAGE, "below cannot be part " + "of an or block"); + if (have_quota) + errx(EX_USAGE, "only one of below|above is allowed"); + if (cmd->len & F_NOT) + errx(EX_USAGE, + "\"not\" not allowed with below option"); + { + char *end = NULL; + uint64_t bound = strtoull(*av, &end, 0); + if (bound) + switch (*end){ + case 'G': bound *= 1024; + case 'M': bound *= 1024; + case 'K': bound *= 1024; + }; + cmd->opcode = O_QBELOW; + ((ipfw_insn_u64 *)cmd)->quota = bound; + cmd->len = F_INSN_SIZE(ipfw_insn_u64) & F_LEN_MASK; + have_quota = cmd; + ac--; av++; + } + break; + + case TOK_QABOVE: + NEED1("above requires numeric value"); + if (open_par) + errx(EX_USAGE, "above cannot be part " + "of an or block"); + if (have_quota) + errx(EX_USAGE, "only one of below|above is allowed"); + if (cmd->len & F_NOT) + errx(EX_USAGE, + "\"not\" not allowed with above option"); + { + char *end = NULL; + uint64_t bound = strtoull(*av, &end, 0); + if (bound) + switch (*end){ + case 'G': bound *= 1024; + case 'M': bound *= 1024; + case 'K': bound *= 1024; + }; + cmd->opcode = O_QABOVE; + ((ipfw_insn_u64 *)cmd)->quota = bound; + cmd->len = F_INSN_SIZE(ipfw_insn_u64) & F_LEN_MASK; + have_quota = cmd; + ac--; av++; + } + break; + + case TOK_CHECK_QUOTA: + NEED1("check-quota requires rule number"); + fill_cmd(cmd, O_CHECK_QUOTA, 0, strtoul(*av, NULL, 0)); + ac--; av++; + break; + default: errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s); } @@ -4506,6 +4608,8 @@ done: /* * Now copy stuff into the rule. + * If we have a quota option, the first instruction MUST BE + * a O_QBELOW or O_QABOVE. * If we have a keep-state option, the first instruction * must be a PROBE_STATE (which is generated here). * If we have a LOG option, it was stored as the first command, @@ -4514,7 +4618,15 @@ dst = (ipfw_insn *)rule->cmd; /* - * First thing to write into the command stream is the match probability. + * First write into the command stream quota instruction + */ + if (have_quota) { + bcopy(have_quota, dst, F_LEN(have_quota) * sizeof(uint32_t)); + dst = next_cmd(dst); + } + + /* + * write the match probability */ if (match_prob != 1) { /* 1 means always match */ dst->opcode = O_PROB; @@ -4531,7 +4643,8 @@ dst = next_cmd(dst); } /* - * copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ + * copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, + * O_QBELOW, O_QABOVE */ for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) { i = F_LEN(src); @@ -4541,6 +4654,8 @@ case O_KEEP_STATE: case O_LIMIT: case O_ALTQ: + case O_QBELOW: + case O_QABOVE: break; default: bcopy(src, dst, i * sizeof(uint32_t)); @@ -4848,7 +4963,7 @@ save_av = av; optind = optreset = 0; - while ((ch = getopt(ac, av, "abcdefhnNqs:STtv")) != -1) + while ((ch = getopt(ac, av, "abcdefhHnNqs:STtv")) != -1) switch (ch) { case 'a': do_acct = 1; @@ -4879,6 +4994,10 @@ free_args(save_ac, save_av); help(); break; /* NOTREACHED */ + + case 'H': /* don't use human-readable output */ + not_humanval = 1; + break; case 'n': test_only = 1; [-- Attachment #3 --] Example: We will enforce traffic shaping and traffic quota in a client's network behind a freebsd gateway. Definitions/policy: 1. clients network: 1.1.1.0/24. 2. Quota policy: unlimited clients: 1.1.1.0/27 100MB/day clients: 1.1.1.32/27 ipfw-set:2 ipfw-range:1000-9999 1GB/week clients: 1.1.1.64/26 ipfw-set:3 ipfw-range:10000-19999 10GB/month clients: 1.1.1.128/25 ipfw-set:4 ipfw-range:20000-29999 3. Shaping policy: 1.1.1.0/27 unlimited 1.1.1.32/27 100Mbps in/out 1.1.1.64/26 10Mbps in/out 1.1.1.128/25 1Mbps in/out quota exceeded 64Kbps in/out ipfw.sh ======= #!/bin/sh ipfw = "/sbin/ipfw" qos = "40000" allow = "65000" lan="em0" wan="em1" # ****************** # * QOS definition * # ****************** # quota exceeded pipes: ${ipfw} pipe 1 config bw 64Kbit/s mask dst-ip 0x000000ff ${ipfw} pipe 2 config bw 64Kbit/s mask src-ip 0x000000ff # 1MB pipes: ${ipfw} pipe 3 config bw 1Mbit/s mask dst-ip 0x000000ff ${ipfw} pipe 4 config bw 1Mbit/s mask src-ip 0x000000ff # 10MB pipes: ${ipfw} pipe 5 config bw 10Mbit/s mask dst-ip 0x000000ff ${ipfw} pipe 6 config bw 10Mbit/s mask src-ip 0x000000ff # 100MB pipes: ${ipfw} pipe 7 config bw 100Mbit/s mask dst-ip 0x000000ff ${ipfw} pipe 8 config bw 100Mbit/s mask src-ip 0x000000ff # ************************* # * RECEIVE Without Quota * # ************************* ${ipfw} add 100 allow ip from any to any in recv ${lan} ${ipfw} add 200 allow ip from any to any in recv ${wan} # *********************** # * 100MB/DAY both ways * # *********************** ${ipfw} add 1000 set 2 allow ip from any to 1.1.1.32/32 out xmit ${lan} check-quota 1001 ${ipfw} add 1001 set 2 skipto ${qos} ip from 1.1.1.32/32 to any out xmit ${wan} above 100M ${ipfw} add 1002 set 2 allow ip from any to 1.1.1.33/32 out xmit ${lan} check-quota 1003 ${ipfw} add 1003 set 2 skipto ${qos} ip from 1.1.1.33/32 to any out xmit ${wan} above 100M .... ${ipfw} add 1062 set 2 allow ip from any to 1.1.1.63/32 out xmit ${lan} check-quota 1063 ${ipfw} add 1063 set 2 skipto ${qos} ip from 1.1.1.63/32 to any out xmit ${wan} above 100M ${ipfw} add 9999 skipto ${allow} pipe 1 ip from any to 1.1.1.32/27 out xmit ${lan} ${ipfw} add 9999 skipto ${allow} pipe 2 ip from 1.1.1.32/27 to any out xmit ${wan} # ********************** # * 1GB/WEEK both ways * # ********************** ${ipfw} add 10000 set 3 allow ip from any to 1.1.1.64/32 out xmit ${lan} check-quota 10001 ${ipfw} add 10001 set 3 skipto ${qos} ip from 1.1.1.64/32 to any out xmit ${wan} above 1G ${ipfw} add 10002 set 3 allow ip from any to 1.1.1.65/32 out xmit ${lan} check-quota 10003 ${ipfw} add 10003 set 3 skipto ${qos} ip from 1.1.1.65/32 to any out xmit ${wan} above 1G .... ${ipfw} add 10126 set 3 allow ip from any to 1.1.1.127/32 out xmit ${lan} check-quota 10063 ${ipfw} add 10127 set 3 skipto ${qos} ip from 1.1.1.127/32 to any out xmit ${wan} above 1G ${ipfw} add 19999 skipto ${allow} pipe 1 ip from any to 1.1.1.64/26 out xmit ${lan} ${ipfw} add 19999 skipto ${allow} pipe 2 ip from 1.1.1.64/26 to any out xmit ${wan} # *********************** # * 10GB/MONTH both ways* # *********************** ${ipfw} add 20000 set 4 allow ip from any to 1.1.1.128/32 out xmit ${lan} check-quota 20001 ${ipfw} add 20001 set 4 skipto ${qos} ip from 1.1.1.128/32 to any out xmit ${wan} above 10G ${ipfw} add 20002 set 4 allow ip from any to 1.1.1.129/32 out xmit ${lan} check-quota 20003 ${ipfw} add 20003 set 4 skipto ${qos} ip from 1.1.1.129/32 to any out xmit ${wan} above 10G .... ${ipfw} add 20254 set 4 allow ip from any to 1.1.1.255/32 out xmit ${lan} check-quota 20255 ${ipfw} add 20255 set 4 skipto ${qos} ip from 1.1.1.255/32 to any out xmit ${wan} above 10G ${ipfw} add 29999 skipto ${allow} pipe 1 ip from any to 1.1.1.128/25 out xmit ${lan} ${ipfw} add 29999 skipto ${allow} pipe 2 ip from 1.1.1.128/25 to any out xmit ${wan} # ************* # * QOS * # ************* # 1.1.1.128/25 each of them has 1MBps in and 1Mbps out shaping ${ipfw} add ${qos} skipto ${allow} pipe 3 ip from any to 1.1.1.128/25 out xmit ${lan} ${ipfw} add ${qos} skipto ${allow} pipe 4 ip from 1.1.1.128/25 to any out xmit ${wan} # 1.1.1.64/26 each of them has 10MBps in and 10Mbps out shaping ${ipfw} add ${qos} skipto ${allow} pipe 5 ip from any to 1.1.1.64/26 out xmit ${lan} ${ipfw} add ${qos} skipto ${allow} pipe 6 ip from 1.1.1.64/26 to any out xmit ${wan} # 1.1.1.32/32 each of them has 100MBps in and 100Mbps out shaping ${ipfw} add ${qos} skipto ${allow} pipe 7 ip from any to 1.1.1.32/27 out xmit ${lan} ${ipfw} add ${qos} skipto ${allow} pipe 8 ip from 1.1.1.32/27 to any out xmit ${wan} # ********* # * allow * # ********* ${ipfw} add ${allow} allow ip from any to any /etc/crontab: ============= # Perform daily/weekly/monthly ipfw counter reset. 0 0 * * * root /sbin/ipfw zero set 2 0 0 * * 0 root /sbin/ipfw zero set 3 0 0 0 * * root /sbin/ipfw zero set 4
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?001c01c58a17$5dbe4a40$0100000a>
