#!/bin/bash
#
#    htb.init v0.8.5
#    Copyright (C) 2002-2004  Lubomir Bulej <pallas@kadan.cz>
#
#    chkconfig:   2345 11 89
#    description: script to set up HTB traffic control
#
#    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, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    To get the latest version, check on Freshmeat for actual location:
#
#		http://freshmeat.net/projects/htb.init
#
#
# VERSION HISTORY
# ---------------
# v0.8.5- Nathan Shafer <nicodemus at users.sourceforge.net>
#	  - allow symlins to class files
#	- Seth J. Blank <antifreeze at users.sourceforge.net>
#	  - replace hardcoded ip/tc location with variables
#	- Mark Davis <mark.davis at gmx.de>
#	  - allow setting of PRIO_{MARK,RULE,REALM} in class file
# v0.8.4- Lubomir Bulej <pallas at kadan.cz>
#	  - fixed small bug in RULE parser to correctly parse
#	    rules with identical source and destination fields
#	  - removed the experimental INJECT keyword
#	  - ignore *~ backup files when looking for classes
#	- Mike Boyer <boyer at administrative.com>
#	  - fix to allow arguments to be passed to "restart" command
#	- <face at pos.sk>
#	  - fix to preserve class priority after timecheck
# v0.8.3- Lubomir Bulej <pallas at kadan.cz>
#	  - use LC_COLLATE="C" when sorting class files
#	- Paulo Sedrez
#	  - fix time2abs to allow hours with leading zero in TIME rules
# v0.8.2- Lubomir Bulej <pallas at kadan.cz>
#	  - thanks to Hasso Tepper for reporting the following problems
#	  - allow dots in interface names for use with VLAN interfaces
#	  - fixed a thinko resulting from "cosmetic overdosage" :)
# v0.8.1- Lubomir Bulej <pallas at kadan.cz>
#	  - added function alternatives for sed/find with less features. To
#	    enable them, you need to set HTB_BASIC to nonempty string.
#	  - added posibility to refer to RATE/CEIL of parent class when
#	    setting RATE/CEIL for child class. Look for "prate" or "pceil"
#	    in the documentation.
#	  - fixed broken "timecheck" invocation
# v0.8	- Lubomir Bulej <pallas at kadan.cz>
#	  - simplified and converted CBQ.init 0.7 into HTB.init
#	  - changed configuration file naming conventions
#	  - lots of HTB specific changes
#
#
# INTRODUCTION
# ------------
#
# This script is a clone of CBQ.init and is meant to simplify setup of HTB
# based traffic control. HTB setup itself is pretty simple compared to CBQ,
# so the purpose of this script is to allow the administrator of large HTB
# configurations to manage individual classes using simple, human readable
# files.
#
# The "H" in HTB stands for "hierarchical", so while many people did not use
# (or know about) the possibility to build hierarchical structures using
# CBQ.init, it should be obvious thing to expect from HTB.init :-)
#
# In HTB.init this is done differently, compared to CBQ.init: the usage of
# PARENT keyword was dropped and instead, class file naming convetion was
# introduced. This convention allows the child class to determine ID of its
# parent class from the filename and also (if not abused :) enforces file
# ordering so that the parent classes are created before their children.
#
# HTB.init uses simple caching mechanism to speed up "start" invocation if the
# configuration is unchanged. When invoked for the first time, it compiles the
# configuration files into simple shell script containing the sequence of "tc"
# commands required to setup the traffic control. This cache-script is stored
# in /var/cache/htb.init by default and is invalidated either by presence of
# younger class config file, or by invoking HTB.init with "start invalidate".
#
# If you want to HTB.init to setup the traffic control directly without the
# cache, invoke it with "start nocache" parameters. Caching is also disabled
# if you have logging enabled (ie. HTB_DEBUG is not empty).
#
# If you only want HTB.init to translate your configuration to "tc" commands,
# invoke it using the "compile" command. Bear in mind that "compile" does not
# check if the "tc" commands were successful - this is done (in certain places)
# only when invoked with "start nocache" command. When you are testing your
# configuration, you should use it to check whether it is completely valid.
#
# In case you are getting strange sed/find errors, try to uncomment line with
# HTB_BASIC setting, or set the variable to nonempty string. This will enable
# function alternatives which require less advanced sed/find functionality. As
# a result, the script will run slower but will probably run. Also the caching
# will not work as expected and you will have to invalidate the cache manually
# by invoking HTB.init with "start invalidate".
#
#
# CONFIGURATION
# -------------
#
# Every traffic class is described by a single file in placed in $HTB_PATH
# directory, /etc/sysconfig/htb by default. The naming convention is different
# compared to CBQ.init. First notable change is missing 'htb-' prefix. This
# was replaced by interface name to improve human readability and to separate
# qdisc-only configuration.
#
# Global qdisc options are placed in $HTB_PATH/<ifname>, where <ifname> is
# (surprisingly) name of the interface, made of characters and numbers. This
# file must be present if you want to setup HTB on that interface. If you
# don't have any options to put into it, leave it empty, but present.
#
# Class options belong to files with names matching this expression:
# $HTB_PATH/<ifname>-<clsid>(:<clsid>)*<description>
#
# <clsid> is class ID which is hexadecimal number in range 0x2-0xFFFF, without
# the "0x" prefix. If a colon-delimited list of class IDs is specified, the
# last <clsid> in the list represents ID of the class in the config file.
#
# <clsid> preceding the last <clsid> is class ID of the parent class. To keep
# ordering so that parent classes are always created before their children, it
# is recommended to include full <clsid> path from root class to the leaf one.
#
# <description> is (almost) arbitrary string where you can put symbolic
# class names for better readability.
#
# Examples of valid names:
#
#	eth0-2		root class with ID 2, on device eth0
#	eth0-2:3	child class with ID 3 and parent 2, on device eth0
#	eth0-2:3:4	child class with ID 4 and parent 3, on device eth0
#	eth1-2.root	root class with ID 2, on device eth1
#
#
# The configuration files may contain the following parameters. For detailed
# description of HTB parameters see http://luxik.cdi.cz/~devik/qos/htb.
#
### HTB qdisc parameters
#
# The following parameters apply to HTB root queuening discipline only and
# are expected to be put into $HTB_PATH/<ifname> files. These files must
# exist (even empty) if you want to configure HTB on given interface.
#
# DEFAULT=<clsid>				optional, default 0
# DEFAULT=30
#
#	<dclsid> is ID of the default class where UNCLASSIFIED traffic goes.
#	Unlike HTB qdisc, HTB.init uses 0 as default class ID, which is
#	internal FIFO queue that will pass packets along at FULL speed!
#
#	If you want to avoid surprises, always define default class and
#	allocate minimal portion of bandwidth to it.
#
# R2Q=<number>					optional, default 10
# R2Q=100
#
#	This allows you to set coefficient for computing DRR (Deficit
#	Round Robin) quanta. The default value of 10 is good for rates
#	from 5-500kbps and should be increased for higher rates.
#
# DCACHE=yes|no					optional, default "no"
#
#	This parameters turns on "dequeue cache" which results in degraded
#	fairness but allows HTB to be used on very fast network devices.
#	This is turned off by default.
#
### HTB class parameters
#
# The following are parameters for HTB classes and are expected
# to be put into $HTB_PATH/<ifname>-<clsid>(:<clsid>)*.* files.
#
# RATE=<speed>|prate|pceil			mandatory
# RATE=5Mbit
#
#	Bandwidth allocated to the class. Traffic going through the class is
#	shaped to conform to specified rate. You can use Kbit, Mbit or bps,
#	Kbps and Mbps as suffices. If you don't specify any unit, bits/sec
#	are used. Also note that "bps" means "bytes per second", not bits.
#
#	The "prate" or "pceil" values will resolve to RATE or CEIL of parent
#	class. This feature is meant to help humans to keep configuration
#	files consistent.
#
# CEIL=<speed>|prate|pceil			optional, default $RATE
# CEIL=6MBit
#
#	The maximum bandwidth that can be used by the class. The difference
#	between CEIL and RATE amounts to bandwidth the class can borrow, if
#	there is unused bandwidth left.
#
#	By default, CEIL is equal to RATE so the class cannot borrow bandwidth
#	from its parent. If you want the class to borrow unused bandwidth, you
#	must specify the maximal amount it can use, if available.
#
#	When several classes compete for the unused bandwidth, each of the
#	classes is given share proportional to their RATE.
#
# BURST=<bytes>					optional, default computed
# BURST=10Kb
#
# CBURST=<bytes>				optional, default computed
# CBURST=2Kb
#
#	BURST and CBURST parameters control the amount of data that can
#	be sent from one class at maximum (hardware) speed before trying
#	to service other class.
#
#	If CBURST is small (one packet size) it shapes bursts not to
#	exceed CEIL rate the same way PEAK works for TBF.
#
# PRIO=<number>					optional, default 0
# PRIO=5
#
#	Priority of class traffic. The higher the number, the lesser the
#	priority. Also, classes with higher priority are offered excess
#	bandwidth first.
#
# LEAF=none|sfq|pfifo|bfifo			optional, default "none"
#
#	Tells the script to attach specified leaf queueing discipline to HTB
#	class. By default, no leaf qdisc is used.
#
#	If you want to ensure (approximately) fair sharing of bandwidth among
#	several hosts in the same class, you should specify LEAF=sfq to attach
#	SFQ as leaf queueing discipline to the class.
#
# MTU=<bytes>  					optional, default "1600"
#
#	Maximum packet size HTB creates rate maps for. The default should
#	be sufficient for most cases, it certainly is for Ethernet.
#
### SFQ qdisc parameters
#
# The SFQ queueing discipline is a cheap way to fairly share class bandwidth
# among several hosts. The fairness is approximate because it is stochastic,
# but is not CPU intensive and will do the job in most cases. If you desire
# real fairness, you should probably use WRR (weighted round robin) or WFQ
# queueing disciplines. Note that SFQ does not do any traffic shaping - the
# shaping is done by the HTB class the SFQ is attached to.
#
# QUANTUM=<bytes>				optional, qdisc default
#
#	Amount of data in bytes a stream is allowed to dequeue before next
#	queue gets a turn. Defaults to one MTU-sized packet. Do not set
#	this parameter below the MTU!
#
# PERTURB=<seconds>				optional, default "10"
#
#	Period of hash function perturbation. If unset, hash reconfiguration
#	will never take place which is what you probably don't want. The
#	default value of 10 seconds is probably a good value.
#
### PFIFO/BFIFO qdisc parameters
#
# Those are simple FIFO queueing disciplines. They only have one parameter
# which determines their length in bytes or packets.
#
# LIMIT=<packets>|<bytes>			optional, qdisc default
# LIMIT=1000
#
#	Number of packets/bytes the queue can hold. The unit depends on
#	the type of queue used.
#
### Filtering parameters
#
# RULE=[[saddr[/prefix]][:port[/mask]],][daddr[/prefix]][:port[/mask]]
#
#	These parameters make up "u32" filter rules that select traffic for
#	each of the classes. You can use multiple RULE fields per config.
#
#	The optional port mask should only be used by advanced users who
#	understand how the u32 filter works.
#
# Some examples:
#
#	RULE=10.1.1.0/24:80
#		selects traffic going to port 80 in network 10.1.1.0
#
#	RULE=10.2.2.5
#		selects traffic going to any port on single host 10.2.2.5
#
#	RULE=10.2.2.5:20/0xfffe
#		selects traffic going to ports 20 and 21 on host 10.2.2.5
#
#	RULE=:25,10.2.2.128/26:5000
#		selects traffic going from anywhere on port 50 to
#		port 5000 in network 10.2.2.128
#
#	RULE=10.5.5.5:80,
#		selects traffic going from port 80 of single host 10.5.5.5
#
#
#
# REALM=[srealm,][drealm]
#
#	These parameters make up "route" filter rules that classify traffic
#	according to packet source/destination realms. For information about
#	realms, see Alexey Kuznetsov's IP Command Reference. This script
#	does not define any realms, it justs builds "tc filter" commands
#	for you if you need to classify traffic this way.
#
#	Realm is either a decimal number or a string referencing entry in
#	/etc/iproute2/rt_realms (usually).
#
# Some examples:
#
#	REALM=russia,internet
#		selects traffic going from realm "russia" to realm "internet"
#
#	REALM=freenet,
#		selects traffic going from realm "freenet"
#
#	REALM=10
#		selects traffic going to realm 10
#
#
#
# MARK=<mark>
#
#	These parameters make up "fw" filter rules that select traffic for
#	each of the classes accoring to firewall "mark". Mark is a decimal
#	number packets are tagged with if firewall rules say so. You can
#	use multiple MARK fields per config.
#
#
# Note:	Rules for different filter types can be combined. Attention must be
#	paid to the priority of filter rules, which can be set below through
#	the PRIO_{RULE,MARK,REALM} variables.
#
### Time ranging parameters
#
# TIME=[<dow><dow>.../]<from>-<till>;<rate>[/<burst>][,<ceil>[/<cburst>]]
# TIME=60123/18:00-06:00;256Kbit/10Kb,384Kbit
# TIME=18:00-06:00;256Kbit
#
#	This parameter allows you to change class bandwidth during the day or
#	week. You can use multiple TIME rules. If there are several rules with
#	overlapping time periods, the last match is taken. The <rate>, <burst>,
#	<ceil> and <cburst> fields correspond to parameters RATE, BURST, CEIL
#	and CBURST.
#
#	<dow> is single digit in range 0-6 and represents day of week as
#	returned by date(1). To specify several days, just concatenate the
#	digits together.
#
#
#
# TRIVIAL EXAMPLE
# ---------------
#
# Consider the following example:
# (taken from Linux Advanced Routing & Traffic Control HOWTO)
#
# You have a Linux server with total of 5Mbit available bandwidth. On this
# machine, you want to limit webserver traffic to 5Mbit, SMTP traffic to 3Mbit
# and everything else (unclassified traffic) to 1Kbit. In case there is unused
# bandwidth, you want to share it between SMTP and unclassified traffic.
#
# The "total bandwidth" implies one top-level class with maximum bandwidth
# of 5Mbit. Under the top-level class, there are three child classes.
#
# First, the class for webserver traffic is allowed to use 5Mbit of bandwidth.
#
# Second, the class for SMTP traffic is allowed to use 3Mbit of bandwidth and
# if there is unused bandwidth left, it can use it but must not exceed 5Mbit
# in total.
#
# And finally third, the class for unclassified traffic is allowed to use
# 1Kbit of bandwidth and borrow unused bandwith, but must not exceed 5Mbit.
#
# If there is demand in all classes, each of them gets share of bandwidth
# proportional to its default rate. If there unused is bandwidth left, they
# (again) get share proportional to their default rate.
#
# Configuration files for this scenario:
# ---------------------------------------------------------------------------
# eth0		eth0-2.root	eth0-2:10.www	eth0-2:20.smtp	eth0-2:30.dfl
# ----		-----------	-------------	--------------	-------------
# DEFAULT=30	RATE=5Mbit	RATE=5Mbit	RATE=3Mbit	RATE=1Kbit
#		BURST=15k	BURST=15k	CEIL=5Mbit	CEIL=5Mbit
#				LEAF=sfq	BURST=15k	BURST=15k
#				RULE=*:80,	LEAF=sfq	LEAF=sfq
#						RULE=*:25
# ---------------------------------------------------------------------------
#
# Remember that you can only control traffic going out of your linux machine.
# If you have a host connected to network and want to control its traffic on
# the gateway in both directions (with respect to the host), you need to setup
# traffic control for that host on both (or all) gateway interfaces.
#
# Enjoy.
#
#############################################################################

