Limit/Throttle per user OpenVPN bandwidth using TC

8,176

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.

Share:
8,176

Related videos on Youtube

user1167223
Author by

user1167223

Updated on September 18, 2022

Comments

  • user1167223
    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 and tun1

    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 and tun1 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 or tun1) 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 the down 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!

  • user1167223
    user1167223 almost 9 years
    this is exactly what I was looking for. thank you very much!
  • Tony-Caffe
    Tony-Caffe about 8 years
    Whats a good simple way to test that it is throttling the connection to the desired speed?
  • hatted
    hatted over 5 years
    What if people remove those 2 lines from the .ovpn script? They will have full speed, right?
  • Oliver
    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.