#!/bin/sh
. /etc/profile

# set -x
# to get the commands required for the startup script for inet1 (DD-WRT firmware based router):
# 1) uncomment set -x (see above) in this script
# 2) on inet1, run /tmp/smbshare/tc.sh start 2>&1 | grep '^+ tc' | awk '{print substr($0,3)}'
# 3) on inet1, run /tmp/smbshare/tc.sh start 2>&1 | grep '^+ iptables' | awk '{print substr($0,3)}'
# 4) copy and paste all the commands into the inet1 web "administration" tab, "commands" tab, startup section


# description: Linux Traffic Control for obtaining maximum performance and responsiveness from your Internet connection
# Matthew Jurgens June 2006
# The Ultimate Setup For Your Internet Connection At Home
# modified from http://www.tldp.org/HOWTO/Adv-Routing-HOWTO/lartc.cookbook.ultimate-tc.html
# with bits added from http://www.arctic.org/~dean/scripts/wshaper (this site appears to have enhanced the one above also)
# and then I found http://www.voip-info.org/wiki/view/QoS+with+Linux+using+PRIO+and+HTB
# which had a VOIP component
# development and tuned over years of usage
# Version 2.5 - October 2010


# In general, we work on the basis that the traffic must be defined and classified to get any priority better than everything else (ie default)
# we define 4 bands 
# BAND 1 (pfifo 100:) for VOIP

# IMPORTANT_HIGH_PRIORITY
# BAND 2 (sfq 200:) for high priority NON-VOIP, data from this band will only be sent if there is nothing in the previous bands

# IMPORTANT_RATE_LIMITED
# BAND 3 (tbf 300:) for high priority bandwidth throttled NON-VOIP, data from this band will only be sent if there is nothing in the previous bands

# BAND 4 (htb 400:) for the rest of the traffic, data from this band will only be sent if there is nothing in the previous bands

# Where Band 4 is split into 4 sub-queues

# STD_HIGH_PRIORITY
# sfq 4001: Normal High Priority Traffic eg Acks, ICMP, SSH and other interactive traffic

# STD_MID_PRIORITY
# sfq 4002: Normal Mid Priority Traffic eg OpenVPN and SSH (port 443)

# NORMAL STD PRIORITY
# sfq 4003: Normal Standard Priority Traffic eg http, https
# GUEST STD PRIORITY
# sfq 4004: Standard Priority for all GUEST Traffic eg http, https

# EVERYTHING ELSE
# sfq 4005: Default Priority Traffic (eg bittorrent)

# Set the following values to somewhat less than your actual download
# and uplink speed. In kilobits
# setting values to about 95% of actual seems to be ok
# sometimes you can go right up to the theoretical bandwidth and even a little over
# you really need to test to see what works best
# do not just take your ISPs word for what the uplink speed is supposed to be you need to test it
DOWNLINK=20000    # my ISP says I have 20000kbit/s so I go just slightly lower
UPLINK=880        # my ISP says I have 1000kbit/s




my_host=`hostname`

# enter an if-then-else section for each hostname you will be running this script on
if [ "$my_host" = "gw.lambert.rd.to" ]; then
   # this is my linux based host, running Fedora
   fedora=1
   
   # name of the ethernet device that will be traffic shaped
   mydev=eth1

   # name of the LAN ethernet device for the server - this is where all the in-house traffic comes from/goes to
   mydev_lan=eth0
elif [ "$my_host" = "inet1" ]; then
   # this is my DDWRT based router, so we do not set the fedora flag
   fedora=

   # name of the ethernet device that will be traffic shaped - this should be the Internet link
   mydev=vlan1
   
   # if the ppp0 interface exists it means you are using PPPoE and this is the upload interface
   ifconfig | grep ppp0 > /dev/null
   if [ "$?" = "0" ]; then
      # using PPPoE
      mydev=ppp0
   fi

   # name of the LAN ethernet device for the router - this is where all the in-house traffic comes from/goes to
   mydev_lan=br0
