#!/bin/bash
# QoS bandwidth classifier v1.2 - by Pau Oliva Fora, October 2004
# http://pof.eslack.org/blog/index.php?s=bw-shaper

# Code based on wondershaper and examples found on http://lartc.org
# Licensed under the GNU GPL. For full terms see http://www.gnu.org/copyleft/gpl.html

# With this script you will get some different classes where
# you can classify your bandwidth: 4 classes for uplink and 3 for downlink
#  - highest priority class
#  - medium priority class
#  - low priority class
#  - special http class only for uplink (useful if you have a webserver)
# all traffic goes to medium priority class, unless you specify it

# it is assumed that you have a firewall with 2 interfaces, one connected to the
# cable modem or DSL router, the other one connected to the internal LAN, otherwise
# you'll have to adapt the script for your needs

# Changes from version 1.1:
# - Added TCP/UDP port separation in PRIOPORT and LIMITEDPORT
# - Changed r2q default value in uplink to avoid quantum < MTU (see http://www.docum.org/docum.org/faq/cache/31.html for details)

# Changes from version 1.0:
# - Added an interior class that holds med & low classes for downlink
# - Added burst & cburst to the highest class, so VoIP works better when the channel is full
# - Added special HTTP class for uplink, http traffic from your webserver is fast even if you have bittorrent running
# - Added an example at the end, to mark on-line gaming traffic

# now let's configure everything...

#### Tune to see how high you can set this:
# this should be a bit less than your downlink speed (in kilobits)
DOWNLINK="8000" # I have 8Mb downlink

#### Tune to see how high you can set this:
# a bit less than your uplink speed (in kilobits) 
UPLINK="560" # I have 640K uplink

# internal and external devices
DEV_INT="eth0" # interface connected to the LAN
DEV_EXT="eth1" # interface connected to the cable modem

### now define which priorities you want to assign to each host/port
### multiple values must be separated by spaces

# services that you run, that get high priority
PRIOPORT_TCP_OUT="32826 22 5000 993 53"  # skype, ssh, openvpn, imaps, dns
PRIOPORT_UDP_OUT="32826 22 5000 53 2727 5060 4569"  # skype, ssh, openvpn, dns, asterisk
# services that you use, that get high priority
PRIOPORT_TCP_IN="32826 22 23 5000 53" # skype, ssh, telnet, openvpn, dns
PRIOPORT_UDP_IN="32826 22 5000 53 2727 5060 4569" # skype, ssh, openvpn, dns, asterisk
# services that you run, that get low priority
LIMITEDPORT_TCP_OUT="110 995 6881 6882 6883 6884 6885 6886 6887 6888 6889 6969 4662 4672 4661 4671" #pop3, pop3s, bittorrent, amule
LIMITEDPORT_UDP_OUT="6881 6882 6883 6884 6885 6886 6887 6888 6889 6969 4662 4672 4661 4671" # bittorrent, amule
# services that you use, that get low priority
LIMITEDPORT_TCP_IN="873 110 6881 6882 6883 6884 6885 6886 6887 6888 6889 6969 4662 4672 4661 4671" # rsync, pop3, bittorrent, amule
LIMITEDPORT_UDP_IN="873 6881 6882 6883 6884 6885 6886 6887 6888 6889 6969 4662 4672 4661 4671" # rsync, bittorrent, amule

# list of hosts that always get high priority
PRIOHOST="192.168.1.102" # my pda :P
# list of host that always get low priority
LIMITEDHOST=


### here we're going to define how much bandwidth can get each class
### for each class we define 2 parameters: RATE and CEIL
### RATE: The fraction of total bandwidth that will be granted to a class if
###       the link is full.
### CEIL: The fraction of total bandwidth that a class can use.
###       This limits how much bandwidth that class can borrow.
### the high priority class doesn't have a definition, because it gets as much bandwidth as it can
### NOTE: If you don't know which values to put here, laeve it as it is and run the script
###       then see the output and you'll understand ;) for most people these defaults will be ok.

