Skip site navigation (1)Skip section navigation (2)
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>