#!/bin/bash
#
# HTB_shaper - based on myshaper from the ADSL Bandwidth Management HOWTO
#
# Written by Dan Singletary (8/7/02)
# Modified by Gustavo Homem (10/07/2006)
#
# This is a simplified version for a system with only 2 interfaces
# HTB is used on both traffic directions, so works on stock kernels
# without the patching required for using IMQ

# configuration options - edit them according to your link

# tested with wget from an external machine (single TCP connection)
# we should limit to a smaller value because the same internal IP
# upload rate will lead to higher ATM rates if instead of a single
# TCP uploads, there are many TCP ACKs due to downloads
# That would lead to queueing in the modem

RATEUP=310
RATEDN=16200
EXTIF=ppp0
INTIF=eth0
QLEN=30         #size of the interface queues, larger queue less packet drops but more latency
PAGESIZE=100000 #max web page size, http connections of less then PAGESIZE have higher prio

# end of configuration options

# nothing below this point should need changes

function clear
{
DEV=$1

# Reset everything to a known state (cleared)
tc qdisc del dev $DEV root    2> /dev/null > /dev/null
tc qdisc del dev $DEV ingress    2> /dev/null > /dev/null
iptables -t mangle -D POSTROUTING -o $DEV -j MYSHAPER-$DEV 2> /dev/null > /dev/null
iptables -t mangle -F MYSHAPER-$DEV 2> /dev/null > /dev/null
iptables -t mangle -X MYSHAPER-$DEV 2> /dev/null > /dev/null

echo "Shaping removed on $DEV."
}

function status
{
DEV=$1
echo
echo "*** status for interface $DEV ***"
echo
echo "[qdisc]"
tc -s qdisc show dev $DEV
echo "[class]"
tc -s class show dev $DEV
echo "[filter]"
tc -s filter show dev $DEV
echo "[iptables]"
iptables -t mangle -L MYSHAPER-$DEV -v -x 2> /dev/null

}

function limit_incoming 
{
###########################################################
# limits the incoming rate of an interface
#
# Arguments are DEV,RATE


DEV=$1
RATE=$2

tc qdisc add dev $DEV handle ffff: ingress

# everything is policed
tc filter add dev $DEV parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${RATE}kbit burst 10k drop flowid :1

}