# UPLINK CLASS DEFINITION
#Default bulk class: 1/4 of downlink granted, 3/4 maximum
DEFAULT_UP_RATE="1/4" # fraction of total bandwidth
DEFAULT_UP_CEIL="3/4"
#Limited class: 1/8 of downlink granted, 1/4 maximum
LIMITED_UP_RATE="1/8"
LIMITED_UP_CEIL="1/4"
#Special HTTP class: 1/6 of downlink granted, 2/3 maximum
HTTP_UP_RATE="1/6"
HTTP_UP_CEIL="2/3"

# DOWNLINK CLASS DEFINITION
#Default bulk class: 1/4 of downlink granted, 5/6 maximum
DEFAULT_DOWN_RATE="1/4"
DEFAULT_DOWN_CEIL="5/6"
#Limited class: 1/8 of downlink granted, 1/2 maximum
LIMITED_DOWN_RATE="1/8"
LIMITED_DOWN_CEIL="1/2"

###########################################################
#### you don't need to modify anything below this line ####
###########################################################

if [ "$1" = "status" ]
then
	echo "==== EXTERNAL DEVICE ===="
	tc -s qdisc ls dev $DEV_EXT
	tc -s class ls dev $DEV_EXT
	echo "==== INTERNAL DEVICE ===="
	tc -s qdisc ls dev $DEV_INT
	tc -s class ls dev $DEV_INT
	exit
fi

# clean existing down and uplink qdiscs, hide errors
tc qdisc del dev $DEV_EXT root    2> /dev/null > /dev/null
tc qdisc del dev $DEV_EXT ingress 2> /dev/null > /dev/null
tc qdisc del dev $DEV_INT root    2> /dev/null > /dev/null
tc qdisc del dev $DEV_INT ingress 2> /dev/null > /dev/null

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

#### calculate
UP_KBPS=$(($UPLINK / 8)) # uplink in kbps
echo 
echo "4 classes will be created for uplink:"
up_class10="rate ${UP_KBPS}kbps ceil $((${UP_KBPS} + 2))kbps prio 0 burst 12k cburst 12k"
echo " 1) Highest priority class: not limited (for interactive traffic)"
echo "		$up_class10"
up_class20="rate $(($UP_KBPS * $DEFAULT_UP_RATE))kbps ceil $(($UP_KBPS * $DEFAULT_UP_CEIL))kbps prio 1"
echo " 2) Default bulk class: $DEFAULT_UP_RATE of downlink granted, $DEFAULT_UP_CEIL maximum"
echo "		$up_class20"
up_class30="rate $(($UP_KBPS * $LIMITED_UP_RATE))kbps ceil $(($UP_KBPS * $LIMITED_UP_CEIL))kbps prio 2"
echo " 3) limited uplink class: $LIMITED_UP_RATE of downlink granted, $LIMITED_UP_CEIL maximum"
echo "		$up_class30"
up_class40="rate $(($UP_KBPS * $HTTP_UP_RATE))kbps ceil $(($UP_KBPS * $HTTP_UP_CEIL))kbps prio 1"
echo " 4) http uplink class: $HTTP_UP_RATE of downlink granted, $HTTP_UP_CEIL maximum"
echo "		$up_class40"
echo
DOWN_KBPS=$(($DOWNLINK / 8)) # downlink in kbps
echo
echo "3 classes will be created for downlink:"
down_class10="rate ${DOWN_KBPS}kbps ceil $((${DOWN_KBPS} + 2))kbps prio 0 burst 12k cburst 12k"
echo " 1) Highest priority class: not limited (for interactive traffic)"
echo "		$down_class10"
down_class2="rate $(($DOWN_KBPS * $DEFAULT_DOWN_RATE + $DOWN_KBPS * $LIMITED_DOWN_RATE))kbps ceil $(($DOWN_KBPS * $DEFAULT_DOWN_CEIL))kbps prio 4"
echo
echo "A interior class will be created to hold those two leaf classes:"
echo "		$down_class2"
down_class20="rate $(($DOWN_KBPS * $DEFAULT_DOWN_RATE))kbps ceil $(($DOWN_KBPS * $DEFAULT_DOWN_CEIL))kbps prio 1"
echo " 2) Default bulk class: $DEFAULT_DOWN_RATE of downlink granted, $DEFAULT_DOWN_CEIL maximum"
echo "		$down_class20"
down_class30="rate $(($DOWN_KBPS * $LIMITED_DOWN_RATE))kbps ceil $(($DOWN_KBPS * $LIMITED_DOWN_CEIL))kbps prio 2"
echo " 3) limited downlink class: $LIMITED_DOWN_RATE of downlink granted, $LIMITED_DOWN_CEIL maximum"
echo "		$down_class30"
echo
echo

