#!/bin/bash
#
# FireQOS - A traffic shaper for humans...
#
#   Copyright
#
#       Copyright (C) 2013-2014 Costa Tsaousis <costa@tsaousis.gr>
#
#   License
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#
#       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, see <http://www.gnu.org/licenses/>.
#
#       See the file COPYING for details.
#

# make sure sbin is included in the path
# it seems that pppd ip-up.d script need this
export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin"

get_version() {
	GIT_REF=' (tag: v2.0.3, stable-v2),commit-8a7193d'
	local IFS=":(), "
	set -- "$GIT_REF"
	ver='$Id: 34ff7df6a0f556d1cd76ea2265b1becbbbec37bf $'
	for i in $@
	do
		case "$i" in
			*[0-9].[0.9]*)
				echo "$i" | sed -e 's/^v//'
				return 0
			;;
			commit-[0-9a-zA-Z]*)
				ver="$i"
			;;
		esac
	done
	echo "$ver"
	return 0
}
VERSION=$(get_version)

export LC_ALL=C

# let everyone read our status info
umask 022

me="$0"

shopt -s extglob

FIREQOS_SYSLOG_FACILITY="daemon"
FIREQOS_CONFIG=/etc/fireqos/fireqos.conf
FIREQOS_LOCK_FILE=/var/run/fireqos.lock
FIREQOS_LOCK_FILE_TIMEOUT=600
FIREQOS_DIR=/var/run/fireqos
FIREQOS_SAVE="${FIREQOS_DIR}/.tmp.save.$$.$RANDOM"

# Gets set to 1 if this system cannot handle sub-second resolution
FIREQOS_LOWRES_TIMER=0

# Set it to 1 to see the tc commands generated.
# Set it in the config file to overwrite this default.
FIREQOS_DEBUG=0

# These are finer debugging options.
FIREQOS_DEBUG_STACK=0
FIREQOS_DEBUG_PORTS=0
FIREQOS_DEBUG_CLASS=0
FIREQOS_DEBUG_QDISC=0
FIREQOS_DEBUG_FILTER=0
FIREQOS_DEBUG_CMD=0

# The default and minimum rate for all classes is 1/100
# of the interface bandwidth
FIREQOS_MIN_RATE_DIVISOR=100

# if set to 1, it will print a line per match statement
FIREQOS_SHOW_MATCHES=0

# the classes priority in balanced mode
FIREQOS_BALANCED_PRIO=4


COLOR_RESET="\e[0m"
COLOR_BLACK="\e[30m"
COLOR_RED="\e[31m"
COLOR_GREEN="\e[32m"
COLOR_YELLOW="\e[33m"
COLOR_BLUE="\e[34m"
COLOR_PURPLE="\e[35m"
COLOR_CYAN="\e[36m"
COLOR_WHITE="\e[37m"
COLOR_BGBLACK="\e[40m"
COLOR_BGRED="\e[41m"
COLOR_BGGREEN="\e[42m"
COLOR_BGYELLOW="\e[43m"
COLOR_BGBLUE="\e[44m"
COLOR_BGPURPLE="\e[45m"
COLOR_BGCYAN="\e[46m"
COLOR_BGWHITE="\e[47m"
COLOR_BOLD="\e[1m"

# Check for external commands
require_cmd() {
	local c=`which $1 2>/dev/null`
	
	eval "${1}_cmd=$c"
	
	if [ -z "$c" ]
	then
		echo >&2 "Command '$1' is not found in this system."
		return 1
	fi
	return 0
}

require_cmd tc || exit 1
require_cmd ip || exit 1
require_cmd modprobe 2> /dev/null
if [ $? -ne 0 ]
then
	require_cmd insmod 2> /dev/null
	if [ $? -ne 0 ]
	then
		echo >&2 "Neither modprobe nor insmod is found in this system."
		exit 1
	fi
	modprobe_cmd=$insmod_cmd
fi
require_cmd flock || exit 1
require_cmd grep || exit 1
require_cmd egrep || exit 1
require_cmd cut || exit 1
require_cmd cat || exit 1
require_cmd sed || exit 1
require_cmd tr || exit 1
require_cmd touch || exit 1
require_cmd logger || exit 1


# service definitions
# taken from firehol, with:
#
# $cat_cmd firehol.sh | $egrep_cmd "^server_.*_ports="
#

server_AH_ports="51/any"
server_amanda_ports="udp/10080"
server_aptproxy_ports="tcp/9999"
server_apcupsd_ports="tcp/6544"
server_apcupsdnis_ports="tcp/3551"
server_asterisk_ports="tcp/5038"
server_cups_ports="tcp/631 udp/631"
server_cvspserver_ports="tcp/2401"
server_darkstat_ports="tcp/666"
server_daytime_ports="tcp/13"
server_dcc_ports="udp/6277"
server_dcpp_ports="tcp/1412 udp/1412"
server_dns_ports="udp/53 tcp/53"
server_dhcprelay_ports="udp/67"
server_dict_ports="tcp/2628"
server_distcc_ports="tcp/3632"
server_eserver_ports="tcp/4661 udp/4661 udp/4665"
server_ESP_ports="50/any"
server_echo_ports="tcp/7"
server_finger_ports="tcp/79"
server_ftp_ports="tcp/21"
server_gift_ports="tcp/4302 tcp/1214 tcp/2182 tcp/2472"
server_giftui_ports="tcp/1213"
server_gkrellmd_ports="tcp/19150"
server_GRE_ports="47/any"
server_h323_ports="tcp/1720"
server_heartbeat_ports="udp/690:699"
server_http_ports="tcp/80"
server_https_ports="tcp/443"
server_iax_ports="udp/5036"
server_iax2_ports="udp/5469 udp/4569"
server_ICMP_ports="icmp/any"
server_icmp_ports="icmp/any"
server_icp_ports="udp/3130"
server_ident_ports="tcp/113"
server_imap_ports="tcp/143"
server_imaps_ports="tcp/993"
server_irc_ports="tcp/6667"
server_isakmp_ports="udp/500"
server_ipsecnatt_ports="udp/4500"
server_jabber_ports="tcp/5222 tcp/5223"
server_jabberd_ports="tcp/5222 tcp/5223 tcp/5269"
server_l2tp_ports="udp/1701"
server_ldap_ports="tcp/389"
server_ldaps_ports="tcp/636"
server_lpd_ports="tcp/515"
server_microsoft_ds_ports="tcp/445"
server_ms_ds_ports="tcp/445"
server_mms_ports="tcp/1755 udp/1755"
server_msn_ports="tcp/6891"
server_mysql_ports="tcp/3306"
server_netbackup_ports="tcp/13701 tcp/13711 tcp/13720 tcp/13721 tcp/13724 tcp/13782 tcp/13783"
server_netbios_ns_ports="udp/137"
server_netbios_dgm_ports="udp/138"
server_netbios_ssn_ports="tcp/139"
server_nntp_ports="tcp/119"
server_nntps_ports="tcp/563"
server_ntp_ports="udp/123 tcp/123"
server_nut_ports="tcp/3493 udp/3493"
server_nxserver_ports="tcp/5000:5200"
server_oracle_ports="tcp/1521"
server_OSPF_ports="89/any"
server_pop3_ports="tcp/110"
server_pop3s_ports="tcp/995"
server_portmap_ports="udp/111 tcp/111"
server_postgres_ports="tcp/5432"
server_pptp_ports="tcp/1723"
server_privoxy_ports="tcp/8118"
server_radius_ports="udp/1812 udp/1813"
server_radiusproxy_ports="udp/1814"
server_radiusold_ports="udp/1645 udp/1646"
server_radiusoldproxy_ports="udp/1647"
server_rdp_ports="tcp/3389"
server_rndc_ports="tcp/953"
server_rsync_ports="tcp/873 udp/873"
server_rtp_ports="udp/10000:20000"
server_sane_ports="tcp/6566"
server_sip_ports="udp/5060"
server_socks_ports="tcp/1080 udp/1080"
server_squid_ports="tcp/3128"
server_smtp_ports="tcp/25"
server_smtps_ports="tcp/465"
server_snmp_ports="udp/161"
server_snmptrap_ports="udp/162"
server_ssh_ports="tcp/22"
server_stun_ports="udp/3478 udp/3479"
server_submission_ports="tcp/587"
server_sunrpc_ports="${server_portmap_ports}"
server_swat_ports="tcp/901"
server_syslog_ports="udp/514"
server_telnet_ports="tcp/23"
server_tftp_ports="udp/69"
server_time_ports="tcp/37 udp/37"
server_upnp_ports="udp/1900 tcp/2869"
server_uucp_ports="tcp/540"
server_whois_ports="tcp/43"
server_vmware_ports="tcp/902"
server_vmwareauth_ports="tcp/903"
server_vmwareweb_ports="tcp/8222 tcp/8333"
server_vnc_ports="tcp/5900:5903"
server_webcache_ports="tcp/8080"
server_webmin_ports="tcp/10000"
server_xdmcp_ports="udp/177"

# FireQOS only services
server_torrents_ports="tcp/6881:6999 udp/6881:6999"
server_facetime_ports="udp/3478:3497 udp/16384:16387 udp/16393:16402"
server_hangouts_ports="udp/19305:19309 tcp/19305:19309"
server_gtalk_ports="tcp/5222 tcp/5228"
server_teamviewer_ports="tcp/5938"
server_ping_ports="icmp/any"
server_tcp_ports="tcp/any"
server_udp_ports="udp/any"
server_surfing_ports="tcp/0:1023"

save() {
	[ ! $interface_save -eq 1 ] && return 0

	local ipv=${force_ipv}
	if [ ! -z "$ipv" ]
	then
		local ipv="ipv$ipv"
	fi
	(
		printf "%q " $ipv "$@"
		printf "\n"
	) >>"${FIREQOS_SAVE}"
}

simple_service() {
	[ $interface_save -eq 1 ] && save ${FUNCNAME} "$@"

	local direction="$1"; shift
	local service="$1"; shift
	
	local s=
	for s in $service
	do
		local sports=
		eval "local sports=\$server_${s}_ports"
		if [ -z "$sports" ]
		then
			error "Service '$s' is not defined."
			exit 1
		fi
		
		# INPUT
		# server_x_ports=tcp/SPORT
		# server x src CLIENT dst SERVER ==>         dport SPORT, src CLIENT, dst SERVER ==> dport SPORT, src CLIENT, dst SERVER
		# client x src CLIENT dst SERVER ==> reverse dport SPORT, src CLIENT, dst SERVER ==> sport SPORT, dst CLIENT, src SERVER

		# OUTPUT
		# server_x_ports=tcp/SPORT
		# server x src CLIENT dst SERVER ==> reverse dport SPORT, src CLIENT, dst SERVER ==> sport SPORT, dst CLIENT, src SERVER
		# client x src CLIENT dst SERVER ==>         dport SPORT, src CLIENT, dst SERVER ==> dport SPORT, src CLIENT, dst SERVER

		# So:
		# 1. use 'server' when you are the server
		# 2. use 'client' when you are the client
		# 3. use 'src' and 'dst' to match the REQUEST, i.e. src=CLIENT, dst=SERVER
		# 4. forget about INPUT and OUTPUT interfaces, FireQOS will figure it out

		local reverse=
		case $direction in
			server)
				[ "$interface_inout" = "output" ] && local reverse="reverse"
				;;
				
			client)
				[ "$interface_inout" = "input" ] && local reverse="reverse"
				;;
				
			*)
				error "A service cannot be applied as '$direction'."
				exit 1
				;;
		esac
		
		local p=
		for p in $sports
		do
			local proto=
			local ports=
			IFS="/" read proto ports <<EOF
$p
EOF
			match -ns $reverse proto $proto dports $ports "${@}"
		done
	done
}

server() {
	simple_service server "${@}"
}

client() {
	simple_service client "${@}"
}

server4() {
	ipv4 server "${@}"
}

server6() {
	ipv6 server "${@}"
}

server46() {
	ipv46 server "${@}"
}

client4() {
	ipv4 client "${@}"
}

client6() {
	ipv6 client "${@}"
}

client46() {
	ipv46 client "${@}"
}

service() {
	error "the 'service' match is no longer supported."
	exit 1
}

fireqos_active_interfaces() {
	ls $FIREQOS_DIR/ 2>/dev/null |\
		$grep_cmd ".conf" |\
		$tr_cmd "\n" " " |\
		$sed_cmd -e "s/\.conf//g" -e "s/ \+/ /g"
}