function HTB_shape
{
###########################################################
# Shapes the traffic of an interface, limiting the outgoing rate
#
# Arguments are DEV,RATE


DEV=$1
RATE=$2

# set queue size to give latency of about 2 seconds on low-prio packets
ip link set dev $DEV qlen $QLEN

# add HTB root qdisc
tc qdisc add dev $DEV root handle 1: htb default 26

# add main rate limit classes
tc class add dev $DEV parent 1: classid 1:1 htb rate ${RATE}kbit

# add leaf classes - We grant each class at LEAST it's "fair share" of bandwidth.
#                    this way no class will ever be starved by another class.  Each
#                    class is also permitted to consume all of the available bandwidth
#                    if no other classes are in use.
tc class add dev $DEV parent 1:1 classid 1:20 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 0
tc class add dev $DEV parent 1:1 classid 1:21 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 1
tc class add dev $DEV parent 1:1 classid 1:22 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 2
tc class add dev $DEV parent 1:1 classid 1:23 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 3
tc class add dev $DEV parent 1:1 classid 1:24 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 4
tc class add dev $DEV parent 1:1 classid 1:25 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 5
tc class add dev $DEV parent 1:1 classid 1:26 htb rate $[$RATE/7]kbit ceil ${RATE}kbit prio 6

# attach qdisc to leaf classes - here we at SFQ to each priority class.  SFQ insures that
#                                within each class connections will be treated (almost) fairly.
tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $DEV parent 1:21 handle 21: sfq perturb 10
tc qdisc add dev $DEV parent 1:22 handle 22: sfq perturb 10
tc qdisc add dev $DEV parent 1:23 handle 23: sfq perturb 10
tc qdisc add dev $DEV parent 1:24 handle 24: sfq perturb 10
tc qdisc add dev $DEV parent 1:25 handle 25: sfq perturb 10
tc qdisc add dev $DEV parent 1:26 handle 26: sfq perturb 10

# filter traffic into classes by fwmark - here we direct traffic into priority class according to
#                                         the fwmark set on the packet (we set fwmark with iptables
#                                         later).  Note that above we've set the default priority
#                                         class to 1:26 so unmarked packets (or packets marked with
#                                         unfamiliar IDs) will be defaulted to the lowest priority
#                                         class.
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 20 fw flowid 1:20
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 21 fw flowid 1:21
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 22 fw flowid 1:22
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 23 fw flowid 1:23
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 24 fw flowid 1:24
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 25 fw flowid 1:25
tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 26 fw flowid 1:26

# add MYSHAPER-$DEV chain to the mangle table in iptables - this sets up the table we'll use
#                                                      to filter and mark packets.
iptables -t mangle -N MYSHAPER-$DEV
iptables -t mangle -I POSTROUTING -o $DEV -j MYSHAPER-$DEV

# add fwmark entries to classify different types of traffic - Set fwmark from 20-26 according to
#                                                             desired class. 20 is highest prio.

# prio 20
iptables -t mangle -A MYSHAPER-$DEV -p icmp -j MARK --set-mark 20               # ICMP (ping) - high prio, impress friends

# prio 21
iptables -t mangle -A MYSHAPER-$DEV -p tcp -m length --length :64 -j MARK --set-mark 21 # small packets (probably just ACK)
iptables -t mangle -A MYSHAPER-$DEV -p udp -j MARK --set-mark 21                # DNS name resolution (small packets)

PORT=dport
if [ $DEV == $EXTIF ]; then
	PORT=sport
fi

iptables -t mangle -A MYSHAPER-$DEV -p tcp --$PORT 4000 -j MARK --set-mark 21    # skype configured to listen on 4000
iptables -t mangle -A MYSHAPER-$DEV -p udp --$PORT 4000 -j MARK --set-mark 21    # skype configured to listen on 4000

# but does skype really respect the configured port?!

# prio 22
iptables -t mangle -A MYSHAPER-$DEV -p tcp --dport ssh -j MARK --set-mark 22    # secure shell
iptables -t mangle -A MYSHAPER-$DEV -p tcp --sport ssh -j MARK --set-mark 22    # secure shell

# prio 23

# give more prio to small http downloads and uploads, iptables must be compiled with the connbytes match
#iptables -t mangle -A MYSHAPER-$DEV -p tcp --dport http -m connbytes --connbytes $PAGESIZE --connbytes-dir both --connbytes-mode bytes -j MARK --set-mark 23
#iptables -t mangle -A MYSHAPER-$DEV -p tcp --sport http -m connbytes --connbytes $PAGESIZE --connbytes-dir both --connbytes-mode bytes -j MARK --set-mark 23

# prio 24
iptables -t mangle -A MYSHAPER-$DEV -p tcp --sport 0:1024 -j MARK --set-mark 24 # Default for low port traffic 
iptables -t mangle -A MYSHAPER-$DEV -p tcp --dport 0:1024 -j MARK --set-mark 24 # "" 
iptables -t mangle -A MYSHAPER-$DEV -p tcp --dport 5190 -j MARK --set-mark 24   # aol instant messenger

#prio 25
if [ $DEV == $EXTIF ]; then
  iptables -t mangle -A MYSHAPER-$DEV -p tcp --sport http -j MARK --set-mark 25   # Local web server
fi

#prio 26
iptables -t mangle -A MYSHAPER-$DEV -p tcp --dport 20 -j MARK --set-mark 26     # ftp-data port, low prio
iptables -t mangle -A MYSHAPER-$DEV -m mark --mark 0 -j MARK --set-mark 26      # redundant- mark any unmarked packets as 26 (low prio)

echo "Shaping added to $DEV.  Rate: ${RATE}Kbit/sec."

# Done with outbound shaping
#
####################################################

}

case "$1" in
  start)
	HTB_shape $INTIF $RATEDN
	limit_incoming $EXTIF $RATEDN
	HTB_shape $EXTIF $RATEUP
	;;
  stop)
	clear $INTIF
	clear $EXTIF
        exit
	;;

  restart)
        $0 stop
        $0 start
        RETVAL=$?
        ;;
  status)
        status $INTIF
	status $EXTIF
        RETVAL=$?
        ;;

  *)
        echo -e "Usage: HTB_shaper {start|stop|restart|status}\n"
        exit 1
esac

exit $RETVAL