# calculate R2Q
# quantum = rate (in bytes) /r2q ===> should be > MTU (1500)
UP_R2Q="10"
DOWN_R2Q="10"
if [ $(($UP_KBPS * $LIMITED_UP_RATE)) -lt 15 ]; then
        UP_R2Q="6"
fi
if [ $(($UP_KBPS * $LIMITED_UP_RATE)) -lt 9 ]; then
        UP_R2Q="$(($UP_KBPS * $LIMITED_UP_RATE))"
fi
if [ $(($DOWN_KBPS * $LIMITED_DOWN_RATE)) -lt 15 ]; then
        DOWN_R2Q="6"
fi
if [ $(($DOWN_KBPS * $LIMITED_DOWN_RATE)) -lt 9 ]; then
        DOWN_R2Q="$(($DOWN_KBPS * $LIMITED_DOWN_RATE))"
fi
echo "UP_R2Q = $UP_R2Q"
echo "DOWN_R2Q = $DOWN_R2Q"



###### uplink (sent out by external interface, to the internet)
# install root HTB, point default traffic to 1:20
tc qdisc add dev $DEV_EXT root handle 1: htb default 20 r2q $UP_R2Q
# shape everything at $UP_KBPS speed - this prevents huge queues in your DSL modem which destroy latency:
tc class add dev $DEV_EXT parent 1: classid 1:1 htb rate ${UP_KBPS}kbps burst 12k cburst 12k
# 4 classes defined here
tc class add dev $DEV_EXT parent 1:1 classid 1:10 htb $up_class10
tc class add dev $DEV_EXT parent 1:1 classid 1:20 htb $up_class20
tc class add dev $DEV_EXT parent 1:1 classid 1:30 htb $up_class30
tc class add dev $DEV_EXT parent 1:1 classid 1:40 htb $up_class40

# all get Stochastic Fairness
tc qdisc add dev $DEV_EXT parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV_EXT parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $DEV_EXT parent 1:30 handle 30: sfq perturb 10
tc qdisc add dev $DEV_EXT parent 1:40 handle 40: sfq perturb 10


###### downlink (sent out by internal interface, to the LAN)


# install root HTB, point default traffic to 1:20
tc qdisc add dev $DEV_INT root handle 1: htb default 20 r2q $DOWN_R2Q
# shape everything at $DOWN_KBPS speed - this prevents huge queues in your DSL modem which destroy latency:
tc class add dev $DEV_INT parent 1: classid 1:1 htb rate ${DOWN_KBPS}kbps burst 12k cburst 12k
# 3 interior classes + 1 leaf class defined here
tc class add dev $DEV_INT parent 1:1 classid 1:10 htb $down_class10
tc class add dev $DEV_INT parent 1:1 classid 1:2 htb $down_class2
tc class add dev $DEV_INT parent 1:2 classid 1:20 htb $down_class20
tc class add dev $DEV_INT parent 1:2 classid 1:30 htb $down_class30

# all get Stochastic Fairness
tc qdisc add dev $DEV_INT parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV_INT parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $DEV_INT parent 1:30 handle 30: sfq perturb 10


