#!/bin/bash
#
# Shaper to share two connection more or less equally among a number of users.
# By Rune Kock, rune.kock@gmail.com
#
# Inspired by Jim diGriz's QoS Scheduler (http://www.digriz.org.uk/jdg-qos-script/)
# and by the WRR example.
#
# Dependencies
# ------------
# 1) HTB3, RED (has been in the kernel for years)
# 3) iptables utility (www.netfilter.org)
# 4) tc utility (developer.osdl.org/dev/iproute2)
# 6) WRR patch (www.zz9.dk/wrr) and tc patch 
# 7) IMQ patch (www.linuximq.net) and iptables patch NOTE: in AA-configuration!!!
# 8) atm and nohyst patches (http://ace-host.stuart.id.au/russell/files/tc/tc-atm/)

# This script can be edited to support more than one ADSL line.
# Each WAN-line is shaped independently -- not quite fair, but the easiest
# (probably the only) way to do it.

# Script parameters:
#	stop		removes QOS setup
#	...any other... removes QOS setup if any, then sets new QOS.

# debug on....comment out if you want BASH script debugging off
# set -x

# load configuration from external file
. /etc/qos/config


############# Functions for each ADSL line ######################
function REMOVE-QOS
{
	local IFACE=$1		# e.g. eth0
	local DWIMQ=$2		# e.g. 0
	
	$TC qdisc del dev imq$DWIMQ root &> /dev/null
	$TC qdisc del dev $IFACE root &> /dev/null

	$IPTABLES -t mangle -D PREROUTING -i $IFACE -j IMQ --todev $DWIMQ &> /dev/null
	$IP link set imq$DWIMQ down &> /dev/null
}
#-----------------------------------------------------------------
function QOS-UPLINK
{
	local IFACE=$1
	local UPLIMIT=$2
	local WRR_CLIENTS=$3
	local OVERHEAD=$4
	local PUBIP=$5

	# more description below at QOS-DOWNLINK

	# The effect of these settings:
	# - the maximum priority is 10x minimum.
	# - it takes 25 hours without activity to gain maximum priority
	# - it takes 90 Mbyte transferred data to be bumped to mimimum priority

	local WRR_WEIGHT1_MODE=1
	local WRR_WEIGHT1_INCR=0.00001
	local WRR_WEIGHT1_DECR=0.00000001
	local WRR_WEIGHT1_MIN=0.1
	local WRR_WEIGHT1_MAX=1.0
	local WRR_WEIGHT1_VAL=1.0

	# Use HTB to limit the total speed
	$TC qdisc add dev $IFACE root handle 8000: htb default 12
	$TC class add dev $IFACE parent 8000: classid 8000:1 htb \
		rate ${UPLIMIT}kbit ceil ${UPLIMIT}kbit overhead $OVERHEAD atm nohyst

	# Create 3 HTB classes for high priority local, normal priority local,
	# and normal priority client traffic.
	$TC class add dev $IFACE parent 8000:1 classid 8000:10 htb\
		rate $[$UPLIMIT/3]kbit ceil ${UPLIMIT}kbit overhead $OVERHEAD atm nohyst prio 2
	$TC class add dev $IFACE parent 8000:1 classid 8000:11 htb \
		rate $[$UPLIMIT/3]kbit ceil ${UPLIMIT}kbit overhead $OVERHEAD atm nohyst prio 1
	$TC class add dev $IFACE parent 8000:1 classid 8000:12 htb \
		rate $[$UPLIMIT/3]kbit ceil ${UPLIMIT}kbit overhead $OVERHEAD atm nohyst prio 1

	# To distinguish between local packet and packets originating from the lan, we have
	# told Shorewall to give the latter mark 1.  (IP source cannot be used, because we are now
	# operating after NAT, so everything has our public IP as source).
	# Put local packets < 512 bytes in 8000:10
	$TC filter add dev $IFACE protocol ip parent 8000:0 prio 1 u32 \
		match mark 0 0xffff match u16 0x0000 0xfe00 at 2 flowid 8000:10
	# Put other local packets in 8000:11
	$TC filter add dev $IFACE protocol ip parent 8000:0 prio 1 u32 \
		match mark 0 0xffff flowid 8000:11
	# Non-local packets defaults to 8000:12

	# Client traffic is shared according to ip-address by WRR (the masqueraded source address)
	$TC qdisc add dev $IFACE parent 8000:12 handle 1000: wrr sour masq $WRR_CLIENTS 0
	$TC qdisc change handle 1000: dev $IFACE wrr qdisc wmode1=$WRR_WEIGHT1_MODE wmode2=0

	# Each WRR class gets a RED queue.
	# RED drops or ECN-marks packets according to queue-length.
	# At <=2kbyte the chance of a RED action is 0%.  At 100kbyte it is 100%.
	# Hopefully, the sender will get the picture at around 15kbytes.
	local RED_MIN=2000	# Start shaping if average queue exceeds 2 kbyte
	local RED_MAX=100000	# Full shaping at 100kbytes
	local RED_LIMIT=100000	# Absolut maximum queue size
	local RED_PROB=1.0	# The probability of dropping/marking a packet when
				# queue >= max. Man-page suggests 0.01 or 0.02
	local RED_AVPKT=500	# man-page suggests 1000
	local RED_BURST=8	# man-page suggests (min+min+max)/(3*avpkt)
				# (but they use min and max a lot differently than we do).

	for LOOP in `seq 1 $WRR_CLIENTS`
	do
		local LOOP_HEX=$(printf %X $LOOP)
		$TC qdisc add dev $IFACE parent 1000:$LOOP_HEX handle $[1000+$LOOP]: \
			red probability $RED_PROB \
			limit ${RED_LIMIT} min ${RED_MIN} max ${RED_MAX} \
			avpkt ${RED_AVPKT} burst $RED_BURST ecn

		$TC class change classid 1000:$LOOP_HEX dev $IFACE \
			wrr min1=$WRR_WEIGHT1_MIN max1=$WRR_WEIGHT1_MAX \
			decr1=$WRR_WEIGHT1_DECR incr1=$WRR_WEIGHT1_INCR \
			weight1=$WRR_WEIGHT1_VAL
	done
}

