Limit/Throttle per user OpenVPN bandwidth using TC
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.
Related videos on Youtube
user1167223
Updated on September 18, 2022Comments
-
user1167223 over 1 year
I have a group of users connecting to my server via OpenVPN TCP and UDP (2 services). The two services are operating on
tun0
andtun1
I'd like to be able to limit each user's bandwidth to say 5mb/s up and 5mb/s down using the TC command.
This was fairly easy to implement with PPTP as each user got their own interface, so I could just create a new class/filter for that interface limiting it to my desired speed limit using something like this:
IF=<taken from up script, i.e. ppp1> tc qdisc del dev $IF root tc qdisc add dev $IF root handle 1: cbq avpkt 1000 bandwidth 100mbit tc class add dev $IF parent 1: classid 1:1 cbq rate 10mbit allot 1500 prio 5 bounded isolated tc filter add dev $IF parent 1: protocol ip prio 16 u32 match ip src 0.0.0.0/0 flowid 1:1 tc qdisc add dev $IF parent 1:1 sfq perturb 10
As far as I can tell with OpenVPN users do not get their own interface, all traffic goes through the main
tun0
andtun1
interfaces.So I have 2 problems here.
1) The script above does not seem to work with OpenVPN for some reason (setting the interface name to
tun0
ortun1
) my test user can still download at their internet's max speed.2) I need to be able to filter this per source IP and add it in OpenVPN's
up
script when they connect while maintaining the other filter/classes and remove that filter/class in thedown
script, again without affecting the other connected user's limits (i.e. I can't simply delete the qdisc for tun0 each time a user connects).The only help I can find when searching is
"you can use TC for that"
but no explanation of how...
thanks!
-
poige almost 9 yearsDon't use CBQ. Use HTB. lartc.org/howto/lartc.qdisc.classful.html#AEN1071
-
-
user1167223 almost 9 yearsthis is exactly what I was looking for. thank you very much!
-
Tony-Caffe about 8 yearsWhats a good simple way to test that it is throttling the connection to the desired speed?
-
hatted over 5 yearsWhat if people remove those 2 lines from the .ovpn script? They will have full speed, right?
-
Oliver over 5 years@hatted the two lines are in the server configuration, not in some user's .ovpn script. If the server admin removes them, obviously the tc stuff won't be applied.