# slow downloads down to somewhat less than the real speed to prevent queuing at our ISP. 
# ISPs tend to have *huge* queues to make sure big downloads are fast

# downlink of external interface (what we get from the internet)
# attach ingress policer:
tc qdisc add dev $DEV_EXT handle ffff: ingress
# filter *everything* to it (0.0.0.0/0), drop everything that's coming in too fast:
tc filter add dev $DEV_EXT parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${DOWNLINK}kbit burst 10k drop flowid :1

# downlink of internal interface (what we send to the internet)
# attach ingress policer:
tc qdisc add dev $DEV_INT handle ffff: ingress
# filter *everything* to it (0.0.0.0/0), drop everything that's coming in too fast:
tc filter add dev $DEV_INT parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${UPLINK}kbit burst 10k drop flowid :1

#  Now we set the filters so we can classify the packets with iptables (UPLINK)
tc filter add dev $DEV_EXT parent 1: protocol ip prio 10 handle 1 fw classid 1:10
tc filter add dev $DEV_EXT parent 1: protocol ip prio 20 handle 2 fw classid 1:20
tc filter add dev $DEV_EXT parent 1: protocol ip prio 30 handle 3 fw classid 1:30
tc filter add dev $DEV_EXT parent 1: protocol ip prio 40 handle 4 fw classid 1:40

#  Now we set the filters so we can classify the packets with iptables (DOWNLINK)
tc filter add dev $DEV_INT parent 1: protocol ip prio 10 handle 1 fw classid 1:10
tc filter add dev $DEV_INT parent 1: protocol ip prio 20 handle 2 fw classid 1:20
tc filter add dev $DEV_INT parent 1: protocol ip prio 30 handle 3 fw classid 1:30


# now we decide where we put the common traffic
echo "TOS Minimize-Delay (16), ICMP and ACK,SYN,RST packets will get highest priority"
echo "TOS Minimize-Cost (2) will get medium priority"
echo "TOS Maximize-Throughput (8) will get lowest priority"

# TOS Minimum Delay (ssh, NOT scp) in 1:10
iptables -I PREROUTING -t mangle -m tos --tos Minimize-Delay -j MARK --set-mark 1
iptables -I POSTROUTING -t mangle -m tos --tos Minimize-Delay -j MARK --set-mark 1
# TOS Minimize-Cost in 1:20 (this should be redundant)
iptables -I PREROUTING -t mangle -m tos --tos Minimize-Cost -j MARK --set-mark 2
iptables -I POSTROUTING -t mangle -m tos --tos Minimize-Cost -j MARK --set-mark 2
# TOS Maximize-Throughput in 1:30
iptables -I PREROUTING -t mangle -m tos --tos Maximize-Throughput -j MARK --set-mark 3
iptables -I POSTROUTING -t mangle -m tos --tos Maximize-Throughput -j MARK --set-mark 3

# ICMP in the interactive class 1:10 so we can do measurements & impress our friends
iptables -I PREROUTING -t mangle -p icmp -j MARK --set-mark 1
iptables -I POSTROUTING -t mangle -p icmp -j MARK --set-mark 1

# To speed up downloads while an upload is going on, put ACK packets in the interactive class
iptables -I PREROUTING -t mangle -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j MARK --set-mark 1
iptables -I POSTROUTING -t mangle -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j MARK --set-mark 1
# all tcp packets except SYN have ACK bit set, so to really mark ACKs we look at the packet size:
iptables -I PREROUTING -t mangle -p tcp -m length --length 40:64 -j MARK --set-mark 1 
iptables -I POSTROUTING -t mangle -p tcp -m length --length 40:64 -j MARK --set-mark 1 


# now we decide where we put the uplink traffic
echo 
echo "UPLINK traffic classification:"