export LC_ALL=C

### Command locations
TC=/sbin/tc
IP=/sbin/ip
MP=/sbin/modprobe

### Default filter priorities (must be different)
PRIO_RULE_DEFAULT=${PRIO_RULE:-100}
PRIO_MARK_DEFAULT=${PRIO_MARK:-200}
PRIO_REALM_DEFAULT=${PRIO_REALM:-300}

### Default HTB_PATH & HTB_CACHE settings
HTB_PATH=/etc/htbinit
HTB_CACHE=/var/cache/htbinit

### Uncomment for sed/find with less features (useful for busybox)
#HTB_BASIC="yes"

### Uncomment to enable logfile for debugging
#HTB_DEBUG="/var/run/htb-$1"

### Modules to probe for. Uncomment the last HTB_PROBE
### line if you have QoS support compiled into kernel
HTB_PROBE="sch_htb sch_sfq cls_fw cls_u32 cls_route"
#HTB_PROBE=""

### Config keywords
HTB_QDISC="DEFAULT\|DCACHE\|R2Q"
HTB_CLASS="RATE\|CEIL\|BURST\|CBURST\|PRIO\|LEAF\|MTU"
HTB_CLASS="$HTB_CLASS\|PRIO_RULE\|PRIO_MARK\|PRIO_REALM"
HTB_CLASS="$HTB_CLASS\|LIMIT\|QUANTUM\|PERTURB"


