treewide: replace `which` with `command -v`
[feed/packages.git] / net / trafficshaper / files / trafficshaper.init
1 #!/bin/sh /etc/rc.common
2
3 # Internal uci firewall chains are flushed and recreated on reload, so
4 # put custom rules into the root chains e.g. INPUT or FORWARD or into the
5 # special user chains, e.g. input_wan_rule or postrouting_lan_rule.
6
7 START=25
8 USE_PROCD=1
9
10 echo_err() {
11 echo "$@" >&2
12 }
13
14 msg() {
15 local level=$1; shift
16 echo_err "$APPNAME[$level]: $*"
17 }
18
19 LOGLEVEL=${LOGLEVEL:-2}
20
21 die() {
22 local err=$1; shift
23 e "$*"
24 exit $err
25 }
26
27 APPNAME="trafficshaper"
28 IPT_CHAIN=$APPNAME
29
30 debug_exec(){
31 local err
32 d "exec: $*"
33 if "$@"; then
34 return 0
35 else
36 err="$?"
37 fi
38 e "exec[err=$err]: $*"
39 return "$err"
40 }
41
42 IP="debug_exec ip"
43 TC="debug_exec tc"
44 IP4T="debug_exec iptables -w 5"
45 IP6T="debug_exec ip6tables -w 5"
46
47 #QDISC="cake autorate_ingress internet ethernet diffserv4 triple-isolate"
48 QDISC="cake"
49
50 REQ_MODULES="sch_htb sch_cake act_connmark act_mirred em_u32"
51 REQ_CMDS="ip tc iptables"
52
53 preinit(){
54 [ "$LOGLEVEL" -ge 1 ] && e() { msg ERROR "$@"; } || e() { true; }
55 [ "$LOGLEVEL" -ge 2 ] && v() { msg INFO "$@"; } || v() { true; }
56 [ "$LOGLEVEL" -ge 3 ] && d() { msg DEBUG "$@"; } || d() { true; }
57 [ "$LOGLEVEL" -ge 4 ] && set -x
58 set -e
59 }
60
61 requires() {
62 for module in $REQ_MODULES; do
63 [ -d /sys/module/$module ] || insert_modules "$module" ||
64 die 2 "cannot load $module. Please install kmod-$module"
65 done
66 for cmd in $REQ_CMDS; do
67 command -v $cmd &>/dev/null ||
68 die 2 "cannot find command $cmd. Please install $cmd"
69 done
70
71 if ! command -v ip6tables &>/dev/null; then
72 v "Disabling IPv6 as ip6tables was not found"
73 IP6T=true
74 fi
75
76 . /lib/functions/network.sh
77
78 config_load $APPNAME
79 }
80
81 do_stop() {
82 local only_int=$1
83
84 preinit
85 requires
86
87 v "Stopping $APPNAME${only_int:+ for interface $only_int}"
88 if [ -z "$only_int" ]; then
89 d "Cleaning iptables"
90 # Cleaning iptables
91 for IPT in "$IP4T" "$IP6T"; do
92 $IPT -t mangle -D FORWARD -j $IPT_CHAIN &>/dev/null || :
93 $IPT -t mangle -F $IPT_CHAIN &>/dev/null || :
94 $IPT -t mangle -X $IPT_CHAIN &>/dev/null || :
95 $IPT -t mangle -F $IPT_CHAIN-classify &>/dev/null || :
96 $IPT -t mangle -X $IPT_CHAIN-classify &>/dev/null || :
97 done
98 fi
99
100 d "Cleaning tc"
101 local dev_done int dev ifb interfaces
102 if [ "$only_int" ]; then
103 config_get type $only_int TYPE
104 if [ "$type" != "wan" ]; then
105 d "interface $only_int not found in trafficshaper config. Ignoring"
106 return 0
107 fi
108 interfaces="$only_int"
109
110 else
111 interfaces="$(config_foreach echo wan)"
112 fi
113
114 for int in $interfaces; do
115 d "Cleaning tc for interface $int"
116 network_get_physdev dev "$int" ||
117 die 1 "failed to get physical dev of interface $int"
118
119 if echo "$dev_done" | grep -x -F -q "$dev"; then
120 continue
121 fi
122 ifb="ifb_$dev"
123 if [ ${#ifb} -gt 15 ]; then
124 die 1 "ifb name too long: ${ifb}"
125 fi
126
127 $TC qdisc del dev ${ifb} root 2> /dev/null || :
128 $TC qdisc del dev ${dev} root 2> /dev/null || :
129 $TC qdisc del dev ${dev} ingress 2> /dev/null || :
130
131 d "Removing ${ifb}..."
132 $IP link set dev ${ifb} down 2>/dev/null || :
133 $IP link delete dev ${ifb} 2>/dev/null || :
134
135 intdev_done="$(echo "$dev_done"; echo -n $dev)"
136 done
137 }
138
139
140 calc_bw() {
141 local value=$1 reference=$2
142 case "${value}" in
143 *%) echo "$((${value%\%} * reference / 100 ))";;
144 *) echo ${value};;
145 esac
146 }
147
148 mask_range() {
149 local mask=$(($1)) n=0 fsb
150 if [ $mask -le 0 ]; then
151 e "mask '$1' must be greater than 0 (have a sequence of set bit)"
152 return 2
153 fi
154 while [ "$((mask & 0x1))" -eq 0 ]; do
155 mask=$((mask >> 1))
156 : $((n++))
157 done
158 fsb="$n"
159 while [ "$((mask & 0x1))" -eq 1 ]; do
160 mask=$((mask >> 1))
161 : $((n++))
162 done
163 if [ $mask -ne 0 ]; then
164 e "mask '$1' must be a continuos sequence of set bit"
165 return 2
166 fi
167 echo $fsb $((n-1))
168 return 0
169 }
170
171 start_iptables(){
172 d "Creating iptables mangle rules"
173
174 config_get mark_mask globals mark_mask 0xFF
175 mark_mask=$(printf '0x%X\n' $(($mark_mask)))
176
177 local fsb_lst class_id_max class_id_shift
178 fsb_lst=$(mask_range $mark_mask)
179 class_id_max=$(((1<<(${fsb_lst#* } - ${fsb_lst% *} +1))+1))
180 class_id_shift=$((${fsb_lst% *}))
181
182 d "General iptables rules:"
183 for IPT in "$IP4T" "$IP6T"; do
184 $IPT -t mangle -N $IPT_CHAIN
185 $IPT -t mangle -N $IPT_CHAIN-classify
186
187 $IPT -t mangle -A FORWARD -j $IPT_CHAIN
188 $IPT -t mangle -A $IPT_CHAIN -j CONNMARK --restore-mark --nfmask $mark_mask --ctmask $mark_mask \
189 -m comment --comment "Get previous class"
190 $IPT -t mangle -A $IPT_CHAIN -m mark --mark 0x0/$mark_mask -j $IPT_CHAIN-classify \
191 -m comment --comment "If no class, try to classify"
192 done
193
194 d "Classes iptables rules:"
195 local class_reserved_uplink class_reserved_downlink class_nets i=2 xi default_class_id
196 for class in $(config_foreach echo class); do
197 config_get class_reserved_uplink $class reserved_uplink
198 config_get class_reserved_downlink $class reserved_downlink
199 config_get class_nets $class network
200 if [ "$class" = default ]; then
201 default_class_id=$i
202 if [ -z "$class_reserved_uplink" -a -z "$class_reserved_downlink" ] ; then
203 die 2 "class default must defined either reserved uplink or downlink!"
204 fi
205 if [ "$class_nets" ]; then
206 die 2 "class default must not have any network defined!"
207 fi
208 else
209 if [ "$i" -ge "$class_id_max" ]; then
210 die 1 "Max client classes reached. Please, use less classes or increase option mark_mask '$mark_mask' in globals. Current mask allows only $((class_id_max-2)) classes if default is the last one."
211 fi
212 fi
213
214 xi=$(printf '0x%X\n' $(((i-1)<<class_id_shift)))
215
216 for class_net in $class_nets; do
217 case $class_net in
218 *:*) IPT="$IP6T" ;;
219 *.*) IPT="$IP4T" ;;
220 *) die 2 "Unknown address family of network $class_net in class $class!"
221 esac
222 if [ "$class_reserved_uplink" ]; then
223 $IPT -t mangle -A $IPT_CHAIN-classify -s $class_net -m mark --mark 0x0/$mark_mask -j MARK --set-mark ${xi}/$mark_mask \
224 -m comment --comment "$APPNAME-$class up"
225 fi
226 if [ "$class_reserved_downlink" ]; then
227 $IPT -t mangle -A $IPT_CHAIN-classify -d $class_net -m mark --mark 0x0/$mark_mask -j MARK --set-mark ${xi}/$mark_mask \
228 -m comment --comment "$APPNAME-$class down"
229 fi
230 done
231 : $((i++))
232 done
233 if [ -z "$default_class_id" ]; then
234 die 2 "No default class defined!"
235 fi
236
237 $IP4T -t mangle -A $IPT_CHAIN-classify -j CONNMARK --save-mark --nfmask $mark_mask --ctmask $mark_mask
238 $IP6T -t mangle -A $IPT_CHAIN-classify -j CONNMARK --save-mark --nfmask $mark_mask --ctmask $mark_mask
239 }
240
241
242
243 start_tc_interface() {
244 local int=$1; shift
245 local dev=$1; shift
246 local default_class_id=$1; shift
247
248 config_get mark_mask globals mark_mask 0xFF
249 local fsb_lst class_id_max class_id_shift
250 fsb_lst=$(mask_range $mark_mask)
251 class_id_max=$(((1<<(${fsb_lst#* } - ${fsb_lst% *} +1))))
252 class_id_shift=$((${fsb_lst% *}))
253
254 local downlink uplink type
255 config_get downlink $int downlink
256 config_get uplink $int uplink
257
258 d "Creating tc rules for $int ($dev)"
259 local dev_down dev_up
260 if [ "$downlink" ]; then
261 local ifb="ifb_$dev"
262 if [ ${#ifb} -gt 15 ]; then
263 die 1 "ifb name too long: ${ifb}"
264 fi
265
266 d "Creating ${ifb}..."
267 $IP link add name ${ifb} type ifb
268 $IP link set dev $ifb up
269 d "Redirect ingress $dev to $ifb..."
270 $TC qdisc add dev $dev handle ffff: ingress
271 $TC filter add dev $dev parent ffff: protocol all u32 match u32 0 0 action connmark action mirred egress redirect dev $ifb
272 dev_down=$ifb
273 else
274 dev_down=
275 fi
276 if [ "$uplink" ]; then
277 dev_up="$dev"
278 fi
279
280 # Download/Upload
281 if [ "$dev_down" ]; then
282 tc qdisc add dev $dev_down root handle 1: htb default "$default_class_id"
283 tc class add dev $dev_down parent 1: classid 1:1 htb rate $(calc_bw ${downlink})kbit burst 500k quantum 1500
284 fi
285
286 if [ "$dev_up" ]; then
287 tc qdisc add dev $dev_up root handle 1: htb default "$default_class_id"
288 tc class add dev $dev_up parent 1: classid 1:1 htb rate $(calc_bw ${uplink})kbit burst 500k quantum 1500
289 fi
290
291 v "$int($dev):" \
292 "${downlink:+downlink of ${downlink}kbit}"\
293 "${uplink:+uplink of ${uplink}kbit}"\
294
295 local class class_reserved_downlink class_reserved_uplink class_allowed_downlink class_allowed_uplink class_nets class_net i=2
296 for class in $(config_foreach echo class); do
297 config_get class_reserved_downlink $class reserved_downlink
298 if [ "$class_reserved_downlink" ]; then
299 if [ "$dev_down" ]; then
300 class_reserved_downlink=$(calc_bw $class_reserved_downlink $downlink)
301 config_get class_allowed_downlink $class allowed_downlink "$class_reserved_downlink"
302 class_allowed_downlink=$(calc_bw $class_allowed_downlink $downlink)
303 else
304 e "class $class defines reserved downlink but not wan $int. Downlink shapping will be ignored"
305 class_reserved_downlink=
306 fi
307 elif [ "$dev_down" ]; then
308 e "class $class does not define reserved downlink but wan $int does. Downlink shapping will use default class"
309 fi
310
311 if [ "$class_allowed_downlink" -lt "$class_reserved_downlink" ]; then
312 die 1 "Allowed downlink bandwitdh in class $class must not be smaller than reserved downlink."
313 fi
314
315 config_get class_reserved_uplink $class reserved_uplink
316 if [ "$class_reserved_uplink" ]; then
317 if [ "$dev_up" ]; then
318 class_reserved_uplink=$(calc_bw $class_reserved_uplink $uplink)
319 config_get class_allowed_uplink $class allowed_uplink "$class_reserved_uplink"
320 class_allowed_uplink=$(calc_bw $class_allowed_uplink $uplink)
321 else
322 e "class $class defines reserved uplink but not wan $int. Downlink shapping will be ignored"
323 class_reserved_uplink=
324 fi
325 elif [ "$dev_up" ]; then
326 e "class $class does not define reserved uplink but wan $int does. Downlink shapping will use default class"
327 fi
328
329 if [ -n "$class_allowed_uplink" -a -n "$class_reserved_uplink" ] && [ "$class_allowed_uplink" -lt "$class_reserved_uplink" ]; then
330 die 1 "Allowed uplink bandwitdh in class $class must not be smaller than reserved uplink."
331 fi
332
333 v "$int($dev): $class(class 1:$i) will have" \
334 "${class_reserved_downlink:+download of ${class_reserved_downlink}kbit (up to ${class_allowed_downlink}kbit)}"\
335 "${class_reserved_uplink:+upload of ${class_reserved_uplink}kbit up (up to ${class_allowed_uplink}kbit)}"
336
337 xi=$(printf '0x%X\n' $(((i-1)<<class_id_shift)))
338 if [ "$class_reserved_uplink" ]; then
339 $TC class add dev $dev_up parent 1:1 classid 1:$i htb rate ${class_reserved_uplink}kbit ceil ${class_allowed_uplink}kbit quantum 1500 burst 50k
340 $TC qdisc add dev $dev_up parent 1:$i handle $i: $QDISC
341 if [ "$class" != default ]; then
342 $TC filter add dev $dev_up parent 1: protocol ip prio $i handle ${xi}/$mark_mask fw flowid 1:$i
343 fi
344 fi
345 if [ "$class_reserved_downlink" ]; then
346 $TC class add dev $dev_down parent 1:1 classid 1:$i htb rate ${class_reserved_downlink}kbit ceil ${class_allowed_downlink}kbit quantum 1500 burst 50k
347 $TC qdisc add dev $dev_down parent 1:$i handle $i: $QDISC
348 if [ "$class" != default ]; then
349 $TC filter add dev $dev_down parent 1: protocol ip prio $i handle ${xi}/$mark_mask fw flowid 1:$i
350 fi
351 fi
352 : $((i++))
353 done
354 }
355
356 start_tc() {
357 d "Creating tc rules"
358 local dev_done int dev interfaces
359 local default_class_id=$1; shift
360 local only_int=$1
361
362 if [ "$only_int" ]; then
363 config_get type $only_int TYPE
364 if [ "$type" != "wan" ]; then
365 d "interface $only_int not found in trafficshaper config. Ignoring"
366 return 0
367 fi
368 interfaces="$only_int"
369
370 else
371 interfaces="$(config_foreach echo wan)"
372 fi
373
374 for int in $interfaces; do
375 network_get_physdev dev "$int" ||
376 die 1 "failed to get physical dev of interface $int"
377
378 if echo "$dev_done" | grep -x -F -q "$dev"; then
379 e "$int uses $dev which was already configured. Only list each WAN once. Skipping..."
380 continue
381 fi
382
383 start_tc_interface $int $dev $ifb "$default_class_id"
384 intdev_done="$(echo "$dev_done"; echo -n $dev)"
385 done
386 }
387
388 do_start() {
389 local only_int=$1 type
390
391 preinit
392 (LOGLEVEL=0 do_stop "$only_int")
393 requires
394
395 trap "set +e; do_stop $only_int" EXIT
396
397 v "Starting $APPNAME${only_int:+ for interface $only_int}"
398
399 local default_class_id
400 if ! default_class_id=$(i=2 config_foreach 'eval echo $((i++))' class '| grep " default"'); then
401 die 2 "No default class defined!"
402 fi
403 default_class_id=${default_class_id% *}
404
405 [ "$only_int" ] || start_iptables
406 start_tc "$default_class_id" "$only_int"
407
408 trap - EXIT
409 }
410
411 start_service() {
412 ( do_start )
413 }
414
415 stop_service() {
416 ( do_stop )
417 }
418
419 restart_service() {
420 ( do_start )
421 }
422
423 is_running() {
424 $IP4T -t mangle -L $IPT_CHAIN &>/dev/null
425 }
426
427 reload_service() {
428 preinit
429 if ! is_running; then
430 d "Not running. Nothing to reload"
431 return 0
432 fi
433 logger -t "$APPNAME" "Reloading $*..."
434 ( do_start "$@" )
435 }
436
437 add_interface_trigger() {
438 procd_add_interface_trigger "interface.update" "$1" /etc/init.d/$APPNAME reload $1
439 }
440
441 service_triggers() {
442 preinit; set +e
443 requires
444
445 procd_add_reload_trigger "$APPNAME"
446 config_foreach add_interface_trigger wan
447
448 procd_open_validate
449 validate_trafficshaper_global
450 validate_trafficshaper_wan
451 validate_trafficshaper_class
452 procd_close_validate
453 }
454
455 validate_trafficshaper_global() {
456 uci_validate_section $APPNAME global "${1}" \
457 'mark_mask:uinteger:0xFF'
458 }
459
460 validate_trafficshaper_wan() {
461 uci_validate_section "$APPNAME" wan "${1}" \
462 'downlink:uinteger' \
463 'uplink:uinteger'
464 }
465
466 validate_trafficshaper_class() {
467 uci_validate_section "$APPNAME" class "${1}" \
468 'network:cidr' \
469 'reserved_downlink:or(uinteger, string)' \
470 'reserved_uplink:or(uinteger, string)' \
471 'allowed_downlink:or(uinteger, string)' \
472 'allowed_uplink:or(uinteger, string)'
473 }
474
475 boot() {
476 LOGLEVEL=1 start
477 }