FIREQOS_COMPLETED=
fireqos_exit() {
	if [ "$FIREQOS_COMPLETED" = "0" ]
	then
		echo >&2 "FAILED TO ACTIVATE TRAFFIC CONTROL."
		
		if [ ! -z "$interface_realdev" ]
		then
			# clear only the interface failed.
			
			echo >&2
			echo >&2 "Clearing failed interface: $interface_name ($interface_dev $interface_inout => $interface_realdev)..."
			echo >&2
			printf >&2 " %16.16s: " $interface_realdev
			echo >&2 "cleared traffic control ${interface_inout}"
			
			if [ $interface_inout = input ]
			then
				runcmd $tc_cmd qdisc del dev $interface_dev ingress >/dev/null 2>&1
				runcmd $tc_cmd qdisc del dev $interface_realdev root >/dev/null 2>&1
				
				if [ -f "$FIREQOS_DIR/ifbs/$interface_realdev" ]
				then
					printf >&2 " %16.16s: " $interface_realdev
					echo >&2 "removed IFB device"
					runcmd $ip_cmd del dev $interface_realdev name $interface_realdev type ifb >/dev/null 2>&1
				fi
			else
				runcmd $tc_cmd qdisc del dev $interface_realdev root >/dev/null 2>&1
			fi
			rm $FIREQOS_DIR/$interface_name.conf 2>/dev/null
			
			local a=
			local ifs="`fireqos_active_interfaces`"
			if [ ! -z "$ifs" ]
			then
				local a="Traffic control on these interfaces is operational: $ifs"
			else
				local a="No traffic control is operational by FireQOS."
			fi
			echo >&2
			echo >&2 "$a"
			echo >&2
			
			syslog error "FireQOS FAILED. Cleared all FireQOS changes on interface '$interface_realdev'. $a"
			
		else
			clear_everything
		fi
		
	elif [ "$FIREQOS_COMPLETED" = "1" ]
	then
		syslog info "QoS applied ok ($tc_count tc commands applied)"
		
	fi
	echo >&2 "bye..."
	
	[ -f "${FIREQOS_LOCK_FILE}" ] && rm -f "${FIREQOS_LOCK_FILE}" >/dev/null 2>&1
}

fireqos_concurrent_run_lock() {
	# open the 200th file descriptor
	exec 200>"${FIREQOS_LOCK_FILE}"
	if [ $? -ne 0 ]
	then
		echo "Cannot setup file locking. Exiting..."
		exit 1
	fi
	
	# open an exclusive lock on the 200th file descriptor
	${flock_cmd} -n 200
	if [ $? -ne 0 ]
	then
		echo >&2 "FireQOS is already running. Exiting..."
		exit 1
	fi
	
	return 0
}

syslog() {
	local p="$1"; shift
	
	$logger_cmd -p ${FIREQOS_SYSLOG_FACILITY}.$p -t "FireQOS[$$]" "${@}"
	return 0
}

error() {
	echo >&2 -e "$COLOR_RED$COLOR_BOLD"
	echo >&2
	echo >&2
	echo >&2 "ERROR:"
	echo -e >&2 "$@"
	echo >&2
	echo >&2 -e "$COLOR_RESET"
	exit 1
}

warning() {
	echo >&2 -e ":	${COLOR_YELLOW}${COLOR_BOLD}WARNING! $* ${COLOR_RESET}"
}

runcmd() {
	local debug=$FIREQOS_DEBUG
	[ $FIREQOS_DEBUG_CMD -eq 1 ] && local debug=1

	if [ $debug -eq 1 ]
	then
		echo -e -n "#$COLOR_YELLOW"
		printf " %q" "${@}"
		echo -e "$COLOR_RESET"
	fi
	
	"${@}"
}

tc_count=0
tc() {
	tc_count=$[tc_count + 1]

	local noerror=0
	if [ "$1" = "ignore-error" ]
	then
		local noerror=1
		shift
	fi
	
	local debug=$FIREQOS_DEBUG
	[ $FIREQOS_DEBUG_CLASS  -eq 1 -a "$1" = "class"  ] && local debug=1
	[ $FIREQOS_DEBUG_QDISC  -eq 1 -a "$1" = "qdisc"  ] && local debug=1
	[ $FIREQOS_DEBUG_FILTER -eq 1 -a "$1" = "filter" ] && local debug=1
	
	if [ $debug -eq 1 ]
	then
		echo -e -n "#$COLOR_YELLOW"
		printf " %q" $tc_cmd "${@}"
		echo -e "$COLOR_RESET"
	fi
	
	if [ $noerror -eq 1 ]
	then
		$tc_cmd "${@}" >/dev/null 2>&1
	else
		$tc_cmd "${@}"
		local ret=$?
		
		if [ $ret -ne 0 ]
		then
			echo >&2 -e "$COLOR_RED$COLOR_BOLD"
			echo >&2
			echo >&2
			echo >&2 "ERROR:"
			echo >&2 "tc failed with error $ret, while executing the command:"
			printf "%q " $tc_cmd "${@}"
			echo
			echo >&2
			echo >&2 -e "$COLOR_RESET"
			exit 1
		fi
	fi
}

device_mtu() {
	$ip_cmd link show dev "${1}" | $sed_cmd "s/^.* \(mtu [0-9]\+\) .*$/\1/g" | $grep_cmd ^mtu | $cut_cmd -d ' ' -f 2
}

rate2bps() {
	local r="$1"
	local p="$2" # is assumed to be the base rate in bytes per second
	
	# calculate it in bits per second (highest resolution)
	case "$r" in
		+([0-9])kbps)
			local label="Kilobytes per second"
			local identifier="kbps"
			local multiplier=$((8 * 1024))
			;;

		+([0-9])Kbps)
			local label="Kilobytes per second"
			local identifier="Kbps"
			local multiplier=$((8 * 1024))
			;;

		+([0-9])mbps)
			local label="Megabytes per second"
			local identifier="mbps"
			local multiplier=$((8 * 1024 * 1024))
			;;

		+([0-9])Mbps)
			local label="Megabytes per second"
			local identifier="Mbps"
			local multiplier=$((8 * 1024 * 1024))
			;;

		+([0-9])gbps)
			local label="Gigabytes per second"
			local identifier="gbps"
			local multiplier=$((8 * 1024 * 1024 * 1024))
			;;

		+([0-9])Gbps)
			local label="Gigabytes per second"
			local identifier="Gbps"
			local multiplier=$((8 * 1024 * 1024 * 1024))
			;;

		+([0-9])bit)
			local label="bits per second"
			local identifier="bit"
			local multiplier=1
			;;

		+([0-9])kbit)
			local label="Kilobits per second"
			local identifier="kbit"
			local multiplier=1000
			;;

		+([0-9])Kbit)
			local label="Kilobits per second"
			local identifier="Kbit"
			local multiplier=1000
			;;

		+([0-9])mbit)
			local label="Megabits per second"
			local identifier="mbit"
			local multiplier=1000000
			;;

		+([0-9])Mbit)
			local label="Megabits per second"
			local identifier="Mbit"
			local multiplier=1000000
			;;

		+([0-9])gbit)
			local label="Gigabits per second"
			local identifier="gbit"
			local multiplier=1000000000
			;;

		+([0-9])Gbit)
			local label="Gigabits per second"
			local identifier="Gbit"
			local multiplier=1000000000
			;;

		+([0-9])bps)
			local label="Bytes per second"
			local identifier="bps"
			local multiplier=8
			;;

		+([0-9])%)
			local label="Percent"
			local identifier="bps"
			local multiplier=8
			r=$((p * multiplier * `echo $r | $sed_cmd "s/%//g"` / 100))
			;;

		+([0-9]))
			local label="Kilobits per second"
			local identifier="Kbit"
			local multiplier=1000
			r=$(( r * multiplier ))
			;;

		*)	
			echo >&2 "Invalid rate '${r}' given."
			return 1
			;;
	esac
	
        local n="`echo "$r" | $sed_cmd "s|$identifier| * $multiplier|g"`"
	
	# evaluate it in bytes per second (the default for a rate in tc)
        eval "local o=\$(($n / 8))"
	
	echo "$o"
	return 0
}

calc_r2q() {
	# r2q is by default 10
	# It is used to find the default quantum (i.e. the size in bytes a class can burst above its ceiling).
	# At the same time quantum cannot be smaller than a single packet (ptu).
	# So, the default is good only if the minimum rate specified to any class is MTU * R2Q = 1500 * 10 = 15000 * 8(bits) = 120kbit
	#
	# To be adaptive, we allocate to the default classes 1/100 of the total bandwidth.
	# This means that we need :
	#
	#  rate = mtu * r2q
	#  or
	#  r2q = rate / mtu
	#
	
	local rate=$1; shift	# we expect the minimum rate that might be given
	local mtu=$1; shift
	[ -z "$mtu" ] && local mtu=1500
	
	local r2q=$(( rate / mtu ))
	
	[ $r2q -lt 1 ] && local r2q=1
	# [ $r2q -gt 10 ] && local r2q=10
	
	echo $r2q
}

parse_class_params() {
	local prefix="$1"; shift
	local parent="$1"; shift
	local ipv4=
	local ipv6=
	
	local priority_mode=
	local prio=
	local qdisc=
	local qdisc_options=
	local minrate=
	local rate=
	local ceil=
	local r2q=
	local burst=
	local cburst=
	local quantum=
	local mtu=
	local mpu=
	local tsize=
	local linklayer=
	local valid_options="$interface_inout"
	local current_options="$interface_inout"

	eval local base_rate="\$${parent}_rate"
	
	case "$force_ipv" in
		4)
			local ipv4=1
			local ipv6=0
			;;
		
		6)
			local ipv4=0
			local ipv6=1
			;;
			
		46)
			local ipv4=1
			local ipv6=1
			;;
	esac
	
	# find all work_X arguments
	while [ ! -z "$1" ]
	do
		case "$1" in
			input|output)
					local current_options="$1"
					;;

			priority|balanced)
					local priority_mode="$1"
					;;
					
			prio)
					local prio="$2"
					shift
					;;
					
			qdisc)	
					local qdisc="$2"
					local qdisc_options="default"
					if [ "$3" = "options" ]
					then
						local qdisc_options="$4"
						shift 2
					fi
					shift
					;;
			
			sfq|pfifo|bfifo|fq_codel|codel|none)
					local qdisc="$1"
					local qdisc_options="default"
					if [ "$2" = "options" ]
					then
						local qdisc_options="$3"
						shift 2
					fi
					;;
					
			minrate)
					[ "$prefix" = "class" ] && error "'$1' cannot be used in classes."
					
					if [ $valid_options = $current_options ]
					then
						local minrate="`rate2bps $2 $base_rate`"
					fi
					shift
					;;
					
			rate|min|commit)
					if [ $valid_options = $current_options ]
					then
						local rate="`rate2bps $2 $base_rate`"
					fi
					shift
					;;
					
			ceil|max)
					if [ $valid_options = $current_options ]
					then
						local ceil="`rate2bps $2 $base_rate`"
					fi
					shift
					;;
					
			r2q)
					[ "$prefix" = "class" ] && error "'$1' cannot be used in classes."
					
					local r2q="$2"
					shift
					;;
					
			burst)
					local burst="$2"
					shift
					;;
					
			cburst)
					local cburst="$2"
					shift
					;;
					
			quantum)
					# must be as small as possible, but larger than mtu
					local quantum="$2"
					shift
					;;
					
			mtu)
					local mtu="$2"
					shift
					;;
			
			mpu)
					local mpu="$2"
					shift
					;;
			
			tsize)
					local tsize="$2"
					shift
					;;
			
			overhead)
					local overhead="$2"
					shift
					;;
			
			adsl)
					local linklayer="$1"
					local linklayer_type="$2"
					local linklayer_encoding="$3"
					local diff=0
					case "$2" in
						local)	local diff=0
							;;
							
						remote)	local diff=-14
							;;
							
						*)	error "Unknown adsl option '$2'."
							return 1
							;;
					esac
					
					# default overhead values taken from http://ace-host.stuart.id.au/russell/files/tc/tc-atm/
					case "$3" in
						IPoA-VC/Mux|ipoa-vcmux|ipoa-vc|ipoa-mux)
								local overhead=$((8 + diff))
								;;
						IPoA-LLC/SNAP|ipoa-llcsnap|ipoa-llc|ipoa-snap)
								local overhead=$((16 + diff))
								;;
						Bridged-VC/Mux|bridged-vcmux|bridged-vc|bridged-mux)
								local overhead=$((24 + diff))
								;;
						Bridged-LLC/SNAP|bridged-llcsnap|bridged-llc|bridged-snap)
								local overhead=$((32 + diff))
								;;
						PPPoA-VC/Mux|pppoa-vcmux|pppoa-vc|pppoa-mux)
								local overhead=$((10 + diff))
								[ "$2" = "remote" ] && local mtu=1478
								;;
						PPPoA-LLC/SNAP|pppoa-llcsnap|pppoa-llc|pppoa-snap)
								local overhead=$((14 + diff))
								;;
						PPPoE-VC/Mux|pppoe-vcmux|pppoe-vc|pppoe-mux)
								local overhead=$((32 + diff))
								;;
						PPPoE-LLC/SNAP|pppoe-llcsnap|pppoe-llc|pppoe-snap)
								local overhead=$((40 + diff))
								[ "$2" = "remote" ] && local mtu=1492
								;;
						*)
								error "Cannot understand adsl protocol '$3'."
								return 1
								;;
					esac
					shift 2
					;;
					
			atm|ethernet)
					local linklayer="$1"
					local linklayer_type=
					local linklayer_encoding=
					;;
			
			linklayer)
					local linklayer="$2"
					local linklayer_type=
					local linklayer_encoding=
					shift
					;;
					
			*)		error "Cannot understand what '${1}' means."
					return 1
					;;
		esac
		
		shift
	done
	
	# export our parameters for the caller
	# for every parameter not set, use the parent value
	# for every one set, use the set value
	local param=
	for param in ceil burst cburst quantum qdisc qdisc_options ipv4 ipv6 priority_mode
	do
		eval local value="\$$param"
		if [ -z "$value" ]
		then
			eval export ${prefix}_${param}="\${${parent}_${param}}"
		else
			eval export ${prefix}_${param}="\$$param"
		fi
	done
	
	# no inheritance for these parameters
	local param=
	for param in rate mtu mpu tsize overhead linklayer linklayer_type linklayer_encoding r2q prio minrate
	do
		eval export ${prefix}_${param}="\$$param"
	done
	
	return 0
}

