rc, iptables, web UI: QoS: add DSCP matching criteria
[tomato.git] / release / src / router / rc / qos.c
blobea194be27b7ff85f6bc71ff9e27990152e7fd8a2
1 /*
3 Tomato Firmware
4 Copyright (C) 2006-2009 Jonathan Zarate
6 */
8 #include "rc.h"
10 #include <sys/stat.h>
13 // in mangle table
14 void ipt_qos(void)
16 char *buf;
17 char *g;
18 char *p;
19 char *addr_type, *addr;
20 char *proto;
21 char *port_type, *port;
22 char *class_prio;
23 char *ipp2p, *layer7;
24 char *bcount;
25 char *dscp;
26 int class_num;
27 int proto_num;
28 int ipv6_ok;
29 int i;
30 char sport[192];
31 char saddr[192];
32 char end[256];
33 char s[32];
34 char app[128];
35 int inuse;
36 const char *chain;
37 unsigned long min;
38 unsigned long max;
39 unsigned long prev_max;
40 int gum;
41 const char *qface;
42 int sizegroup;
43 int class_flag;
44 int rule_num;
46 if (!nvram_get_int("qos_enable")) return;
48 inuse = 0;
49 ipv6_ok = 1;
50 gum = 0x100;
51 sizegroup = 0;
52 prev_max = 0;
53 rule_num = 0;
55 ip46t_write(
56 ":QOSO - [0:0]\n"
57 "-A QOSO -j CONNMARK --restore-mark --mask 0xff\n"
58 "-A QOSO -m connmark ! --mark 0/0x0f00 -j RETURN\n");
60 g = buf = strdup(nvram_safe_get("qos_orules"));
61 while (g) {
65 addr_type<addr<proto<port_type<port<ipp2p<L7<bcount<dscp<class_prio<desc
67 addr_type:
68 0 = any
69 1 = dest ip
70 2 = src ip
71 3 = src mac
72 addr:
73 ip/mac if addr_type == 1-3
74 proto:
75 0-65535 = protocol
76 -1 = tcp or udp
77 -2 = any protocol
78 port_type:
79 if proto == -1,tcp,udp:
80 d = dest
81 s = src
82 x = both
83 a = any
84 port:
85 port # if proto == -1,tcp,udp
86 bcount:
87 min:max
88 blank = none
89 dscp:
90 empty - any
91 numeric (0:63) - dscp value
92 afXX, csX, be, ef - dscp class
93 class_prio:
94 0-8
95 -1 = disabled
99 if ((p = strsep(&g, ">")) == NULL) break;
100 i = vstrsep(p, "<", &addr_type, &addr, &proto, &port_type, &port, &ipp2p, &layer7, &bcount, &dscp, &class_prio, &p);
101 rule_num++;
102 if (i == 10) {
103 // fixup < v1.28.XX55
104 class_prio = dscp;
105 dscp = "";
107 else if (i == 9) {
108 // fixup < v0.08 // !!! temp
109 class_prio = bcount;
110 bcount = "";
111 dscp = "";
113 else if (i != 11) continue;
115 class_num = atoi(class_prio);
116 if ((class_num < 0) || (class_num > 9)) continue;
118 i = 1 << class_num;
119 ++class_num;
121 if ((inuse & i) == 0) {
122 inuse |= i;
125 ipv6_ok = 1;
126 class_flag = gum;
128 // mac or ip address
129 if ((*addr_type == '1') || (*addr_type == '2')) { // match ip
130 ipt_addr(saddr, sizeof(saddr), addr, (*addr_type == '1') ? "dst" : "src");
131 #ifdef TCONFIG_IPV6
132 // if it appears to be ipv4 dotted decimal or IPv4 range,
133 // don't apply it to ip6tables; otherwise assume it's ok for both.
134 if (sscanf(addr, "%[0-9.]-%[0-9.]", s, s) == 2 || sscanf(addr, "%[0-9.]", s) == 1)
135 ipv6_ok = 0;
136 #endif
138 else if (*addr_type == '3') { // match mac
139 sprintf(saddr, "-m mac --mac-source %s", addr); // (-m mac modified, returns !match in OUTPUT)
141 else {
142 saddr[0] = 0;
146 if (ipt_ipp2p(ipp2p, app)) ipv6_ok = 0;
147 else ipt_layer7(layer7, app);
148 if (app[0]) {
149 ipv6_ok = 0; // temp: l7 not working either!
150 class_flag = 0x100;
151 // IPP2P and L7 rules may need more than one packet before matching
152 // so port-based rules that come after them in the list can't be sticky
153 // or else these rules might never match.
154 gum = 0;
156 strcpy(end, app);
158 // dscp
159 if (ipt_dscp(dscp, s)) {
160 #ifndef LINUX26
161 ipv6_ok = 0; // dscp ipv6 match is not present in K2.4
162 #endif
163 strcat(end, s);
166 // -m connbytes --connbytes x:y --connbytes-dir both --connbytes-mode bytes
167 if (*bcount) {
168 min = strtoul(bcount, &p, 10);
169 if (*p != 0) {
170 strcat(end, " -m connbytes --connbytes-mode bytes --connbytes-dir both --connbytes ");
171 ++p;
172 if (*p == 0) {
173 sprintf(end + strlen(end), "%lu:", min * 1024);
175 else {
176 max = strtoul(p, NULL, 10);
177 sprintf(end + strlen(end), "%lu:%lu", min * 1024, (max * 1024) - 1);
178 if (gum) {
179 if (!sizegroup) {
180 // Create table of connbytes sizes, pass appropriate connections there
181 // and only continue processing them if mark was wiped
182 // FIX: if the current rule isn't ipv6_ok ?
183 ip46t_write(
184 ":QOSSIZE - [0:0]\n"
185 "-I QOSO 3 -m connmark ! --mark 0/0xff000 -j QOSSIZE\n"
186 "-I QOSO 4 -m connmark ! --mark 0/0xff000 -j RETURN\n");
188 if (max != prev_max && sizegroup<255) {
189 class_flag = ++sizegroup << 12;
190 prev_max = max;
191 ip46t_flagged_write( ipv6_ok,
192 "-A QOSSIZE -m connmark --mark 0x%x/0xff000"
193 " -m connbytes --connbytes-mode bytes --connbytes-dir both --connbytes %lu: -j CONNMARK --set-return 0x00000/0xFF\n",
194 (sizegroup << 12), (max * 1024));
196 else {
197 class_flag = sizegroup << 12;
203 else {
204 bcount = "";
208 chain = "QOSO";
209 class_num |= class_flag;
210 class_num |= rule_num << 20;
211 sprintf(end + strlen(end), " -j CONNMARK --set-return 0x%x/0xFF\n", class_num);
213 // protocol & ports
214 proto_num = atoi(proto);
215 if (proto_num > -2) {
216 if ((proto_num == 6) || (proto_num == 17) || (proto_num == -1)) {
217 if (*port_type != 'a') {
218 if ((*port_type == 'x') || (strchr(port, ','))) {
219 // dst-or-src port matches, and anything with multiple lists "," use multiport
220 sprintf(sport, "-m multiport --%sports %s", (*port_type == 's') ? "s" : ((*port_type == 'd') ? "d" : ""), port);
222 else {
223 // single or simple x:y range, use built-in tcp/udp match
224 sprintf(sport, "--%sport %s", (*port_type == 's') ? "s" : ((*port_type == 'd') ? "d" : ""), port);
227 else {
228 sport[0] = 0;
230 if (proto_num != 6) ip46t_flagged_write(ipv6_ok, "-A %s -p %s %s %s %s", chain, "udp", sport, saddr, end);
231 if (proto_num != 17) ip46t_flagged_write(ipv6_ok, "-A %s -p %s %s %s %s", chain, "tcp", sport, saddr, end);
233 else {
234 ip46t_flagged_write(ipv6_ok, "-A %s -p %d %s %s", chain, proto_num, saddr, end);
237 else { // any protocol
238 ip46t_flagged_write(ipv6_ok, "-A %s %s %s", chain, saddr, end);
242 free(buf);
244 qface = wanfaces.iface[0].name;
246 i = nvram_get_int("qos_default");
247 if ((i < 0) || (i > 9)) i = 3; // "low"
248 class_num = i + 1;
249 class_num |= 0xFF00000; // use rule_num=255 for default
250 ip46t_write("-A QOSO -j CONNMARK --set-return 0x%x\n", class_num);
252 ipt_write(
253 "-A FORWARD -o %s -j QOSO\n"
254 "-A OUTPUT -o %s -j QOSO\n",
255 qface, qface);
257 #ifdef TCONFIG_IPV6
258 ip6t_write(
259 "-A FORWARD -o %s -j QOSO\n"
260 "-A OUTPUT -o %s -j QOSO\n",
261 wan6face, wan6face);
262 #endif
264 inuse |= (1 << i) | 1; // default and highest are always built
265 sprintf(s, "%d", inuse);
266 nvram_set("qos_inuse", s);
269 g = buf = strdup(nvram_safe_get("qos_irates"));
270 for (i = 0; i < 10; ++i) {
271 if ((!g) || ((p = strsep(&g, ",")) == NULL)) continue;
272 if ((inuse & (1 << i)) == 0) continue;
273 if (atoi(p) > 0) {
274 ipt_write("-A PREROUTING -i %s -j CONNMARK --restore-mark --mask 0xff\n", qface);
275 #ifdef TCONFIG_IPV6
276 ip6t_write("-A PREROUTING -i %s -j CONNMARK --restore-mark --mask 0xff\n", wan6face);
277 #endif
278 break;
281 free(buf);
286 static const char *qosfn = "/etc/qos";
288 static unsigned calc(unsigned bw, unsigned pct)
290 unsigned n = ((unsigned long)bw * pct) / 100;
291 return (n < 2) ? 2 : n;
294 void start_qos(void)
296 int i;
297 char *buf, *g, *p;
298 unsigned int rate;
299 unsigned int ceil;
300 unsigned int bw;
301 unsigned int mtu;
302 unsigned int r2q;
303 FILE *f;
304 int x;
305 int inuse;
306 char s[256];
307 int first;
308 char burst_root[32];
309 char burst_leaf[32];
312 // move me?
313 x = nvram_get_int("ne_vegas");
314 #ifdef LINUX26
315 if (x) {
316 char alpha[10], beta[10], gamma[10];
317 sprintf(alpha, "alpha=%d", nvram_get_int("ne_valpha"));
318 sprintf(beta, "beta=%d", nvram_get_int("ne_vbeta"));
319 sprintf(gamma, "gamma=%d", nvram_get_int("ne_vgamma"));
320 modprobe("tcp_vegas", alpha, beta, gamma);
321 f_write_string("/proc/sys/net/ipv4/tcp_congestion_control", "vegas", 0, 0);
323 else {
324 modprobe_r("tcp_vegas");
325 f_write_string("/proc/sys/net/ipv4/tcp_congestion_control", "", FW_NEWLINE, 0);
327 #else
328 f_write_string("/proc/sys/net/ipv4/tcp_vegas_cong_avoid", x ? "1" : "0", 0, 0);
329 if (x) {
330 f_write_string("/proc/sys/net/ipv4/tcp_vegas_alpha", nvram_safe_get("ne_valpha"), 0, 0);
331 f_write_string("/proc/sys/net/ipv4/tcp_vegas_beta", nvram_safe_get("ne_vbeta"), 0, 0);
332 f_write_string("/proc/sys/net/ipv4/tcp_vegas_gamma", nvram_safe_get("ne_vgamma"), 0, 0);
334 #endif
336 if (!nvram_get_int("qos_enable")) return;
338 if ((f = fopen(qosfn, "w")) == NULL) return;
340 i = nvram_get_int("qos_burst0");
341 if (i > 0) sprintf(burst_root, "burst %dk", i);
342 else burst_root[0] = 0;
343 i = nvram_get_int("qos_burst1");
344 if (i > 0) sprintf(burst_leaf, "burst %dk", i);
345 else burst_leaf[0] = 0;
347 mtu = strtoul(nvram_safe_get("wan_mtu"), NULL, 10);
348 bw = strtoul(nvram_safe_get("qos_obw"), NULL, 10);
350 r2q = 10;
351 if ((bw * 1000) / (8 * r2q) < mtu) {
352 r2q = (bw * 1000) / (8 * mtu);
353 if (r2q < 1) r2q = 1;
354 } else if ((bw * 1000) / (8 * r2q) > 60000) {
355 r2q = (bw * 1000) / (8 * 60000) + 1;
358 fprintf(f,
359 "#!/bin/sh\n"
360 "I=%s\n"
361 "TQA=\"tc qdisc add dev $I\"\n"
362 "TCA=\"tc class add dev $I\"\n"
363 "TFA=\"tc filter add dev $I\"\n"
364 "Q=\"%s\"\n"
365 "\n"
366 "case \"$1\" in\n"
367 "start)\n"
368 "\ttc qdisc del dev $I root 2>/dev/null\n"
369 "\t$TQA root handle 1: htb default %u r2q %u\n"
370 "\t$TCA parent 1: classid 1:1 htb rate %ukbit ceil %ukbit %s\n",
371 get_wanface(),
372 nvram_get_int("qos_pfifo") ? "pfifo limit 256" : "sfq perturb 10",
373 (nvram_get_int("qos_default") + 1) * 10, r2q,
374 bw, bw, burst_root);
376 inuse = nvram_get_int("qos_inuse");
378 g = buf = strdup(nvram_safe_get("qos_orates"));
379 for (i = 0; i < 10; ++i) {
380 if ((!g) || ((p = strsep(&g, ",")) == NULL)) break;
382 if ((inuse & (1 << i)) == 0) continue;
384 if ((sscanf(p, "%u-%u", &rate, &ceil) != 2) || (rate < 1)) continue; // 0=off
386 if (ceil > 0) sprintf(s, "ceil %ukbit ", calc(bw, ceil));
387 else s[0] = 0;
388 x = (i + 1) * 10;
389 fprintf(f,
390 "# egress %d: %u-%u%%\n"
391 "\t$TCA parent 1:1 classid 1:%d htb rate %ukbit %s %s prio %d quantum %u\n"
392 "\t$TQA parent 1:%d handle %d: $Q\n"
393 "\t$TFA parent 1: prio %d protocol ip handle %d fw flowid 1:%d\n",
394 i, rate, ceil,
395 x, calc(bw, rate), s, burst_leaf, (i >= 6) ? 7 : (i + 1), mtu,
396 x, x,
397 x, i + 1, x);
399 free(buf);
401 // "\t$TFA parent 1: prio 10 protocol ip u32 match ip tos 0x10 0xff flowid :10\n" // TOS EF -> Highest
405 if (nvram_match("qos_ack", "1")) {
406 fprintf(f,
407 "\n"
408 "\t$TFA parent 1: prio 15 protocol ip u32 "
409 "match ip protocol 6 0xff " // TCP
410 "match u8 0x05 0x0f at 0 " // IP header length
411 "match u16 0x0000 0xffc0 at 2 " // total length (0-63)
412 "match u8 0x10 0xff at 33 " // ACK only
413 "flowid 1:10\n");
415 if (nvram_match("qos_icmp", "1")) {
416 fputs("\n\t$TFA parent 1: prio 14 protocol ip u32 match ip protocol 1 0xff flowid 1:10\n", f);
421 if (nvram_get_int("qos_ack")) {
422 fprintf(f,
423 "\n"
424 "\t$TFA parent 1: prio 14 protocol ip u32 "
425 "match ip protocol 6 0xff " // TCP
426 "match u8 0x05 0x0f at 0 " // IP header length
427 "match u16 0x0000 0xff80 at 2 " // total length (0-127)
428 "match u8 0x10 0xff at 33 " // ACK only
429 "flowid 1:10\n");
432 if (nvram_get_int("qos_syn")) {
433 // 10000 = ACK
434 // 00010 = SYN
436 fprintf(f,
437 "\n"
438 "\t$TFA parent 1: prio 15 protocol ip u32 "
439 "match ip protocol 6 0xff " // TCP
440 "match u8 0x05 0x0f at 0 " // IP header length
441 "match u16 0x0000 0xff80 at 2 " // total length (0-127)
442 "match u8 0x02 0xff at 33 " // SYN only
443 "flowid 1:10\n"
444 "\n"
445 "\t$TFA parent 1: prio 16 protocol ip u32 "
446 "match ip protocol 6 0xff " // TCP
447 "match u8 0x05 0x0f at 0 " // IP header length
448 "match u16 0x0000 0xff80 at 2 " // total length (0-127)
449 "match u8 0x12 0xff at 33 " // SYN,ACK
450 "flowid 1:10\n");
453 if (nvram_get_int("qos_fin")) {
454 // 10000 = ACK
455 // 00001 = FIN
457 fprintf(f,
458 "\n"
459 "\t$TFA parent 1: prio 17 protocol ip u32 "
460 "match ip protocol 6 0xff " // TCP
461 "match u8 0x05 0x0f at 0 " // IP header length
462 "match u8 0x11 0xff at 33 " // ACK,FIN
463 "flowid 1:10\n"
464 "\n"
465 "\t$TFA parent 1: prio 18 protocol ip u32 "
466 "match ip protocol 6 0xff " // TCP
467 "match u8 0x05 0x0f at 0 " // IP header length
468 "match u8 0x01 0xff at 33 " // FIN
469 "flowid 1:10\n");
472 if (nvram_get_int("qos_rst")) {
473 // 10000 = ACK
474 // 00100 = RST
475 fprintf(f,
476 "\n"
477 "\t$TFA parent 1: prio 19 protocol ip u32 "
478 "match ip protocol 6 0xff " // TCP
479 "match u8 0x05 0x0f at 0 " // IP header length
480 "match u8 0x14 0xff at 33 " // ACK,RST
481 "flowid 1:10\n"
482 "\n"
483 "\t$TFA parent 1: prio 20 protocol ip u32 "
484 "match ip protocol 6 0xff " // TCP
485 "match u8 0x05 0x0f at 0 " // IP header length
486 "match u8 0x04 0xff at 33 " // RST
487 "flowid 1:10\n");
491 if (nvram_get_int("qos_icmp")) {
492 fputs("\n\t$TFA parent 1: prio 13 protocol ip u32 match ip protocol 1 0xff flowid 1:10\n", f);
497 10000 = ACK
498 00100 = RST
499 00010 = SYN
500 00001 = FIN
503 if (nvram_get_int("qos_ack")) {
504 fprintf(f,
505 "\n"
506 "\t$TFA parent 1: prio 14 protocol ip u32 "
507 "match ip protocol 6 0xff " // TCP
508 "match u8 0x05 0x0f at 0 " // IP header length
509 // "match u16 0x0000 0xff80 at 2 " // total length (0-127)
510 "match u16 0x0000 0xffc0 at 2 " // total length (0-63)
511 "match u8 0x10 0xff at 33 " // ACK only
512 "flowid 1:10\n");
515 if (nvram_get_int("qos_syn")) {
516 fprintf(f,
517 "\n"
518 "\t$TFA parent 1: prio 15 protocol ip u32 "
519 "match ip protocol 6 0xff " // TCP
520 "match u8 0x05 0x0f at 0 " // IP header length
521 "match u16 0x0000 0xffc0 at 2 " // total length (0-63)
522 "match u8 0x02 0x02 at 33 " // SYN,*
523 "flowid 1:10\n");
526 if (nvram_get_int("qos_fin")) {
527 fprintf(f,
528 "\n"
529 "\t$TFA parent 1: prio 17 protocol ip u32 "
530 "match ip protocol 6 0xff " // TCP
531 "match u8 0x05 0x0f at 0 " // IP header length
532 "match u16 0x0000 0xffc0 at 2 " // total length (0-63)
533 "match u8 0x01 0x01 at 33 " // FIN,*
534 "flowid 1:10\n");
537 if (nvram_get_int("qos_rst")) {
538 fprintf(f,
539 "\n"
540 "\t$TFA parent 1: prio 19 protocol ip u32 "
541 "match ip protocol 6 0xff " // TCP
542 "match u8 0x05 0x0f at 0 " // IP header length
543 "match u16 0x0000 0xffc0 at 2 " // total length (0-63)
544 "match u8 0x04 0x04 at 33 " // RST,*
545 "flowid 1:10\n");
549 if (nvram_get_int("qos_icmp")) {
550 fputs("\n\t$TFA parent 1: prio 13 protocol ip u32 match ip protocol 1 0xff flowid 1:10\n", f);
553 // ingress
555 first = 1;
556 bw = strtoul(nvram_safe_get("qos_ibw"), NULL, 10);
557 g = buf = strdup(nvram_safe_get("qos_irates"));
558 for (i = 0; i < 10; ++i) {
559 if ((!g) || ((p = strsep(&g, ",")) == NULL)) break;
561 if ((inuse & (1 << i)) == 0) continue;
563 if ((rate = atoi(p)) < 1) continue; // 0 = off
565 if (first) {
566 first = 0;
567 fprintf(f,
568 "\n"
569 "\ttc qdisc del dev $I ingress 2>/dev/null\n"
570 "\t$TQA handle ffff: ingress\n");
573 // rate in kb/s
574 unsigned int u = calc(bw, rate);
576 // burst rate
577 unsigned int v = u / 25;
578 if (v < 50) v = 50;
579 // const unsigned int v = 200;
581 x = i + 1;
582 fprintf(f,
583 "# ingress %d: %u%%\n"
584 "\t$TFA parent ffff: prio %d protocol ip handle %d fw police rate %ukbit burst %ukbit drop flowid ffff:%d\n",
585 i, rate,
586 x, x, u, v, x);
588 free(buf);
590 fputs(
591 "\t;;\n"
592 "stop)\n"
593 "\ttc qdisc del dev $I root 2>/dev/null\n"
594 "\ttc qdisc del dev $I ingress 2>/dev/null\n"
595 "\t;;\n"
596 "*)\n"
597 "\ttc -s -d qdisc ls dev $I\n"
598 "\techo\n"
599 "\ttc -s -d class ls dev $I\n"
600 "esac\n",
603 fclose(f);
604 chmod(qosfn, 0700);
605 eval((char *)qosfn, "start");
608 void stop_qos(void)
610 eval((char *)qosfn, "stop");
612 if (!nvram_match("debug_keepfiles", "1")) {
613 unlink(qosfn);
620 PREROUTING (mn) ----> x ----> FORWARD (f) ----> + ----> POSTROUTING (n)
621 QD | ^
624 INPUT (f) OUTPUT (mnf)