From 295ad77ba050a91736fa99e725765dfb788cc116 Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 13 Feb 2011 03:01:43 -0500 Subject: [PATCH] rc, iptables, web UI: QoS: add DSCP matching criteria --- release/src/router/iptables/extensions/Makefile | 2 +- .../src/router/iptables/extensions/libip6t_dscp.c | 172 +++++++++++++++++++++ release/src/router/rc/firewall.c | 25 +++ release/src/router/rc/qos.c | 28 +++- release/src/router/rc/rc.h | 1 + release/src/router/www/qos-classify.asp | 135 +++++++++++++--- 6 files changed, 332 insertions(+), 31 deletions(-) create mode 100644 release/src/router/iptables/extensions/libip6t_dscp.c diff --git a/release/src/router/iptables/extensions/Makefile b/release/src/router/iptables/extensions/Makefile index a52d85a2c8..48fb5cd58d 100644 --- a/release/src/router/iptables/extensions/Makefile +++ b/release/src/router/iptables/extensions/Makefile @@ -23,7 +23,7 @@ ifeq ($(DO_IPV6), 1) PF6_EXT_SLIB:=connmark icmp6 length limit mac mark multiport standard state rt hl web PF6_EXT_SLIB+=tcp udp CONNMARK LOG MARK IMQ TCPMSS REJECT ifeq ($(CONFIG_LINUX26),y) -PF6_EXT_SLIB+=connlimit hashlimit recent NFQUEUE ROUTE HL connbytes iprange webmon +PF6_EXT_SLIB+=connlimit hashlimit recent NFQUEUE ROUTE HL connbytes iprange webmon dscp endif endif diff --git a/release/src/router/iptables/extensions/libip6t_dscp.c b/release/src/router/iptables/extensions/libip6t_dscp.c new file mode 100644 index 0000000000..27ae9bf6c2 --- /dev/null +++ b/release/src/router/iptables/extensions/libip6t_dscp.c @@ -0,0 +1,172 @@ +/* Shared library add-on to iptables for DSCP + * + * (C) 2002 by Harald Welte + * + * This program is distributed under the terms of GNU GPL v2, 1991 + * + * libipt_dscp.c borrowed heavily from libipt_tos.c + * + * --class support added by Iain Barnes + * + * For a list of DSCP codepoints see + * http://www.iana.org/assignments/dscp-registry + * + */ +#include +#include +#include +#include + +#include +#include +#include + +/* This is evil, but it's my code - HW*/ +#include "libipt_dscp_helper.c" + +static void help(void) +{ + printf( +"DSCP match v%s options\n" +"[!] --dscp value Match DSCP codepoint with numerical value\n" +" This value can be in decimal (ex: 32)\n" +" or in hex (ex: 0x20)\n" +"[!] --dscp-class name Match the DiffServ class. This value may\n" +" be any of the BE,EF, AFxx or CSx classes\n" +"\n" +" These two options are mutually exclusive !\n" + , IPTABLES_VERSION +); +} + +static struct option opts[] = { + { "dscp", 1, 0, 'F' }, + { "dscp-class", 1, 0, 'G' }, + { 0 } +}; + +static void +parse_dscp(const char *s, struct xt_dscp_info *dinfo) +{ + unsigned int dscp; + + if (string_to_number(s, 0, 255, &dscp) == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid dscp `%s'\n", s); + + if (dscp > XT_DSCP_MAX) + exit_error(PARAMETER_PROBLEM, + "DSCP `%d` out of range\n", dscp); + + dinfo->dscp = (u_int8_t )dscp; + return; +} + + +static void +parse_class(const char *s, struct xt_dscp_info *dinfo) +{ + unsigned int dscp = class_to_dscp(s); + + /* Assign the value */ + dinfo->dscp = (u_int8_t)dscp; +} + + +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ip6t_entry *entry, + unsigned int *nfcache, + struct ip6t_entry_match **match) +{ + struct xt_dscp_info *dinfo + = (struct xt_dscp_info *)(*match)->data; + + switch (c) { + case 'F': + if (*flags) + exit_error(PARAMETER_PROBLEM, + "DSCP match: Only use --dscp ONCE!"); + check_inverse(optarg, &invert, &optind, 0); + parse_dscp(argv[optind-1], dinfo); + if (invert) + dinfo->invert = 1; + *flags = 1; + break; + + case 'G': + if (*flags) + exit_error(PARAMETER_PROBLEM, + "DSCP match: Only use --dscp-class ONCE!"); + check_inverse(optarg, &invert, &optind, 0); + parse_class(argv[optind - 1], dinfo); + if (invert) + dinfo->invert = 1; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "DSCP match: Parameter --dscp is required"); +} + +static void +print_dscp(u_int8_t dscp, int invert, int numeric) +{ + if (invert) + fputc('!', stdout); + + printf("0x%02x ", dscp); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ip6t_ip6 *ip, + const struct ip6t_entry_match *match, + int numeric) +{ + const struct xt_dscp_info *dinfo = + (const struct xt_dscp_info *)match->data; + printf("DSCP match "); + print_dscp(dinfo->dscp, dinfo->invert, numeric); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void +save(const struct ip6t_ip6 *ip, const struct ip6t_entry_match *match) +{ + const struct xt_dscp_info *dinfo = + (const struct xt_dscp_info *)match->data; + + printf("--dscp "); + print_dscp(dinfo->dscp, dinfo->invert, 1); +} + +static struct ip6tables_match dscp = { + .next = NULL, + .name = "dscp", + .version = IPTABLES_VERSION, + .size = IP6T_ALIGN(sizeof(struct xt_dscp_info)), + .userspacesize = IP6T_ALIGN(sizeof(struct xt_dscp_info)), + .help = &help, + .parse = &parse, + .final_check = &final_check, + .print = &print, + .save = &save, + .extra_opts = opts +}; + +void _init(void) +{ + register_match6(&dscp); +} diff --git a/release/src/router/rc/firewall.c b/release/src/router/rc/firewall.c index 004c8cd9b6..bc41886515 100644 --- a/release/src/router/rc/firewall.c +++ b/release/src/router/rc/firewall.c @@ -180,6 +180,29 @@ void ip6t_write(const char *format, ...) // ----------------------------------------------------------------------------- +int ipt_dscp(const char *v, char *opt) +{ + unsigned int n; + + if (*v == 0) { + *opt = 0; + return 0; + } + + n = strtoul(v, NULL, 0); + if (n > 63) n = 63; + sprintf(opt, " -m dscp --dscp 0x%02X", n); + +#ifdef LINUX26 + modprobe("xt_dscp"); +#else + modprobe("ipt_dscp"); +#endif + return 1; +} + +// ----------------------------------------------------------------------------- + int ipt_ipp2p(const char *v, char *opt) { @@ -1255,12 +1278,14 @@ int start_firewall(void) modprobe_r("xt_length"); modprobe_r("xt_web"); modprobe_r("xt_webmon"); + modprobe_r("xt_dscp"); #else modprobe_r("ipt_layer7"); modprobe_r("ipt_recent"); modprobe_r("ipt_TTL"); modprobe_r("ipt_web"); modprobe_r("ipt_webmon"); + modprobe_r("ipt_dscp"); #endif modprobe_r("ipt_ipp2p"); diff --git a/release/src/router/rc/qos.c b/release/src/router/rc/qos.c index dd4ca7476f..ea194be27b 100644 --- a/release/src/router/rc/qos.c +++ b/release/src/router/rc/qos.c @@ -22,6 +22,7 @@ void ipt_qos(void) char *class_prio; char *ipp2p, *layer7; char *bcount; + char *dscp; int class_num; int proto_num; int ipv6_ok; @@ -61,7 +62,7 @@ void ipt_qos(void) /* - addr_type")) == NULL) break; - i = vstrsep(p, "<", &addr_type, &addr, &proto, &port_type, &port, &ipp2p, &layer7, &bcount, &class_prio, &p); + i = vstrsep(p, "<", &addr_type, &addr, &proto, &port_type, &port, &ipp2p, &layer7, &bcount, &dscp, &class_prio, &p); rule_num++; - if (i == 9) { + if (i == 10) { + // fixup < v1.28.XX55 + class_prio = dscp; + dscp = ""; + } + else if (i == 9) { // fixup < v0.08 // !!! temp class_prio = bcount; bcount = ""; + dscp = ""; } - else if (i != 10) continue; + else if (i != 11) continue; class_num = atoi(class_prio); if ((class_num < 0) || (class_num > 9)) continue; @@ -144,6 +155,14 @@ void ipt_qos(void) } strcpy(end, app); + // dscp + if (ipt_dscp(dscp, s)) { +#ifndef LINUX26 + ipv6_ok = 0; // dscp ipv6 match is not present in K2.4 +#endif + strcat(end, s); + } + // -m connbytes --connbytes x:y --connbytes-dir both --connbytes-mode bytes if (*bcount) { min = strtoul(bcount, &p, 10); @@ -219,7 +238,6 @@ void ipt_qos(void) ip46t_flagged_write(ipv6_ok, "-A %s %s %s", chain, saddr, end); } - } free(buf); diff --git a/release/src/router/rc/rc.h b/release/src/router/rc/rc.h index 8847c665cc..0c22d75193 100644 --- a/release/src/router/rc/rc.h +++ b/release/src/router/rc/rc.h @@ -257,6 +257,7 @@ extern void ip6t_write(const char *format, ...); #define ip46t_cond_write(do_ip6t, args...) ipt_write(args) #endif extern void ipt_addr(char *addr, int maxlen, const char *s, const char *dir); +extern int ipt_dscp(const char *v, char *opt); extern int ipt_ipp2p(const char *v, char *opt); extern int ipt_layer7(const char *v, char *opt); extern void ipt_layer7_inbound(void); diff --git a/release/src/router/www/qos-classify.asp b/release/src/router/www/qos-classify.asp index 556c5c3af0..c51e59847e 100644 --- a/release/src/router/www/qos-classify.asp +++ b/release/src/router/www/qos-classify.asp @@ -38,11 +38,11 @@ width: 40px; } #qg .x1a { - width: 35%; + width: 34%; float: left; } #qg .x1b { - width: 64%; + width: 66%; float: left; } @@ -71,21 +71,31 @@ } #qg .x4a { + width: 58%; float: left; clear: left; - width: 70px; } #qg .x4b { + width: 41%; + float: left; +} +#qg .x5a { + float: left; + clear: left; + width: 70px; +} + +#qg .x5b { float: left; padding: 2px 8px 0 8px; width: 10px; text-align: center; } -#qg .x4c { +#qg .x5c { float: left; width: 70px; } -#qg .x4d { +#qg .x5d { float: left; padding: 2px 0 0 8px; width: 100px; @@ -103,7 +113,16 @@ var abc = ['Highest', 'High', 'Medium', 'Low', 'Lowest', 'A','B','C','D','E']; var ipp2p = [ [0,'IPP2P (disabled)'],[0xFFF,'All IPP2P filters'],[1,'AppleJuice'],[2,'Ares'],[4,'BitTorrent'],[8,'Direct Connect'], - [16,'eDonkey'],[32,'Gnutella'],[64,'Kazaa'],[128,'Mute'],[256,'SoulSeek'],[512,'Waste'],[1024,'WinMX'],[2048,'XDCC']] + [16,'eDonkey'],[32,'Gnutella'],[64,'Kazaa'],[128,'Mute'],[256,'SoulSeek'],[512,'Waste'],[1024,'WinMX'],[2048,'XDCC']]; + +var dscp = [ + ['','DSCP (any)'],['0x00','BE'], + ['0x08','CS1'],['0x10','CS2'],['0x18','CS3'],['0x20','CS4'],['0x28','CS5'],['0x30','CS6'],['0x38','CS7'], + ['0x0a','AF11'],['0x0c','AF12'],['0x0e','AF13'],['0x12','AF21'],['0x14','AF22'],['0x16','AF23'], + ['0x1a','AF31'],['0x1c','AF32'],['0x1e','AF33'],['0x22','AF41'],['0x24','AF42'],['0x26','AF43'], + ['0x2e','EF'],['*','DSCP value']]; +for (i = 1; i < dscp.length - 1; ++i) + dscp[i][1] = 'DSCP Class ' + dscp[i][1]; // <% layer7(); %> layer7.sort(); @@ -118,6 +137,21 @@ var ruleCounter = 0; var qosg = new TomatoGrid(); +function dscpClass(v) +{ + var s, i; + + s = ''; + if (v != '') { + for (i = 1; i < dscp.length - 1; ++i) // skip 1st and last elements + if (dscp[i][0] * 1 == v * 1) { + s = dscp[i][1]; + break; + } + } + return s; +} + qosg.dataToView = function(data) { var b = []; var s, i; @@ -138,7 +172,7 @@ qosg.dataToView = function(data) { if (data[5] != 0) { for (i = 0; i < ipp2p.length; ++i) if (ipp2p[i][0] == data[5]) { - b.push('IPP2P: ' + ipp2p[i][1]) + b.push('IPP2P: ' + ipp2p[i][1]); break; } @@ -147,20 +181,40 @@ qosg.dataToView = function(data) { b.push('L7: ' + data[6]) } + if (data[9] != '') { + s = dscpClass(data[9]); + if (s != '') b.push(s); + else b.push('DSCP Value: ' + data[9]); + } + if (data[7] != '') { b.push('Transferred: ' + data[7] + ((data[8] == '') ? 'KB+' : (' - ' + data[8] + 'KB'))); } - return [b.join('
'), class1[(data[9] * 1) + 1][1], escapeHTML(data[10]), (ruleCounter >= 0) ? ''+ ++ruleCounter : '']; + + return [b.join('
'), class1[(data[10] * 1) + 1][1], escapeHTML(data[11]), (ruleCounter >= 0) ? ''+ ++ruleCounter : '']; } qosg.fieldValuesToData = function(row) { var f = fields.getAll(row); var proto = f[2].value; var dir = f[3].value; + var vdscp = (f[7].value == '*') ? f[8].value : f[7].value; if ((proto != -1) && (proto != 6) && (proto != 17)) dir = 'a'; return [f[0].value, f[0].selectedIndex ? f[1].value : '', proto, dir, (dir != 'a') ? f[4].value : '', - f[5].value, f[6].value, f[7].value, f[8].value, f[9].value, f[10].value]; + f[5].value, f[6].value, f[9].value, f[10].value, + vdscp, f[11].value, f[12].value]; +} + +qosg.dataToFieldValues = function(data) { + var s = ''; + + if (data[9] != '') { + if (dscpClass(data[9]) == '') s = '*'; + else s = data[9].toLowerCase(); + } + + return [data[0], data[1], data[2], data[3], data[4], data[5], data[6], s, data[9], data[7], data[8], data[10], data[11]]; } qosg.resetNewEditor = function() { @@ -172,10 +226,12 @@ qosg.resetNewEditor = function() { f[4].value = ''; f[5].selectedIndex = 0; f[6].selectedIndex = 0; - f[7].value = ''; + f[7].selectedIndex = 0; f[8].value = ''; - f[9].selectedIndex = 5; + f[9].value = ''; f[10].value = ''; + f[11].selectedIndex = 5; + f[12].value = ''; this.enDiFields(this.newEditor); ferror.clearAll(fields.getAll(this.newEditor)); } @@ -201,6 +257,21 @@ qosg.enDiFields = function(row) { f[6].disabled = (f[5].selectedIndex != 0); f[5].disabled = (f[6].selectedIndex != 0); + + f[8].disabled = (f[7].value != '*'); +} + +function v_dscp(e, quiet) +{ + if ((e = E(e)) == null) return 0; + var v = e.value; + if ((!v.match(/^ *(0x)?[0-9A-Fa-f]+ *$/)) || (v < 0) || (v > 63)) { + ferror.set(e, 'Invalid DSCP value. Valid range: 0x00-0x3F', quiet); + return 0; + } + e.value = '0x' + (v * 1).hex(2); + ferror.clear(e); + return 1; } qosg.verifyFields = function(row, quiet) { @@ -221,32 +292,37 @@ qosg.verifyFields = function(row, quiet) { var BMAX = 1024 * 1024; - e = f[7]; + e = f[9]; a = e.value = e.value.trim(); if (a != '') { if (!v_range(e, quiet, 0, BMAX)) return 0; a *= 1; } - e = f[8]; + e = f[10]; b = e.value = e.value.trim(); if (b != '') { b *= 1; if (b >= BMAX) e.value = ''; else if (!v_range(e, quiet, 0, BMAX)) return 0; - if (a == '') f[7].value = a = 0; + if (a == '') f[9].value = a = 0; } else if (a != '') { b = BMAX; } if ((b != '') && (a >= b)) { - ferror.set(f[7], 'Invalid range', quiet); + ferror.set(f[9], 'Invalid range', quiet); return 0; } - if (!v_nodelim(f[10], quiet, 'Description', 1)) return 0; - return v_length(f[10], quiet); + if (f[7].value == '*') { + if (!v_dscp(f[8], quiet)) return 0; + } + else f[8].value = f[7].value; + + if (!v_nodelim(f[12], quiet, 'Description', 1)) return 0; + return v_length(f[12], quiet); } qosg.setup = function() { @@ -262,16 +338,20 @@ qosg.setup = function() { { type: 'select', options: [[0,'Any Address'],[1,'Dst IP'],[2,'Src IP'],[3,'Src MAC']], prefix: '
', suffix: '
' }, { type: 'text', prefix: '
', suffix: '
' }, + { type: 'select', prefix: '
', suffix: '
', options: a }, { type: 'select', prefix: '
', suffix: '
', options: [['a','Any Port'],['d','Dst Port'],['s','Src Port'],['x','Src or Dst']] }, - { type: 'text', prefix: '
', suffix: '
' }, + { type: 'text', prefix: '
', suffix: '
' }, + { type: 'select', prefix: '
', suffix: '
', options: ipp2p }, { type: 'select', prefix: '
', suffix: '
', options: layer7 }, - { type: 'text', prefix: '
', suffix: '
' }, - { type: 'text', prefix: '
-
', suffix: '
KB Transferred
' } + { type: 'select', prefix: '
', suffix: '
', options: dscp }, + { type: 'text', prefix: '
', suffix: '
' }, + { type: 'text', prefix: '
', suffix: '
' }, + { type: 'text', prefix: '
-
', suffix: '
KB Transferred
' } ] }, { type: 'select', options: class1, vtop: 1 }, { type: 'text', maxlen: 32, vtop: 1 } @@ -279,19 +359,25 @@ qosg.setup = function() { this.headerSet(['Match Rule', 'Class', 'Description', '#']); -// addr_type < addr < proto < port_type < port < ipp2p < L7 < bcount < class < desc +// addr_type < addr < proto < port_type < port < ipp2p < L7 < bcount < dscp < class < desc a = nvram.qos_orules.split('>'); for (i = 0; i < a.length; ++i) { b = a[i].split('<'); + if (b.length == 9) { // fixup < 0.08 !!! temp - b.splice(7, 0, '', ''); + b.splice(7, 0, '', '', ''); } else if (b.length == 10) { + // fixup < 1.28.xx55 + b.splice(8, 0, ''); + } + + if (b.length == 11) { c = b[7].split(':'); b.splice(7, 1, c[0], (c.length == 1) ? '' : c[1]); - b[10] = unescape(b[10]); + b[11] = unescape(b[11]); } else continue; b[4] = b[4].replace(/:/g, '-'); @@ -321,7 +407,7 @@ function save() b = c[i].slice(0); b[4] = b[4].replace(/-/g, ':'); b.splice(7, 2, (b[7] == '') ? '' : [b[7],b[8]].join(':')); - b[9] = escapeD(b[9]); + b[10] = escapeD(b[10]); a.push(b.join('<')); } fom.qos_orules.value = a.join('>'); @@ -381,4 +467,3 @@ else { - -- 2.11.4.GIT