parent_path=
parent_stack_size=0
parent_push() {
	local param=
	local prefix="$1"; shift
	local vars="classid major sumrate default_class default_added filters_to name ceil burst cburst quantum qdisc rate mtu mpu tsize overhead linklayer linklayer_type linklayer_encoding r2q prio ipv4 ipv6 minrate priority_mode class_counter stab class_prio"
	
	if [ $FIREQOS_DEBUG_STACK -eq 1 ]
	then
		eval "local before=\$parent_stack_${parent_stack_size}"
		echo "PUSH $prefix: OLD(${parent_stack_size}): $before"
	fi
	
	# refresh the existing parent_* values to stack
	eval "parent_stack_${parent_stack_size}="
	for param in $vars
	do
		eval "parent_stack_${parent_stack_size}=\"\${parent_stack_${parent_stack_size}}parent_$param=\$parent_$param;\""
	done
	
	if [ $FIREQOS_DEBUG_STACK -eq 1 ]
	then
		eval "local after=\$parent_stack_${parent_stack_size}"
		echo "PUSH $prefix: REFRESHED(${parent_stack_size}): $after"
	fi
	
	# now push the new values into the stack
	parent_stack_size=$((parent_stack_size + 1))
	eval "parent_stack_${parent_stack_size}="
	for param in $vars
	do
		eval "parent_$param=\$${prefix}_$param"
		eval "parent_stack_${parent_stack_size}=\"\${parent_stack_${parent_stack_size}}parent_$param=\$${prefix}_$param;\""
	done
	
	if [ $FIREQOS_DEBUG_STACK -eq 1 ]
	then
		eval "local push=\$parent_stack_${parent_stack_size}"
		echo "PUSH $prefix: NEW(${parent_stack_size}): $push"
		#-- set | $grep_cmd ^parent
	fi
	
	if [ "$prefix" = "interface" ]
	then
		parent_path=
	else
		parent_path="$parent_path$parent_name/"
	fi
	[ $FIREQOS_DEBUG_STACK -eq 1 ] && echo "PARENT_PATH=$parent_path"
	
	set_tabs
}

parent_pull() {
	if [ $parent_stack_size -lt 1 ]
	then
		error "Cannot pull a not pushed set of values from stack."
		exit 1
	fi
	
	parent_stack_size=$((parent_stack_size - 1))
	
	eval "eval \${parent_stack_${parent_stack_size}}"
	
	if [ $FIREQOS_DEBUG_STACK -eq 1 ]
	then
		eval "local pull=\$parent_stack_${parent_stack_size}"
		echo "PULL(${parent_stack_size}): $pull"
		#-- set | $grep_cmd ^parent
	fi
	
	if [ $parent_stack_size -gt 1 ]
	then
		parent_path="`echo $parent_path | $cut_cmd -d '/' -f 1-$((parent_stack_size - 1))`/"
	else
		parent_path=
	fi
	[ $FIREQOS_DEBUG_STACK -eq 1 ] && echo "PARENT_PATH=$parent_path"
	
	set_tabs
}

parent_clear() {
	parent_stack_size=0

	set_tabs
}

class_tabs=
set_tabs() {
	class_tabs=
	local x=
	for x in `seq 1 $parent_stack_size`
	do
		class_tabs="$class_tabs	"
	done
}

check_constrains() {
	local prefix="$1"
	eval "local mtu=\$${prefix}_mtu"
	eval "local burst=\$${prefix}_burst"
	eval "local cburst=\$${prefix}_cburst"
	eval "local quantum=\$${prefix}_quantum"
	eval "local rate=\$${prefix}_rate"
	eval "local ceil=\$${prefix}_ceil"
	eval "local minrate=\$${prefix}_minrate"
	
	[ -z "$mtu" ] && eval "local mtu=$parent_mtu"
	[ -z "$mtu" ] && eval "local mtu=$interface_mtu"
	
	# check the constrains
	if [ ! -z "$mtu" ]
	then
		if [ ! -z "$quantum" ]
		then
			if [ $quantum -lt $mtu ]
			then
				warning "quantum ($quantum bytes) is less than MTU ($mtu bytes). Fixed it by setting quantum to MTU."
				eval "${prefix}_quantum=$mtu"
			fi
		fi
		
		if [ ! -z "$burst" ]
		then
			if [ $burst -lt $mtu ]
			then
				warning "burst ($burst bytes) is less than MTU ($mtu bytes). Fixed it by setting burst to MTU."
				eval "${prefix}_burst=$mtu"
			fi
		fi
		
		if [ ! -z "$cburst" ]
		then
			if [ $cburst -lt $mtu ]
			then
				warning "cburst ($cburst bytes) is less than MTU ($mtu bytes). Fixed it by setting cburst to MTU."
				eval "${prefix}_cburst=$mtu"
			fi
		fi
		
		if [ ! -z "$minrate" ]
		then
			if [ $minrate -lt $mtu ]
			then
				warning "minrate ($minrate bytes per second) is less than MTU ($mtu bytes). Fixed it by setting minrate to MTU."
				eval "${prefix}_minrate=$mtu"
			fi
		fi
	fi
	
	if [ ! -z "$ceil" ]
	then
		if [ $ceil -lt $rate ]
		then
			warning "ceil ($((ceil * 8 / 1000))kbit) is less than rate ($((rate * 8 / 1000))kbit). Fixed it by setting ceil to rate."
			eval "${prefix}_ceil=$rate"
		fi
	fi
	
	[ "$prefix" = "interface" ] && return 0
	
	if [ ! -z "$ceil" ]
	then
		if [ $ceil -gt $parent_ceil ]
		then
			warning "ceil ($((ceil * 8 / 1000))kbit) is higher than its parent's ceil ($((parent_ceil * 8 / 1000))kbit). Fixed it by settting ceil to parent's ceil."
			eval "${prefix}_ceil=$parent_ceil"
		fi
	fi
	
	if [ ! -z "$burst" -a ! -z "$parent_burst" ]
	then
		if [ $burst -gt $parent_burst ]
		then
			warning "burst ($burst bytes) is higher than its parent's burst ($parent_burst bytes). Fixed it by setting burst to parent's burst."
			eval "${prefix}_burst=$parent_burst"
		fi
	fi
	
	if [ ! -z "$cburst" -a ! -z "$parent_cburst" ]
	then
		if [ $cburst -gt $parent_cburst ]
		then
			warning "cburst ($cburst bytes) is higher than its parent's cburst ($parent_cburst bytes). Fixed it by setting cburst to parent's cburst."
			eval "${prefix}_cburst=$parent_cburst"
		fi
	fi
	
	return 0
}

check_committed_rate() {
	if [ $parent_rate -ge $parent_sumrate ]
	then
		echo -e ": $class_tabs${COLOR_BLUE}${COLOR_BOLD}committed rate $((parent_sumrate * 8 / 1000))kbit ($((parent_sumrate * 100 / parent_rate))%), the remaining $(((parent_rate - parent_sumrate)*8/1000))kbit will be spare bandwidth.${COLOR_RESET}"
	else
		echo -e ": $class_tabs${COLOR_RED}${COLOR_BOLD}committed rate $((parent_sumrate * 8 / 1000))kbit, ($((parent_sumrate * 100 / parent_rate))%), overbooked by $((-(parent_rate - parent_sumrate)*8/1000))kbit. PLEASE FIX.${COLOR_RESET}"
	fi
}

interface_major=
interface_dev=
interface_name=
interface_inout=
interface_realdev=
interface_minrate=
interface_global_class_counter=
interface_class_counter=
interface_class_prio=0
interface_qdisc_counter=
interface_default_added=
interface_default_class=
interface_classes=
interface_classes_ids=
interface_classes_monitor=
interface_sumrate=0
interface_classid=
interface_stab=
interface_save=0

ifb_interfaces=0
class_matchid=
force_ipv=

interface_close() {
	if [ -f "${FIREQOS_SAVE}" ]
	then
		# we have the output of a bidirectional interface to run

		# debug info
		# cat "${FIREQOS_SAVE}"

		# move the exiting file to a new place, to avoid recursion
		mv "${FIREQOS_SAVE}" "${FIREQOS_SAVE}.run"

		# close the existing, input, interface
		interface_close

		# run the new, output, interface
		source "${FIREQOS_SAVE}.run"

		# delete the file, we don't need it any more
		rm "${FIREQOS_SAVE}.run"

		# continue running this interface_close function, to close the output interface too
	fi

	if [ ! -z "$interface_dev" ]
	then
		# close all open class groups
		while [ $parent_stack_size -gt 1 ]
		do
			class group end
		done
		
		# if we have not added the default class
		# for the interface, add it now
		if [ $parent_default_added -eq 0 ]
		then
			class default
			parent_default_added=1
		fi
		
		check_committed_rate

		# NOT NEEDED - the default for interfaces works via kernel.
		# match all class default flowid $interface_major:$parent_default_class prio 0xffff
	fi
	
	FIREQOS_INTERFACES_COMPLETED="$interface_name $FIREQOS_INTERFACES_COMPLETED"
	
	echo "interface_classes='TOTAL|${interface_major}:1 $interface_classes'" >>"${FIREQOS_DIR}/${interface_name}.conf"
	echo "interface_classes_ids='${interface_major}_1 $interface_classes_ids'" >>"${FIREQOS_DIR}/${interface_name}.conf"
	echo "interface_classes_monitor='$interface_classes_monitor'" >>"${FIREQOS_DIR}/${interface_name}.conf"
	echo
	parent_clear
	
	interface_major=1
	interface_dev=
	interface_name=
	interface_inout=
	interface_realdev=
	interface_minrate=
	interface_global_class_counter=1
	interface_class_counter=10
	interface_class_prio=0
	interface_qdisc_counter=10
	interface_default_added=0
	interface_default_class=5000
	interface_classes=
	interface_classes_ids=
	interface_classes_monitor=
	interface_sumrate=0
	interface_classid=
	interface_stab=1
	interface_save=0
	class_matchid=1
	parent_stack_size=0
	
	class_name=
	class_filters_flowid=
	class_classid=
	class_major=
	class_group=0
	
	last_class_prio=0
	
	return 0
}

ipv4() {
	force_ipv="4"
	"${@}"
	force_ipv=
}

ipv6() {
	force_ipv="6"
	"${@}"
	force_ipv=
}

ipv46() {
	force_ipv="46"
	"${@}"
	force_ipv=
}

interface4() {
	ipv4 interface "${@}"
}

interface6() {
	ipv6 interface "${@}"
}

interface46() {
	ipv46 interface "${@}"
}

interface_count=0
interface() {
	if [ "$3" = "both" -o "$3" = "bidirectional" ]
	then
		local a1="$1"
		local a2="$2"
		local a3="$3"
		shift 3

		interface "$a1" "${a2}-in" input "$@" || return $?
		interface_save=1

		save interface "$a1" "${a2}-out" output "$@"
		return 0
	fi

	interface_count=$[interface_count + 1]

	interface_close
	
	printf ": ${FUNCNAME} %s" "$*"
	
	interface_dev="$1"; shift
	interface_name="$1"; shift
	interface_inout="$1"; shift

	if [ "$interface_inout" = "input" ]
	then
		ifb_interfaces=$((ifb_interfaces + 1))
		
		interface_realdev=$interface_dev-ifb
		interface_realdev=${interface_realdev:0:15}
		
		runcmd $ip_cmd link add dev $interface_realdev name $interface_realdev type ifb 2>/dev/null
		if [ $? -ne 0 -a $? -ne 2 ]
		then
			error "Cannot add IFB device $interface_realdev."
			exit 1
		fi
		
		# remember we used this interface
		$touch_cmd $FIREQOS_DIR/ifbs/$interface_realdev
		
		runcmd $ip_cmd link set dev $interface_realdev up
		if [ $? -ne 0 ]
		then
			error "Cannot bring device $interface_realdev UP. Do you have 'ifb' enabled in the kernel?"
			exit 1
		fi
	else
		# for 'output' interfaces, realdev is dev
		interface_realdev=$interface_dev
	fi
	
	# parse the parameters given
	parse_class_params interface noparent "${@}"
	
	[ -z "$interface_priority_mode" ] && interface_priority_mode="priority"
	
