#!/bin/sh
#
# SuperShaper-SOHO 1.2
#
# Bandwidth shaper for SOHO DSL connection.
#
# Copyright (C) Robin Smidsrød<robin@smidsrod.no> September 24 2004
# License details can be found on www.smidsrod.no
#
# Please consider donating if you find this script helpful.
# Contact information available on www.smidsrod.no.
#
# If you need help setting up this script, or have other
# problems related to Linux networking, I'm available
# for contracting. Contact me at robin@smidsrod.no or see
# contact information on www.smidsrod.no.
#
# This script is designed to shape your upstream bandwidth to
# minimize latency for interactive applications like SSH and making
# sure P2P applications doesn't saturate your upstream. Standard surfing/mail
# software is also given priority over P2P to make them snappy.
# VoIP is given maximum priority (SIP/RTP) (only surpassed by TCP ACKs) to make
# sure IP telephony doesn't suffer even on a very congested link.
#
# Ingress filtering is not applied at all, since it would have little
# effect on the final result. Let's stick to egress which works.
#
# This script needs iproute2 (tc) and HTB/SFQ netfilter schedulers (linux kernel >=2.4.20)
# This script has been verified to work out of the box on IPCop-1.3.0_fixes9,
# which is the preferred deployment platform.
#
# Please verify the ports for P2P software if they're non-standard.
#
# Queue classes are set up to handle this kind of traffic in prioritized order:
#
# 10: TCP/ACK
# 20: VoIP (sip tos 0x68/rtp tos 0xb8)
# 30: tos_minimum_delay 0x10 / tos_maximum_reliability 0x04
# 40: ICMP/DNS/Shoutcast/tos_minimum_cost 0x02/IMAP/SMTP/POP3/HTTP/FTP/Usenet/SSH/tos_maximum_throughput 0x08
# 50: P2P (BitTorrent/eMule/DirectConnect/Kazaa/Gnutella)
# 60: default / bulk traffic
#

# Change these values to reflect your own setup

# Your outbound interface
DEV=eth1

# Your upstream capacity in kbit
#UPLINK_REAL=704
UPLINK_REAL=551

# DSL modems usually have large queues. That breaks latency. Set this as high as your DSL
# modem can handle without queuing packets itself. This value is in percent.
# I usually saturate the upstream with traffic/uploads and use iptraf to measure the outbound traffic
# on the interface to determine when the shaping takes effect. If iptraf reports higher bandwidth than
# your calculated bandwidth (see UPLINK below) you modem is probably still queuing packets. This can
# only be set by trial an error, but 90% is probably a good ballpark number.
UPLINK_PERCENT=90

# Calculate actual max bandwidth
UPLINK=$[UPLINK_PERCENT*UPLINK_REAL/100]

# Set how much bandwidth to use for each class
UPLINK_10_R=$UPLINK 
UPLINK_20_R=$UPLINK
UPLINK_30_R=$UPLINK
UPLINK_40_R=$[9*UPLINK/10]
UPLINK_50_R=$[1*UPLINK/10]
UPLINK_60_R=$[5*UPLINK/10]

# The same as above, but here you set the ceiling, ie. how much a class is allowed to borrow from another
UPLINK_10_C=$UPLINK
UPLINK_20_C=$UPLINK
UPLINK_30_C=$UPLINK
UPLINK_40_C=$[9*UPLINK/10]
UPLINK_50_C=$[5*UPLINK/10]
UPLINK_60_C=$[8*UPLINK/10]

# DirectConnection port
# In newer versions of DC++ there is no default.
# Please set your port used in DC++ here.
PORT_DC=20000

# Additional BitTorrent port
# The standard BitTorrent port is already defined.
PORT_BT=50000

# Additional ED2K ports
# The standard ED2K and Kademelia ports are already defined.
PORT_ED2K_TCP=30000
PORT_ED2K_UDP=40000

# Set full path to TC command, unless it's in PATH
TC=tc