#----------------------------------------------------------------
function QOS-DOWNLINK
{
	local IFACE=$1
	local DWLIMIT=$2
	local WRR_CLIENTS=$3
	local OVERHEAD=$4
	local PUBIP=$5

	# The following parameters defines the way heavy downloaders are gradually
	# given a lesser share than new downloaders (a burst effect).
	# Each class has a weight that is dynamically modified as follows:
	# (Actually, there are two identical sets of parameters, but I can see absolutely
	#  no reason to use the second, so it's always inactive).

	# The effect of these settings:
	# - the maximum priority is 10x minimum.
	# - it takes 25 hours without activity to gain maximum priority
	# - it takes 900 Mbyte transferred data to be bumped to mimimum priority

	local WRR_WEIGHT1_MODE=1
	# 0: The weight parameter is not modified
	# 1: The weight parameter is decremented by decr*<packet size>
	# 2: As if wmode is 1, but also multiply with number of machines waiting
	#    to transfer data
	# 3: As if wmode is 1, but multiplied with the sum of the priorities of the
	#    machines waiting divided by the priority of this machine.

	local WRR_WEIGHT1_INCR=0.00001
	# Every second the weight parameter is incremented by this value. If
    	# weight1 reaches 1.0 it is not incremented further.
	# The default value is 0.0.

	local WRR_WEIGHT1_DECR=0.000000001
	# Every time a packet is sent the weight parameter is modified depending
    	# on the value of wmode for the qdisc

	local WRR_WEIGHT1_MIN=0.1
	# min: (0<min)
	# This is a lower bound for weight. 
	# The default value for this parameter is 1.0.

	local WRR_WEIGHT1_MAX=1.0
	# max: (0<max)
	# This is a upper bound for weight. 
	# The default value for this parameter is 1.0.

	local WRR_WEIGHT1_VAL=1.0
	# weight: (min<=weight<=1.0)
	# As mentioned above the used weight of the class is proportional to
	# this value. The default value is 1.0.

	# Use HTB to limit the speed
	$TC qdisc add dev $IFACE root handle 8000: htb default 12
	$TC class add dev $IFACE parent 8000: classid 8000:1 htb \
		rate ${DWLIMIT}kbit ceil ${DWLIMIT}kbit overhead $OVERHEAD atm nohyst

	# Create 3 HTB classes for high priority local, normal priority local,
	# and normal priority client traffic.
	$TC class add dev $IFACE parent 8000:1 classid 8000:10 htb\
		rate $[$DWLIMIT/3]kbit ceil ${DWLIMIT}kbit overhead $OVERHEAD atm nohyst prio 2
	$TC class add dev $IFACE parent 8000:1 classid 8000:11 htb \
		rate $[$DWLIMIT/3]kbit ceil ${DWLIMIT}kbit overhead $OVERHEAD atm nohyst prio 1
	$TC class add dev $IFACE parent 8000:1 classid 8000:12 htb \
		rate $[$DWLIMIT/3]kbit ceil ${DWLIMIT}kbit overhead $OVERHEAD atm nohyst prio 1

	# Put local packets < 512 bytes in 8000:10
	# (IMQ in AA configuration grabs the packets after NAT, so we can test on destination).
	$TC filter add dev $IFACE protocol ip parent 8000:0 prio 1 u32 \
		match ip dst $PUBIP match u16 0x0000 0xfe00 at 2 flowid 8000:10

	# Put other local packets in 8000:11
	$TC filter add dev $IFACE protocol ip parent 8000:0 prio 1 u32 \
		match ip dst $PUBIP flowid 8000:11
	# Non-local packets defaults to 8000:12

	# Share according to ip-address by WRR
	$TC qdisc add dev $IFACE parent 8000:12 handle 1000: wrr dest ip $WRR_CLIENTS 0
	$TC qdisc change handle 1000: dev $IFACE wrr qdisc wmode1=$WRR_WEIGHT1_MODE wmode2=0

	# Each WRR class gets a RED queue.
	# RED drops or ECN-marks packets according to queue-length.
	# At 2kbyte the chance of a RED action is 0%.  At 1Mbyte it is 100%.
	# Hopefully, the sender will get the picture at around 10-20kbytes.
	local RED_MIN=2000	# Start shaping if average queue exceeds 1 kbyte
	local RED_MAX=1000000	# Full shaping at 1Mbytes
	local RED_LIMIT=1000000	# Absolut maximum queue size
	local RED_PROB=1.0	# The probability of dropping/marking a packet when
				# queue >= max. Man-page suggests 0.01 or 0.02
	local RED_AVPKT=1000	# man-page suggests 1000
	local RED_BURST=2	# man-page suggests (min+min+max)/(3*avpkt)
				# (but they use min and max a lot differently than we do).

	for LOOP in `seq 1 $WRR_CLIENTS`
	do
		local LOOP_HEX=$(printf %X $LOOP)
		$TC qdisc add dev $IFACE parent 1000:$LOOP_HEX handle $[1000+$LOOP]: \
			red probability $RED_PROB \
			limit ${RED_LIMIT} min ${RED_MIN} max ${RED_MAX} \
			avpkt ${RED_AVPKT} burst $RED_BURST ecn

		$TC class change classid 1000:$LOOP_HEX dev $IFACE \
			wrr min1=$WRR_WEIGHT1_MIN max1=$WRR_WEIGHT1_MAX \
			decr1=$WRR_WEIGHT1_DECR incr1=$WRR_WEIGHT1_INCR \
			weight1=$WRR_WEIGHT1_VAL
	done
}
#-----------------------------------------------------------------------
function IFACE-SETUP
{
	local IFACE=$1
	local DWIMQ=$2

	$IPTABLES -t mangle -I PREROUTING -i $IFACE -j IMQ --todev $DWIMQ
	$IP link set imq$DWIMQ up 
}

##############################################################################

############## Remove QOS configuration ##########################

REMOVE-QOS $IF1 0		# Once for each ADSL-line
# REMOVE-QOS $IF2 1

# removing imq (version 2.6.14-imq1) causes kernel panic
#$MODPROBE -r imq &> /dev/null

if ( [ "$1" = "stop" ] )
then
	exit
fi

############## Set up QOS configuration ###########################

$MODPROBE imq numdevs=$LINES

QOS-UPLINK $IF1 $UPLIMIT1 $CLIENTS $UPOVERHEAD1 $PUBLICIP1 # Once for each ADSL-line
# QOS-UPLINK $IF2 $UPLIMIT2 $CLIENTS $UPOVERHEAD2 $PUBLICIP2

QOS-DOWNLINK imq0 $DWLIMIT1 $CLIENTS $DWOVERHEAD1 $PUBLICIP1  # Once for each ADSL-line
# QOS-DOWNLINK imq1 $DWLIMIT2 $CLIENTS $DWOVERHEAD2 $PUBLICIP2

IFACE-SETUP $IF1 0		# Once for each ADSL-line
# IFACE-SETUP $IF2 1