#!/bin/sh # # Shorewall 4.2 -- /usr/share/shorewall/lib.tc # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) # # tcstart from tc4shorewall Version 0.5 # (c) 2005 Arne Bernin # Modified by Tom Eastep for integration into the Shorewall distribution # published under GPL Version 2# # # Complete documentation is available at http://shorewall.net # # This program is free software; you can redistribute it and/or modify # it under the terms of Version 2 of the GNU General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # This library is loaded by /usr/share/shorewall/compiler when TC_ENABLED=Internal # and the tcdevices and/or the tcclasses file is non-empty. It is also loaded under # the same circumstances by the compiled firewall script when processing the # 'refresh' command. # # # Arne Bernin's 'tc4shorewall' # setup_traffic_shaping() { local mtu local r2q local tc_all_devices local device local mark local rate local ceil local prio local options local devfile devfile=$(find_file tcdevices) local classfile classfile=$(find_file tcclasses) local devnum devnum=1 local last_device last_device= r2q=10 indent= prefix=1 rate_to_kbit() { local rateunit local rate rate=$1 rateunit=$( echo $rate | sed -e 's/[0-9]*//') rate=$( echo $rate | sed -e 's/[a-zA-Z]*//g') case $rateunit in kbit|Kbit) rate=$rate ;; mbit|Mbit) rate=$(expr $rate \* 1024) ;; mbps|Mbps) rate=$(expr $rate \* 8192) ;; kbps|Kbps) rate=$(expr $rate \* 8) ;; *) [ -n "$rateunit" ] && fatal_error "Invalid Rate ($1)" rate=$(expr $rate / 128) ;; esac echo $rate } calculate_quantum() { local rate rate=$(rate_to_kbit $1) echo $(( $rate * ( 128 / $r2q ) )) } # get given outbandwidth for device get_outband_for_dev() { local device local inband local outband while read device inband outband; do tcdev="$device $inband $outband" if [ "$1" = "$device" ] ; then echo $outband return fi done < $TMP_DIR/tcdevices } check_tcclasses_options() { while [ $# -gt 1 ]; do shift case $1 in default|tcp-ack|tos-minimize-delay|tos-maximize-throughput|tos-maximize-reliability|tos-minimize-cost|tos-normal-service) ;; tos=0x[0-9a-f][0-9a-f]|tos=0x[0-9a-f][0-9a-f]/0x[0-9a-f][0-9a-f]) ;; *) echo $1 return 1 ;; esac done return 0 } get_defmark_for_dev() { local searchdev local searchmark local device local ceil local prio local options searchdev=$1 while read device mark rate ceil prio options; do options=$(separate_list $options | tr '[A-Z]' '[a-z]') tcdev="$device $mark $rate $ceil $prio $options" if [ "$searchdev" = "$device" ] ; then list_search "default" $options && echo $mark &&return 0 fi done < $TMP_DIR/tcclasses return 1 } check_defmark_for_dev() { get_defmark_for_dev $1 >/dev/null } validate_tcdevices_file() { progress_message2 "Validating $devfile..." local device local inband local outband while read device inband outband; do tcdev="$device $inband $outband" check_defmark_for_dev $device || fatal_error "Option default is not defined for any class in tcclasses for interface $device" case $interface in *:*|+) fatal_error "Invalid Interface Name: $interface" ;; esac list_search $device $devices && fatal_error "Interface $device is defined more than once in tcdevices" inband=$(rate_to_kbit $inband) outband=$(rate_to_kbit $outband) tc_all_devices="$tc_all_devices $device" done < $TMP_DIR/tcdevices } validate_tcclasses_file() { progress_message2 "Validating $classfile..." local classlist local device local mark local rate local ceil local prio local bandw local wrongopt local allopts local opt allopts="" while read device mark rate ceil prio options; do tcdev="$device $mark $rate $ceil $prio $options" ratew=$(get_outband_for_dev $device) options=$(separate_list $options | tr '[A-Z]' '[a-z]') for opt in $options; do case $opt in tos=0x??) opt="$opt/0xff" ;; esac list_search "$device-$opt" $allopts && fatal_error "option $opt already defined in a chain for interface $device in tcclasses" allopts="$allopts $device-$opt" done wrongopt=$(check_tcclasses_options $options) || fatal_error "unknown option $wrongopt for class iface $device mark $mark in tcclasses file" if [ -z "$ratew" ] ; then fatal_error "device $device seems not to be configured in tcdevices" fi list_search "$device-$mark" $classlist && fatal_error "Mark $mark for interface $device defined more than once in tcclasses" # # Convert HEX/OCTAL mark representation to decimal # mark=$(($mark)) verify_mark $mark [ $mark -lt 256 ] || fatal_error "Invalid Mark Value" classlist="$classlist $device-$mark" done < $TMP_DIR/tcclasses } add_root_tc() { local defmark local dev dev=$(chain_base $device) save_command "if interface_is_up $device; then" indent="$INDENT" INDENT="$INDENT " save_command ${dev}_exists=Yes save_command qt tc qdisc del dev $device root save_command qt tc qdisc del dev $device ingress defmark=$(get_defmark_for_dev $device) run_tc qdisc add dev $device root handle $devnum: htb default ${prefix}${defmark} save_command "${dev}_mtu=\$(get_device_mtu $device)" run_tc "class add dev $device parent $devnum: classid $devnum:1 htb rate $outband mtu \$${dev}_mtu" if [ $(rate_to_kbit ${inband}) -gt 0 ]; then run_tc qdisc add dev $device handle ffff: ingress run_tc filter add dev $device parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${inband} burst 10k drop flowid :1 fi eval ${dev}_devnum=$devnum devnum=$(($devnum + 1)) save_progress_message_short " TC Device $tcdev defined." INDENT="$indent" save_command else INDENT="$INDENT " save_command error_message "\"WARNING: Device $device is not in the UP state -- traffic-shaping configuration skipped\"" save_command "${dev}_exists=" INDENT="$indent" save_command "fi" save_command return 0 } add_tc_class() { local full local classid local tospair local tosmask local quantum full=$(get_outband_for_dev $device) full=$(rate_to_kbit $full) if [ -z "$prio" ] ; then prio=1 fi case $rate in *full*) rate=$(echo $rate | sed -e "s/full/$full/") rate="$(($rate))kbit" ;; esac case $ceil in *full*) ceil=$(echo $ceil | sed -e "s/full/$full/") ceil="$(($ceil))kbit" ;; esac eval devnum=\$${dev}_devnum # # Convert HEX/OCTAL mark representation to decimal # mark=$(($mark)) classid=$devnum:${prefix}${mark} [ -n "$devnum" ] || fatal_error "Device $device not defined in $devfile" quantum=$(calculate_quantum $rate) save_command "[ \$${dev}_mtu -gt $quantum ] && quantum=\$${dev}_mtu || quantum=$quantum" run_tc "class add dev $device parent $devnum:1 classid $classid htb rate $rate ceil $ceil prio $prio mtu \$${dev}_mtu quantum \$quantum" run_tc qdisc add dev $device parent $classid handle ${prefix}${mark}: sfq perturb 10 # # add filters # if [ -n "$CLASSIFY_TARGET" ] && known_interface $device; then run_iptables -t mangle -A tcpost -o $device -m mark --mark $mark/0xFF -j CLASSIFY --set-class $classid else run_tc filter add dev $device protocol ip parent $devnum:0 prio 1 handle $mark fw classid $classid fi # #options # list_search "tcp-ack" $options && run_tc filter add dev $device parent $devnum:0 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 $classid list_search "tos-minimize-delay" $options && options="$options tos=0x10/0x10" list_search "tos-maximize-throughput" $options && options="$options tos=0x08/0x08" list_search "tos-maximize-reliability" $options && options="$options tos=0x04/0x04" list_search "tos-minimize-cost" $options && options="$options tos=0x02/0x02" list_search "tos-normal-service" $options && options="$options tos=0x00/0x1e" for tospair in $(list_walk "tos=" $options) ; do case $tospair in */*) tosmask=${tospair##*/} ;; *) tosmask=0xff ;; esac run_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos ${tospair%%/*} $tosmask flowid $classid done save_progress_message_short " TC Class $tcdev defined." return 0 } finish_device() { INDENT="$indent" save_command fi save_command } validate_tcdevices_file validate_tcclasses_file cat >&3 << __EOF__ # # Set up Traffic Shaping # setup_traffic_shaping() { __EOF__ INDENT=" " if [ -s $TMP_DIR/tcdevices ]; then [ $(list_count1 $all_tc_devices) -gt 10 ] && prefix=10 save_progress_message "Setting up Traffic Control..." progress_message2 "$DOING $devfile..." while read device inband outband; do tcdev="$device $inband $outband" add_root_tc && progress_message " TC Device $tcdev defined." done < $TMP_DIR/tcdevices fi if [ -s $TMP_DIR/tcclasses ]; then progress_message2 "$DOING $classfile..." last_device= while read device mark rate ceil prio options; do tcdev="$device $mark $rate $ceil $prio $options" options=$(separate_list $options | tr '[A-Z]' '[a-z]') dev=$(chain_base $device) if [ "$device" != "$last_device" ]; then [ -n "$last_device" ] && finish_device save_command "if [ -n \"\$${dev}_exists\" ] ; then" indent="$INDENT" INDENT="$INDENT " last_device=$device else save_command fi add_tc_class && progress_message " TC Class $tcdev defined." done < $TMP_DIR/tcclasses [ -n "$last_device" ] && finish_device fi INDENT= save_command "}" save_command }