7f2376389d058502571270edd55ae70f183dd81d
[14.07/openwrt.git] / package / network / config / qos-scripts / files / usr / lib / qos / generate.sh
1 #!/bin/sh
2 [ -e /lib/functions.sh ] && . /lib/functions.sh || . ./functions.sh
3 [ -x /sbin/modprobe ] && {
4         insmod="modprobe"
5         rmmod="$insmod -r"
6 } || {
7         insmod="insmod"
8         rmmod="rmmod"
9 }
10
11 add_insmod() {
12         eval "export isset=\${insmod_$1}"
13         case "$isset" in
14                 1) ;;
15                 *) {
16                         [ "$2" ] && append INSMOD "$rmmod $1 >&- 2>&-" "$N"
17                         append INSMOD "$insmod $* >&- 2>&-" "$N"; export insmod_$1=1
18                 };;
19         esac
20 }
21
22 [ -e /etc/config/network ] && {
23         # only try to parse network config on openwrt
24
25         find_ifname() {(
26                 reset_cb
27                 include /lib/network
28                 scan_interfaces
29                 config_get "$1" ifname
30         )}
31 } || {
32         find_ifname() {
33                 echo "Interface not found."
34                 exit 1
35         }
36 }
37
38 parse_matching_rule() {
39         local var="$1"
40         local section="$2"
41         local options="$3"
42         local prefix="$4"
43         local suffix="$5"
44         local proto="$6"
45         local mport=""
46         local ports=""
47
48         append "$var" "$prefix" "$N"
49         for option in $options; do
50                 case "$option" in
51                         proto) config_get value "$section" proto; proto="${proto:-$value}";;
52                 esac
53         done
54         config_get type "$section" TYPE
55         case "$type" in
56                 classify) unset pkt; append "$var" "-m mark --mark 0/0x0f";;
57                 default) pkt=1; append "$var" "-m mark --mark 0/0xf0";;
58                 reclassify) pkt=1;;
59         esac
60         append "$var" "${proto:+-p $proto}"
61         for option in $options; do
62                 config_get value "$section" "$option"
63                 
64                 case "$pkt:$option" in
65                         *:srchost)
66                                 append "$var" "-s $value"
67                         ;;
68                         *:dsthost)
69                                 append "$var" "-d $value"
70                         ;;
71                         *:layer7)
72                                 add_insmod ipt_layer7
73                                 add_insmod xt_layer7
74                                 append "$var" "-m layer7 --l7proto $value${pkt:+ --l7pkt}"
75                         ;;
76                         *:ports|*:srcports|*:dstports)
77                                 value="$(echo "$value" | sed -e 's,-,:,g')"
78                                 lproto=${lproto:-tcp}
79                                 case "$proto" in
80                                         ""|tcp|udp) append "$var" "-m ${proto:-tcp -p tcp} -m multiport";;
81                                         *) unset "$var"; return 0;;
82                                 esac
83                                 case "$option" in
84                                         ports)
85                                                 config_set "$section" srcports ""
86                                                 config_set "$section" dstports ""
87                                                 config_set "$section" portrange ""
88                                                 append "$var" "--ports $value"
89                                         ;;
90                                         srcports)
91                                                 config_set "$section" ports ""
92                                                 config_set "$section" dstports ""
93                                                 config_set "$section" portrange ""
94                                                 append "$var" "--sports $value"
95                                         ;;
96                                         dstports)
97                                                 config_set "$section" ports ""
98                                                 config_set "$section" srcports ""
99                                                 config_set "$section" portrange ""
100                                                 append "$var" "--dports $value"
101                                         ;;
102                                 esac
103                                 ports=1
104                         ;;
105                         *:portrange)
106                                 config_set "$section" ports ""
107                                 config_set "$section" srcports ""
108                                 config_set "$section" dstports ""
109                                 value="$(echo "$value" | sed -e 's,-,:,g')"
110                                 case "$proto" in
111                                         ""|tcp|udp) append "$var" "-m ${proto:-tcp -p tcp} --sport $value --dport $value";;
112                                         *) unset "$var"; return 0;;
113                                 esac
114                                 ports=1
115                         ;;
116                         *:connbytes)
117                                 value="$(echo "$value" | sed -e 's,-,:,g')"
118                                 add_insmod ipt_connbytes
119                                 append "$var" "-m connbytes --connbytes $value --connbytes-dir both --connbytes-mode bytes"
120                         ;;
121                         *:comment)
122                                 add_insmod xt_comment
123                                 append "$var" "-m comment --comment '$value'"
124                         ;;
125                         *:tos)
126                                 add_insmod ipt_tos
127                                 case "$value" in
128                                         !*) append "$var" "-m tos ! --tos $value";;
129                                         *) append "$var" "-m tos --tos $value"
130                                 esac
131                         ;;
132                         *:dscp)
133                                 add_insmod ipt_dscp
134                                 dscp_option="--dscp"
135                                 [ -z "${value%%[EBCA]*}" ] && dscp_option="--dscp-class"
136                                 case "$value" in
137                                         !*) append "$var" "-m dscp ! $dscp_option $value";;
138                                         *) append "$var" "-m dscp $dscp_option $value"
139                                 esac
140                         ;;
141                         *:direction)
142                                 value="$(echo "$value" | sed -e 's,-,:,g')"
143                                 if [ "$value" = "out" ]; then
144                                         append "$var" "-o $device"
145                                 elif [ "$value" = "in" ]; then
146                                         append "$var" "-i $device"
147                                 fi
148                         ;;
149                         1:pktsize)
150                                 value="$(echo "$value" | sed -e 's,-,:,g')"
151                                 add_insmod ipt_length
152                                 append "$var" "-m length --length $value"
153                         ;;
154                         1:limit)
155                                 add_insmod ipt_limit
156                                 append "$var" "-m limit --limit $value"
157                         ;;
158                         1:tcpflags)
159                                 case "$proto" in
160                                         tcp) append "$var" "-m tcp --tcp-flags ALL $value";;
161                                         *) unset $var; return 0;;
162                                 esac
163                         ;;
164                         1:mark)
165                                 config_get class "${value##!}" classnr
166                                 [ -z "$class" ] && continue;
167                                 case "$value" in
168                                         !*) append "$var" "-m mark ! --mark $class/0x0f";;
169                                         *) append "$var" "-m mark --mark $class/0x0f";;
170                                 esac
171                         ;;
172                         1:TOS)
173                                 add_insmod ipt_TOS
174                                 config_get TOS "$rule" 'TOS'
175                                 suffix="-j TOS --set-tos "${TOS:-"Normal-Service"}
176                         ;;
177                         1:DSCP)
178                                 add_insmod ipt_DSCP
179                                 config_get DSCP "$rule" 'DSCP'
180                                 [ -z "${DSCP%%[EBCA]*}" ] && set_value="--set-dscp-class $DSCP" \
181                                 || set_value="--set-dscp $DSCP"
182                                 suffix="-j DSCP $set_value"
183                         ;;
184                 esac
185         done
186         append "$var" "$suffix"
187         case "$ports:$proto" in
188                 1:)     parse_matching_rule "$var" "$section" "$options" "$prefix" "$suffix" "udp";;
189         esac
190 }
191
192 config_cb() {
193         option_cb() {
194                 return 0
195         }
196
197         # Section start
198         case "$1" in
199                 interface)
200                         config_set "$2" "classgroup" "Default"
201                         config_set "$2" "upload" "128"
202                 ;;
203                 classify|default|reclassify)
204                         option_cb() {
205                                 append options "$1"
206                         }
207                 ;;
208         esac
209
210     # Section end
211         config_get TYPE "$CONFIG_SECTION" TYPE
212         case "$TYPE" in
213                 interface)
214                         config_get_bool enabled "$CONFIG_SECTION" enabled 1
215                         [ 1 -eq "$enabled" ] || return 0
216                         config_get classgroup "$CONFIG_SECTION" classgroup
217                         config_set "$CONFIG_SECTION" ifbdev "$C"
218                         C=$(($C+1))
219                         append INTERFACES "$CONFIG_SECTION"
220                         config_set "$classgroup" enabled 1
221                         config_get device "$CONFIG_SECTION" device
222                         [ -z "$device" ] && {
223                                 device="$(find_ifname ${CONFIG_SECTION})"
224                                 config_set "$CONFIG_SECTION" device "${device:-eth0}"
225                         }
226                 ;;
227                 classgroup) append CG "$CONFIG_SECTION";;
228                 classify|default|reclassify)
229                         case "$TYPE" in
230                                 classify) var="ctrules";;
231                                 *) var="rules";;
232                         esac
233                         config_get target "$CONFIG_SECTION" target
234                         config_set "$CONFIG_SECTION" options "$options"
235                         append "$var" "$CONFIG_SECTION"
236                         unset options
237                 ;;
238         esac
239 }
240
241
242 enum_classes() {
243         local c="0"
244         config_get classes "$1" classes
245         config_get default "$1" default
246         for class in $classes; do
247                 c="$(($c + 1))"
248                 config_set "${class}" classnr $c
249                 case "$class" in
250                         $default) class_default=$c;;
251                 esac
252         done
253         class_default="${class_default:-$c}"
254 }
255
256 cls_var() {
257         local varname="$1"
258         local class="$2"
259         local name="$3"
260         local type="$4"
261         local default="$5"
262         local tmp tmp1 tmp2
263         config_get tmp1 "$class" "$name"
264         config_get tmp2 "${class}_${type}" "$name"
265         tmp="${tmp2:-$tmp1}"
266         tmp="${tmp:-$tmp2}"
267         export ${varname}="${tmp:-$default}"
268 }
269
270 tcrules() {
271         _dir=/usr/lib/qos
272         [ -e $_dir/tcrules.awk ] || _dir=.
273         echo "$cstr" | awk \
274                 -v device="$dev" \
275                 -v linespeed="$rate" \
276                 -v direction="$dir" \
277                 -f $_dir/tcrules.awk
278 }
279
280 start_interface() {
281         local iface="$1"
282         local num_ifb="$2"
283         config_get device "$iface" device
284         config_get_bool enabled "$iface" enabled 1
285         [ -z "$device" -o 1 -ne "$enabled" ] && {
286                 return 1 
287         }
288         config_get upload "$iface" upload
289         config_get_bool halfduplex "$iface" halfduplex
290         config_get download "$iface" download
291         config_get classgroup "$iface" classgroup
292         config_get_bool overhead "$iface" overhead 0
293         
294         download="${download:-${halfduplex:+$upload}}"
295         enum_classes "$classgroup"
296         for dir in ${halfduplex:-up} ${download:+down}; do
297                 case "$dir" in
298                         up)
299                                 [ "$overhead" = 1 ] && upload=$(($upload * 98 / 100 - (15 * 128 / $upload)))
300                                 dev="$device"
301                                 rate="$upload"
302                                 dl_mode=""
303                                 prefix="cls"
304                         ;;
305                         down)
306                                 [ "$(ls -d /proc/sys/net/ipv4/conf/ifb* 2>&- | wc -l)" -ne "$num_ifb" ] && add_insmod ifb numifbs="$num_ifb"
307                                 config_get ifbdev "$iface" ifbdev
308                                 [ "$overhead" = 1 ] && download=$(($download * 98 / 100 - (80 * 1024 / $download)))
309                                 dev="ifb$ifbdev"
310                                 rate="$download"
311                                 dl_mode=1
312                                 prefix="d_cls"
313                         ;;
314                         *) continue;;
315                 esac
316                 cstr=
317                 for class in $classes; do
318                         cls_var pktsize "$class" packetsize $dir 1500
319                         cls_var pktdelay "$class" packetdelay $dir 0
320                         cls_var maxrate "$class" limitrate $dir 100
321                         cls_var prio "$class" priority $dir 1
322                         cls_var avgrate "$class" avgrate $dir 0
323                         cls_var qdisc "$class" qdisc $dir ""
324                         cls_var filter "$class" filter $dir ""
325                         config_get classnr "$class" classnr
326                         append cstr "$classnr:$prio:$avgrate:$pktsize:$pktdelay:$maxrate:$qdisc:$filter" "$N"
327                 done
328                 append ${prefix}q "$(tcrules)" "$N"
329                 export dev_${dir}="ifconfig $dev up txqueuelen 5 >&- 2>&-
330 tc qdisc del dev $dev root >&- 2>&-
331 tc qdisc add dev $dev root handle 1: hfsc default ${class_default}0
332 tc class add dev $dev parent 1: classid 1:1 hfsc sc rate ${rate}kbit ul rate ${rate}kbit"
333         done
334         [ -n "$download" ] && {
335                 add_insmod cls_u32
336                 add_insmod em_u32
337                 add_insmod act_connmark
338                 add_insmod act_mirred
339                 add_insmod sch_ingress
340         }
341         if [ -n "$halfduplex" ]; then
342                 export dev_up="tc qdisc del dev $device root >&- 2>&-
343 tc qdisc add dev $device root handle 1: hfsc
344 tc filter add dev $device parent 1: protocol ip prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifb$ifbdev"
345         elif [ -n "$download" ]; then
346                 append dev_${dir} "tc qdisc del dev $device ingress >&- 2>&-
347 tc qdisc add dev $device ingress
348 tc filter add dev $device parent ffff: protocol ip prio 1 u32 match u32 0 0 flowid 1:1 action connmark action mirred egress redirect dev ifb$ifbdev" "$N"
349         fi
350         add_insmod cls_fw
351         add_insmod sch_hfsc
352         add_insmod sch_fq_codel
353
354         cat <<EOF
355 ${INSMOD:+$INSMOD$N}${dev_up:+$dev_up
356 $clsq
357 }${ifbdev:+$dev_down
358 $d_clsq
359 $d_clsl
360 $d_clsf
361 }
362 EOF
363         unset INSMOD clsq clsf clsl d_clsq d_clsl d_clsf dev_up dev_down
364 }
365
366 start_interfaces() {
367         local C="$1"
368         for iface in $INTERFACES; do
369                 start_interface "$iface" "$C"
370         done
371 }
372
373 add_rules() {
374         local var="$1"
375         local rules="$2"
376         local prefix="$3"
377         
378         for rule in $rules; do
379                 unset iptrule
380                 config_get target "$rule" target
381                 config_get target "$target" classnr
382                 config_get options "$rule" options
383
384                 ## If we want to override the TOS field, let's clear the DSCP field first.
385                 [ ! -z "$(echo $options | grep 'TOS')" ] && {
386                         s_options=${options%%TOS}
387                         add_insmod ipt_DSCP
388                         parse_matching_rule iptrule "$rule" "$s_options" "$prefix" "-j DSCP --set-dscp 0"
389                         append "$var" "$iptrule" "$N"
390                         unset iptrule
391                 }
392
393                 target=$(($target | ($target << 4)))
394                 parse_matching_rule iptrule "$rule" "$options" "$prefix" "-j MARK --set-mark $target/0xff"
395                 append "$var" "$iptrule" "$N"
396         done
397 }
398
399 start_cg() {
400         local cg="$1"
401         local iptrules
402         local pktrules
403         local sizerules
404         enum_classes "$cg"
405         add_rules iptrules "$ctrules" "iptables -t mangle -A qos_${cg}_ct"
406         config_get classes "$cg" classes
407         for class in $classes; do
408                 config_get mark "$class" classnr
409                 config_get maxsize "$class" maxsize
410                 [ -z "$maxsize" -o -z "$mark" ] || {
411                         add_insmod ipt_length
412                         append pktrules "iptables -t mangle -A qos_${cg} -m mark --mark $mark/0x0f -m length --length $maxsize: -j MARK --set-mark 0/0xff" "$N"
413                 }
414         done
415         add_rules pktrules "$rules" "iptables -t mangle -A qos_${cg}"
416         for iface in $INTERFACES; do
417                 config_get classgroup "$iface" classgroup
418                 config_get device "$iface" device
419                 config_get ifbdev "$iface" ifbdev
420                 config_get upload "$iface" upload
421                 config_get download "$iface" download
422                 config_get halfduplex "$iface" halfduplex
423                 download="${download:-${halfduplex:+$upload}}"
424                 append up "iptables -t mangle -A OUTPUT -o $device -j qos_${cg}" "$N"
425                 append up "iptables -t mangle -A FORWARD -o $device -j qos_${cg}" "$N"
426         done
427         cat <<EOF
428 $INSMOD
429 iptables -t mangle -N qos_${cg} >&- 2>&-
430 iptables -t mangle -N qos_${cg}_ct >&- 2>&-
431 ${iptrules:+${iptrules}${N}iptables -t mangle -A qos_${cg}_ct -j CONNMARK --save-mark --mask 0xff}
432 iptables -t mangle -A qos_${cg} -j CONNMARK --restore-mark --mask 0x0f
433 iptables -t mangle -A qos_${cg} -m mark --mark 0/0x0f -j qos_${cg}_ct
434 $pktrules
435 ${iptrules:+${iptrules}${N}iptables -t mangle -A qos_${cg} -j CONNMARK --save-mark --mask 0xf0}
436 $up$N${down:+${down}$N}
437 EOF
438         unset INSMOD
439 }
440
441 start_firewall() {
442         add_insmod ipt_multiport
443         add_insmod ipt_CONNMARK
444         stop_firewall
445         for group in $CG; do
446                 start_cg $group
447         done
448 }
449
450 stop_firewall() {
451         # Builds up a list of iptables commands to flush the qos_* chains,
452         # remove rules referring to them, then delete them
453
454         # Print rules in the mangle table, like iptables-save
455         iptables -t mangle -S |
456                 # Find rules for the qos_* chains
457                 grep '^-N qos_\|-j qos_' |
458                 # Exclude rules in qos_* chains (inter-qos_* refs)
459                 grep -v '^-A qos_' |
460                 # Replace -N with -X and hold, with -F and print
461                 # Replace -A with -D
462                 # Print held lines at the end (note leading newline)
463                 sed -e '/^-N/{s/^-N/-X/;H;s/^-X/-F/}' \
464                         -e 's/^-A/-D/' \
465                         -e '${p;g}' |
466                 # Make into proper iptables calls
467                 # Note:  awkward in previous call due to hold space usage
468                 sed -n -e 's/^./iptables -t mangle &/p'
469 }
470
471 C="0"
472 INTERFACES=""
473 [ -e ./qos.conf ] && {
474         . ./qos.conf
475         config_cb
476 } || config_load qos
477
478 C="0"
479 for iface in $INTERFACES; do
480         export C="$(($C + 1))"
481 done
482
483 case "$1" in
484         all)
485                 start_interfaces "$C"
486                 start_firewall
487         ;;
488         interface)
489                 start_interface "$2" "$C"
490         ;;
491         interfaces)
492                 start_interfaces
493         ;;
494         firewall)
495                 case "$2" in
496                         stop)
497                                 stop_firewall
498                         ;;
499                         start|"")
500                                 start_firewall
501                         ;;
502                 esac
503         ;;
504 esac