Limit/Throttle per user OpenVPN bandwidth using TC
Solution 1:
I once did something like this for individually firewalling each user's connection. I have implemented it using the learn-address
script in OpenVPN which is called when a user connects or disconnects. I have adapted it for your use case.
The script looks as follows:
#!/bin/bash
statedir=/tmp/
function bwlimit-enable() {
ip=$1
user=$2
# Disable if already enabled.
bwlimit-disable $ip
# Find unique classid.
if [ -f $statedir/$ip.classid ]; then
# Reuse this IP's classid
classid=`cat $statedir/$ip.classid`
else
if [ -f $statedir/last_classid ]; then
classid=`cat $statedir/last_classid`
classid=$((classid+1))
else
classid=1
fi
echo $classid > $statedir/last_classid
fi
# Find this user's bandwidth limit
# downrate: from VPN server to the client
# uprate: from client to the VPN server
if [ "$user" == "myuser" ]; then
downrate=10mbit
uprate=10mbit
elif [ "$user" == "anotheruser"]; then
downrate=2mbit
uprate=2mbit
else
downrate=5mbit
uprate=5mbit
fi
# Limit traffic from VPN server to client
tc class add dev $dev parent 1: classid 1:$classid htb rate $downrate
tc filter add dev $dev protocol all parent 1:0 prio 1 u32 match ip dst $ip/32 flowid 1:$classid
# Limit traffic from client to VPN server
tc filter add dev $dev parent ffff: protocol all prio 1 u32 match ip src $ip/32 police rate $uprate burst 80k drop flowid :$classid
# Store classid and dev for further use.
echo $classid > $statedir/$ip.classid
echo $dev > $statedir/$ip.dev
}
function bwlimit-disable() {
ip=$1
if [ ! -f $statedir/$ip.classid ]; then
return
fi
if [ ! -f $statedir/$ip.dev ]; then
return
fi
classid=`cat $statedir/$ip.classid`
dev=`cat $statedir/$ip.dev`
tc filter del dev $dev protocol all parent 1:0 prio 1 u32 match ip dst $ip/32
tc class del dev $dev classid 1:$classid
tc filter del dev $dev parent ffff: protocol all prio 1 u32 match ip src $ip/32
# Remove .dev but keep .classid so it can be reused.
rm $statedir/$ip.dev
}
# Make sure queueing discipline is enabled.
tc qdisc add dev $dev root handle 1: htb 2>/dev/null || /bin/true
tc qdisc add dev $dev handle ffff: ingress 2>/dev/null || /bin/true
case "$1" in
add|update)
bwlimit-enable $2 $3
;;
delete)
bwlimit-disable $2
;;
*)
echo "$0: unknown operation [$1]" >&2
exit 1
;;
esac
exit 0
The script needs to be installed in your OpenVPN's server.conf as follows:
learn-address <path-to-script>
script-security 3
script-security 3
is necessary so OpenVPN actually calls the script.
When a user connects, the script is called as <path-to-script> add <ip> <username>
, furthermore the network interface is placed in the environment variable $dev
(e.g. tun0
).
The script configures the network interface for queueing discipline and attaches the necessary filters and classes to it. It keeps track of the IPs for which it installed filters and classes so it can later remove them if the user disconnects. That state is kept in the directory /tmp
, this should probably be changed.
Note that I'm not sure I have gotten the traffic control stuff 100% right. Download traffic shaping (i.e. from OpenVPN to the user) works fine, but limiting the upload isn't very precise, which is somewhat normal from what I have understood. Maybe you can find a better way and integrate it in the script.