#############################################################################
############################# SUPPORT FUNCTIONS #############################
#############################################################################

if [ -z "$HTB_BASIC" ]; then
	### List of network devices
	all_device_list () {
		ip link show \
		| sed -n "/^[0-9]/ { s/[[:space:]]//g; \
		s/^[0-9]\+:\([^@-]\+\)\(@.\+\)\?:<.*/\1/; p; }"
	} # all_device_list


	### Load & filter file $HTB_PATH/$1
	htb_filter_file () {
		sed -n "s/#.*//; s/[^a-zA-Z0-9.,;:=/*-_]\+//g; \
		/^[a-zA-Z0-9]\+=[a-zA-Z0-9.,:;/*-_]\+$/ p" $HTB_PATH/$1
	} # htb_filter_file


	### Parse class ID chain from file name
	htb_clsid_chain () {
		echo "${1#*-}" \
		| sed -n "/^[0-9a-fA-F]/ { s/^\([0-9a-fA-F:]\+\).*/\1/; \
		s/::/:/g; s/:$//; p; }"
	} # htb_clsid_chain


	### List of classes in $HTB_PATH
	htb_class_list () {
		for dev in `htb_device_list`; do
			find $HTB_PATH \( -type f -or -type l \) \
			-name "$dev-*" -not -name '*~' -maxdepth 1 \
			-printf "%f\n"| sort
		done
	} # htb_class_list

	### Gather $1 rules from $CFILE
	htb_cfile_rules () {
		echo "$CFILE"| sed -n "/^$1=/ { s/.*=//; p; }"
	} # htb_cfile_rules


	### Validate cache against config files
	htb_valid_cache () {
		for dev in `htb_device_list`; do
			[ `find $HTB_PATH \( -type f -or -type l \) \
			  -name "$dev*" -maxdepth 1 -newer $HTB_CACHE| \
			  wc -l` -gt 0 ] && VALID=0
			[ $VALID -ne 1 ] && break
		done
	} # htb_valid_cache


	### Find class config for device $1, which is newer than cache
	htb_cache_older () {
		[ `find $HTB_PATH -type f -name "$1*" -maxdepth 1 \
		   -newer $HTB_CACHE| wc -l` -gt 0 ] && return 0
		return 1
	} # htb_cache_older


	### Get current RATE and CEIL
	htb_class_state () {
		tc class show dev $1 \
		| sed -n "s/[[:space:]]\+/ /g; /^class htb 1:$2 / \
		{ s/.*rate \(.\+\) burst.*/\1/; p; q; }"
	} # htb_class_state

