by Ivan Skytte Jørgensen, April 2013
You may have read about my non-trivial network setup. If not, then do so now.
Some months ago I noticed that some of my neighbours were using my access point (which is fine), but they used it quite heavily - around 150GB/month. That was a bit too close to the fair-use limit I have on my cable connection.
I considered re-activating the shaping rules, but then I would have to identify the MAC address of them etc. Instead I chose to implement fair-use on the access point.
First I had to get the byte counters for the users. Because I don't know the users I could use the MAC address to identify them, but since most of them are not technically adept to get a new IP-address from my DHCP server the IP-address is enough to identify the user (and much easier).
The easiest way to count bytes is to simply use iptables to do it. I modified my firewall script to include this:
#per-user counters iptables -N vlan61_output ip=0 while [ $ip -le 255 ]; do iptables -A vlan61_output -o vlan61 -d 10.0.61.$ip ip=$[ $ip + 1 ] done iptables -A FORWARD -j vlan61_output iptables -N vlan61_input ip=0 while [ $ip -le 255 ]; do iptables -A vlan61_input -i vlan61 -s 10.0.61.$ip ip=$[ $ip + 1 ] done iptables -A FORWARD -j vlan61_input
I then made a script, record_vlan61_usage.sh, which parsed they output of iptables -vn -L vlan61_output:
Chain vlan61_output (1 references) pkts bytes target prot opt in out source destination ... 0 0 all -- * vlan61 0.0.0.0/0 10.0.61.239 9958 6240K all -- * vlan61 0.0.0.0/0 10.0.61.240 899K 1143M all -- * vlan61 0.0.0.0/0 10.0.61.241 363K 248M all -- * vlan61 0.0.0.0/0 10.0.61.242 0 0 all -- * vlan61 0.0.0.0/0 10.0.61.243 ...
and puts the input/output byte counters into a file in /var/lib/fair_use:
... 2013-04-10 0 2013-04-11 0 2013-04-12 90177536 2013-04-13 1288192 2013-04-14 233472 2013-04-15 32505856 2013-04-16 0 2013-04-17 4184064 2013-04-18 13631488
The script is run just after midnight every day.
I then made a script, check_fair_use.sh, which iterates over the IP-addresses counting how much has been transferred in the last month and week. Then it determines the tier/class of the user:
#determine bandwidth class from usage #input: ip (10.0.61.xxx) #output: tier1/tier2/tier3/tier4 determine_ip_tier() { IP="$1" month_bytes=`count_total_octets $IP 30` if [ $month_bytes -ne 0 ]; then week_bytes=`count_total_octets $IP 7` else week_bytes=0 fi if [ "$month_bytes" -eq 0 ]; then echo "tier1" return fi if [ "$month_bytes" -lt 20000000000 ]; then #tier 1 unless >=7GB this week if [ "$week_bytes" -le 7000000000 ]; then echo "tier1" else echo "tier2" fi elif [ "$month_bytes" -lt 40000000000 ]; then echo "tier2" elif [ "$month_bytes" -lt 50000000000 ]; then echo "tier3" elif [ "$month_bytes" -lt 60000000000 ]; then echo "tier4" elif [ "$month_bytes" -lt 80000000000 ]; then echo "tier5" else echo "tier6" fi }
The tier is then mapped to bandwidth
process_ip() { IP="$1" tier=`determine_ip_tier $IP` case "$tier" in tier1) #full speed output_speed=100mbit input_speed=100mbit ;; tier2) #10Mbps/1Mbps output_speed=10mbit input_speed=1mbit ;; tier3) #5Mbps/512Kbps output_speed=5mbit input_speed=512kbit ;; tier4) #5Mbps/512Kbps output_speed=3mbit input_speed=384kbit ;; tier5) #2MBps/128Kbps output_speed=2mbit input_speed=256kbit ;; tier6) #1MBps/128Kbps output_speed=1mbit input_speed=128kbit ;; *) return esac echo "$output_speed" }
The output (speed) is then used to generate a tc script:
generate_tc_script() { echo "tc qdisc del dev vlan61 root handle 1:0" echo "tc qdisc add dev vlan61 root handle 1:0 htb default 500" echo "tc class add dev vlan61 parent 1:0 classid 1:1 htb rate 100mbit burst 500k cburst 500k" echo " tc class add dev vlan61 parent 1:1 classid 1:500 htb rate 384kbit ceil 100mbit burst 500k cburst 500k" ip4=$IP_START while [ $ip4 -le 254 ]; do output_speed=`process_ip "10.0.61.$ip4"` echo " tc class add dev vlan61 parent 1:1 classid 1:$ip4 htb rate 384kbit ceil $output_speed burst 500k cburst 500k" ip4=$[ $ip4 + 1 ] done ip4=$IP_START while [ $ip4 -le 254 ]; do echo "tc filter add dev vlan61 protocol ip parent 1:0 prio 1 u32 match ip dst 10.0.61.$ip4 classid 1:$ip4" ip4=$[ $ip4 + 1 ] done } generate_tc_script > /tmp/tc_script || exit chmod +x /tmp/tc_script /tmp/tc_script
The resulting script looks something like this:
tc qdisc del dev vlan61 root handle 1:0 tc qdisc add dev vlan61 root handle 1:0 htb default 500 tc class add dev vlan61 parent 1:0 classid 1:1 htb rate 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:500 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:2 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:3 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:4 htb rate 384kbit ceil 100mbit burst 500k cburst 500k ... tc class add dev vlan61 parent 1:1 classid 1:190 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:191 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:192 htb rate 384kbit ceil 10mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:193 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:194 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:195 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:196 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:197 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:198 htb rate 384kbit ceil 1mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:199 htb rate 384kbit ceil 100mbit burst 500k cburst 500k tc class add dev vlan61 parent 1:1 classid 1:200 htb rate 384kbit ceil 100mbit burst 500k cburst 500k ...
Never forget to clean up potentially ever-growing files. I purge records older than 2 months in a script:
#!/bin/bash #purges records older than 2 months DATA_DIR=/var/lib/fair_use DAYS_TO_KEEP=62 for file in $DATA_DIR/*.{input,output}; do lines=`wc -l <$file` if [ "$lines" -gt $DAYS_TO_KEEP ]; then tmpfile=/tmp/$$.tmp tail -$DAYS_TO_KEEP <$file >$tmpfile && cp $tmpfile $file rm $tmpfile fi done
This script is run from crontab once a month.
It is possible to implement (simple) fair-use using iptables and traffic-control. It is vulnerable to IP-address change, it doesn't really identify the user, only download speed is throttled, it doesn't handle when one of my two upstream links dies for extended period. But it suits my needs so far.
Note: My former employer develops, among other things, fair-use solutions for mobile operators and ISPs. The above scripts have nothing to do with what they sell. First of all, they don't use iptables/tc to do this. They integrate to probes from Procera, Ipoque, Allot, Cisco, Sandvine... using 3GPP Gx/Gy signalling or proprietary protocols. Secondly, they have much more advanced and flexible configuration of users/class/tiers/shaping/protools/"packages"/... than the scripts above provide.