elif [ "$my_host" = "inet2" ]; then
   # this is my DDWRT based router, so we do not set the fedora flag
   fedora=

   # name of the ethernet device that will be traffic shaped - this should be the Internet link
   mydev=vlan1
   
   # if the ppp0 interface exists it means you are using PPPoE and this is the upload interface
   ifconfig | grep ppp0 > /dev/null
   if [ "$?" = "0" ]; then
      # using PPPoE
      mydev=ppp0
   fi
   
   DOWNLINK=9500     # my ISP says I have 10000kbit/s so I go just slightly lower
   UPLINK=256        # my ISP says I have 256kbit/s

   # name of the LAN ethernet device for the router - this is where all the in-house traffic comes from/goes to
   mydev_lan=br0
else
   echo "You must configure this script with the hostname: `hostname`"
   exit 1
fi

DEV=$mydev

if [ "$fedora" ]; then
   # source function library
   # this is for Fedora to make the output look pretty
   . /etc/rc.d/init.d/functions
fi


# --------------- BANDWIDTH THROTTLE Settings for UPLOAD ---------------
# These bandwidth limiting values are used by queue settings

# OVERALL_MAX_RATE is the rate we throttle the entire upload bandwidth to
# we make sure that we are the bottleneck so that we are in control of the queues
let OVERALL_MAX_RATE=97*$UPLINK/100

# IMPORTANT_RATE_LIMITED_MAX_RATE is the rate we throttle the Important but Rate Limited Class to 10:3
let IMPORTANT_RATE_LIMITED_MAX_RATE=80*$OVERALL_MAX_RATE/100

# STD_TRAFFIC_MAX_RATE is the rate we throttle all traffic in the lowest PRIO band (10:4), to make sure that there is always at least some reserved for VOIP and other important traffic
# at the moment it is 100% of the OVERALL_MAX_RATE - we originally had it a little less, but it probably does not need
let STD_TRAFFIC_MAX_RATE=100*$OVERALL_MAX_RATE/100

# GUEST_RATE_LIMITED_MAX_RATE is the rate we throttle the GUEST traffic to
# this traffic must be marked in the squid configuration so that it makes it into this queue
# this is a subset of the STD_TRAFFIC_MAX_RATE
let GUEST_RATE_LIMITED_MAX_RATE=60*$STD_TRAFFIC_MAX_RATE/100

#---------------------------- QUEUE/TRAFFIC DEFINTIONS --------------------------
# these definitions are by host/port
# you can leave them blank if required
# these are space delimited lists

###############################################################################
##### DEFINE NETMASKS/HOSTS FOR MEGA HIGH PRIORITY QUEUE 10:2 (sfq 200:) ######
###############################################################################
# netmasks are defined like 192.168.2.2/32 or 192.168.2.0/24 etc
IMPORTANT_HIGH_PRIORITY_SRC_NETMASKS=""
IMPORTANT_HIGH_PRIORITY_DST_NETMASKS=""

# LOCAL_SRC_PORTS are destination ports for incoming packets on the interface eg if you are hosting on port 442
#               responses to hosted port are effectively source ports for the filter match and these are the ones that need traffic control
# REMOTE_DEST_PORTS are destination ports for outgoing packets on the interface eg if you are sending port 32768
#               these are the remote destination ports for the filter match and these are the ones that need traffic control


# the following are space delimited lists
# 442 is for SSH to GW -only use this high priority port if 443 is not accessable or too slow
# 5615 is for ventrilo
# 8767 is for teamspeak
IMPORTANT_HIGH_PRIORITY_LOCAL_SRC_PORTS="442 28960"      
IMPORTANT_HIGH_PRIORITY_REMOTE_DEST_PORTS="5615 8767"

###############################################################################
##### DEFINE NETMASKS/HOSTS FOR MEGA HIGH PRIORITY BUT RATE LIMITED QUEUE 10:3 (sfq 300:)
###############################################################################
# netmasks are defined like 192.168.2.2/32 or 192.168.2.0/24 etc
IMPORTANT_RATE_LIMITED_SRC_NETMASKS=""
IMPORTANT_RATE_LIMITED_DST_NETMASKS=""

