#!/bin/sh
#
# Shorewall 3.4 -- /usr/share/shorewall/lib.tc
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
#     (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 <arne@ucbering.de>
#     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., 675 Mass Ave, Cambridge, MA 02139, 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 r2q tc_all_devices device mark rate ceil prio options devfile=$(find_file tcdevices) classfile=$(find_file tcclasses) devnum=1 last_device=
    r2q=10 indent= prefix=1

    rate_to_kbit() {
	local rateunit rate
	rate=$1
	rateunit=$( echo $rate | sed -e 's/[0-9]*//')
	rate=$( echo $rate | sed -e 's/[a-z]*//gi')

	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_to_kbit $1)
	echo $(( $rate * ( 128 / $r2q ) ))
    }

    # get given outbandwidth for device
    get_outband_for_dev() {
	local device inband 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 searchmark device ceil prio 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 device inband 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 device mark rate ceil prio bandw wrongopt allopts 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 dev

	dev=$(chain_base $device)

	save_command "if interface_is_usable $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 not up and configured -- traffic-shaping configuration skipped\""
	save_command "${dev}_exists="
	INDENT="$indent"
	save_command "fi"
	save_command

	return 0
    }

    add_tc_class() {
	local full classid tospair tosmask 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
}