else ### Less feature-hungry versions of above functions

	all_device_list () {
		ip link show \
		| grep "^[0-9]" \
		| sed "s/[[:space:]]//g; \
		s/^[0-9]\+:\([^@-]\+\)\(@.\+\)\?:<.*/\1/"
	} # all_device_list

	htb_filter_file () {
		sed 's/#.*//; s/[^a-zA-Z0-9.,;:=/*-_]\+//g' $HTB_PATH/$1 \
		| grep '^[a-zA-Z0-9]\+=[a-zA-Z0-9.,;:/*-_]\+$'
	} # htb_filter_file

	htb_clsid_chain () {
		echo "${1#*-}" \
		| grep '^[a-fA-F0-9]' \
		| sed 's/^\([a-fA-F0-9:]\+\).*/\1/; s/::/:/g; s/:$//'
	} # htb_clsid_chain

	htb_class_list () {
		PFX=`echo "$HTB_PATH"| sed 's/\//\\\\\//g'`
		for dev in `htb_device_list`; do
			find $HTB_PATH -type f -name "$dev-*" \
			| grep "^$HTB_PATH/$dev-[^/]\+[^~]$" \
			| sed "s/$PFX\///" \
			| sort
		done
	} # htb_class_list

	htb_cfile_rules () {
		echo "$CFILE"| grep "^$1="| cut -d"=" -f2
	} # htb_cfile_rules

	htb_cache_older () {
		### cache is always up-to-date
		return 1
	} # htb_cache_older

	htb_class_state () {
		tc class show dev $1 \
		| sed 's/[[:space:]]\+/ /g' \
		| grep "^class htb 1:$2 " \
		| sed 's/.*rate \(.\+\) burst.*/\1/'
	} # htb_class_state
