#!/bin/sh ############################################################################## # # author : mathias gumz # file : aqosh # start : 031114 16:09:06 # $Id : $ # ############################################################################## # small shell-script for doing some qos-stuff ############################################################################## # Copyright (c) 2003 Mathias Gumz # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ############################################################################## # # global settings # {{{ TC="/sbin/tc" IT="/sbin/iptables" MP="/sbin/modprobe" RCFILE="/etc/aqosh.conf" ACTION="status" MASQ_NETWORK="" USE_MODULES="no" MREFRESH="10" DO_DEBUG="no" # }}} ############################################################################## # # parse options etc # {{{ while (( $# > 0 )) do case "$1" in -c ) [ -n "$2" -a -f "$2" ] || ( echo "error, no such file, exit"; exit 1 )# {{{ RCFILE="$2" shift 2 ;; # }}} -T ) [ -n "$2" -a -f "$2" ] || ( echo "error, no such file, exit"; exit 1) # {{{ TC="$2" shift 2 ;; # }}} -I ) [ -n "$2" -a -f "$2" ] || ( echo "error, no such file, exit"; exit 1) # {{{ IT="$2" shift 2 ;; # }}} -M ) [ -n "$2" -a -f "$2" ] || ( echo "error, no such file, exit"; exit 1) # {{{ MP="$2" shift 2 ;; # }}} -m ) [ -n "$2" ] || ( echo "error, missing argument, exit"; exit 1 ) # {{{ MASQ_NETWORK="$2" shift 2 ;; # }}} -k ) USE_MODULES="yes" # {{{ shift 1; ;; # }}} -d ) DO_DEBUG="yes" # {{{ shift 1; ;; # }}} -r ) [ -n "$2" ] || ( echo "error, missing argument, exit"; exit 1 ) # {{{ MREFRESH="$2" shift 2 ;; # }}} # help text # {{{ -h ) cat < opt_qos: hannes ebner < he at fli4l dot de > oliver walter < owb at gmx dot de > FAQ how does this all work ? -> read everything you can in the internet :) for what does the 'a' in aqosh stands for ? -> its dedicated to my girlfriend who is a very fair person and her name starts with the 'a' :) EOF exit 0 ;; # }}} start|stop|show|restart|status|dump|monitor|dangle|dilter) # {{{ ACTION="$1" shift 1 break ;; # }}} *) echo "error, no such option or action [$1], exit"; exit 1 ;; esac done # }}} ############################################################################## # # main loop # {{{ if [ ! -f "$RCFILE" ] then echo "error, no config file found, exit" exit 1 fi source $RCFILE [ -n "`$TC -s qdisc`" ] && status='1' [ "$DO_DEBUG" = "yes" ] && set -x ############################################################################## ############################################################################## case "$ACTION" in start) # {{{ if [ "$status" = '1' ] then echo ' >> error, qos still running <<' else echo ' >> starting qos ... <<' if [ ! -n "$MASQ_NETWORK" ] then echo " >> error, no massqueraded network defined, exit <<" exit 1 fi if [ "$USE_MODULES" = "yes" ] then $MP sch_htb $MP sch_sfq $MP cls_fw $MP iptable_mangle $MP ipt_mark $MP ipt_length $MP ipt_tos fi ## --- global settings --- ## burst='' # autocalculated by tc mtu='' # using MTU of device by default perturb='perturb 10' ## --- initialize vars --- ## idx_down=1 idx_up=1 ## --- inbound traffic --- ## if [ "$QOS_INTERNET_DEFAULT_DOWN" != '0' ] then QOS_INTERNET_DEFAULT_DOWN=$(($QOS_INTERNET_DEFAULT_DOWN + 2)) else QOS_INTERNET_DEFAULT_DOWN='1' fi ## HTB für Inbound-Device aktivieren: htb_error=`$TC qdisc add dev $QOS_LOCALNET_DEV root handle 10: htb \ default $QOS_INTERNET_DEFAULT_DOWN r2q 1 2>&1` ## check for an error [ "$htb_error" != '' ] && echo "$RCFILE: error, creating main qos configuration" ## Klasse für die den beschränkten Internet Traffic ## (Traffic, nicht aus dem maskierte LAN kommt) einrichten. htb_error=`$TC class add dev $QOS_LOCALNET_DEV parent 10: classid 10:1 htb \ rate $QOS_INTERNET_BAND_DOWN $burst 2>&1` $TC qdisc add dev $QOS_LOCALNET_DEV parent 10:1 sfq $mtu $perturb >/dev/null 2>&1 ## check for an error [ "$htb_error" != '' ] && echo "$RCFILE: error, creating main qos configuration" ## Klasse für den unbeschränkten LAN-Traffic einrichten. htb_error=`$TC class add dev $QOS_LOCALNET_DEV parent 10: classid 10:2 htb \ rate $QOS_LOCALNET_BAND 2>&1` $TC qdisc add dev $QOS_LOCALNET_DEV parent 10:2 sfq $mtu $perturb >/dev/null 2>&1 ## check for an error [ "$htb_error" != '' ] && echo "$RCFILE: error, creating main qos configuration" ## --- outbound traffic --- ## QOS_INTERNET_DEFAULT_UP=$((QOS_INTERNET_DEFAULT_UP + 2)) ## HTB für Outbound-Device aktivieren: htb_error=`$TC qdisc add dev $QOS_INTERNET_DEV root handle 20: htb \ default $QOS_INTERNET_DEFAULT_UP r2q 1 2>&1` ## check for an error [ -n "$htb_error" ] && echo "$RCFILE: error, creating main qos configuration" # ack and icmp stuff automagicly in a own class $TC class add dev $QOS_INTERNET_DEV parent 20: classid 20:1 htb \ rate 8Kbit ceil 32Kbit prio 1 # add a sfq discipline to the ack/icmp - class $TC qdisc add dev $QOS_INTERNET_DEV parent 20:1 handle 30: sfq perturb 10 # filter icmp $TC filter add dev $QOS_INTERNET_DEV parent 20: protocol ip prio 10 u32 \ match ip protocol 1 0xff flowid 20:1 # filter ack $TC filter add dev $QOS_INTERNET_DEV parent 20: protocol ip prio 10 u32 \ match ip protocol 6 0xff \ match u8 0x05 0x0f at 0 \ match u16 0x0000 0xffc0 at 2 \ match u8 0x10 0xff at 33 \ flowid 20:1 ## Standardklasse einrichten: htb_error=`$TC class add dev $QOS_INTERNET_DEV parent 20: classid 20:2 htb \ rate $QOS_INTERNET_BAND_UP $burst 2>&1` $TC qdisc add dev $QOS_INTERNET_DEV parent 20:2 sfq $mtu $perturb >/dev/null 2>&1 ## check for an error [ -n "$htb_error" ] && echo "$RCFILE: error, creating main qos configuration" # create classes # {{{ for (( idx= 1; idx <= $QOS_CLASS_N; idx+= 1 )) do eval parent='$QOS_CLASS_'$idx'_PARENT' eval maxband='$QOS_CLASS_'$idx'_MAXBANDWIDTH' eval minband='$QOS_CLASS_'$idx'_MINBANDWIDTH' eval direction='$QOS_CLASS_'$idx'_DIRECTION' eval prio='$QOS_CLASS_'$idx'_PRIO' [ "$prio" != '' ] && prio='prio '$prio [ "$maxband" != '' ] && maxband='ceil '$maxband if [ "$direction" = 'down' ] # {{{ then class=$((idx + 2)) eval class='10:'$class if [ "$parent" = '0' ] then cparent='10:1' else cparent=$((parent + 2)) eval cparent='10:'$cparent fi htb_error=`$TC class add dev $QOS_LOCALNET_DEV parent $cparent classid $class htb \ rate $minband $maxband $burst $prio 2>&1` $TC qdisc add dev $QOS_LOCALNET_DEV parent $class sfq $mtu $perturb >/dev/null 2>&1 fi # }}} if [ "$direction" = 'up' ] # {{{ then class=$((idx + 2)) eval class='20:'$class if [ "$parent" = '0' ] then cparent='20:2' else cparent=$((parent + 2)) eval cparent='20:'$cparent fi htb_error=`$TC class add dev $QOS_INTERNET_DEV parent $cparent classid $class htb \ rate $minband $maxband $burst $prio 2>&1` $TC qdisc add dev $QOS_INTERNET_DEV parent $class sfq $mtu $perturb >/dev/null 2>&1 fi # }}} [ -n "$htb_error" ] && echo "$RCFILE: error, creating class no. $idx" done # }}} # create filter rules # {{{ ot="-A aqosh_pre -t mangle" oit="-A aqosh_inbound -t mangle" it="-A aqosh_post -t mangle" sm="-j MARK --set-mark" lip=$(/sbin/ifconfig $QOS_LOCALNET_DEV | sed '/inet/!d' | sed -e 's/^.*inet addr://;s/ .*//') $IT -t mangle -N aqosh_post $IT -t mangle -N aqosh_inbound $IT -t mangle -N aqosh_pre $IT -A PREROUTING -t mangle -i $QOS_LOCALNET_DEV -j aqosh_pre $IT -A OUTPUT -t mangle -j aqosh_inbound $IT -A POSTROUTING -t mangle -o $QOS_LOCALNET_DEV -j aqosh_post for (( idx= $QOS_FILTER_N; idx >= 0; idx-= 1 )) # {{{ do eval class='$QOS_FILTER_'$idx'_CLASS' eval intip='$QOS_FILTER_'$idx'_IP_INTERN' eval extip='$QOS_FILTER_'$idx'_IP_EXTERN' eval port='$QOS_FILTER_'$idx'_PORT' eval type='$QOS_FILTER_'$idx'_PORT_TYPE' eval option='$QOS_FILTER_'$idx'_OPTION' [ -z "$intip" ] && intip='dummy' [ -z "$extip" ] && extip='dummy' [ -z "$port" ] && port='dummy' [ -z "$type" ] && type='dummy' for fclass in $class do eval class_direction='$QOS_CLASS_'$fclass'_DIRECTION' for fintip in $intip do [ "$fintip" = 'dummy' ] && fintip='' for fextip in $extip do [ "$fextip" = 'dummy' ] && fextip='' for fport in $port do [ "$fport" = 'dummy' ] && fport='' for ftype in $type do fport=`echo $fport | sed 's/-/:/'` du32_param='' f1_error='' f2_error='' f3_error='' if [ "$class_direction" = 'down' ] # {{{ then ffclass=$(($fclass + 2)) eval ffclass='10:'$ffclass parent="10:0" [ -n "$fintip" ] && du32_param="--dst $fintip " [ -n "$fextip" ] && du32_param="$du32_param --src $fextip " if [ "$fport" != '' ] then if [ "$ftype" = 'client' ] then du32_param="$du32_param --sport $fport " elif [ "$ftype" = 'server' ] then du32_param="$du32_param --dport $fport " fi fi case "$option" in ICMP ) f1_error=`$IT $it -p icmp $du32_param $sm $idx_down 2>&1` ;; TCP ) f1_error=`$IT $it -p tcp $du32_param $sm $idx_down 2>&1` ;; UDP ) f1_error=`$IT $it -p udp $du32_param $sm $idx_down 2>&1` ;; ACK ) f1_error=`$IT $it -p tcp -m length --length :64 $du32_param $sm $idx_down 2>&1` ;; TOSMD ) f1_error=`$IT $it -p tcp -m tos --tos 16 $du32_param $sm $idx_down 2>&1` f2_error=`$IT $it -p udp -m tos --tos 16 $du32_param $sm $idx_down 2>&1` ;; TOSMT ) f1_error=`$IT $it -p tcp -m tos --tos 8 $du32_param $sm $idx_down 2>&1` f2_error=`$IT $it -p udp -m tos --tos 8 $du32_param $sm $idx_down 2>&1` ;; TOSMR ) f1_error=`$IT $it -p tcp -m tos --tos 4 $du32_param $sm $idx_down 2>&1` f2_error=`$IT $it -p udp -m tos --tos 4 $du32_param $sm $idx_down 2>&1` ;; TOSMC) f1_error=`$IT $it -p tcp -m tos --tos 2 $du32_param $sm $idx_down 2>&1` f2_error=`$IT $it -p udp -m tos --tos 2 $du32_param $sm $idx_down 2>&1` ;; * ) if [ "$fport" != '' ] then f1_error=`$IT $it -p tcp $du32_param $sm $idx_down 2>&1` f2_error=`$IT $it -p udp $du32_param $sm $idx_down 2>&1` else f1_error=`$IT $it $du32_param $sm $idx_down 2>&1` fi ;; esac f3_error=`$TC filter add dev $QOS_LOCALNET_DEV parent $parent protocol ip handle $idx_down fw flowid $ffclass 2>&1` idx_down=$((idx_down + 1)) fi # }}} if [ "$class_direction" = 'up' ] # {{{ then if [ "$fintip" = "$lip" ] then t=$oit fintip="0.0.0.0/0" else t=$ot fi ffclass=$((fclass + 2)) eval ffclass='20:'$ffclass parent='20:0' [ -n "$fintip" ] && du32_param="--src $fintip " [ -n "$fextip" ] && du32_param="$du32_param --dst $fextip " if [ -n "$fport" ] then if [ "$ftype" = 'client' ] then du32_param="$du32_param --dport $fport " elif [ "$ftype" = 'server' ] then du32_param="$du32_param --sport $fport " fi fi case "$option" in ICMP ) f1_error=`$IT $t -p icmp $du32_param $sm $idx_up 2>&1` ;; TCP ) f1_error=`$IT $t -p tcp $du32_param $sm $idx_up 2>&1` ;; UDP ) f1_error=`$IT $t -p udp $du32_param $sm $idx_up 2>&1` ;; ACK ) f1_error=`$IT $t -p tcp -m length --length :64 $du32_param $sm $idx_up 2>&1` ;; TOSMD ) f1_error=`$IT $t -p tcp -m tos --tos 16 $du32_param $sm $idx_up 2>&1` f2_error=`$IT $t -p udp -m tos --tos 16 $du32_param $sm $idx_up 2>&1` ;; TOSMT ) f1_error=`$IT $t -p tcp -m tos --tos 8 $du32_param $sm $idx_up 2>&1` f2_error=`$IT $t -p udp -m tos --tos 8 $du32_param $sm $idx_up 2>&1` ;; TOSMR ) f1_error=`$IT $t -p tcp -m tos --tos 4 $du32_param $sm $idx_up 2>&1` f2_error=`$IT $t -p udp -m tos --tos 4 $du32_param $sm $idx_up 2>&1` ;; TOSMC ) f1_error=`$IT $t -p tcp -m tos --tos 2 $du32_param $sm $idx_up 2>&1` f2_error=`$IT $t -p udp -m tos --tos 2 $du32_param $sm $idx_up 2>&1` ;; * ) if [ -n "$fport" ] then f1_error=`$IT $t -p tcp $du32_param $sm $idx_up 2>&1` f2_error=`$IT $t -p udp $du32_param $sm $idx_up 2>&1` else f1_error=`$IT $t $du32_param $sm $idx_up 2>&1` fi ;; esac f3_error=`$TC filter add dev $QOS_INTERNET_DEV parent $parent protocol ip handle $idx_up fw flowid $ffclass 2>&1` idx_up=$((idx_up + 1)) fi # }}} ## check for an error [ -n "$f1_error" -o -n "$f2_error" -o \ -n "$f3_error" -o -n "$f3_error" ] && echo "$RCFILE: error, creating filter no. $idx" done done done done done done # }}} # }}} ## Filter einrichten, der den gesamten Traffic, der aus dem maskierten LAN kommt, ## auch in die LAN-Traffic Klasse steckt. for j in $MASQ_NETWORK do f1_error=`$IT -A aqosh_post -t mangle --src $j -j MARK --set-mark $idx_down 2>&1` f2_error=`$TC filter add dev $QOS_LOCALNET_DEV parent 10: protocol ip handle $idx_down fw flowid 10:2 2>&1` idx_down=$((idx_down + 1)) ## check for an error [ -n "$f1_error" -o -n "$f2_error" ] && echo "$RCFILE: error, creating main qos configuration" done $IT -A aqosh_pre -t mangle -j RETURN $IT -A aqosh_post -t mangle -j RETURN $IT -A aqosh_inbound -t mangle -j RETURN echo ' >> ... finished <<' fi ;; # }}} ############################################################################## ############################################################################## stop ) # {{{ if [ "$status" = '1' ] then echo ' >> stopping qos ...' $TC qdisc del dev $QOS_LOCALNET_DEV root $TC qdisc del dev $QOS_INTERNET_DEV root $IT -D PREROUTING -t mangle -i $QOS_LOCALNET_DEV -j aqosh_pre $IT -F aqosh_pre -t mangle $IT -X aqosh_pre -t mangle $IT -D OUTPUT -t mangle -j aqosh_inbound $IT -F aqosh_inbound -t mangle $IT -X aqosh_inbound -t mangle $IT -D POSTROUTING -t mangle -o $QOS_LOCALNET_DEV -j aqosh_post $IT -F aqosh_post -t mangle $IT -X aqosh_post -t mangle if [ "$USE_MODULES" = "yes" ] then /sbin/rmmod cls_fw 2>&1 > /dev/null /sbin/rmmod sch_sfq 2>&1 > /dev/null /sbin/rmmod sch_htb 2>&1 > /dev/null /sbin/rmmod ipt_tos 2>&1 > /dev/null /sbin/rmmod ipt_length 2>&1 > /dev/null /sbin/rmmod ipt_mark 2>&1 > /dev/null /sbin/rmmod iptable_mangle 2>&1 > /dev/null fi echo '... finished' else echo ' >> error, qos is NOT running <<' fi ;; # }}} ############################################################################## ############################################################################## restart) # {{{ echo ' >> restarting qos ...' $0 stop $0 start ;; # }}} ############################################################################## ############################################################################## status) # {{{ if [ "$status" = 1 ] then echo ' >> qos is [on] <<' else echo ' >> qos is [off] <<' fi ;; # }}} ############################################################################## ############################################################################## dump ) # {{{ if [ "$status" = "1" ] then echo " >> classes for [$QOS_INTERNET_DEV]:" $TC -s class show dev $QOS_INTERNET_DEV echo " >> classes for [$QOS_LOCALNET_DEV]:" $TC -s class show dev $QOS_LOCALNET_DEV echo " << done" else echo " >> qos isnt running, nothing to see << " fi ;; # }}} ############################################################################## ############################################################################## monitor ) # {{{ inc="$QOS_INTERNET_DEV" out="$QOS_LOCALNET_DEV" tmp=/tmp/rate.$$ while [ 1 ] do echo -e "\n ] aqosh - monitor [ \n" > $tmp for d in $inc $out do echo -e "\n >> upload to [$d]:\n " >> $tmp $TC -s -d class show dev $d|grep rate | \ sed -e :a -e '$!N;s/\n[[:space:]]*rate/| /;ta' -e 'P;D' | \ while read line do class=$(echo $line | awk '{ print $1, $3, $5; }' | sed -e 's/rate/root/' ) rate=$(echo $line | cut -d '|' -f 2 | awk '{print $1;}' | sed -e 's/^class.*/0bps/g' ) echo -e " \_ $class \t$rate ">> $tmp done done clear cat $tmp sleep $MREFRESH done ;; # }}} ############################################################################## ############################################################################## dangle ) $IT -L -t mangle -n -v ;; dilter ) $TC -s -d filter show dev $QOS_INTERNET_DEV $TC -s -d filter show dev $QOS_LOCALNET_DEV ;; esac set +x # }}} ############################################################################## # vim:tw=0:ts=2:sw=2:fdm=marker:fdc=4