	# IPv, this is only used by matches
	# here we just give the defaults for inheritance to work
	if [ -z "$interface_ipv4" -a -z "$interface_ipv6" ]
	then
		interface_ipv4=1
		interface_ipv6=0
	elif [ -z "$interface_ipv4" ]
	then
		interface_ipv4=0
	elif [ -z "$interface_ipv6" ]
	then
		interface_ipv6=0
	fi
	
	# check important arguments
	if [ -z "$interface_rate" ]
	then
		error "Cannot figure out the rate of interface '${interface_dev}'."
		return 1
	fi
	
	if [ -z "$interface_mtu" ]
	then
		# to find the mtu, we query the original device, not an ifb device
		interface_mtu=`device_mtu $interface_dev`
		
		if [ -z "$interface_mtu" ]
		then
			interface_mtu=1500
			warning "Device MTU cannot be detected. Setting it to 1500 bytes."
		fi
	fi
	
	# fix stab
	local stab=
	if [ ! -z "$interface_linklayer" ]
	then
		local stab="stab"
		test ! -z "$interface_linklayer"	&& local stab="$stab linklayer $interface_linklayer"
		test ! -z "$interface_overhead"		&& local stab="$stab overhead $interface_overhead"
		test ! -z "$interface_tsize"		&& local stab="$stab tsize $interface_tsize"
		test ! -z "$interface_mtu"		&& local stab="$stab mtu $interface_mtu"
		test ! -z "$interface_mpu"		&& local stab="$stab mpu $interface_mpu"
	fi
	
	# the default ceiling for the interface, is the rate of the interface
	# if we don't respect this, all unclassified traffic will get just 1kbit!
	[ -z "$interface_ceil" ] && interface_ceil=$interface_rate
	
	# set the default qdisc for all classes
	if [ -z "$interface_qdisc" ]
	then
		interface_qdisc="$FIREQOS_DEFAULT_QDISC"
		interface_qdisc_options="$FIREQOS_DEFAULT_QDISC_OPTIONS"
	fi
	
	# the desired minimum rate for all classes
	[ -z "$interface_minrate" ] && interface_minrate=$((interface_rate / FIREQOS_MIN_RATE_DIVISOR))
	
	# calculate the default r2q for this interface
	# *** THIS MAY NOT BE NEEDED ANYMORE, SINCE WE ALWAYS SET QUANTUM ***
	if [ -z "$interface_r2q" ]
	then
		interface_r2q=`calc_r2q $interface_minrate $interface_mtu`
	fi
	
	# the actual minimum rate we can get
	local r=$((interface_r2q * interface_mtu))
	[ $r -gt $interface_minrate ] && interface_minrate=$r
	
	# set the default quantum
	[ -z "$interface_quantum" ] && interface_quantum=$interface_mtu
	
	check_constrains interface
	
	local rate="rate $((interface_rate * 8 / 1000))kbit"
	local minrate="rate $((interface_minrate * 8 / 1000))kbit"
	[ ! -z "$interface_ceil" ]			&& local ceil="ceil $((interface_ceil * 8 / 1000))kbit"
	[ ! -z "$interface_burst" ]			&& local burst="burst $interface_burst"
	[ ! -z "$interface_cburst" ]			&& local cburst="cburst $interface_cburst"
	[ ! -z "$interface_quantum" ]			&& local quantum="quantum $interface_quantum"
	[ ! -z "$interface_r2q" ]			&& local r2q="r2q $interface_r2q"
	
	echo -e " $COLOR_BOLD$COLOR_BLUE($interface_realdev, $((interface_rate*8/1000))kbit, MTU $interface_mtu, quantum $interface_quantum)$COLOR_RESET"
	
	# remember we used this interface
	echo "$interface_name" >$FIREQOS_DIR/ifaces/$interface_realdev
	
	# Add root qdisc with proper linklayer and overheads
	tc ignore-error qdisc del dev $interface_realdev root
	tc qdisc add dev $interface_realdev root handle $interface_major: $stab htb default $interface_default_class $r2q
	
	# redirect all incoming traffic to ifb
	if [ $interface_inout = input ]
	then
		# Redirect all incoming traffic to ifb
		# We then shape the traffic in the output of ifb
		tc ignore-error qdisc del dev $interface_dev ingress
		tc qdisc add dev $interface_dev ingress
		
		tc filter add dev $interface_dev parent ffff: protocol all prio 39999 u32 match u32 0 0 action mirred egress redirect dev $interface_realdev
	fi
	
	interface_classid="$interface_major:1"
	
	# Add the root class for the interface
	tc class add dev $interface_realdev parent $interface_major: classid $interface_classid htb $rate $ceil $burst $cburst $quantum
	
	interface_filters_to="$interface_major:0"
	
	parent_push interface
	
	# default IPv for the classes
	class_ipv4=$interface_ipv4
	class_ipv6=$interface_ipv6
	class_name=root
	
	[ -f "${FIREQOS_DIR}/${interface_name}.conf" ] && rm "${FIREQOS_DIR}/${interface_name}.conf"
	$cat_cmd >"${FIREQOS_DIR}/${interface_name}.conf" <<EOF
interface_name="$interface_name"
interface_rate="$interface_rate"
interface_ceil="$interface_ceil"
interface_dev="$interface_dev"
interface_realdev="$interface_realdev"
interface_inout="$interface_inout"
interface_minrate="$interface_minrate"
interface_linklayer="$interface_linklayer"
interface_linklayer_type="$interface_linklayer_type"
interface_linklayer_encoding="$interface_linklayer_encoding"
interface_overhead="$interface_overhead"
interface_minrate="$interface_minrate"
interface_r2q="$interface_r2q"
interface_burst="$interface_burst"
interface_cburst="$interface_cburst"
interface_quantum="$interface_quantum"
interface_mtu="$interface_mtu"
interface_mpu="$interface_mpu"
interface_tsize="$interface_tsize"
interface_qdisc="$interface_qdisc"
interface_qdisc_options="$interface_qdisc_options"
class_${interface_major}_1_name="TOTAL"
class_${interface_major}_1_classid="CLASSID"
class_${interface_major}_1_priority="PRIORITY"
class_${interface_major}_1_rate="COMMIT"
class_${interface_major}_1_ceil="MAX"
class_${interface_major}_1_burst="BURST"
class_${interface_major}_1_cburst="CBURST"
class_${interface_major}_1_quantum="QUANTUM"
class_${interface_major}_1_qdisc="QDISC"
EOF
	
	return 0
}

class_name=
class_classid=
class_major=
class_group=0

class4() {
	ipv4 class "${@}"
}

class6() {
	ipv6 class "${@}"
}

class46() {
	ipv46 class "${@}"
}

class_count=0
class() {
	[ $interface_save -eq 1 ] && save ${FUNCNAME} "$@"

	class_count=$[class_count + 1]

	# check if the have to push into the stack the last class (if it was a group class)
	if [ $class_group -eq 1 ]
	then
		# the last class was a group 
		# filters have been added to it, and now we have reached its first child class
		# we push the previous class, into the our parents stack
		
		class_default_added=0
		parent_push class
		
		# the current command is the first child class
	fi
	
	# reset the values of the current class
	class_name=
	class_classid=
	class_major=
	class_group=0
	
	# if this is a group class
	if [ "$1" = "group" ]
	then
		shift
		
		# if this is the end of a group class
		if [ "$1" = "end" ]
		then
			shift
			
			if [ $parent_stack_size -le 1 ]
			then
				error "No open class group to end."
				exit 1
			fi
			
			if [ $parent_default_added -eq 0 ]
			then
				class default
			fi
			
			# In nested classes, the default of the parent class is not respected
			# by the kernel. This rule, sends all remaining traffic to the inner default.
			match all class default flowid $parent_major:$parent_default_class prio 0xfffe
			
			check_committed_rate

			if [ $parent_stab -eq 1 ]
			then
				parent_pull
			else
				local pc=$parent_class_counter
				parent_pull
				parent_class_counter=$pc
			fi
			return 0
		elif [ "$1" = "default" ]
		then
			error "The default class cannot have subclasses."
			exit 1
		fi
		
		class_group=1
	fi
	
	printf ": $class_tabs${FUNCNAME} %s" "$*"
	
	class_name="$1"; shift
	
	# increase the interface global class counter
	interface_global_class_counter=$((interface_global_class_counter + 1))
	
	# increase the parent's class counter
	# this is used for determining the minor of the class
	parent_class_counter=$((parent_class_counter + 1))
	
	# if this is the default class, use the pre-defined id,
	# otherwise use the classid we just increased
	if [ "$class_name" = "default" ]
	then
		local id=$parent_default_class
	else
		local id=$parent_class_counter
	fi
	
	# the tc classid that we will create
	# this is used for the parent of all child classed
	class_classid="$parent_major:$id"
	
	# the flowid the matches on this class will classify the packets
	class_filters_flowid="$parent_major:$id"
	
	# the id of the class in the config, for getting status info about it
	local ncid="${parent_major}_$id"
	
	# the handle of the new qdisc we will create
	interface_qdisc_counter=$((interface_qdisc_counter + 1))
	class_major=$interface_qdisc_counter
	
	parse_class_params class parent quantum "RESET" "${@}"
	
	if [ "$class_quantum" = "RESET" ]
	then
		if [ ! -z "$class_mtu" ]
		then
			class_quantum=$class_mtu
		else
			class_quantum=$parent_quantum
		fi
	fi
	
	# the priority of this class, compared to the others in the same interface
	if [ "$class_prio" = "keep" -o "$class_prio" = "last" ]
	then
		class_prio=$last_class_prio
	elif [ -z "$class_prio" ]
	then
		if [ "$parent_priority_mode" = "balanced" ]
		then
			class_prio=$FIREQOS_BALANCED_PRIO
		else
			class_prio=$parent_class_prio
			
			# increase the parent's priority of subclasses
			parent_class_prio=$((parent_class_prio + 1))
		fi
	else
		parent_class_prio=$((class_prio + 1))
	fi

	[ $class_prio -lt 0 ] && class_prio=0
	[ $class_prio -gt 7 ] && class_prio=7
	
	# remember this prio, in case we need it later
	last_class_prio="$class_prio"
	
	# if not specified, set the minimum rate
	if [ -z "$class_rate" ]
	then
		[ $class_group -eq 1 ] && error "Class group '$class_name' does not specify a committed rate.\nClass groups should have a committed rate."
		class_rate=$interface_minrate
	fi

	# class rate cannot go bellow 1/100 of the interface rate
	if [ $class_rate -lt $interface_minrate ]
	then
		warning "class rate ($((class_rate * 8 /1000))kbit) was less than the interface minrate ($((interface_minrate * 8 /1000))kbit). Fixed it by setting class rate to minrate."
		class_rate=$interface_minrate
	fi
	
	check_constrains class
	
	[ ! -z "$class_rate" ]		&& local rate="rate $((class_rate * 8 / 1000))kbit"
	[ ! -z "$class_ceil" ]		&& local ceil="ceil $((class_ceil * 8 / 1000))kbit"
	[ ! -z "$class_burst" ]		&& local burst="burst $class_burst"
	[ ! -z "$class_cburst" ]	&& local cburst="cburst $class_cburst"
	[ ! -z "$class_quantum" ]	&& local quantum="quantum $class_quantum"
	
	# construct the stab for group class
	# later we will check if this is accidentaly used in leaf classes
	local stab=
	if [ ! -z "$class_linklayer" ]
	then
		[ -z "$class_mtu" ] && class_mtu="$parent_mtu"
		
		local stab="stab"
		test ! -z "$class_linklayer"	&& local stab="$stab linklayer $class_linklayer"
		test ! -z "$class_overhead"	&& local stab="$stab overhead $class_overhead"
		test ! -z "$class_tsize"	&& local stab="$stab tsize $class_tsize"
		test ! -z "$class_mtu"		&& local stab="$stab mtu $class_mtu"
		test ! -z "$class_mpu"		&& local stab="$stab mpu $class_mpu"
	fi
	
	# check it the user overbooked the parent
	parent_sumrate=$((parent_sumrate + $class_rate))
	local info_color="$COLOR_BLUE"
	[ $parent_sumrate -gt $parent_rate ] && local info_color="$COLOR_RED"
	
	if [ $class_group -eq 1 -a ! -z "$stab" ]
	then
		echo -e " $info_color$COLOR_BOLD($class_classid, $((class_rate*8/1000))kbit, prio $class_prio, MTU $class_mtu, quantum $class_quantum)$COLOR_RESET"
	else
		echo -e " $info_color$COLOR_BOLD($class_classid, $((class_rate*8/1000))kbit, prio $class_prio)$COLOR_RESET"
	fi
	
	# keep track of all classes in the interface, so that the matches can name them to get their flowid
	interface_classes="$interface_classes $class_name|$class_filters_flowid"
	interface_classes_ids="$interface_classes_ids $ncid"
	
	class_default_class=
	if [ $class_group -eq 1 ]
	then
		# this class will have subclasses
		
		class_class_prio=0
		
		# the default class that all unmatched traffic will be sent to
		class_default_class="$((interface_default_class + interface_qdisc_counter))"
		
