Thursday, May 26, 2011

Bandwidth Shaping with Linux

On the router, the device, br0 is the bridged device that is in the internet side of things. We will use that device for shaping our internet traffic in and out of the router. I am using my home internet connection, which is Cox cable internet with 12Mbps download and 2Mbps upload or 12288 kilobits per second and 2048 kilobits per second, respectively.
First, lets setup some bash variables to point to the various commands we will use throughout the script. We will also set variables to indicate our maximum bandwidth in both directions.

TC="/usr/sbin/tc"
IPT="/usr/sbin/iptables"
IP="/usr/sbin/ip"
INSMOD="/sbin/insmod"
RMMOD="/sbin/rmmod"
DNLD_BANDWIDTH="12288kbit"
UPLD_BANDWIDTH="2048kbit"
Next, lets setup the download shaping by adding a queuing discipline (qdisc) to device br0 and tell the qdisc the maximum throughput.
TCA="${TC} class add dev br0"
TFA="${TC} filter add dev br0"
TQA="${TC} qdisc add dev br0"
${TQA} root handle 1: htb
${TCA} parent 1: classid 1:1 htb rate ${DNLD_BANDWIDTH}
Next, we are going to setup 2 classes. Each class will be setup with a guaranteed bandwidth and a maximum bandwidth if the other class is underutilized. The first class will have a guaranteed download bandwidth of 8Mbps. The second class will have a guaranteed download bandwidth of 4Mbps. Both classes will be able to utilize up to 12Mbps depending on how active the other class is.
{TCA} parent 1:1 classid 1:10 htb rate 8192kbit ceil ${DNLD_BANDWIDTH} prio 1
{TCA} parent 1:1 classid 1:11 htb rate 4096kbit ceil ${DNLD_BANDWIDTH} prio 2
{TQA} parent 1:10 handle 10: sfq perturb 10
{TQA} parent 1:11 handle 11: sfq perturb 10
Now we need to setup a filter for each class. This is ensure only packets marked with the filter’s tag will be restricted. The mark is denoted by the ip handle. Here we will use 10 and 11, to match the classids of the classes we just defined.
${TFA} parent 1:0 prio 2 protocol ip handle 10 fw flowid 1:10
${TFA} parent 1:0 prio 2 protocol ip handle 11 fw flowid 1:11
Now to setup the upload shaping, we have to create a imq device.
${INSMOD} imq
${INSMOD} ipt_IMQ
${IP} link set imq0 up
Next, we need to setup a qdisc, classes, and filters for our 2 classes. Just like we did for download, only this time using the imq0 device and the upload bandwidths. The first class will have a guaranteed upload bandwidth of ~1.3Mbps and the second class will have a guaranteed upload bandwidth of 700Kbps.
TCAU="${TC} class add dev imq0"
TFAU="${TC} filter add dev imq0"
TQAU="${TC} qdisc add dev imq0"
${TQAU} root handle 1: htb
${TCAU} parent 1: classid 1:1 htb rate ${UPLD_BANDWIDTH}
${TCAU} parent 1:1 classid 1:10 htb rate 1348kbit ceil ${UPLD_BANDWIDTH} prio 1
${TCAU} parent 1:1 classid 1:11 htb rate 700kbit ceil ${UPLD_BANDWIDTH} prio 2
${TQAU} parent 1:10 handle 10: sfq perturb 10
${TQAU} parent 1:11 handle 11: sfq perturb 10
${TFAU} parent 1:0 prio 2 protocol ip handle 10 fw flowid 1:10
${TFAU} parent 1:0 prio 2 protocol ip handle 11 fw flowid 1:11
Finally, now that all our shaping is setup, we need to use iptables to mark packets to indicate which packets to shape. My internal network is 192.168.1.0/24. So we only mark packets headed for the internet and leave internal traffic alone. The first class will be restricted to a single IP address. The second class will be restrict to a group of IPs using CIDR notation (32 DHCP addresses from the router).
# download
${IPT} -t mangle -A POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.2 -j MARK --set-mark 10
${IPT} -t mangle -A POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.128/27 -j MARK --set-mark 11
# upload
${IPT} -t mangle -A PREROUTING -j IMQ --todev 0
${IPT} -t mangle -A PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.2 -j MARK --set-mark 10
${IPT} -t mangle -A PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.128/27 -j MARK --set-mark 11
Note: I would really like to use iptables range of address notation (e.g. -m iprange –src-range 192.168.1.3-192.168.1.254), but current builds of DD-WRT do not seem to work with that notation. I posted to the DD-WRT forums and I’ll I got was IP ranges are not supported. So for my own purposes, I just have giant script with many iptables commands, one for each IP address I want to shape.
To make it easy to start, stop, restart, I wrote an init.d style script for the above. I use it as part of the DD-WRT custom firewall script.