################### Nothing to change below this line unless you're adventurous ###############

# Remove existing qdisc
$TC qdisc del dev $DEV root 2>&1 >/dev/null

################### QUEUE DISCIPLINES

# Add root qdisc
$TC qdisc add dev $DEV root handle 1: htb default 60

# Add master qdisc
$TC class add dev $DEV parent 1: classid 1:1 htb rate ${UPLINK}kbit

# Add prio 0 queue (highest)
$TC class add dev $DEV parent 1:1 classid 1:10 htb rate ${UPLINK_10_R}kbit ceil ${UPLINK_10_C}kbit prio 0
$TC qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10

# Add prio 1 queue
$TC class add dev $DEV parent 1:1 classid 1:20 htb rate ${UPLINK_20_R}kbit ceil ${UPLINK_20_C}kbit prio 1
$TC qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10

# Add prio 2 queue
$TC class add dev $DEV parent 1:1 classid 1:30 htb rate ${UPLINK_30_R}kbit ceil ${UPLINK_30_C}kbit prio 2
$TC qdisc add dev $DEV parent 1:30 handle 30: sfq perturb 10

# Add prio 3 queue
$TC class add dev $DEV parent 1:1 classid 1:40 htb rate ${UPLINK_40_R}kbit ceil ${UPLINK_40_C}kbit prio 3
$TC qdisc add dev $DEV parent 1:40 handle 40: sfq perturb 10

# Add prio 4 queue (lowest)
$TC class add dev $DEV parent 1:1 classid 1:50 htb rate ${UPLINK_50_R}kbit ceil ${UPLINK_50_C}kbit prio 4
$TC qdisc add dev $DEV parent 1:50 handle 50: sfq perturb 10

# Add prio 5 queue (default queue)
$TC class add dev $DEV parent 1:1 classid 1:60 htb rate ${UPLINK_60_R}kbit ceil ${UPLINK_60_C}kbit prio 5
$TC qdisc add dev $DEV parent 1:60 handle 60: sfq perturb 10

################### FILTERS

# CLASS 10: TCP/ACK
$TC filter add dev $DEV protocol ip parent 1: prio 1 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 1:10

# CLASS 20: VoIP (prio 1) (SIP/skinny packets)
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x68 0xff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x58 0xff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x28 0xff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x38 0xff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport 5060 0xffff flowid 1:20

# CLASS 20: VideoConferencing (prio 1) (SquidCam)
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport 16967 0xffff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport 16968 0xffff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport 16969 0xffff flowid 1:20

# CLASS 20: VoIP (prio 2) (RTP data)
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip tos 0xb8 0xff flowid 1:20
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 16384 0xffff flowid 1:20

# CLASS 30: IP TOS 0x10 (prio 1) (minimum delay)
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0x10 0xff flowid 1:30

# CLASS 30: IP TOS 0x04 (prio 2) (maximum reliability)
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip tos 0x04 0xff flowid 1:30

# CLASS 30: World Of Warcraft (prio 3) (TCP/3724 and TCP/6112)
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip dport 3724 0xffff flowid 1:30
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip dport 6112 0xffff flowid 1:30

# CLASS 40: ICMP (prio 1)
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:40

# CLASS 40: DNS (prio 2)
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip dport 53 0xffff flowid 1:40

# CLASS 40: Shoutcast (prio 3)
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip dport 8000 0xffff flowid 1:40

# CLASS 40: IP TOS 0x02 (prio 4) (minimum cost)
$TC filter add dev $DEV parent 1: protocol ip prio 4 u32 match ip tos 0x02 0xff flowid 1:40

# CLASS 40: IMAP (prio 5) (with and without SSL)
$TC filter add dev $DEV parent 1: protocol ip prio 5 u32 match ip dport 143 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 5 u32 match ip dport 220 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 5 u32 match ip dport 993 0xffff flowid 1:40

