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!

up vote 8 down vote accepted
+100

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.

  • 1
    this is exactly what I was looking for. thank you very much! – user1167223 Jun 30 '15 at 8:39
  • Whats a good simple way to test that it is throttling the connection to the desired speed? – Tony-Caffe Mar 16 '16 at 16:21
  • What if people remove those 2 lines from the .ovpn script? They will have full speed, right? – hatted Nov 22 at 4:45
  • @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. – Oliver Nov 22 at 7:27

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.