#!/bin/bash
TC="/usr/sbin/tc"
IPT="/usr/sbin/iptables"
IP="/usr/sbin/ip"
INSMOD="/sbin/insmod"
RMMOD="/sbin/rmmod"
DNLD_BANDWIDTH="12228kbit"
UPLD_BANDWIDTH="2048kbit"
DNLD_CLASS10_BANDWIDTH="8192kbit"
DNLD_CLASS11_BANDWIDTH="4096kbit"
UPLD_CLASS10_BANDWIDTH="1348kbit"
UPLD_CLASS11_BANDWIDTH="700kbit"
[ -x ${TC} ] || exit 1
[ -x ${IPT} ] || exit 1
[ -x ${IP} ] || exit 1
[ -x ${INSMOD} ] || exit 1
[ -x ${RMMOD} ] || exit 1
case "${1}" in
    start)
        echo -n "Starting bandwidth shaping service ... "
        # download
        TCA="${TC} class add dev br0"
        TFA="${TC} filter add dev br0"
        TQA="${TC} qdisc add dev br0"
        ${TQA} root handle 1: htb
        ${TCA} parent 1: classid 1:1 htb rate ${DNLD_BANDWIDTH}
        ${TCA} parent 1:1 classid 1:10 htb rate ${DNLD_CLASS10_BANDWIDTH} ceil ${DNLD_BANDWIDTH} prio 1
        ${TCA} parent 1:1 classid 1:11 htb rate ${DNLD_CLASS11_BANDWIDTH} ceil ${DNLD_BANDWIDTH} prio 2
        ${TQA} parent 1:10 handle 10: sfq perturb 10
        ${TQA} parent 1:11 handle 11: sfq perturb 10
        ${TFA} parent 1:0 prio 2 protocol ip handle 10 fw flowid 1:10
        ${TFA} parent 1:0 prio 2 protocol ip handle 11 fw flowid 1:11
        # upload
        ${INSMOD} imq
        ${INSMOD} ipt_IMQ
        ${IP} link set imq0 up
        TCAU="${TC} class add dev imq0"
        TFAU="${TC} filter add dev imq0"
        TQAU="${TC} qdisc add dev imq0"
        ${TQAU} root handle 1: htb
        ${TCAU} parent 1: classid 1:1 htb rate ${UPLD_BANDWIDTH}
        ${TCAU} parent 1:1 classid 1:10 htb rate ${UPLD_CLASS10_BANDWIDTH} ceil ${UPLD_BANDWIDTH} prio 1
        ${TCAU} parent 1:1 classid 1:11 htb rate ${UPLD_CLASS11_BANDWIDTH} ceil ${UPLD_BANDWIDTH} prio 2
        ${TQAU} parent 1:10 handle 10: sfq perturb 10
        ${TQAU} parent 1:11 handle 11: sfq perturb 10
        ${TFAU} parent 1:0 prio 2 protocol ip handle 10 fw flowid 1:10
        ${TFAU} parent 1:0 prio 2 protocol ip handle 11 fw flowid 1:11
        # download
        ${IPT} -t mangle -A POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.2 -j MARK --set-mark 10
        ${IPT} -t mangle -A POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.128/27 -j MARK --set-mark 11
        # upload
        ${IPT} -t mangle -A PREROUTING -j IMQ --todev 0
        ${IPT} -t mangle -A PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.2 -j MARK --set-mark 10
        ${IPT} -t mangle -A PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.128/27 -j MARK --set-mark 11
        echo "done"
    ;;
    stop)
        echo -n "Stopping bandwidth shaping service ... "
        # download
        ${IPT} -t mangle -D POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.2 -j MARK --set-mark 10
        ${IPT} -t mangle -D POSTROUTING -s ! 192.168.1.0/24 -d 192.168.1.128/27 -j MARK --set-mark 11
        # upload
        ${IPT} -t mangle -D PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.2 -j MARK --set-mark 10
        ${IPT} -t mangle -D PREROUTING -d ! 192.168.1.0/24 -s 192.168.1.128/27 -j MARK --set-mark 11
        ${IPT} -t mangle -D PREROUTING -j IMQ --todev 0
        ${TC} qdisc del dev br0 root
        ${TC} qdisc del dev imq0 root
        ${IP} link set imq0 down
        ${RMMOD} ipt_IMQ
        ${RMMOD} imq
        echo "done"
    ;;
    restart)
        ${0} stop
        sleep 1
        ${0} start
    ;;
    status)
        ${TC} qdisc show dev br0
        ${TC} class show dev br0
        ${TC} filter show dev br0
        ${TC} qdisc show dev imq0
        ${TC} class show dev imq0
        ${TC} filter show dev imq0
    ;;
    *)
        echo "Usage: ${0} {start|stop|restart|status}"
        exit 1
    ;;
esac
exit 0
http://roback.cc/wp/2009/09/bandwidth-shaping-with-linux/#Introduction