		# if the user added a stab, we need a qdisc and a slave class bellow the qdisc
		if [ ! -z "$stab" ]
		then
			# this is a group class with a linklayer
			# we add a qdisc with the stab, and an HTB class bellow it
			
			# add the class
			tc class add dev $interface_realdev parent $parent_classid classid $class_classid htb $rate $ceil $burst $cburst prio $class_prio $quantum
			
			# attach a qdisc
			tc qdisc add dev $interface_realdev parent $class_classid handle $class_major: $stab htb default $class_default_class
			
			# attach a class bellow the qdisc
			tc class add dev $interface_realdev parent $class_major: classid $class_major:1 htb $rate $ceil $burst $cburst $quantum
			
			# the parent of the child classes
			class_classid="$class_major:1"
			
			# the qdisc the filters of all child classes should be attached to
			class_filters_to="$class_major:0"
			
			class_class_counter=10
			class_stab=1
		else
			# this is a group class without a linklayer
			
			# add the class
			tc class add dev $interface_realdev parent $parent_classid classid $class_classid htb $rate $ceil $burst $cburst prio $class_prio $quantum
			
			# there is no need for a qdisc (HTB class directly attached to an HTB class)
			
			class_major=$parent_major
			class_filters_to="$class_classid"
			
			class_class_counter=$parent_class_counter
			class_stab=0
		fi
		
		# if the user didn't give an mtu, set it to our parent's mtu.
		# we do this, just for maintaining inheritance.
		# (we don't set it to this class - will only be used by the subclasses)
		[ -z "$class_mtu" ] && class_mtu=$parent_mtu
		
		# this class will become a parent [parent_push()], as soon as we encounter the next class.
		# we don't push it now as the parent, because we need to add filters to its parent, redirecting traffic to this class.
		# so we add the filters and when we encounter the next class, we push it into the parents' stack, so that it becomes
		# the parent for all classes following, until we encounter its matching 'class group end'.
		
	else
		# this is a leaf class (no child classes possible)
		
		if [ ! -z "$stab" ]
		then
			error "Linklayer can be used only in interfaces and group classes."
			exit 1
		fi
		
		# add the class
		tc class add dev $interface_realdev parent $parent_classid classid $class_classid htb $rate $ceil $burst $cburst prio $class_prio $quantum
		
		# default qdisc options
		if [ -z "$class_qdisc_options" -o "$class_qdisc_options" = "default" ]
		then
			# the user didn't give options to this class' qdisc
			# find the global qdisc default
			
			eval "local qdisc_options=\$FIREQOS_DEFAULT_QDISC_OPTIONS_$class_qdisc"
			
			if [ -z "$qdisc_options" ]
			then
				# there is no global default
				# check if we have an internal default for it
				
				case "$class_qdisc" in
					sfq)	qdisc_options="perturb 10 quantum $class_quantum"
						;;
				esac
			fi
		else
			local qdisc_options="$class_qdisc_options"
		fi
		local qdisc="$class_qdisc $qdisc_options"
		
		# attach a qdisc, if we have to
		if [ ! "$class_qdisc" = "none" ]
		then
			local qdisc_cmd="dev $interface_realdev $stab parent $class_classid handle $class_major: $qdisc"
			tc qdisc add $qdisc_cmd
		fi
		
		# if this is the default, make sure we don't added again
		if [ "$class_name" = "default" ]
		then
			parent_default_added=1
			interface_classes_monitor="$interface_classes_monitor $parent_path$class_name|$parent_path$class_name|$class_classid|$class_major:"
		else
			interface_classes_monitor="$interface_classes_monitor $class_name|$parent_path$class_name|$class_classid|$class_major:"
		fi
	fi
	
	local name="$class_name"
	[ $parent_stack_size -gt 1 ] && local name="${parent_name:0:2}/$class_name"
	
	# save the configuration
	$cat_cmd >>"${FIREQOS_DIR}/${interface_name}.conf" <<EOF
class_${ncid}_name="$name"
class_${ncid}_path="$parent_path$class_name"
class_${ncid}_dev="$interface_realdev"
class_${ncid}_classid="$class_classid"
class_${ncid}_priority="$class_prio"
class_${ncid}_rate="$class_rate"
class_${ncid}_ceil="$class_ceil"
class_${ncid}_burst="$class_burst"
class_${ncid}_cburst="$class_cburst"
class_${ncid}_quantum="$class_quantum"
class_${ncid}_qdisc="$class_qdisc"
class_${ncid}_qdisc_parent="$class_classid"
class_${ncid}_qdisc_handle="$class_major:"
class_${ncid}_qdisc_stab="$stab"
class_${ncid}_qdisc_options="$qdisc_options"
class_${ncid}_qdisc_cmd="$qdisc_cmd"
class_${ncid}_group="$class_group"
EOF
	
	return 0
}

find_port_masks() {
	local from=$(($1))
	local to=$(($2))
	
	if [ -z "$to" ]
	then
		[ $FIREQOS_DEBUG_PORTS -eq 1 ] && echo >&2 "$from/0xffff"
		echo "$from/0xffff"
		return 0
	fi
	
	if [ $from -ge $to ]
	then
		[ $FIREQOS_DEBUG_PORTS -eq 1 ] && echo >&2 "$from/0xffff"
		echo "$from/0xffff"
		return 0
	fi
	
	# find the biggest power of two that fits in the range
	# starting from $from
	local i=
	for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
	do
		local base=$(( (from >> i) << $i ))
		local end=$(( base + (1 << $i) - 1 ))

		[ $FIREQOS_DEBUG_PORTS -eq 1 ] && printf >&2 ": >>> examine bit %d, from 0x%04x (%s) to 0x%04x (%s), " $i $base $base $end $end

		[ $base -ne $from ] && break
		[ $end -gt $to ] && break

		[ $FIREQOS_DEBUG_PORTS -eq 1 ] && echo >&2 " ok"
	done
	[ $FIREQOS_DEBUG_PORTS -eq 1 ] && echo >&2 " failed"
	
	i=$[ i - 1 ]
	local base=$(( (from >> i) << $i ))
	local end=$(( base + (1 << $i) - 1 ))
	local mask=$(( (0xffff >> $i) << $i ))
	
	[ $FIREQOS_DEBUG_PORTS -eq 1 ] && printf >&2 ": 0x%04x (%d) to 0x%04x (%d),  match 0x%04x (%d) to 0x%04x (%d) with mask 0x%04x \n" $from $from $to $to $base $base $end $$
	printf "%d/0x%04x\n" $base $mask
	if [ $end -lt $to ]
	then
		local next=$[end + 1]
		[ $FIREQOS_DEBUG_PORTS -eq 1 ] && printf >&2 "\n: next range 0x%04x (%d) to 0x%04x (%d)\n" $next $next $to $to
		find_port_masks $next $to
	fi
	return 0
}

expand_ports() {
	while [ ! -z "$1" ]
	do
		local p=`echo $1 | $tr_cmd ":-" "  "`
		case $p in
			any|all)
				echo $p
				;;
			
			*)	find_port_masks $p
				;;
		esac
		shift
	done
	return 0
}

match4() {
	ipv4 match "${@}"
}

match6() {
	ipv6 match "${@}"
}

match46() {
	ipv46 match "${@}"
}