# high priority ports
for a in $PRIOPORT_TCP_OUT ; do
    iptables -I POSTROUTING -t mangle -o $DEV_EXT -p tcp --sport $a -j MARK --set-mark 1
    echo "Traffic coming from TCP port $a will get highest priority"
done
for a in $PRIOPORT_UDP_OUT ; do
    iptables -I POSTROUTING -t mangle -o $DEV_EXT -p udp --sport $a -j MARK --set-mark 1
    echo "Traffic coming from UDP port $a will get highest priority"
done

# limited ports
for a in $LIMITEDPORT_TCP_OUT ; do
    iptables -I POSTROUTING -t mangle -o $DEV_EXT -p tcp --sport $a -j MARK --set-mark 3
    echo "Traffic coming from TCP port $a will get lowest priority"
done
for a in $LIMITEDPORT_UDP_OUT ; do
    iptables -I POSTROUTING -t mangle -o $DEV_EXT -p udp --sport $a -j MARK --set-mark 3
    echo "Traffic coming from UDP port $a will get lowest priority"
done

# special http uplink class
iptables -I POSTROUTING -t mangle -o $DEV_EXT -p tcp --sport 80 -j MARK --set-mark 4
iptables -I POSTROUTING -t mangle -o $DEV_EXT -p tcp --sport 443 -j MARK --set-mark 4
echo "HTTP traffic coming from internal server will get Special HTTP Uplink priority"


echo


# now we decide where we put the downlink traffic
echo "DOWNLINK traffic classification:"

# high priority ports
for a in $PRIOPORT_TCP_IN ; do
    iptables -I POSTROUTING -t mangle -o $DEV_INT -p tcp --sport $a -j MARK --set-mark 1
    echo "Traffic coming from TCP port $a will get highest priority"
done
for a in $PRIOPORT_UDP_IN ; do
    iptables -I POSTROUTING -t mangle -o $DEV_INT -p udp --sport $a -j MARK --set-mark 1
    echo "Traffic coming from UDP port $a will get highest priority"
done

# limited ports
for a in $LIMITEDPORT_TCP_IN ; do
    iptables -I POSTROUTING -t mangle -o $DEV_INT -p tcp --sport $a -j MARK --set-mark 3
    echo "Traffic coming from TCP port $a will get lowest priority"
done
for a in $LIMITEDPORT_UDP_IN ; do
    iptables -I POSTROUTING -t mangle -o $DEV_INT -p udp --sport $a -j MARK --set-mark 3
    echo "Traffic coming from UDP port $a will get lowest priority"
done

echo

# high priority hosts
for a in $PRIOHOST ; do
	iptables -I PREROUTING -t mangle -s $a -j MARK --set-mark 1
	iptables -I PREROUTING -t mangle -d $a -j MARK --set-mark 1
	iptables -I POSTROUTING -t mangle -s $a -j MARK --set-mark 1
	iptables -I POSTROUTING -t mangle -d $a -j MARK --set-mark 1
	echo "Traffic from/to host $a will get highest priority"
done

# limited hosts
for a in $LIMITEDHOST ; do
    iptables -I PREROUTING -t mangle -s $a -j MARK --set-mark 3
    iptables -I PREROUTING -t mangle -d $a -j MARK --set-mark 3
    iptables -I POSTROUTING -t mangle -s $a -j MARK --set-mark 3
    iptables -I POSTROUTING -t mangle -d $a -j MARK --set-mark 3
    echo "Traffic coming from/to host $a will get lowest priority"
done



###### you can add your own iptables rules to mark traffic here ######

### EXAMPLE: wolfenstein enemy territory (small udp packets from 17 to 200 bytes)
#echo "wolfenstein enemy territory from/to ip 192.168.1.34 will get highest priority"
#iptables -I PREROUTING -t mangle -p udp -m length --length 17:200 -s 192.168.1.34 -j MARK --set-mark 1 
#iptables -I PREROUTING -t mangle -p udp -m length --length 17:200 -d 192.168.1.34 -j MARK --set-mark 1 

echo
# EOF