# LOCAL_SRC_PORTS are destination ports for incoming packets on the interface eg if you are hosting on port 442
#               responses to hosted port are effectively source ports for the filter match and these are the ones that need traffic control
# REMOTE_DEST_PORTS are destination ports for outgoing packets on the interface eg if you are sending port 32768
#               these are the remote destination ports for the filter match and these are the ones that need traffic control


# the following are space delimited lists
# 40000 - Skype must be signed out for you to change the send UDP port. This must be configured into Skype as by default it picks a random port.
IMPORTANT_RATE_LIMITED_LOCAL_SRC_PORTS="40000"      
IMPORTANT_RATE_LIMITED_REMOTE_DEST_PORTS=""

###################################################################################
##### DEFINE NETMASKS/HOSTS FOR ULTRA HIGH PRIORITY QUEUE 400:10 (sfq 4001:) ######
###################################################################################
# netmasks are defined like 192.168.2.2/32 or 192.168.2.0/24 etc
STD_HIGH_PRIORITY_SRC_NETMASKS=""
STD_HIGH_PRIORITY_DST_NETMASKS=""
STD_HIGH_PRIORITY_LOCAL_SRC_PORTS="443" # this 443 is for incoming SSH
STD_HIGH_PRIORITY_REMOTE_DEST_PORTS="53" # this is for DNS

############################################################################
##### DEFINE NETMASKS/HOSTS FOR MID PRIORITY QUEUE 400:20 (sfq 4002:)######
############################################################################
# netmasks are defined like 192.168.2.2/32 or 192.168.2.0/24 etc
STD_MID_PRIORITY_SRC_NETMASKS=""
STD_MID_PRIORITY_DST_NETMASKS=""
STD_MID_PRIORITY_LOCAL_SRC_PORTS="1194 1723" # 1194 is for OpenVPN
STD_MID_PRIORITY_REMOTE_DEST_PORTS="2078 3131 4500" # 2078 is for openvpn, 3131 ssh to xxxxx.com.au, 4500 is for Ipsec VPN

############################################################################
##### DEFINE NETMASKS/HOSTS FOR STANDARD PRIORITY QUEUE 400:30 (sfq 4003:)######
############################################################################
# netmasks are defined like 192.168.2.2/32 or 192.168.2.0/24 etc
STD_NORMAL_PRIORITY_SRC_NETMASKS=""
STD_NORMAL_PRIORITY_DST_NETMASKS=""
STD_NORMAL_PRIORITY_LOCAL_SRC_PORTS="" 
STD_NORMAL_PRIORITY_REMOTE_DEST_PORTS="80 443" # standard http traffic




#---------------------------- MISC --------------------------
DEFAULT_PRIO=1

#===================================== FUNCTIONS ========================================

