| 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/0xff";; |
| 57 | default) pkt=1; append "$var" "-m mark --mark 0/0xff";; |
| 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 | *:tos) |
| 122 | add_insmod ipt_tos |
| 123 | case "$value" in |
| 124 | !*) append "$var" "-m tos ! --tos $value";; |
| 125 | *) append "$var" "-m tos --tos $value" |
| 126 | esac |
| 127 | ;; |
| 128 | *:dscp) |
| 129 | add_insmod ipt_dscp |
| 130 | dscp_option="--dscp" |
| 131 | [ -z "${value%%[EBCA]*}" ] && dscp_option="--dscp-class" |
| 132 | case "$value" in |
| 133 | !*) append "$var" "-m dscp ! $dscp_option $value";; |
| 134 | *) append "$var" "-m dscp $dscp_option $value" |
| 135 | esac |
| 136 | ;; |
| 137 | *:direction) |
| 138 | value="$(echo "$value" | sed -e 's,-,:,g')" |
| 139 | if [ "$value" = "out" ]; then |
| 140 | append "$var" "-o $device" |
| 141 | elif [ "$value" = "in" ]; then |
| 142 | append "$var" "-i $device" |
| 143 | fi |
| 144 | ;; |
| 145 | 1:pktsize) |
| 146 | value="$(echo "$value" | sed -e 's,-,:,g')" |
| 147 | add_insmod ipt_length |
| 148 | append "$var" "-m length --length $value" |
| 149 | ;; |
| 150 | 1:limit) |
| 151 | add_insmod ipt_limit |
| 152 | append "$var" "-m limit --limit $value" |
| 153 | ;; |
| 154 | 1:tcpflags) |
| 155 | case "$proto" in |
| 156 | tcp) append "$var" "-m tcp --tcp-flags ALL $value";; |
| 157 | *) unset $var; return 0;; |
| 158 | esac |
| 159 | ;; |
| 160 | 1:mark) |
| 161 | config_get class "${value##!}" classnr |
| 162 | [ -z "$class" ] && continue; |
| 163 | case "$value" in |
| 164 | !*) append "$var" "-m mark ! --mark $class/0xff";; |
| 165 | *) append "$var" "-m mark --mark $class/0xff";; |
| 166 | esac |
| 167 | ;; |
| 168 | 1:TOS) |
| 169 | add_insmod ipt_TOS |
| 170 | config_get TOS "$rule" 'TOS' |
| 171 | suffix="-j TOS --set-tos "${TOS:-"Normal-Service"} |
| 172 | ;; |
| 173 | 1:DSCP) |
| 174 | add_insmod ipt_DSCP |
| 175 | config_get DSCP "$rule" 'DSCP' |
| 176 | [ -z "${DSCP%%[EBCA]*}" ] && set_value="--set-dscp-class $DSCP" \ |
| 177 | || set_value="--set-dscp $DSCP" |
| 178 | suffix="-j DSCP $set_value" |
| 179 | ;; |
| 180 | esac |
| 181 | done |
| 182 | append "$var" "$suffix" |
| 183 | case "$ports:$proto" in |
| 184 | 1:) parse_matching_rule "$var" "$section" "$options" "$prefix" "$suffix" "udp";; |
| 185 | esac |
| 186 | } |
| 187 | |
| 188 | config_cb() { |
| 189 | option_cb() { |
| 190 | return 0 |
| 191 | } |
| 192 | |
| 193 | # Section start |
| 194 | case "$1" in |
| 195 | interface) |
| 196 | config_set "$2" "classgroup" "Default" |
| 197 | config_set "$2" "upload" "128" |
| 198 | ;; |
| 199 | classify|default|reclassify) |
| 200 | option_cb() { |
| 201 | append options "$1" |
| 202 | } |
| 203 | ;; |
| 204 | esac |
| 205 | |
| 206 | # Section end |
| 207 | config_get TYPE "$CONFIG_SECTION" TYPE |
| 208 | case "$TYPE" in |
| 209 | interface) |
| 210 | config_get_bool enabled "$CONFIG_SECTION" enabled 1 |
| 211 | [ 1 -eq "$enabled" ] || return 0 |
| 212 | config_get classgroup "$CONFIG_SECTION" classgroup |
| 213 | config_set "$CONFIG_SECTION" ifbdev "$C" |
| 214 | C=$(($C+1)) |
| 215 | append INTERFACES "$CONFIG_SECTION" |
| 216 | config_set "$classgroup" enabled 1 |
| 217 | config_get device "$CONFIG_SECTION" device |
| 218 | [ -z "$device" ] && { |
| 219 | device="$(find_ifname ${CONFIG_SECTION})" |
| 220 | config_set "$CONFIG_SECTION" device "${device:-eth0}" |
| 221 | } |
| 222 | ;; |
| 223 | classgroup) append CG "$CONFIG_SECTION";; |
| 224 | classify|default|reclassify) |
| 225 | case "$TYPE" in |
| 226 | classify) var="ctrules";; |
| 227 | *) var="rules";; |
| 228 | esac |
| 229 | config_get target "$CONFIG_SECTION" target |
| 230 | config_set "$CONFIG_SECTION" options "$options" |
| 231 | append "$var" "$CONFIG_SECTION" |
| 232 | unset options |
| 233 | ;; |
| 234 | esac |
| 235 | } |
| 236 | |
| 237 | |
| 238 | enum_classes() { |
| 239 | local c="0" |
| 240 | config_get classes "$1" classes |
| 241 | config_get default "$1" default |
| 242 | for class in $classes; do |
| 243 | c="$(($c + 1))" |
| 244 | config_set "${class}" classnr $c |
| 245 | case "$class" in |
| 246 | $default) class_default=$c;; |
| 247 | esac |
| 248 | done |
| 249 | class_default="${class_default:-$c}" |
| 250 | } |
| 251 | |
| 252 | cls_var() { |
| 253 | local varname="$1" |
| 254 | local class="$2" |
| 255 | local name="$3" |
| 256 | local type="$4" |
| 257 | local default="$5" |
| 258 | local tmp tmp1 tmp2 |
| 259 | config_get tmp1 "$class" "$name" |
| 260 | config_get tmp2 "${class}_${type}" "$name" |
| 261 | tmp="${tmp2:-$tmp1}" |
| 262 | tmp="${tmp:-$tmp2}" |
| 263 | export ${varname}="${tmp:-$default}" |
| 264 | } |
| 265 | |
| 266 | tcrules() { |
| 267 | dir=/usr/lib/qos |
| 268 | [ -e $dir/tcrules.awk ] || dir=. |
| 269 | echo "$cstr" | awk \ |
| 270 | -v device="$dev" \ |
| 271 | -v linespeed="$rate" \ |
| 272 | -f $dir/tcrules.awk |
| 273 | } |
| 274 | |
| 275 | start_interface() { |
| 276 | local iface="$1" |
| 277 | local num_ifb="$2" |
| 278 | config_get device "$iface" device |
| 279 | config_get_bool enabled "$iface" enabled 1 |
| 280 | [ -z "$device" -o 1 -ne "$enabled" ] && { |
| 281 | return 1 |
| 282 | } |
| 283 | config_get upload "$iface" upload |
| 284 | config_get_bool halfduplex "$iface" halfduplex |
| 285 | config_get download "$iface" download |
| 286 | config_get classgroup "$iface" classgroup |
| 287 | config_get_bool overhead "$iface" overhead 0 |
| 288 | |
| 289 | download="${download:-${halfduplex:+$upload}}" |
| 290 | enum_classes "$classgroup" |
| 291 | for dir in ${halfduplex:-up} ${download:+down}; do |
| 292 | case "$dir" in |
| 293 | up) |
| 294 | [ "$overhead" = 1 ] && upload=$(($upload * 98 / 100 - (15 * 128 / $upload))) |
| 295 | dev="$device" |
| 296 | rate="$upload" |
| 297 | dl_mode="" |
| 298 | prefix="cls" |
| 299 | ;; |
| 300 | down) |
| 301 | [ "$(ls -d /proc/sys/net/ipv4/conf/ifb* 2>&- | wc -l)" -ne "$num_ifb" ] && add_insmod ifb numifbs="$num_ifb" |
| 302 | config_get ifbdev "$iface" ifbdev |
| 303 | [ "$overhead" = 1 ] && download=$(($download * 98 / 100 - (80 * 1024 / $download))) |
| 304 | dev="ifb$ifbdev" |
| 305 | rate="$download" |
| 306 | dl_mode=1 |
| 307 | prefix="d_cls" |
| 308 | ;; |
| 309 | *) continue;; |
| 310 | esac |
| 311 | cstr= |
| 312 | for class in $classes; do |
| 313 | cls_var pktsize "$class" packetsize $dir 1500 |
| 314 | cls_var pktdelay "$class" packetdelay $dir 0 |
| 315 | cls_var maxrate "$class" limitrate $dir 100 |
| 316 | cls_var prio "$class" priority $dir 1 |
| 317 | cls_var avgrate "$class" avgrate $dir 0 |
| 318 | cls_var qdisc "$class" qdisc $dir "" |
| 319 | cls_var filter "$class" filter $dir "" |
| 320 | config_get classnr "$class" classnr |
| 321 | append cstr "$classnr:$prio:$avgrate:$pktsize:$pktdelay:$maxrate:$qdisc:$filter" "$N" |
| 322 | done |
| 323 | append ${prefix}q "$(tcrules)" "$N" |
| 324 | export dev_${dir}="ifconfig $dev up txqueuelen 5 >&- 2>&- |
| 325 | tc qdisc del dev $dev root >&- 2>&- |
| 326 | tc qdisc add dev $dev root handle 1: hfsc default ${class_default}0 |
| 327 | tc class add dev $dev parent 1: classid 1:1 hfsc sc rate ${rate}kbit ul rate ${rate}kbit" |
| 328 | done |
| 329 | [ -n "$download" ] && { |
| 330 | add_insmod cls_u32 |
| 331 | add_insmod em_u32 |
| 332 | add_insmod act_connmark |
| 333 | add_insmod act_mirred |
| 334 | add_insmod sch_ingress |
| 335 | } |
| 336 | if [ -n "$halfduplex" ]; then |
| 337 | export dev_up="tc qdisc del dev $device root >&- 2>&- |
| 338 | tc qdisc add dev $device root handle 1: hfsc |
| 339 | 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" |
| 340 | elif [ -n "$download" ]; then |
| 341 | append dev_${dir} "tc qdisc del dev $device ingress >&- 2>&- |
| 342 | tc qdisc add dev $device ingress |
| 343 | 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" |
| 344 | fi |
| 345 | add_insmod cls_fw |
| 346 | add_insmod sch_hfsc |
| 347 | add_insmod sch_fq_codel |
| 348 | |
| 349 | cat <<EOF |
| 350 | ${INSMOD:+$INSMOD$N}${dev_up:+$dev_up |
| 351 | $clsq |
| 352 | }${ifbdev:+$dev_down |
| 353 | $d_clsq |
| 354 | $d_clsl |
| 355 | $d_clsf |
| 356 | } |
| 357 | EOF |
| 358 | unset INSMOD clsq clsf clsl d_clsq d_clsl d_clsf dev_up dev_down |
| 359 | } |
| 360 | |
| 361 | start_interfaces() { |
| 362 | local C="$1" |
| 363 | for iface in $INTERFACES; do |
| 364 | start_interface "$iface" "$C" |
| 365 | done |
| 366 | } |
| 367 | |
| 368 | add_rules() { |
| 369 | local var="$1" |
| 370 | local rules="$2" |
| 371 | local prefix="$3" |
| 372 | |
| 373 | for rule in $rules; do |
| 374 | unset iptrule |
| 375 | config_get target "$rule" target |
| 376 | config_get target "$target" classnr |
| 377 | config_get options "$rule" options |
| 378 | |
| 379 | ## If we want to override the TOS field, let's clear the DSCP field first. |
| 380 | [ ! -z "$(echo $options | grep 'TOS')" ] && { |
| 381 | s_options=${options%%TOS} |
| 382 | add_insmod ipt_DSCP |
| 383 | parse_matching_rule iptrule "$rule" "$s_options" "$prefix" "-j DSCP --set-dscp 0" |
| 384 | append "$var" "$iptrule" "$N" |
| 385 | unset iptrule |
| 386 | } |
| 387 | |
| 388 | parse_matching_rule iptrule "$rule" "$options" "$prefix" "-j MARK --set-mark $target/0xff" |
| 389 | append "$var" "$iptrule" "$N" |
| 390 | done |
| 391 | } |
| 392 | |
| 393 | start_cg() { |
| 394 | local cg="$1" |
| 395 | local iptrules |
| 396 | local pktrules |
| 397 | local sizerules |
| 398 | enum_classes "$cg" |
| 399 | add_rules iptrules "$ctrules" "iptables -t mangle -A qos_${cg}_ct" |
| 400 | config_get classes "$cg" classes |
| 401 | for class in $classes; do |
| 402 | config_get mark "$class" classnr |
| 403 | config_get maxsize "$class" maxsize |
| 404 | [ -z "$maxsize" -o -z "$mark" ] || { |
| 405 | add_insmod ipt_length |
| 406 | append pktrules "iptables -t mangle -A qos_${cg} -m mark --mark $mark/0xff -m length --length $maxsize: -j MARK --set-mark 0/0xff" "$N" |
| 407 | } |
| 408 | done |
| 409 | add_rules pktrules "$rules" "iptables -t mangle -A qos_${cg}" |
| 410 | for iface in $INTERFACES; do |
| 411 | config_get classgroup "$iface" classgroup |
| 412 | config_get device "$iface" device |
| 413 | config_get ifbdev "$iface" ifbdev |
| 414 | config_get upload "$iface" upload |
| 415 | config_get download "$iface" download |
| 416 | config_get halfduplex "$iface" halfduplex |
| 417 | download="${download:-${halfduplex:+$upload}}" |
| 418 | append up "iptables -t mangle -A OUTPUT -o $device -j qos_${cg}" "$N" |
| 419 | append up "iptables -t mangle -A FORWARD -o $device -j qos_${cg}" "$N" |
| 420 | done |
| 421 | cat <<EOF |
| 422 | $INSMOD |
| 423 | iptables -t mangle -N qos_${cg} >&- 2>&- |
| 424 | iptables -t mangle -N qos_${cg}_ct >&- 2>&- |
| 425 | ${iptrules:+${iptrules}${N}iptables -t mangle -A qos_${cg}_ct -j CONNMARK --save-mark --mask 0xff} |
| 426 | iptables -t mangle -A qos_${cg} -j CONNMARK --restore-mark --mask 0xff |
| 427 | iptables -t mangle -A qos_${cg} -m mark --mark 0/0xff -j qos_${cg}_ct |
| 428 | $pktrules |
| 429 | $up$N${down:+${down}$N} |
| 430 | EOF |
| 431 | unset INSMOD |
| 432 | } |
| 433 | |
| 434 | start_firewall() { |
| 435 | add_insmod ipt_multiport |
| 436 | add_insmod ipt_CONNMARK |
| 437 | stop_firewall |
| 438 | for group in $CG; do |
| 439 | start_cg $group |
| 440 | done |
| 441 | } |
| 442 | |
| 443 | stop_firewall() { |
| 444 | # Builds up a list of iptables commands to flush the qos_* chains, |
| 445 | # remove rules referring to them, then delete them |
| 446 | |
| 447 | # Print rules in the mangle table, like iptables-save |
| 448 | iptables -t mangle -S | |
| 449 | # Find rules for the qos_* chains |
| 450 | grep '^-N qos_\|-j qos_' | |
| 451 | # Exclude rules in qos_* chains (inter-qos_* refs) |
| 452 | grep -v '^-A qos_' | |
| 453 | # Replace -N with -X and hold, with -F and print |
| 454 | # Replace -A with -D |
| 455 | # Print held lines at the end (note leading newline) |
| 456 | sed -e '/^-N/{s/^-N/-X/;H;s/^-X/-F/}' \ |
| 457 | -e 's/^-A/-D/' \ |
| 458 | -e '${p;g}' | |
| 459 | # Make into proper iptables calls |
| 460 | # Note: awkward in previous call due to hold space usage |
| 461 | sed -n -e 's/^./iptables -t mangle &/p' |
| 462 | } |
| 463 | |
| 464 | C="0" |
| 465 | INTERFACES="" |
| 466 | [ -e ./qos.conf ] && { |
| 467 | . ./qos.conf |
| 468 | config_cb |
| 469 | } || config_load qos |
| 470 | |
| 471 | C="0" |
| 472 | for iface in $INTERFACES; do |
| 473 | export C="$(($C + 1))" |
| 474 | done |
| 475 | |
| 476 | case "$1" in |
| 477 | all) |
| 478 | start_interfaces "$C" |
| 479 | start_firewall |
| 480 | ;; |
| 481 | interface) |
| 482 | start_interface "$2" "$C" |
| 483 | ;; |
| 484 | interfaces) |
| 485 | start_interfaces |
| 486 | ;; |
| 487 | firewall) |
| 488 | case "$2" in |
| 489 | stop) |
| 490 | stop_firewall |
| 491 | ;; |
| 492 | start|"") |
| 493 | start_firewall |
| 494 | ;; |
| 495 | esac |
| 496 | ;; |
| 497 | esac |
| 498 | |