match_count=0
match() {
	match_count=$[match_count + 1]

	if [ "z$1" = "z-ns" ]
	then
		shift
	else
		[ $interface_save -eq 1 ] && save ${FUNCNAME} "$@"
	fi

	[ $FIREQOS_DEBUG -eq 1 -o $FIREQOS_SHOW_MATCHES -eq 1 ] && echo ":		${FUNCNAME} $*"
	
	local proto=any
	local port=any
	local sport=any
	local dport=any
	local src=any
	local dst=any
	local ip=any
	local tos=any
	local mark=any
	local srcmac=any
	local dstmac=any
	local class=$class_name
	local flowid=$class_filters_flowid
	local ack=0
	local syn=0
	local at=
	local custom=
	local tcproto=
	local ipv4=$class_ipv4
	local ipv6=$class_ipv6
	local reverse=0
	local estimator_interval=
	local estimator_decay=
	local police_arg=

	case "$force_ipv" in
		4)
			local ipv4=1
			local ipv6=0
			;;
		
		6)
			local ipv4=0
			local ipv6=1
			;;
			
		46)
			local ipv4=1
			local ipv6=1
			;;
	esac
	
	while [ ! -z "$1" ]
	do
		case "$1" in
			input|output)
				local reverse=1
				[ "$interface_inout" = "$1" ] && local reverse=0
				;;

			reverse)
				local reverse=1
				;;

			at)
				local at="$2"
				shift
				;;
			
			root)
				local at="root"
				;;
			
			syn|syns)
				local syn=1
				;;
				
			ack|acks)
				local ack=1
				;;
				
			arp)
				local tcproto="$1"
				;;
				
			tcp|TCP|udp|UDP|icmp|ICMP|gre|GRE|ipv6|IPv6|all)
				local proto="$1"
				;;
				
			tos|priority)
				local tos="$2"
				shift
				;;
				
			mark|marks)
				local mark="$2"
				shift
				;;
				
			proto|protocol|protocols)
				local proto="$2"
				shift
				;;
			
			port|ports)
				local port="$2"
				shift
				;;
			
			sport|sports)
				local sport="$2"
				shift
				;;
			
			dport|dports)
				local dport="$2"
				shift
				;;
			
			src)
				local src="$2"
				shift
				;;
			
			dst)
				local dst="$2"
				shift
				;;
			
			prio)
				local prio="$2"
				shift
				;;
			
			ip|ips|net|nets|host|hosts)
				local ip="$2"
				shift
				;;
			
			class)
				local class="$2"
				shift
				;;
			
			flowid)
				local flowid="$2"
				shift
				;;
			
			custom)
				local custom="$2"
				shift
				;;
				
			estimator)
				local estimator_interval="$2"
				local estimator_decay="$3"
				shift 2
				;;

			police)
				local police_arg="$2"
				shift
				;;

			limit)
				local limit_rate="`rate2bps $2 $class_rate`"
				local estimator_interval="500msec"
				local estimator_decay="1sec"
				local police_arg="rate $[limit_rate * 8 / 1000]kbit burst 50kb continue"
				shift
				;;

			srcmac|smac)
				local srcmac=`echo "$2" | sed -e "s/://g"`
				shift
				;;

			dstmac|dmac)
				local dstmac=`echo "$2" | sed -e "s/://g"`
				shift
				;;

			*)	error "Cannot understand what the filter '${1}' is."
				return 1
				;;
		esac
		shift
	done
	
	# if reverse, flip src/dst sport/dport
	if [ $reverse -eq 1 ]
	then
		local t="$src"
		local src="$dst"
		local dst="$t"

		local t="$sport"
		local sport="$dport"
		local dport="$t"

		local t="$srcmac"
		local srcmac="$dstmac"
		local dstmac="$t"
	fi

	if [ -z "$prio" ]
	then
		local prio=$((class_matchid * 10))
		class_matchid=$((class_matchid + 1))
	fi
	
	local p=`echo $port | $tr_cmd "," " "`; local port=`expand_ports $p`
	local p=`echo $sport | $tr_cmd "," " "`; local sport=`expand_ports $p`
	local p=`echo $dport | $tr_cmd "," " "`; local dport=`expand_ports $p`
	
	local proto=`echo $proto | $tr_cmd "," " "`;
	local ip=`echo $ip | $tr_cmd "," " "`;
	local src=`echo $src | $tr_cmd "," " "`;
	local dst=`echo $dst | $tr_cmd "," " "`;
	local mark=`echo $mark | $tr_cmd "," " "`;
	local tos=`echo $tos | $tr_cmd "," " "`;
	
	[ -z "$proto" ]	&& error "Cannot accept empty protocol."		&& return 1
	[ -z "$port" ]	&& error "Cannot accept empty ports."			&& return 1
	[ -z "$sport" ]	&& error "Cannot accept empty source ports."		&& return 1
	[ -z "$dport" ]	&& error "Cannot accept empty destination ports."	&& return 1
	[ -z "$src" ]	&& error "Cannot accept empty source IPs."		&& return 1
	[ -z "$dst" ]	&& error "Cannot accept empty destination IPs."		&& return 1
	[ -z "$ip" ]	&& error "Cannot accept empty IPs."			&& return 1
	[ -z "$tos" ]	&& error "Cannot accept empty TOS."			&& return 1
	[ -z "$mark" ]	&& error "Cannot accept empty MARK."			&& return 1
	[ -z "$srcmac" ]	&& error "Cannot accept empty source MAC."		&& return 1
	[ -z "$dstmac" ]	&& error "Cannot accept empty destination MAC."		&& return 1
	
	[ ! "$port" = "any" -a ! "$sport" = "any" ]	&& error "Cannot match 'port' and 'sport'." && exit 1
	[ ! "$port" = "any" -a ! "$dport" = "any" ]	&& error "Cannot match 'port' and 'dport'." && exit 1
	[ ! "$ip" = "any" -a ! "$src" = "any" ]		&& error "Cannot match 'ip' and 'src'." && exit 1
	[ ! "$ip" = "any" -a ! "$dst" = "any" ]		&& error "Cannot match 'ip' and 'dst'." && exit 1
	
	local device=$interface_realdev
	local parent="$parent_filters_to"
	if [ -z "$flowid" ]
	then
		error "Please set 'flowid' for match statements above all classes."
		exit 1
	elif [ ! "$class" = "$class_name" ]
	then
		local c=
		for c in $interface_classes
		do
			local cn="`echo $c | $cut_cmd -d '|' -f 1`"
			local cf="`echo $c | $cut_cmd -d '|' -f 2`"
			
			if [ "$class" = "$cn" ]
			then
				local flowid=$cf
				break
			fi
		done
		
		if [ -z "$flowid" ]
		then
			error "Cannot find class '$class'"
			exit 1
		fi
	fi
	
	if [ ! -z "$at" ]
	then
		case "$at" in
			root)
				local parent="$interface_filters_to"
				;;

			*)
				local c=
				for c in $interface_classes
				do
					local cn="`echo $c | $cut_cmd -d '|' -f 1`"
					local cf="`echo $c | $cut_cmd -d '|' -f 2`"
					
					if [ "$class" = "$cn" ]
					then
						local parent=$cf
						break
					fi
				done
				
				if [ -z "$parent" ]
				then
					error "Cannot find class '$class'"
					exit 1
				fi
				;;
		esac
	fi
	
	if [ -z "$tcproto" ]
	then
		[ $ipv4 -eq 1 ] && local tcproto="$tcproto ip"
		[ $ipv6 -eq 1 ] && local tcproto="$tcproto ipv6"
	fi
	
	# create all tc filter statements
	for tcproto_arg in $tcproto
	do
		local ipvx=
		local ether_type=

		case $tcproto_arg in
			ip)
				local ether_type="0x0800"
				;;

			ipv6)
				local ether_type="0x86DD"
				local ipvx="6"
				;;

			*)
		esac
		
		local tproto=
		for tproto in $proto
		do
			local ack_arg=
			local syn_arg=
			local proto_arg=
			case $tproto in
					any)	;;
					
					all)
							local proto_arg="match ip$ipvx protocol 0 0x00"
							;;
					
					ipv6|IPv6)
							local proto_arg="match ip$ipvx protocol 41 0xff"
							;;
							
					icmp|ICMP)
							if [ "$ipvx" = "6" ]
							then
								local proto_arg="match ip$ipvx protocol 58 0xff"
							else
								local proto_arg="match ip$ipvx protocol 1 0xff"
							fi
							;;
							
					tcp|TCP)
							local proto_arg="match ip$ipvx protocol 6 0xff"
							
							# http://www.lartc.org/lartc.html#LARTC.ADV-FILTER
							if [ $ack -eq 1 ]
							then
								if [ "$ipvx" = "6" ]
								then
									local ack_arg="match u8 0x10 0xff at nexthdr+13"
									error "I don't know how to match ACKs in ipv6"
									exit 1
								else
									local ack_arg="match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33"
								fi
							fi
							
							if [ $syn -eq 1 ]
							then
								if [ "$ipvx" = "6" ]
								then
									# I figured this out, based on the ACK match
									local syn_arg="match u8 0x02 0x02 at nexthdr+13"
									
									error "I don't know how to match SYNs in ipv6"
									exit 1
								else
									# I figured this out, based on the ACK match
									local syn_arg="match u8 0x02 0x02 at 33"
								fi
							fi
							;;
					
					udp|UDP)
							local proto_arg="match ip$ipvx protocol 17 0xff"
							;;
					
					gre|GRE)
							local proto_arg="match ip$ipvx protocol 47 0xff"
							;;

					+([0-9]))
							local proto_arg="match ip$ipvx protocol $tproto 0xff"
							;;
					
					*)		local pid=`$cat_cmd /etc/protocols | $egrep_cmd -i "^$tproto[[:space:]]" | tail -n 1 | $sed_cmd "s/[[:space:]]\+/ /g" | $cut_cmd -d ' ' -f 2`
							if [ -z "$pid" ]
							then
								error "Cannot find protocol '$tproto' in /etc/protocols."
								return 1
							fi
							local proto_arg="match ip$ipvx protocol $pid 0xff"
							;;
			esac
			
			local tip=
			local mtip=src
			local otherip="dst $ip"
			[ "$ip" = "any" ] && local otherip=
			for tip in $ip $otherip
			do
				[ "$tip" = "dst" ] && local mtip="dst" && continue
				
				local ip_arg=
				case "$tip" in
					any)
						;;
					
					all)
						local ip_arg="match ip$ipvx $mtip 0.0.0.0/0"
						;;
					
					*)	local ip_arg="match ip$ipvx $mtip $tip"
						;;
				esac
				
				local tsrc=
				for tsrc in $src
				do
					local src_arg=
					case "$tsrc" in
						any)	;;
						
						all)
							local src_arg="match ip$ipvx src 0.0.0.0/0"
							;;
						
						*)	local src_arg="match ip$ipvx src $tsrc"
							;;
					esac
					
					local tdst=
					for tdst in $dst
					do
						local dst_arg=
						case "$tdst" in
							any)	;;
							
							all)	local dst_arg="match ip$ipvx dst 0.0.0.0/0"
								;;
								
							*)	local dst_arg="match ip$ipvx dst $tdst"
								;;
						esac
						
						local tport=
						local mtport=sport
						local otherport="dport $port"
						[ "$port" = "any" ] && local otherport=
						for tport in $port $otherport
						do
							[ "$tport" = "dport" ] && local mtport="dport" && continue
							
							local port_arg=
							case "$tport" in
								any)	;;
								
								all)	local port_arg="match ip$ipvx $mtport 0 0x0000"
									;;
								
								*)	local mportmask=`echo $tport | $tr_cmd "/" " "`
									local port_arg="match ip$ipvx $mtport $mportmask"
									;;
							esac
							
							local tsport=
							for tsport in $sport
							do
								local sport_arg=
								case "$tsport" in
									any)	;;
									
									all)	local sport_arg="match ip$ipvx sport 0 0x0000"
										;;
									
									*)	local mportmask=`echo $tsport | $tr_cmd "/" " "`
										local sport_arg="match ip$ipvx sport $mportmask"
										;;
								esac
							
								local tdport=
								for tdport in $dport
								do
									local dport_arg=
									case "$tdport" in
										any)	;;
										
										all)	local dport_arg="match ip$ipvx dport 0 0x0000"
											;;
										
										*)	local mportmask=`echo $tdport | $tr_cmd "/" " "`
											local dport_arg="match ip$ipvx dport $mportmask"
											;;
									esac
								
									local ttos=
									for ttos in $tos
									do
										local tos_arg=
										local tos_value=
										local tos_mask=
										case "$ttos" in
											any)	;;
											
											min-delay|minimize-delay|minimum-delay|low-delay|interactive)
												local tos_value="0x10"
												local tos_mask="0x10"
												;;
												
											maximize-throughput|maximum-throughput|max-throughput|high-throughput|bulk)
												local tos_value="0x08"
												local tos_mask="0x08"
												;;
												
											maximize-reliability|maximum-reliability|max-reliability|reliable)
												local tos_value="0x04"
												local tos_mask="0x04"
												;;
												
											min-cost|minimize-cost|minimum-cost|low-cost|cheap)
												local tos_value="0x02"
												local tos_mask="0x02"
												;;
												
											normal|normal-service)
												local tos_value="0x00"
												local tos_mask="0x1e"
												;;
												
											all)
												local tos_value="0x00"
												local tos_mask="0x00"
												;;
											
											*)
												local tos_value="`echo "$ttos/" | $cut_cmd -d '/' -f 1`"
												local tos_mask="`echo "$ttos/" | $cut_cmd -d '/' -f 2`"
												[ -z "$tos_mask" ] && local tos_mask="0xff"
												
												if [ -z "$tos_value" ]
												then
													error "Empty TOS value is not allowed."
													exit 1
												fi
												;;
										esac
										if [ ! -z "$tos_value" -a ! -z "$tos_mask" ]
										then
											if [ "$ipvx" = "6" ]
											then
												local tos_arg="match ip6 priority $tos_value $tos_mask"
											else
												local tos_arg="match ip tos $tos_value $tos_mask"
											fi
										fi
										
										local tmark=
										for tmark in $mark
										do
											local mark_arg=
											case "$tmark" in
												any)	;;
												
												*)	local mark_arg="handle $tmark fw"
													;;
											esac
											
											local smac=
											for smac in $srcmac
											do
												local smac_arg=
												if [ ! "$smac" = "any" ]
												then
													local sm1=`echo "$smac" | cut -b 1-8`
													local sm2=`echo "$smac" | cut -b 9-12`
													local smac_arg="u32"
													test ! -z "$ether_type" && local smac_arg="$smac_arg match u16 $ether_type 0xFFFF at -2"
													local smac_arg="$smac_arg match u16 0x$sm2 0xFFFF at -4 match u32 0x$sm1 0xFFFFFFFF at -8"
												fi

												local dmac=
												for dmac in $dstmac
												do
													local dmac_arg=
													if [ ! "$dmac" = "any" ]
													then
														local dm1=`echo "$dmac" | cut -b 1-4`
														local dm2=`echo "$dmac" | cut -b 5-12`
														local dmac_arg="u32"
														test ! -z "$ether_type" && local dmac_arg="$dmac_arg match u16 $ether_type 0xFFFF at -2"
														local dmac_arg="$dmac_arg match u32 0x$dm2 0xFFFFFFFF at -12 match u16 0x$dm1 0xFFFF at -14"
													fi

													if [ "$tcproto_arg" = "arp" ]
													then
														local u32="u32 match u32 0 0"
													else
														local u32="u32"
														[ -z "$proto_arg$ip_arg$src_arg$dst_arg$port_arg$sport_arg$dport_arg$tos_arg$ack_arg$syn_arg" ] && local u32=
													fi
													
													[ ! -z "$u32" -a ! -z "$mark_arg" ] && local mark_arg="and $mark_arg"
													
													local estimator=
													if [ ! -z "$estimator_interval" -a ! -z "$estimator_decay" ]
													then
														local estimator="estimator $estimator_interval $estimator_decay"
													fi
													local police=
													if [ ! -z "$police_arg" ]
													then
														local police="police $police_arg"
													fi

													tc filter add \
														dev $device parent $parent protocol $tcproto_arg prio $prio \
														$estimator \
														$u32 \
														$proto_arg $ip_arg $src_arg $dst_arg $port_arg $sport_arg $dport_arg $tos_arg $ack_arg $syn_arg $mark_arg \
														$smac_arg \
														$dmac_arg \
														$custom \
														flowid $flowid \
														$police

												done # dstmac
											done # srcmac

										done # mark
									done # tos
								
								done # dport
							done # sport
						done # port
						
					done # dst
				done # src
			done # ip
			
		done # proto
		
		# increase the counter between tc protocols
		local prio=$((prio + 1))
	done # tcproto (ipv4, ipv6)
	
	return 0
}

clear_everything() {
	local iface=
	
	echo >&2 
	echo >&2 "Clearing all FireQOS changes..."
	echo >&2 
	
	# remove all qdiscs from all interfaces
	for iface in `ls $FIREQOS_DIR/ifaces/ 2>/dev/null`
	do
		printf >&2 " %16.16s: " $iface
		echo >&2 "cleared traffic control"
		
		# remove existing qdisc from all devices
		runcmd $tc_cmd qdisc del dev $iface ingress >/dev/null 2>&1
		runcmd $tc_cmd qdisc del dev $iface root >/dev/null 2>&1
		rm "$FIREQOS_DIR/ifaces/$iface"
	done
	
	# remove all FireQOS IFB devices
	for iface in `ls $FIREQOS_DIR/ifbs/ 2>/dev/null`
	do
		printf >&2 " %16.16s: " $iface
		echo >&2 "removed IFB device"
		
		runcmd $ip_cmd link del dev $iface name $iface type ifb >/dev/null 2>&1
		rm "$FIREQOS_DIR/ifbs/$iface"
	done
	
	for iface in `fireqos_active_interfaces`
	do
		printf >&2 " %16.16s: " $iface
		echo >&2 "cleared status info"
		
		rm $FIREQOS_DIR/$iface.conf
	done
	
	echo >&2 
	syslog error "Cleared all FireQOS changes on interface '$interface_realdev'"
	
	return 0
}

clear_all_qos_on_all_interfaces() {
	echo >&2 
	echo >&2 "Clearing all QoS on all interfaces..."
	echo >&2 
	
    local dev=
    for dev in `cat /proc/net/dev | grep ':' |  cut -d ':' -f 1 | sed "s/ //g" | grep -v "^lo$"`
    do
		printf >&2 " %16.16s: " $dev
		echo >&2 "cleared traffic control"

        # remove existing qdisc from all devices
        tc ignore-error qdisc del dev $dev ingress >/dev/null 2>&1
        tc ignore-error qdisc del dev $dev root >/dev/null 2>&1
    done
    
	echo >&2
    rmmod ifb 2>/dev/null
	echo >&2 " - removed all IFB devices"
    
    if [ -d $FIREQOS_DIR ]
    then
            cd $FIREQOS_DIR
            if [ $? -eq 0 ]
            then
                    rm interfaces *.conf 2>/dev/null
            fi
			echo >&2 " - cleared FireQOS status"
    fi
    return 0
}