#----------------------------------------------------------------------------------------
start () {

# remove existing setup
stop

#========================================================================================
#===================================== UPLINK ===========================================
#========================================================================================

#########################################################################################
################################### QUEUE/CLASS DEFINTIONS ##############################
#########################################################################################


# we have to do these calculation here
# this script used to use the notation $[95*$STD_TRAFFIC_MAX_RATE/100]
# but this proved not to work in all /bin/sh - namely dd-wrt firmware 
# this then stopped the QOS working properly
# these limits are used for the HTB Classful Queue Discipline (qdisc)
# HTB allows for borrowing of bandwidth credits from parents
# the percentages defined eg 95 + 3 + 2 should be less than or equal to 100
# as these will define the minimum bandwidth a specific class will get
LIM40010=`expr 30 \* $STD_TRAFFIC_MAX_RATE / 100`
LIM40020=`expr 20 \* $STD_TRAFFIC_MAX_RATE / 100`
LIM40030=`expr 10 \* $STD_TRAFFIC_MAX_RATE / 100`
LIM40040=`expr 5 \* $STD_TRAFFIC_MAX_RATE / 100`
LIM40050=`expr 1 \* $STD_TRAFFIC_MAX_RATE / 100` # this is for the default class so make it very low


tc qdisc add dev ${DEV} root handle 1: tbf rate ${OVERALL_MAX_RATE}kbit burst 4k latency 30ms

# I define the priomap so that the priomap always sends traffic wanting TOS (Type of Service) to the last PRIO class band which is class3 (since there are 4 bands and they start at zero)
tc qdisc add dev ${DEV} parent 1: handle 10: prio bands 4 priomap 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 


# -----------------------------------------------------------------------
# now define each of the PRIO Classes
tc qdisc add dev ${DEV} parent 10:1 handle 100: pfifo
tc qdisc add dev ${DEV} parent 10:2 handle 200: sfq perturb 10
tc qdisc add dev ${DEV} parent 10:3 handle 300: tbf rate ${IMPORTANT_RATE_LIMITED_MAX_RATE}kbit burst 4k latency 30ms
tc qdisc add dev ${DEV} parent 10:4 handle 400: htb

# -----------------------------------------------------------------------
# these classes/qdiscs are for the remaining traffic
tc class add dev ${DEV} parent 400: classid 400:1 htb rate ${STD_TRAFFIC_MAX_RATE}kbit burst 5k

tc class add dev ${DEV} parent 400:1 classid 400:10 htb \
    rate ${LIM40010}kbit ceil ${STD_TRAFFIC_MAX_RATE}kbit burst 5k prio 4

tc class add dev ${DEV} parent 400:1 classid 400:20 htb \
    rate ${LIM40020}kbit ceil ${STD_TRAFFIC_MAX_RATE}kbit burst 5k prio 5

# add a slightly larger burst as this class contains generic HTTP
tc class add dev ${DEV} parent 400:1 classid 400:30 htb \
    rate ${LIM40030}kbit ceil ${STD_TRAFFIC_MAX_RATE}kbit burst 5k prio 6

# this one is for the GUEST traffic and hence has a different ceiling limit
tc class add dev ${DEV} parent 400:1 classid 400:40 htb \
    rate ${LIM40040}kbit ceil ${GUEST_RATE_LIMITED_MAX_RATE}kbit burst 2k prio 7

# this the bulk/non-interactive traffic/default
# low burst, low priority etc
tc class add dev ${DEV} parent 400:1 classid 400:50 htb \
    rate ${LIM40050}kbit ceil ${STD_TRAFFIC_MAX_RATE}kbit burst 2k prio 8

tc qdisc add dev ${DEV} parent 400:10 handle 4001: sfq perturb 10
tc qdisc add dev ${DEV} parent 400:20 handle 4002: sfq perturb 10
tc qdisc add dev ${DEV} parent 400:30 handle 4003: sfq perturb 10
tc qdisc add dev ${DEV} parent 400:40 handle 4004: sfq perturb 10
tc qdisc add dev ${DEV} parent 400:50 handle 4005: sfq perturb 10
# -----------------------------------------------------------------------


#########################################################################################
##################################### FILTER DEFINTIONS #################################
#########################################################################################
# Filters only place traffic into classes, not Qdiscs 
# It appears to be very important to define the filters with the prio value in the order that you want the filters to be applied in
# We use the $my_prio variable to specify the prio value for filters 


#========================================================================================
#=================================== BAND 10:1 ==========================================
#========================================================================================
#---------------------------------- VOIP FILTERS ----------------------------------------
my_prio=10

# VoIP traffic always gets first in line 
# my ATA tags them with TOS 0x68 or 0xb8 or runs on UDP port 5060, or runs in subnet 192.168.5.x)

# uncomment the filters you need

# my ATA tags them with TOS 0x68 or 0xb8
#tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
#    match ip tos 0x68 0xff flowid 10:1


if [ "$my_host" = "inet1" ]; then
   :
   # special handling of VOIP for inet1 dd-wrt firmware based router
   # we can not rely on 
   # TCP PORT Number - since some VOIP providers/configs don't use 5060 eg for a 2nd line
   # So we have decided to configure all traffic from the VOIP devices as VOIP class
   # We can not match VOIP traffic on the outgoing interface using
   # IP SRC address - since it is already NATed by the time we see it
   # SRC MAC Address - since it is already on the outbound interface using that MAC address by the time we see it
   # So we have to use iptables to mark it on the LAN interface as it comes in and then
   # filter it on the outgoing WAN interface using the mark we made
   # use ip tables to mark incoming VOIP packets on the LAN interface
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.0.51 --set-mark 1
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.0.52 --set-mark 1
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.0.53 --set-mark 1
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.0.54 --set-mark 1
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.0.55 --set-mark 1

   # now use tc filter to find that mark we made
   tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip handle 1 fw flowid 10:1
else
   :
   # VOIP Source port 5060
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip sport 5060 0xffff flowid 10:1
   	   
   # VOIP Dest port 5060
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip dport 5060 0xffff flowid 10:1
   
   # VOIP Source Subnet     
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip src 192.168.5.0/24 flowid 10:1
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip src 192.168.5.51 flowid 10:1
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip src 192.168.0.52 flowid 10:1
   
   # VOIP Dest Subnet    
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip dst 192.168.5.0/24 flowid 10:1
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip dst 192.168.5.51 flowid 10:1
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match ip dst 192.168.0.52 flowid 10:1
   
   # example match on source mac address of 000fb5db8c4d
   #tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
   #	   match u16 0x0800 0xFFFF at -2 match u16 0x8c4d 0xFFFF at -4 match u32 \
   #      0x000fb5db 0xFFFFFFFF at -8 flowid 10:1

fi






#========================================================================================
#=================================== BAND 10:2 ==========================================
#========================================================================================
#------------------------- VERY IMPORTANT NON-VOIP FILTERS ------------------------------

my_prio=20
set_netmasks_port_filters "10:2" "10:" $my_prio "$IMPORTANT_HIGH_PRIORITY_SRC_NETMASKS" "$IMPORTANT_HIGH_PRIORITY_DST_NETMASKS" "$IMPORTANT_HIGH_PRIORITY_LOCAL_SRC_PORTS" "$IMPORTANT_HIGH_PRIORITY_REMOTE_DEST_PORTS"


if [ "$my_host" = "inet1" ]; then
   :
   # similar to detection of VOIP above - we want to do it based on source IP Address
   # mark packets on the lan interface for Joint operations 
   # from my PC to Internet server on port 32768
   # this manually defined here because it is an iptables based filter
   iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan --protocol udp --source 192.168.2.2 --source-port 32768 --set-mark 2

   # from Gold to UGN server on port 32768
   # iptables -t mangle -A PREROUTING -j MARK -i $mydev_lan -s 192.168.2.8 -d 203.123.80.157 --sport 32768 --set-mark 2

   # now use tc filter to find that mark we made and assign it to a queue/class
   # this filter must be compared after the ones for 10:3 since it seems to clash
   # to force this compare, then we increase the priority number (less priority)
   my_prio=35
   tc filter add dev ${DEV} protocol ip parent 10:0 prio $my_prio handle 2 fw flowid 10:2
fi






#========================================================================================
#=================================== BAND 10:3 ==========================================
#========================================================================================
#-------------------------- VERY IMPORTANT NON-VOIP FILTERS -----------------------------
#--------------------------          RATE LIMITED           -----------------------------


my_prio=30
set_netmasks_port_filters "10:3" "10:" $my_prio "$IMPORTANT_RATE_LIMITED_SRC_NETMASKS" "$IMPORTANT_RATE_LIMITED_DST_NETMASKS" "$IMPORTANT_RATE_LIMITED_LOCAL_SRC_PORTS" "$IMPORTANT_RATE_LIMITED_REMOTE_DEST_PORTS"



#========================================================================================
#=================================== BAND 10:4 ==========================================
#========================================================================================
#-------------------------------- NON-VOIP FILTERS --------------------------------------



# Grab all remaining traffic for this band
my_prio=40
tc filter add dev ${DEV} parent 10:0 prio $my_prio protocol ip u32 \
    match ip src 0.0.0.0/0 \
    flowid 10:4


#--------------------------- NORMAL HIGH PRIORITY FILTERS -------------------------------
# TOS Minimum Delay (ssh, NOT scp) 
my_prio=50
tc filter add dev ${DEV} parent 400: protocol ip prio $my_prio u32 \
    match ip tos 0x10 0xff \
    flowid 400:10

# ICMP (ip protocol 1) in the interactive class
tc filter add dev ${DEV} parent 400: protocol ip prio $my_prio u32 \
        match ip protocol 1 0xff flowid 400:10

# To speed up downloads while an upload is going on, put ACK packets in
# the interactive class:
tc filter add dev ${DEV} parent 400: protocol ip prio $my_prio u32 \
   match ip protocol 6 0xff \
   match u8 0x05 0x0f at 0 \
   match u16 0x0000 0xffc0 at 2 \
   match u8 0x10 0xff at 33 \
   flowid 400:10

set_netmasks_port_filters "400:10" "400:" $my_prio "$STD_HIGH_PRIORITY_SRC_NETMASKS" "$STD_HIGH_PRIORITY_DST_NETMASKS" "$STD_HIGH_PRIORITY_LOCAL_SRC_PORTS" "$STD_HIGH_PRIORITY_REMOTE_DEST_PORTS"

#--------------------------- NORMAL MID PRIORITY FILTERS -------------------------------
# All UDP traffic - more specific stuff gets caught by port definitions above 
#tc filter add dev $DEV parent 400: protocol ip prio $my_prio u32 \
#   match ip protocol 17 0xff flowid 400:20

my_prio=60
set_netmasks_port_filters "400:20"  "400:" $my_prio "$STD_MID_PRIORITY_SRC_NETMASKS" "$STD_MID_PRIORITY_DST_NETMASKS" "$STD_MID_PRIORITY_LOCAL_SRC_PORTS" "$STD_MID_PRIORITY_REMOTE_DEST_PORTS"


#--------------------------- NORMAL STANDARD PRIORITY FILTERS -------------------------------
# TCP port 80 and 443 except for GUESTS
my_prio=80
set_netmasks_port_filters "400:30"  "400:" $my_prio "$STD_NORMAL_PRIORITY_SRC_NETMASKS" "$STD_NORMAL_PRIORITY_DST_NETMASKS" "$STD_NORMAL_PRIORITY_LOCAL_SRC_PORTS" "$STD_NORMAL_PRIORITY_REMOTE_DEST_PORTS"



#------------------------------------ GUEST FILTERS --------------------------------------
# !!! IMPORTANT NOTE:
# !!! The GUEST queue is defined as 400:40 which after 400:30
# !!! We define the filter with a lower priority number (more important) than 400:30 because we want to pick the HTTP traffic 
# !!! from GUESTS out off the normal HTTP traffic BEFORE it gets collected by the standard HTTP filter in 400:30


# this traffic must be marked in the squid configuration using the syntax "tcp_outgoing_tos 0x18 GUESTS"
# where GUESTS is a squid acl which defines the GUEST IP address ranges
my_prio=70
tc filter add dev ${DEV} parent 400: protocol ip prio $my_prio u32 \
    match ip tos 0x18 0xff \
    flowid 400:40


#---------------------------- DEFAULT PRIORITY FILTERS ---------------------------------
# rest is 'non-interactive' ie 'bulk' and ends up in 1:30
my_prio=100
tc filter add dev ${DEV} parent 400: protocol ip prio $my_prio u32 \
    match ip src 0.0.0.0/0 \
    flowid 400:50  



#========================================================================================
#=================================== DOWNLINK ===========================================
#========================================================================================
# slow downloads down to somewhat less than the real speed  to prevent 
# queuing at our ISP. Tune to see how high you can set it.
# ISPs tend to have *huge* queues to make sure big downloads are fast
#
# attach ingress policer:

tc qdisc add dev $DEV 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 parent ffff: protocol ip prio $DEFAULT_PRIO u32 match ip src \
   0.0.0.0/0 police rate ${DOWNLINK}kbit burst 10k drop flowid :1

}


