Rate shaping

This example was given by Dave Stahr (dave<at>stewireless.com).

Goal

I'm running Fedora Core 3 with bridge-utils-0.9.6-2 and ebtables-v2.0.6 to rate shape bandwidth for our wireless subscribers, based on their MAC address. This bridge sits between our core wireless link and the switch connected to our servers and internet gateway. A perl script runs out of cron that connects to our mysql database to create the traffic queues automatically. I'm not including that here, but any programmer could figure out how to write their own suited to their own database, etc, or just write the config by hand for smaller networks. We're running on a little 400mhz PentiumII with 128mb RAM and it doesn't even sweat. We have around 500 customers, all rate shaped in their own individual queues. This could hypothetically support up to 20,000 individual traffic queues.

This is a very simple setup, but in trying to figure out how to do it, I found many unanswered posts on many web forums by people trying to do this.

Bridge configuration

This gives us the ability to log in to the bridge, as well as give the bridge the ability to connect out to our mysql database server, etc:

------------- ifcfg-br0 -------------
DEVICE=br0
ONBOOT=no
BOOTPROTO=static
IPADDR=192.168.111.11
NETMASK=255.255.255.0

----------- bridge_up.sh --------------------
#!/bin/bash

ifdown eth0
ifdown eth1

ifconfig eth0 0.0.0.0 up
ifconfig eth1 0.0.0.0 up

brctl addbr br0

brctl addif br0 eth0
brctl addif br0 eth1

ifconfig br0 up

----------- bridge_down.sh --------------------
#!/bin/bash
 
ifdown eth0
ifdown eth1
ifconfig br0 down
brctl delbr br0

The rate shaping part

We're using tc to do the deed. This is my first attempt at this, so I may be doing some things wrong, especially with the tc commands - BUT IT WORKS - so I figure, I'll fix it later. You can use ebtables -L --Lc to see your customer's usage. I dump this out hourly, adding the -Z option to zero the counters out, then have a perl script parse that output and dump it into a mysql table where I can make better use of it.

--------------------------- rateshape -----------------------
#!/bin/bash
#
#  All Rates are in Kbits, so in order to gets Bytes divide by 8
#  e.g. 25Kbps == 3.125KB/s
#
TC=/sbin/tc
EBTABLES=/sbin/ebtables   # Location of ebtables
 
cd /usr/local/bridge
 
tc_start() {
    $TC qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit avpkt 1000 mpu 64
    $TC qdisc add dev eth1 root handle 1:0 cbq bandwidth 100Mbit avpkt 1000 mpu 64
 
#Customer A
#Two MACs: 00:0D:BD:A4:E1:C8 and 00:20:78:B0:25:7D
#256kbps download speed
${TC} class add dev eth0 parent 1:0 classid 1:1 cbq rate 256KBit allot 1514 prio 1 avpkt 1000 bounded
${TC} filter add dev eth0 parent 1:0 protocol ip handle 1 fw flowid 1:1
${EBTABLES} -A FORWARD -d 00:0D:BD:A4:E1:C8 -j mark --set-mark 1 --mark-target ACCEPT
${EBTABLES} -A FORWARD -d 00:20:78:B0:25:7D -j mark --set-mark 1 --mark-target ACCEPT
#128kbps upload speed
${TC} class add dev eth1 parent 1:0 classid 1:1 cbq rate 128KBit allot 1514 prio 1 avpkt 1000 bounded
${TC} filter add dev eth1 parent 1:0 protocol ip handle 1 fw flowid 1:1
${EBTABLES} -A FORWARD -s 00:0D:BD:A4:E1:C8 -j mark --set-mark 1 --mark-target ACCEPT
${EBTABLES} -A FORWARD -s 00:20:78:B0:25:7D -j mark --set-mark 1 --mark-target ACCEPT

#Customer B
#MAC Address: 00:0D:BD:A4:D6:54
#800kbps download speed
${TC} class add dev eth0 parent 1:0 classid 1:2 cbq rate 800KBit allot 1514 prio 1 avpkt 1000 bounded
${TC} filter add dev eth0 parent 1:0 protocol ip handle 2 fw flowid 1:2
${EBTABLES} -A FORWARD -d 00:0D:BD:A4:D6:54 -j mark --set-mark 2 --mark-target ACCEPT
#64kbps upload speed
${TC} class add dev eth1 parent 1:0 classid 1:2 cbq rate 64KBit allot 1514 prio 1 avpkt 1000 bounded
${TC} filter add dev eth1 parent 1:0 protocol ip handle 2 fw flowid 1:2
${EBTABLES} -A FORWARD -s 00:0D:BD:A4:D6:54 -j mark --set-mark 2 --mark-target ACCEPT

#Customer C
#MAC Address: 00:0A:5E:22:D1:A3
#do not rate shape!
${EBTABLES} -A FORWARD -s 00:0A:5E:22:D1:A3 -j ACCEPT
${EBTABLES} -A FORWARD -d 00:0A:5E:22:D1:A3 -j ACCEPT

#Block anything we didn't specify above.
${EBTABLES} -A FORWARD -j DROP --log

#<my config has over 500 customers and over 1100 MAC addresses>
#Just keep incrementing the classid, handle, flowid, and mark values for each customer's
#individual speed queues.
}
 
tc_stop() {
 
    ./save_and_reset_counters
 
    ${EBTABLES} -F
 
    $TC qdisc del dev eth0 root
    $TC qdisc del dev eth1 root
}
 
tc_restart() {
 
    tc_stop
    sleep 1
    tc_start
 
}
 
tc_show() {
 
    echo ""
    echo "eth0"
    $TC qdisc show dev eth0
    $TC class show dev eth0
    $TC filter show dev eth0
    echo ""
    echo "eth1"
    $TC qdisc show dev eth1
    $TC class show dev eth1
}
 
tc_stop() {
 
    ./save_and_reset_counters
 
    ${EBTABLES} -F
 
    $TC qdisc del dev eth0 root
    $TC qdisc del dev eth1 root
}
 
tc_restart() {
 
    tc_stop
    sleep 1
    tc_start
 
}
 
tc_show() {
 
    echo ""
    echo "eth0"
    $TC qdisc show dev eth0
    $TC class show dev eth0
    $TC filter show dev eth0
    echo ""
    echo "eth1"
    $TC qdisc show dev eth1
    $TC class show dev eth1
    $TC filter show dev eth1
 
}
case "$1" in
 
  start)
 
    echo -n "Starting bandwidth shaping: "
    tc_start
    echo "done"
    ;;
 
  stop)
 
    echo -n "Stopping bandwidth shaping: "
    tc_stop
    echo "done"
    ;;
 
  restart)
 
    echo -n "Restarting bandwidth shaping: "
    tc_restart
    echo "done"
    ;;
 
  show)
 
    tc_show
    ;;
 
  *)
 
    echo "Usage: rateshape {start|stop|restart|show}"
    ;;
 
esac