check_root() {
	if [ ! "${UID}" = 0 ]
	then
		echo >&2
		echo >&2
		echo >&2 "Only user root can run FireQOS."
		echo >&2
		exit 1
	fi
}

show_interfaces() {
	if [ -d $FIREQOS_DIR ]
	then
		echo
		echo "The following interfaces are available:"
		fireqos_active_interfaces
		echo
	else
		echo "No interfaces have been configured."
	fi
}

FIREQOS_STATS_ID="stats.$$.$RANDOM"
cleanup_stats() {
	local x=
	for x in `ls $FIREQOS_DIR/$FIREQOS_STATS_ID.* 2>/dev/null`
	do
		rm $x
	done
}

stats_colors() {
		local drops="$1"
		local overlimits="$2"
		local requeues="$3"
		local backlog="$4"
		
		local fcolor=
		local bcolor=
		
		[ $((backlog))    -gt 0 ] && local fcolor="${COLOR_BOLD}${COLOR_YELLOW}"
		[ $((requeues))   -gt 0 ] && local bcolor="${COLOR_BGBLUE}"
		[ $((overlimits)) -gt 0 ] && local bcolor="${COLOR_BGPURPLE}"
		[ $((drops))      -gt 0 ] && local bcolor="${COLOR_BGRED}"
		
		echo -e -n "$fcolor$bcolor"
}

htb_stats() {
	local x=
	
	require_cmd awk || exit 1
	
	trap cleanup_stats EXIT
	trap cleanup_stats SIGHUP

	if [ `date +%N` = "%N" ]
	then
		warning "System has low-res time, stats may be inaccurate"
		FIREQOS_LOWRES_TIMER=1
	fi
	
	if [ -z "$2" -o ! -f "${FIREQOS_DIR}/$2.conf" ]
	then
		echo >&2 "There is no interface named '$2' to show."
		show_interfaces
		exit 1
	fi
	
	# load the interface configuration
	source "${FIREQOS_DIR}/$2.conf" || exit 1
	
	# create the awk file to parse tc output
	local title="UNSET"
	local unit="Unknown/s"
	local maxn="0"
	local show_speeds=0
	case "$1" in
		drops|dropped)
			local title="Packet Drops"
			local resolution=1
			local unit="packets/s"
			local maxn=99999
			local show_speeds=0
			local show=TCDROPS
			;;
		
		overlimits|over)
			local title="Packet Overlimits"
			local resolution=1
			local unit="packets/s"
			local maxn=99999
			local show_speeds=0
			local show=TCOVERS
			;;
		
		requeues)
			local title="Packet Requeues"
			local resolution=1
			local unit="packets/s"
			local maxn=99999
			local show_speeds=0
			local show=TCREQUEUES
			;;
		
		status)
			local title="Class Utilization"
			local show_speeds=1
			local show=TCSTATS
			
			# pick the right unit for this interface (bit/s, Kbit, Mbit)
			local resolution=1
			[ $((interface_rate * 8)) -gt $((100 * 1000)) ] && local resolution=1000
			[ $((interface_rate * 8)) -gt $((200 * 1000000)) ] && local resolution=1000000
			
			local unit="bits/s"
			[ $resolution = 1000 ] && local unit="Kbit/s"
			[ $resolution = 1000000 ] && local unit="Mbit/s"
			
			local maxn="$(( interface_rate * 8 / resolution * 120 / 100))"
			;;
		
		*)
			echo "Cannot understand what '$1' status is."
			exit 1
			;;
	esac
	shift
	
	cat >$FIREQOS_DIR/$FIREQOS_STATS_ID.stats.awk <<EOF || exit 1
{
	if( \$2 == "htb" ) {
		value = \$13;
		drops = \$18;
		overs = \$20;
		requeues = \$22;
		backlog = \$28;
		
		print "TCSTATS_" \$2 "_" \$3 "=\$(( (" value "*8) - OLD_TCSTATS_" \$2 "_" \$3 "));"
		print "OLD_TCSTATS_" \$2 "_" \$3 "=\$((" value "*8));"
		
		print "TCDROPS_" \$2 "_" \$3 "=\$(( (" drops ") - OLD_TCDROPS_" \$2 "_" \$3 "));"
		print "OLD_TCDROPS_" \$2 "_" \$3 "=\$((" drops "));"
		
		print "TCOVERS_" \$2 "_" \$3 "=\$(( (" overs ") - OLD_TCOVERS_" \$2 "_" \$3 "));"
		print "OLD_TCOVERS_" \$2 "_" \$3 "=\$((" overs "));"
		
		print "TCREQUEUES_" \$2 "_" \$3 "=\$(( (" requeues ") - OLD_TCREQUEUES_" \$2 "_" \$3 "));"
		print "OLD_TCREQUEUES_" \$2 "_" \$3 "=\$((" requeues "));"
		
		print "TCBACKLOG_" \$2 "_" \$3 "=\$((" backlog "));"
	}
	else {
		print "# Cannot parse " \$2 " class " \$3;
		value = 0;
	}
}
EOF
	local awk_script="`cat $FIREQOS_DIR/$FIREQOS_STATS_ID.stats.awk`"
	rm $FIREQOS_DIR/$FIREQOS_STATS_ID.stats.awk
	
	# attempt to shrink the list horizontally
	# find how many digits we need
	local number_digits=${#maxn}
	local number_digits=$((number_digits + 1))
	[ $number_digits -lt 6 ] && local number_digits=6
	
	# find what number we have to add, to round to closest number
	# instead of round down (the only available in shell).
	local round=0
	if [ ${resolution} -gt 1 ]
	then
		local round=$((resolution / 2))
	fi
	
	local banner_every_lines=20
	
	getdata() {
		eval "`$tc_cmd -s class show dev $1 | $tr_cmd "\n,()" "|   " | $sed_cmd \
			-e "s/[^|]|class /||class /g"		\
			-e "s/ \+/ /g"				\
			-e "s/ *| */|/g"			\
			-e "s/||/\n/g"				\
			-e "s/|/ /g"				\
			-e "s/\([0-9]\+\)Mbit /\1000000 /g"	\
			-e "s/\([0-9]\+\)Kbit /\1000 /g"	\
			-e "s/\([0-9]\+\)bit /\1 /g"		\
			-e "s/\([0-9]\+\)pps /\1 /g"		\
			-e "s/\([0-9]\+\)b /\1 /g"		\
			-e "s/\([0-9]\+\)p /\1 /g" 		\
			-e "s/:/_/g" 				\
			-e "s/ prio [0-9]\+ / /g"		\
			-e "s/ root / /g"			\
			-e "s/ parent [0-9]*_[0-9]* / /g"	\
			-e "s/ leaf [0-9]*_[0-9]* / /g"	|\
			$awk_cmd "$awk_script"`"
	}
	
	getms() {
		local d=`date +'%s.%N'`
		local s=`echo $d | $cut_cmd -d '.' -f 1`
		local n=`echo $d | $cut_cmd -d '.' -f 2 | $cut_cmd -b 1-3`
		if [ ${FIREQOS_LOWRES_TIMER} -eq 1 ]
		then
			n=000
		fi
		echo "${s}${n}"
	}
	
	local startedms=0
	starttime() {
		startedms=`getms`
	}
	
	local endedms=0
	endtime() {
		endedms=`getms`
	}
	
	sleepms() {
		local timetosleep="$1"
	
		local diffms=$((endedms - startedms))
		[ $diffms -gt $timetosleep ] && return 0
	
		local sleepms=$((timetosleep - diffms))
		local secs=$((sleepms / 1000))
		local ms=$((sleepms - (secs * 1000)))
	
		# echo "Sleeping for ${secs}.${ms} (started ${startedms}, ended ${endedms}, diffms ${diffms})"
		if [ ${FIREQOS_LOWRES_TIMER} -eq 1 ]
		then
			sleep "${secs}"
		else
			sleep "${secs}.${ms}"
		fi
	}
	
	echo
	echo "$interface_name: $interface_dev $interface_inout => $interface_realdev, type: $interface_linklayer, overhead: $interface_overhead"
	[ $show_speeds -eq 1 ] && echo "Rate: $((((interface_rate*8)+round)/resolution))$unit, min: $((((interface_minrate*8)+round)/resolution))$unit"
	echo "Values in $unit"
	echo
	
	starttime
	getdata $interface_realdev
	
	# render the configuration
	local x=
	for x in $interface_classes_ids
	do
		eval local name="\${class_${x}_name}"
		[ "$name" = "TOTAL" ] && local name="CLASS"
		printf "% ${number_digits}.${number_digits}s " $name
	done
	echo
	
	for x in $interface_classes_ids
	do
		eval local classid="\${class_${x}_classid}"
		printf "% ${number_digits}.${number_digits}s " $classid
	done
	echo
	
	if [ $show_speeds -eq 1 ]
	then
		for x in $interface_classes_ids
		do
			eval "local drops=\$TCDROPS_htb_${x}"
			eval "local overlimits=\$TCOVERS_htb_${x}"
			eval "local requeues=\$TCREQUEUES_htb_${x}"
			stats_colors "$drops" "$overlimits" "$requeues" 0
			
			eval local rate="\${class_${x}_rate}"
			[ ! "${rate}" = "COMMIT" ] && local rate=$(( ((rate * 8) + round) / resolution ))
			printf "% ${number_digits}.${number_digits}s " $rate
		
			echo -e -n "$COLOR_RESET"
		done
		echo
		
		for x in $interface_classes_ids
		do
			eval local ceil="\${class_${x}_ceil}"
			[ ! "${ceil}" = "MAX" ] && local ceil=$(( ((ceil * 8) + round) / resolution ))
			printf "% ${number_digits}.${number_digits}s " $ceil
		done
		echo
	fi
	echo
	
	for x in $interface_classes_ids
	do
		eval local priority="\${class_${x}_priority}"
		printf "% ${number_digits}.${number_digits}s " $priority
	done
	echo
	
	for x in $interface_classes_ids
	do
		eval local qdisc="\${class_${x}_qdisc}"
		printf "% ${number_digits}.${number_digits}s " $qdisc
	done
	echo
	
	# the main loop
	endtime
	sleepms 1000
	starttime
	local c=$((banner_every_lines - 1))
	while [ 1 = 1 ]
	do
		local c=$((c+1))
		getdata $interface_realdev
		
		if [ $c -eq ${banner_every_lines} ]
		then
			echo
			if [ "$show" = "TCSTATS" ]
			then
				echo -n " color code (packets): "
				stats_colors 0 0 0 1
				echo -e -n " backlog ${COLOR_RESET} | "
				stats_colors 1 0 0 0
				echo -e -n " dropped ${COLOR_RESET} | "
				stats_colors 0 1 0 0
				echo -e -n " delayed ${COLOR_RESET} | "
				stats_colors 0 0 1 0
				echo -e " requeued ${COLOR_RESET}"
			fi
			
			echo " $title on $interface_name ($interface_dev $interface_inout => $interface_realdev) - values in $unit"
			for x in $interface_classes_ids
			do
				eval local name="\${class_${x}_name}"
				printf "% ${number_digits}.${number_digits}s " $name
			done
			echo
			local c=0
		fi
		
		for x in $interface_classes_ids
		do
			eval "local y=\$${show}_htb_${x}"
			if [ "$show" = "TCSTATS" ]
			then
				eval "local drops=\$TCDROPS_htb_${x}"
				eval "local overlimits=\$TCOVERS_htb_${x}"
				eval "local requeues=\$TCREQUEUES_htb_${x}"
				eval "local backlog=\$TCBACKLOG_htb_${x}"
				stats_colors "$drops" "$overlimits" "$requeues" "$backlog"
			fi
			
			if [ -z "$y" ]
			then
				printf "% ${number_digits}.${number_digits}s " ERROR
			elif [ "$y" = "0" ]
			then
				printf "% ${number_digits}.${number_digits}s " "-"
			elif [ "$y" -lt 0 ]
			then
				printf "% ${number_digits}.${number_digits}s " RESET
			else
				printf "% ${number_digits}d " $(( (y+round) / resolution ))
			fi
			
			[ "$show" = "TCSTATS" ] && echo -e -n "$COLOR_RESET"
		done
		echo
		
		endtime
		sleepms 1000
		starttime
	done
}