#----------------------------------------------------------------------------------------
set_netmasks_port_filters () {
QUEUE=$1                   # eg 400:30
PARENT=$2                  # eg 400:
PRIO=$3                    # The priority of the filter, lower is higher priority
SRC_NETMASKS=$4            # space delimited list of netmasks
DST_NETMASKS=$5
LOCAL_SRC_PORTS=$6               # space delimited list of IP port numbers
REMOTE_DEST_PORTS=$7

my_prio=$PRIO

#### SET THE FILTERS FOR SOURCE NETMASKS
for a in $SRC_NETMASKS
do
 	tc filter add dev $DEV parent $PARENT protocol ip prio $my_prio u32 \
	   match ip src $a flowid $QUEUE
done

#### SET THE FILTERS FOR DESTINATION NETMASKS
for a in $DST_NETMASKS
do
 	tc filter add dev $DEV parent $PARENT protocol ip prio $my_prio u32 \
	   match ip dst $a flowid $QUEUE
done

#### SET THE FILTERS FOR SOURCE PORTS
for a in $LOCAL_SRC_PORTS
do
 	tc filter add dev $DEV parent $PARENT protocol ip prio $my_prio u32 \
	   match ip sport $a 0xffff flowid $QUEUE
done

#### SET THE FILTERS FOR DESTINATION PORTS
for a in $REMOTE_DEST_PORTS
do
	tc filter add dev $DEV parent $PARENT protocol ip prio $my_prio u32 \
	   match ip dport $a 0xffff flowid $QUEUE
done

}
#----------------------------------------------------------------------------------------
stop () {
# clean existing down and uplink qdiscs, hide errors
tc qdisc del dev $DEV root       2> /dev/null > /dev/null
tc qdisc del dev $DEV ingress    2> /dev/null > /dev/null

}
#----------------------------------------------------------------------------------------
show () {
echo "===== tc qdisc show ====="
tc qdisc show 
echo "===== tc class show dev $DEV ====="
tc class show dev $DEV
echo "===== tc filter show dev $DEV ====="
tc filter show dev $DEV
echo "===== tc -s qdisc ls dev $DEV ====="
tc -s qdisc ls dev $DEV
echo "===== tc -s class ls dev $DEV ====="
tc -s class ls dev $DEV
}
#----------------------------------------------------------------------------------------
ctrl_c () {
# ctrl C pressed
# if it is done twice, quickly then exit, else just clear the screen
press_time=$SECONDS

action="clear"

if [ "$last_press_time" ]; then
   # we have a last press time
   # see how long ago it was
   if [ `expr $last_press_time - $press_time` -le 2 ]; then
      echo "Exiting ..."
      action="ls"
   fi
fi

$action

last_press_time=$press_time
}
#----------------------------------------------------------------------------------------
show_queues () {
tc -s qdisc ls dev $DEV
}
#----------------------------------------------------------------------------------------
show_raw_queue_data () {
# set a default delay if not defined in $1
delay=${1:-1}

if [ "$delay" = "0" ]; then
   show_queues
   return
fi

# clear the screen to start with
clear

# nice idea but for some reason CTRL-C triggers the trap and still exits?
# trap "ctrl_c" 2 

while true
do
   echo -e "\033[H"
   tc -s qdisc ls dev $DEV
   sleep $delay
done
}
#----------------------------------------------------------------------------------------
show_queue_data () {
# set a default delay if not defined in $1
delay=${1:-1}

if [ "$delay" = "0" ]; then
   show_queues
   return
fi

# clear the screen to start with
clear

# nice idea but for some reason CTRL-C triggers the trap and still exits?
# trap "ctrl_c" 2 
set +x 

while true
do
   tc -s qdisc ls dev $DEV
   sleep $delay
done | awk 'BEGIN {
   # define the last qdisc in the list so we know we are at the end
   last_qdisc="tbf 1:"
   
   # define the first qdisc in the list so we know that we are at the start
   first_qdisc="ingress ffff:"
   
   
   # list the real queues that we are interested in and give them a nice name
   # it is only these ones we use to calculate percentages
   qdisc_nice_name["sfq 4005:"]="Default"
   qdisc_nice_name["sfq 4004:"]="Guests"
   qdisc_nice_name["sfq 4003:"]="Std HTTP"
   qdisc_nice_name["sfq 4002:"]="Std Mid"
   qdisc_nice_name["sfq 4001:"]="Std High"
   qdisc_nice_name["tbf 300:"]="Important Rate Limited"
   qdisc_nice_name["sfq 200:"]="Important"
   qdisc_nice_name["pfifo 100:"]="VOIP"
   
   display_format="%15s %15s %-10s %11s %-10s %-20s\n"
   
}{
if ($1=="qdisc") {
   qdisc=$2 " " $3
   
   # see if we are at the start of the list
   if (qdisc==first_qdisc) {
      # we want to clear the screen
      # send the escape sequence to jump to the home position
      printf("\033[H")
      printf(display_format,"Queue Name","Total Bytes","(Percent)","Delta Bytes","(Percent)","Description")
      printf(display_format,"----------","-----------","---------","-----------","---------","-----------")
      total_change=0
      total_total=0
      counter=0 # for counting the qdiscs we find
   }
} else if ($1=="Sent") {
   sent=$2
   
   # store the name of this qdisc in the list - this is to help maintain the order
   qdisc_list[counter]=qdisc
   
   # we have existing data for this qdisc - so show the difference
   qdisc_diff[qdisc]=sent - data[qdisc]
   data[qdisc]=sent
   
   # clear out any percentages stored
   qdisc_percent_change[qdisc]=""
   qdisc_percent_total[qdisc]=""
   
   if (qdisc_nice_name[qdisc]) {
      # this qdisc should be included in the list for percentages
      total_change=total_change+qdisc_diff[qdisc]
      total_total=total_total+data[qdisc]
   }   
   
   if (qdisc==last_qdisc && have_data) {
      # we now want to show the queue data
      # we have to loop through qdisc_list to show it in order
      for (i=0;i<=counter;i++) {
         qdisc_name=qdisc_list[i]
         if (total_change>0 && qdisc_nice_name[qdisc_name]) {
            # this qdisc should be included in the list for percentages
            qdisc_percent_change[qdisc_name]="(" int(qdisc_diff[qdisc_name]/total_change*1000)/10 "%)"
         }
         if (total_total>0 && qdisc_nice_name[qdisc_name]) {
            # this qdisc should be included in the list for total
            qdisc_percent_total[qdisc_name]="(" int(data[qdisc_name]/total_total*1000)/10 "%)"
         }
         printf(display_format,qdisc_name,data[qdisc_name],qdisc_percent_total[qdisc_name],qdisc_diff[qdisc_name],qdisc_percent_change[qdisc_name],qdisc_nice_name[qdisc_name])
      }
   } else if (qdisc==last_qdisc) {
      print "Collecting initial data"
      have_data=1
   }
   
   
   counter++
   
}
}'
}
#----------------------------------------------------------------------------------------

#======================================== MAIN ==========================================

case "$1" in
  start)
      echo -n "Enabling Traffic Control on $DEV to speed $UPLINK($OVERALL_MAX_RATE,$STD_TRAFFIC_MAX_RATE)/$DOWNLINK kbit/s: "
	   # start does a stop before it runs
	   start
	   RETVAL=0
      if [ "$fedora" ]; then
         [ $RETVAL -eq 0 ] && success || failure
      fi
      echo
	   ;;
  stop) 
      echo -n "Disabling Traffic Control on $DEV: "
	   stop
	   RETVAL=0
      if [ "$fedora" ]; then
         [ $RETVAL -eq 0 ] && success || failure
      fi
      echo
	   ;;
  restart|reload)
      # only need to do a start since start does a stop before it runs
      $0 start
      ;;
  status)
      show
      ;;
  monitorraw)
      show_raw_queue_data $2
      ;;
  monitor|mon)
      show_queue_data $2
      ;;
  *)
	   echo "Usage: $0 {start|stop|restart|status|(monitor [delay])|(monitorraw [delay])}"
	   exit 1
esac

exit $RETVAL