# CLASS 40: SMTP (prio 6) (with and without SSL)
$TC filter add dev $DEV parent 1: protocol ip prio 6 u32 match ip dport 25 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 6 u32 match ip dport 465 0xffff flowid 1:40

# CLASS 40: POP (prio 7) (with and without SSL)
$TC filter add dev $DEV parent 1: protocol ip prio 7 u32 match ip dport 106 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 7 u32 match ip dport 109 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 7 u32 match ip dport 110 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 7 u32 match ip dport 995 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 7 u32 match ip dport 1109 0xffff flowid 1:40

# CLASS 40: HTTP (prio 8) (with and without SSL)
$TC filter add dev $DEV parent 1: protocol ip prio 8 u32 match ip dport 80 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 8 u32 match ip dport 443 0xffff flowid 1:40

# CLASS 40: FTP (prio 9) (with and without SSL)
$TC filter add dev $DEV parent 1: protocol ip prio 9 u32 match ip dport 20 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 9 u32 match ip dport 21 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 9 u32 match ip dport 115 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 9 u32 match ip dport 2431 0xffff flowid 1:40
$TC filter add dev $DEV parent 1: protocol ip prio 9 u32 match ip dport 2433 0xffff flowid 1:40

# CLASS 40: Usenet (prio 10)
$TC filter add dev $DEV parent 1: protocol ip prio 10 u32 match ip dport 119 0xffff flowid 1:40

# CLASS 40: SSH (prio 11) (without tos bit set, caters for buggy clients like PuTTY and ssh.com windows client)
$TC filter add dev $DEV parent 1: protocol ip prio 11 u32 match ip dport 22 0xffff mat ip tos 0x00 0xff flowid 1:40

# CLASS 40: IP TOS 0x08 (prio 12) (maximum throughput)
$TC filter add dev $DEV parent 1: protocol ip prio 12 u32 match ip tos 0x08 0xff flowid 1:40

# CLASS 40: HTTP outbound (prio 13)
$TC filter add dev $DEV parent 1: protocol ip prio 13 u32 match ip sport 80 0xffff flowid 1:40

# CLASS 50: BitTorrent (prio 1)
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport 6881 0xffff flowid 1:50
$TC filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip sport $PORT_BT 0xffff flowid 1:50

# CLASS 50: eMule (prio 2)
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 4661 0xffff flowid 1:50 # eMule TCP
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 4662 0xffff flowid 1:50 # eMule TCP
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 4663 0xffff flowid 1:50 # eMule TCP
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 4672 0xffff flowid 1:50 # eMule UDP
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 4711 0xffff flowid 1:50 # eMule Webserver
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport 5768 0xffff flowid 1:50 # Overnet
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport $PORT_ED2K_TCP 0xffff flowid 1:50 # eMule TCP - custom
$TC filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip sport $PORT_ED2K_UDP 0xffff flowid 1:50 # eMule UDP - custom

# CLASS 50: DirectConnect (prio 3)
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip dport 411 0xffff flowid 1:50 # Hub connection
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip sport 1412 0xffff flowid 1:50 # Default transfer/search port
$TC filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip sport $PORT_DC 0xffff flowid 1:50 # Custom transfer/search port

# CLASS 50: Kazaa (prio 4)
$TC filter add dev $DEV parent 1: protocol ip prio 4 u32 match ip sport 6699 0xffff flowid 1:50

# CLASS 50: Gnutella (prio 5)
$TC filter add dev $DEV parent 1: protocol ip prio 5 u32 match ip sport 6346 0xffff flowid 1:50

################### PRINT OUT SETTINGS

# Report settings
echo "************************* QDISC ******************************"
$TC qdisc show dev $DEV
echo "************************* CLASS ******************************"
$TC class show dev $DEV
# Disabled cause output is quite cryptic, anyone now a more readable way to report this?
#echo "************************* FILTER *****************************"
#$TC filter show dev $DEV