FIREQOS_MONITOR_ADDED=0
remove_monitor() {
	if [ "$FIREQOS_MONITOR_ADDED" -eq 1 ]
	then
		runcmd $tc_cmd filter del dev $class_monitor_dev parent $class_monitor_qdisc_handle protocol all prio 1 u32 match u32 0 0 action mirred egress mirror dev fireqos_monitor
		runcmd $ip_cmd link set dev fireqos_monitor down
		runcmd $ip_cmd link del dev fireqos_monitor name fireqos_monitor type dummy
		
		case "$class_monitor_qdisc" in
			none)	
					runcmd $tc_cmd qdisc del dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb
					;;
			
			htb)
					;;
			
			*)
					runcmd $tc_cmd qdisc del dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb
					runcmd $tc_cmd qdisc add $class_monitor_qdisc_cmd
					;;
		esac
		
		echo "FireQOS: monitor removed from device '$class_monitor_dev', qdisc '$class_monitor_qdisc_handle'."
		FIREQOS_MONITOR_ADDED=0
	fi
	
	echo >&2 "bye..."
	
	[ -f "${FIREQOS_LOCK_FILE}" ] && rm -f "${FIREQOS_LOCK_FILE}" >/dev/null 2>&1
}

add_monitor() {	
	check_root
	
	if [ -z "$class_monitor_dev" -o -z "$class_monitor_qdisc"  -o -z "$class_monitor_qdisc_handle" ]
	then
		echo "Cannot setup monitor on device '$class_monitor_dev' for handle '$class_monitor_qdisc_handle'."
		exit 1
	fi
	
	FIREQOS_LOCK_FILE_TIMEOUT=$((86400 * 30))
	fireqos_concurrent_run_lock
	trap remove_monitor EXIT
	trap remove_monitor SIGHUP
	
	runcmd $modprobe_cmd dummy numdummies=0 >/dev/null 2>&1
	runcmd $ip_cmd link del dev fireqos_monitor name fireqos_monitor type dummy >/dev/null 2>&1
	runcmd $ip_cmd link add dev fireqos_monitor name fireqos_monitor type dummy || exit 1
	runcmd $ip_cmd link set dev fireqos_monitor up || exit 1
	
	case "$class_monitor_qdisc" in
		none)	
				runcmd $tc_cmd qdisc add dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb || exit 1
 				;;
		
		htb)
				;;
		
		*)
				runcmd $tc_cmd qdisc del $class_monitor_qdisc_cmd || exit 1
				runcmd $tc_cmd qdisc add dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb || exit 1
				;;
	esac
	FIREQOS_MONITOR_ADDED=1
	
	runcmd $tc_cmd filter add dev $class_monitor_dev parent $class_monitor_qdisc_handle protocol all prio 1 u32 match u32 0 0 action mirred egress mirror dev fireqos_monitor || exit 1
	
	echo "FireQOS: monitor added to device '$class_monitor_dev', class '$class_monitor_classid', qdisc '$class_monitor_qdisc_handle'."
}

monitor() {
	require_cmd tcpdump
	
	if [ -z "$1" -o ! -f "${FIREQOS_DIR}/$1.conf" ]
	then
		echo >&2 "There is no interface named '$1' to show."
		show_interfaces
		exit 1
	fi
	
	# load the interface configuration
	source "${FIREQOS_DIR}/$1.conf" || exit 1
	
	local x=
	local foundname=
	local foundflow=
	for x in $interface_classes_monitor
	do
		local name=`echo "$x|" | $cut_cmd -d '|' -f 1`
		local name2=`echo "$x|" | $cut_cmd -d '|' -f 2`
		local flow=`echo "$x|" | $cut_cmd -d '|' -f 3`
		local monitor=`echo "$x|" | $cut_cmd -d '|' -f 4`
		
		if [ "$name" = "$2" -o "$flow" = "$2" -o "$name2" = "$2" -o "$monitor" = "$2" ]
		then
			local foundname="$name"
			local foundname2="$name2"
			local foundflow="$flow"
			local foundmonitor="$monitor"
			local foundncid="`echo $foundflow | $tr_cmd ":" "_"`"
			break
		fi
	done
	
	if [ -z "$foundname" ]
	then
		echo
		echo "No class found with name '$2' in interface '$1'."
		echo
		echo "Use one of the following names, class ids or qdisc handles:"
		
		local x=
		for x in `echo "$interface_classes_monitor" | $tr_cmd ' ' '\n' | $grep_cmd -v "^$"`
		do
			echo "$x" | (
				local name=
				local name2=
				local flow=
				local monitor=
				IFS="|" read name name2 flow monitor
				if [ "$name" = "$name2" -o "$name" = "default" ]
				then
					echo -e "  $COLOR_BOLD$COLOR_YELLOW $name2 $COLOR_RESET or classid $COLOR_BOLD$COLOR_YELLOW $flow $COLOR_RESET or handle $COLOR_BOLD$COLOR_YELLOW $monitor $COLOR_RESET"
				else
					echo -e "  $COLOR_BOLD$COLOR_YELLOW $name $COLOR_RESET or $COLOR_BOLD$COLOR_YELLOW $name2 $COLOR_RESET or classid $COLOR_BOLD$COLOR_YELLOW $flow $COLOR_RESET or handle $COLOR_BOLD$COLOR_YELLOW $monitor $COLOR_RESET"
				fi
			)
		done
		exit 1
	fi
	
	shift 2
	
	# make all class variables available as class_monitor_*
	eval "`set | grep "^class_${foundncid}_" | sed "s/^class_${foundncid}_/class_monitor_/g"`"
	
	if [ $class_monitor_group -eq 1 ]
	then
		echo "Class $class_monitor_path is a class group. Please give a leaf class."
		exit 1
	fi
	
	local warning=
	case "$class_monitor_qdisc" in
		none)
				local warning="$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nThe class '$class_monitor_path' does not have a qdisc attached.\\nTo monitor its traffic, FireQOS will attach an 'htb' qdisc to this class.\\nThis qdisc will be removed once you stop monitoring the traffic."
				;;
		
		htb)
				;;
				
		*)
				local warning="$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nThe class '$class_monitor_path' cannot be monitored directly.\\nFireQOS will REMOVE the existing '$class_monitor_qdisc' qdisc from the class\\nand temporarily attach an 'htb' qdisc, to allow monitoring the traffic.\\nThe original qdisc will be restored once you stop monitoring the traffic."
				;;
	esac
	
	if [ "$interface_linklayer" = "adsl" -a "$interface_linklayer_type" = "local" -a "$interface_inout" = "output" ]
	then
		[ ! -z "$warning" ] && warning="$warning\\n"
		local warning="$warning\\n$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nWhen monitoring the packets sent by a PPPoE device, tcpdump sees the\\npackets encapsulated in something that is not PPPoE or Ethernet frames.\\nTherefore they cannot be decoded by tcpdump, wireshark or other tools."
 	fi
	
	if [ ! -z "$warning" ]
	then
		echo
		echo -e "$warning"
		echo
		echo -n "Press ENTER to continue, or Control-C to stop now > "
		read
	fi
	
	FIREQOS_DEBUG=1
	echo "Monitoring qdisc '$class_monitor_qdisc_handle' for class '$class_monitor_path' ($class_monitor_classid)..."
	add_monitor || exit 1
	
	echo
	runcmd $tcpdump_cmd -i fireqos_monitor "${@}"
	echo
	
	# add_monitor() adds a trap that will remove the monitor on exit
}

$cat_cmd <<EOF
FireQOS $VERSION
(C) 2013-2014 Costa Tsaousis, GPL

EOF

show_usage() {
local msg="

${COLOR_YELLOW}${COLOR_BOLD}$me${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}action${COLOR_RESET}

${COLOR_BLUE}${COLOR_BOLD}action${COLOR_RESET} can be one of:

	${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
		or
	[${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] ${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET} [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
		activates traffic shapping rules according to rules given in
		${COLOR_BLUE}${COLOR_BOLD}${FIREQOS_CONFIG}${COLOR_RESET}
		
		if ${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET} is given, it will be used instead of the
		default ${COLOR_BLUE}${COLOR_BOLD}${FIREQOS_CONFIG}${COLOR_RESET}

		all ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET} after ${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} will be given as options to the config
		file when it will be executed by FireQOS

	${COLOR_YELLOW}${COLOR_BOLD}debug${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
		or
	[${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] ${COLOR_YELLOW}${COLOR_BOLD}debug${COLOR_RESET} [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
		same as ${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET}, but shows also the generated tc commands
	
	${COLOR_YELLOW}${COLOR_BOLD}stop${COLOR_RESET}
		stops all traffic shapping applied by FireQOS
		(it does not touch QoS on other interfaces and IFBs used by
			other tools)

	${COLOR_YELLOW}${COLOR_BOLD}clear_all_qos${COLOR_RESET}
		- stops all traffic shapping on all network interfaces
		- removes all IFB devices from the system
		(clears even QoS applied by other tools)
	
	${COLOR_YELLOW}${COLOR_BOLD}status${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} [ ${COLOR_YELLOW}${COLOR_BOLD}dump${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}] ] ]
		shows live class utilization for the interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} the
		name given mathes the name of an interface statement given
		in the config

		if ${COLOR_YELLOW}${COLOR_BOLD}dump' is specified, it tcpdumps the traffic in the
		${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET} of interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
	
	${COLOR_YELLOW}${COLOR_BOLD}tcpdump${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}
		or
	${COLOR_YELLOW}${COLOR_BOLD}dump${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}
		tcpdumps all traffic in the ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET} of interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}

	${COLOR_YELLOW}${COLOR_BOLD}drops${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
		shows packets dropped per second, per class for the
		interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
		
	${COLOR_YELLOW}${COLOR_BOLD}overlimits${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
		shows packets delayed per second, per class for the
		interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
	
	${COLOR_YELLOW}${COLOR_BOLD}requeues${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
		shows packets requeued per second, per class for the
		interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
"
echo -e "$msg"

}

FIREQOS_MODE=
while [ ! -z "$1" ]
do
	case "$1" in

		clear_all_qos)
			clear_all_qos_on_all_interfaces
			syslog info "Cleared all QOS on all interfaces"
			exit 0
			;;

		stop)
			clear_everything
			syslog info "Cleared all FireQOS changes"
			exit 0
			;;
		
		status) 
			shift
			if [ "$2" = "dump" -o "$2" = "tcpdump" ]
			then
				iface="$1"
				shift 2
				monitor $iface "$@"
			else
				htb_stats status "$@"
			fi
			exit 0
			;;
		
		drops|overlimits|requeues)
			htb_stats "$@"
			exit 0
			;;
		
		dump|tcpdump)
			shift
			monitor "$@"
			exit $?
			;;
		
		debug)	
			FIREQOS_MODE=START
			FIREQOS_DEBUG=1
			;;
		
		start)	
			FIREQOS_MODE=START
			;;
		
		--)
			shift
			break;
			;;
			
		--help|-h)
			FIREQOS_MODE=
			break;
			;;
			
		*)
			echo >&2 "Using file '$1' for FireQOS configuration..."
			FIREQOS_CONFIG="$1"
			;;
	esac
	
	shift
done

if [ -z "$FIREQOS_MODE" ]
then
	show_usage
	exit 1
fi

check_root

# ----------------------------------------------------------------------------
# Normal startup

if [ ! -f "${FIREQOS_CONFIG}" ]
then
	error "Cannot find file '${FIREQOS_CONFIG}'."
	exit 1
fi

if [ ! -d "${FIREQOS_DIR}" ]
then
	mkdir -p "${FIREQOS_DIR}" || exit 1
fi
if [ ! -d "${FIREQOS_DIR}/ifbs" ]
then
	mkdir -p "${FIREQOS_DIR}/ifbs" || exit 1
fi
if [ ! -d "${FIREQOS_DIR}/ifaces" ]
then
	mkdir -p "${FIREQOS_DIR}/ifaces" || exit 1
fi

FIREQOS_DEFAULT_QDISC="fq_codel"
FIREQOS_DEFAULT_QDISC_OPTIONS="default"

# check if this system has fq_codel
runcmd $modprobe_cmd sch_$FIREQOS_DEFAULT_QDISC >/dev/null 2>&1
[ $? -ne 0 ] && FIREQOS_DEFAULT_QDISC="codel"

# check if this system has codel
runcmd $modprobe_cmd sch_$FIREQOS_DEFAULT_QDISC >/dev/null 2>&1
[ $? -ne 0 ] && FIREQOS_DEFAULT_QDISC="sfq"

# make sure we are not running in parallel
fireqos_concurrent_run_lock

# enable cleanup in case of failure
FIREQOS_COMPLETED=0
trap fireqos_exit EXIT
trap fireqos_exit SIGHUP

# load the IFB kernel module
runcmd $modprobe_cmd ifb numifbs=0 >/dev/null 2>&1

# Run the configuration
enable -n trap					# Disable the trap buildin shell command.
source ${FIREQOS_CONFIG} "$@"			# Run the configuration as a normal script.
if [ $? -ne 0 ]
then
	exit 1
fi
enable trap					# Enable the trap buildin shell command.

interface_close					# close the last interface.

echo
echo "  Traffic is classified:"
echo
echo "      - on $interface_count interfaces"
echo "      - to $class_count classes"
echo "      - by $match_count FireQOS matches"
echo
echo "  $tc_count TC commands executed"
echo
echo "All Done! Enjoy..."

# inform the trap everything is ok
FIREQOS_COMPLETED=1

exit 0