fi # HTB_BASIC


### List of HTB devices
htb_device_list () {
	for dev in `all_device_list`; do
		[ -f $HTB_PATH/$dev ] && echo $dev
	done
} # htb_device_list


### Remove root class from device $1
htb_device_off () {
	tc qdisc del dev $1 root 2> /dev/null
} # htb_device_off


### Remove HTB from all devices
htb_off () {
	for dev in `htb_device_list`; do
		htb_device_off $dev
	done
} # htb_off


### Prefixed message
htb_message () {
	echo -e "**HTB: $@"
} # htb_message

### Failure message
htb_failure () {
	htb_message "$@"
	exit 1
} # htb_failure

### Failure w/htb_off
htb_fail_off () {
	htb_message "$@"
	htb_off
	exit 1
} # htb_fail_off


### Convert time to absolute value
htb_time2abs () {
	local min=${1##*:}; min=${min##0}
	local hrs=${1%%:*}; hrs=${hrs##0}
	echo $[hrs*60 + min]
} # htb_time2abs


### Display traffic control setup
htb_show () {
	for dev in `all_device_list`; do
		[ `tc qdisc show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: queueing disciplines\n"
		tc $1 qdisc show dev $dev; echo

		[ `tc class show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: traffic classes\n"
		tc $1 class show dev $dev; echo

		[ `tc filter show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: filtering rules\n"
		tc $1 filter show dev $dev; echo
	done
} # htb_show



### Derive DEVICE, CLASS and PARENT from $1
### Check validity of CLASS and PARENT class IDs
### Load class configuration from $HTP_PATH/$1
### Configure class parameters from CFILE
htb_load_class () {
	DEVICE=${1%%-*}
	CLSIDS=`htb_clsid_chain $1`
	CLASS=${CLSIDS##*:}; [ -z "$CLASS" ] &&
		htb_fail_off "$1 has invalid class ID!"

	[ $[0x$CLASS] -lt 2 -o $[0x$CLASS] -gt 65535 ] &&
		htb_fail_off "class ID of $1 must be in range 0x2-0xFFFF!"

	CLSIDS=${CLSIDS%$CLASS}; CLSIDS=${CLSIDS%:}
	PARENT=${CLSIDS##*:}; [ -n "$PARENT" ] &&
		[ $[0x$PARENT] -lt 2 -o $[0x$PARENT] -gt 65535 ] &&
			htb_fail_off "parent ID of $1 must be in range 0x2-0xFFFF!"

	CFILE=`htb_filter_file $1`


	### Set defaults & load class
	MTU=""; LEAF=none; PERTURB=10
	RATE=""; BURST=""; CEIL=""; CBURST=""
	PRIO=""; LIMIT=""; QUANTUM=""
	
	PRIO_RULE=$PRIO_RULE_DEFAULT
	PRIO_MARK=$PRIO_MARK_DEFAULT
	PRIO_REALM=$PRIO_REALM_DEFAULT

	eval `echo "$CFILE"| grep "^\($HTB_CLASS\)="`
	RNAME=""; CNAME=""

	### Resolve RATE if needed
	[ "$RATE" = "prate" ] && RNAME=RATE_$PARENT
	[ "$RATE" = "pceil" ] && RNAME=CEIL_$PARENT
	[ -n "$RNAME" ] && RATE=${!RNAME}

	### RATE is required
	[ -z "$RATE" ] &&
		htb_fail_off "missing or unresolvable RATE in $1!"

	### Resolve CEIL if needed
	[ "$CEIL" = "prate" ] && CNAME=RATE_$PARENT
	[ "$CEIL" = "pceil" ] && CNAME=CEIL_$PARENT
	[ -n "$CNAME" ] && CEIL=${!CNAME}

	### Store CEIL & RATE for children
	eval RATE_$CLASS=$RATE
	eval CEIL_$CLASS=${CEIL:-$RATE}
} # htb_load_class


#############################################################################
#################################### INIT ###################################
#############################################################################

### Check iproute2 tools
[ -x $TC -a -x $IP ] ||
	htb_failure "iproute2 utilities not installed or executable!"

### Check $HTB_PATH directory
[ -d $HTB_PATH -a -r $HTB_PATH -a -x $HTB_PATH ] ||
	htb_failure "$HTB_PATH does not exist or is not readable!"

### ip/tc wrappers
if [ "$1" = "compile" ]; then
	### no module probing
	HTB_PROBE=""

	ip () {
		$IP "$@"
	} # ip
	
	### echo-only version of "tc" command
	tc () {
		echo "$TC $@"
	} # tc

elif [ -n "$HTB_DEBUG" ]; then
	echo -e "# `date`" > $HTB_DEBUG

	### Logging version of "ip" command
	ip () {
		echo -e "\n# ip $@" >> $HTB_DEBUG
		$IP "$@" 2>&1 | tee -a $HTB_DEBUG
	} # ip

	### Logging version of "tc" command
	tc () {
		echo -e "\n# tc $@" >> $HTB_DEBUG
		$TC "$@" 2>&1 | tee -a $HTB_DEBUG
	} # tc
else
	# default wrappers
	
	ip () {
		$IP "$@"
	} # ip
	
	tc () {
		$TC "$@"
	} # tc
fi # ip/tc wrappers


case "$1" in

#############################################################################
############################### START/COMPILE ###############################
#############################################################################

start|compile)

### Probe QoS modules (start only)
for module in $HTB_PROBE; do
	$MP $module || htb_failure "failed to load module $module"
done

### If we are in compile/nocache/logging mode, don't bother with cache
if [ "$1" != "compile" -a "$2" != "nocache" -a -z "$HTB_DEBUG" ]; then
	VALID=1

	### validate the cache
	[ "$2" = "invalidate" -o ! -f $HTB_CACHE ] && VALID=0
	[ $VALID -eq 1 ] && for dev in `htb_device_list`; do
		htb_cache_older $dev && VALID=0
		[ $VALID -ne 1 ] && break
	done

	### compile the config if the cache is invalid
	if [ $VALID -ne 1 ]; then
		$0 compile > $HTB_CACHE ||
			htb_fail_off "failed to compile HTB configuration!"
	fi

	### run the cached commands
	exec /bin/sh $HTB_CACHE 2> /dev/null
fi


### Setup root qdisc on all configured devices
DEVICES=`htb_device_list`
[ -z "$DEVICES" ] && htb_failure "no configured devices found!"

for dev in $DEVICES; do
	### Retrieve root qdisc options
	DEFAULT=""; DCACHE=""; R2Q=""
	eval `htb_filter_file $dev| grep "^\($HTB_QDISC\)="`
	[ "$DCACHE" = "yes" ] && DCACHE="dcache" || DCACHE=""

	### Remove old root qdisc from device
	htb_device_off $dev

	### Setup root qdisc for the device
	tc qdisc add dev $dev root handle 1 htb \
	default ${DEFAULT:-0} ${R2Q:+r2q $R2Q} $DCACHE ||
		htb_fail_off "failed to set root qdisc on $dev!"

	[ "$1" = "compile" ] && echo
done # dev


### Setup traffic classes (if configured)
for classfile in `htb_class_list`; do
	htb_load_class $classfile

	### Create the class
	tc class add dev $DEVICE parent 1:$PARENT classid 1:$CLASS \
	htb rate $RATE ${CEIL:+ceil $CEIL} ${BURST:+burst $BURST} \
	${PRIO:+prio $PRIO} ${CBURST:+cburst $CBURST} ${MTU:+mtu $MTU} ||
		htb_fail_off "failed to add class $CLASS with parent $PARENT on $DEVICE!"

	### Create leaf qdisc if set
	if [ "$LEAF" != "none" ]; then
		if [ "$LEAF" = "sfq" ]; then
			LEAFPARM="${PERTURB:+perturb $PERTURB} ${QUANTUM:+quantum $QUANTUM}"
		elif [ "$LEAF" = "pfifo" -o "$LEAF" = "bfifo" ]; then
			LEAFPARM="${LIMIT:+limit $LIMIT}"
		else
			htb_fail_off "unknown leaf qdisc ($LEAF) in $classfile!"
		fi

		tc qdisc add dev $DEVICE \
		parent 1:$CLASS handle $CLASS $LEAF $LEAFPARM ||
			htb_fail_off "failed to add leaf qdisc to class $CLASS on $DEVICE!"
	fi


	### Create fw filter for MARK fields
	for mark in `htb_cfile_rules MARK`; do
		### Attach fw filter to root class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_MARK handle $mark fw classid 1:$CLASS
	done ### mark

	### Create route filter for REALM fields
	for realm in `htb_cfile_rules REALM`; do
		### Split realm into source & destination realms
		SREALM=${realm%%,*}; DREALM=${realm##*,}
		[ "$SREALM" = "$DREALM" ] && SREALM=""

		### Convert asterisks to empty strings
		SREALM=${SREALM#\*}; DREALM=${DREALM#\*}

		### Attach route filter to the root class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_REALM route ${SREALM:+from $SREALM} \
		${DREALM:+to $DREALM} classid 1:$CLASS
	done ### realm

	### Create u32 filter for RULE fields
	for rule in `htb_cfile_rules RULE`; do
		### Split rule into source & destination
		SRC=${rule%%,*}; DST=${rule##*,}
		[ "$SRC" = "$rule" ] && SRC=""


		### Split destination into address, port & mask fields
		DADDR=${DST%%:*}; DTEMP=${DST##*:}
		[ "$DADDR" = "$DST" ] && DTEMP=""

		DPORT=${DTEMP%%/*}; DMASK=${DTEMP##*/}
		[ "$DPORT" = "$DTEMP" ] && DMASK="0xffff"


		### Split up source (if specified)
		SADDR=""; SPORT=""
		if [ -n "$SRC" ]; then
			SADDR=${SRC%%:*}; STEMP=${SRC##*:}
			[ "$SADDR" = "$SRC" ] && STEMP=""

			SPORT=${STEMP%%/*}; SMASK=${STEMP##*/}
			[ "$SPORT" = "$STEMP" ] && SMASK="0xffff"
		fi


		### Convert asterisks to empty strings
		SADDR=${SADDR#\*}; DADDR=${DADDR#\*}

		### Compose u32 filter rules
		u32_s="${SPORT:+match ip sport $SPORT $SMASK}"
		u32_s="${SADDR:+match ip src $SADDR} $u32_s"
		u32_d="${DPORT:+match ip dport $DPORT $DMASK}"
		u32_d="${DADDR:+match ip dst $DADDR} $u32_d"

		### Uncomment the following if you want to see parsed rules
		#echo "$rule: $u32_s $u32_d"

		### Attach u32 filter to the appropriate class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_RULE u32 $u32_s $u32_d classid 1:$CLASS
	done ### rule

	[ "$1" = "compile" ] && echo
done ### classfile
;;


#############################################################################
################################# TIME CHECK ################################
#############################################################################

timecheck)

### Get time + weekday
TIME_TMP=`date +%w/%k:%M`
TIME_DOW=${TIME_TMP%%/*}
TIME_NOW=${TIME_TMP##*/}
TIME_ABS=`htb_time2abs $TIME_NOW`

### Check all classes (if configured)
for classfile in `htb_class_list`; do
	### Load class and gather all TIME rules
	htb_load_class $classfile
	TIMESET=`htb_cfile_rules TIME`
	[ -z "$TIMESET" ] && continue

	MATCH=0; CHANGE=0
	for timerule in $TIMESET; do
		### Split TIME rule to pieces
		TIMESPEC=${timerule%%;*}; PARAMS=${timerule##*;}
		WEEKDAYS=${TIMESPEC%%/*}; INTERVAL=${TIMESPEC##*/}
		BEG_TIME=${INTERVAL%%-*}; END_TIME=${INTERVAL##*-}

		### Check the day-of-week (if present)
		[ "$WEEKDAYS" != "$INTERVAL" -a \
		  -n "${WEEKDAYS##*$TIME_DOW*}" ] && continue

		### Compute interval boundaries
		BEG_ABS=`htb_time2abs $BEG_TIME`
		END_ABS=`htb_time2abs $END_TIME`

		### Midnight wrap fixup
		if [ $BEG_ABS -gt $END_ABS ]; then
			[ $TIME_ABS -le $END_ABS ] &&
				TIME_ABS=$[TIME_ABS + 24*60]

			END_ABS=$[END_ABS + 24*60]
		fi

		### If time period matches, remember params and set MATCH flag
		if [ $TIME_ABS -ge $BEG_ABS -a $TIME_ABS -lt $END_ABS ]; then
			RATESPEC=${PARAMS%%,*}; CEILSPEC=${PARAMS##*,}
			[ "$RATESPEC" = "$CEILSPEC" ] && CEILSPEC=""

			NEW_RATE=${RATESPEC%%/*}; NEW_BURST=${RATESPEC##*/}
			[ "$NEW_RATE" = "$NEW_BURST" ] && NEW_BURST=""

			NEW_CEIL=${CEILSPEC%%/*}; NEW_CBURST=${CEILSPEC##*/}
			[ "$NEW_CEIL" = "$NEW_CBURST" ] && NEW_CBURST=""

			MATCH=1
		fi
	done ### timerule


	### Get current RATE and CEIL of a class
	read RATE_NOW JUNK CEIL_NOW <<-EOT
	`htb_class_state $DEVICE $CLASS`
	EOT

	[ -z "$RATE_NOW" -o -z "$CEIL_NOW" ] && continue


	### Fill empty values if matched
	if [ $MATCH -ne 0 ]; then
		NEW_RATE=${NEW_RATE:-$RATE_NOW}
		NEW_CEIL=${NEW_CEIL:-$CEIL_NOW}

		NEW_BURST=${NEW_BURST:-$BURST}
		NEW_CBURST=${NEW_CBURST:-$CBURST}

	### Force configured values if not matched
	else
		NEW_RATE=$RATE; NEW_CEIL=$CEIL
		NEW_BURST=$BURST; NEW_CBURST=$CBURST
	fi



	### Check for RATE and CEIL changes
	[ "$RATE_NOW" != "$NEW_RATE" ] && CHANGE=1
	[ "$CEIL_NOW" != "$NEW_CEIL" ] && CHANGE=1

	### If there are no changes, go for next class
	[ $CHANGE -eq 0 ] && continue


	### Replace HTB class
	tc class change dev $DEVICE classid 1:$CLASS htb \
	prio $PRIO rate $NEW_RATE ${NEW_CEIL:+ceil $NEW_CEIL} \
	${NEW_BURST:+burst $NEW_BURST} ${NEW_CBURST:+cburst $NEW_CBURST}

	htb_message "$TIME_NOW: change on $DEVICE:$CLASS ($RATE_NOW/$CEIL_NOW -> $NEW_RATE/$NEW_CEIL)"
done ### class file
;;


#############################################################################
################################## THE REST #################################
#############################################################################

stop)
	htb_off
	;;

list)
	htb_show
	;;

stats)
	htb_show -s
	;;

restart)
	shift
	$0 stop
	$0 start "$@"
	;;

*)
	echo "Usage: `basename $0` {start|compile|stop|restart|timecheck|list|stats}"
esac
