OpenVPN per client traffic control
To have a simple solution for traffic control on a per client basis, you could do something like the following. This solution only works for a /24
VPN subnet. Tested on Ubuntu 14.04.
OpenVPN server example configuration:
port 1194
proto udp
dev tun
topology subnet
server 10.8.0.0 255.255.255.0
keepalive 10 60
comp-lzo
persist-key
persist-tun
log /var/log/openvpn.log
verb 3
#user openvpn
#group nogroup
script-security 2
down-pre
up /etc/openvpn/tc.sh
down /etc/openvpn/tc.sh
client-connect /etc/openvpn/tc.sh
client-disconnect /etc/openvpn/tc.sh
Traffic control script /etc/openvpn/tc.sh
:
#!/bin/bash
TC=$(which tc)
interface="$dev"
interface_speed="100mbit"
client_ip="$trusted_ip"
client_ip_vpn="$ifconfig_pool_remote_ip"
download_limit="512kbit"
upload_limit="10mbit"
handle=`echo "$client_ip_vpn" | cut -d. -f4`
function start_tc {
tc qdisc show dev $interface | grep -q "qdisc pfifo_fast 0"
[ "$?" -gt "0" ] && tc qdisc del dev $interface root; sleep 1
$TC qdisc add dev $interface root handle 1: htb default 30
$TC class add dev $interface parent 1: classid 1:1 htb rate $interface_speed burst 15k
$TC class add dev $interface parent 1:1 classid 1:10 htb rate $download_limit burst 15k
$TC class add dev $interface parent 1:1 classid 1:20 htb rate $upload_limit burst 15k
$TC qdisc add dev $interface parent 1:10 handle 10: sfq perturb 10
$TC qdisc add dev $interface parent 1:20 handle 20: sfq perturb 10
}
function stop_tc {
tc qdisc show dev $interface | grep -q "qdisc pfifo_fast 0"
[ "$?" -gt "0" ] && tc qdisc del dev $interface root
}
function filter_add {
$TC filter add dev $interface protocol ip handle ::${handle} parent 1: prio 1 u32 match ip ${1} ${2}/32 flowid 1:${3}
}
function filter_del {
$TC filter del dev $interface protocol ip handle 800::${handle} parent 1: prio 1 u32
}
function ip_add {
filter_add "dst" $client_ip_vpn "10"
filter_add "src" $client_ip_vpn "20"
}
function ip_del {
filter_del
filter_del
}
if [ "$script_type" == "up" ]; then
start_tc
elif [ "$script_type" == "down" ]; then
stop_tc
elif [ "$script_type" == "client-connect" ]; then
ip_add
elif [ "$script_type" == "client-disconnect" ]; then
ip_del
fi
Note, this a very simple script for tc
testing purposes, a more sophisticated approach for OpenVPN traffic control can be found in this answer.
Make the script executable:
chmod +x /etc/openvpn/tc.sh
Running script as root in unprivileged mode
If you run OpenVPN in unprivileged mode and the script needs to be run as root
, modify the following directives in the server configuration:
user openvpn
group nogroup
up "/usr/bin/sudo /etc/openvpn/tc.sh"
down "/usr/bin/sudo /etc/openvpn/tc.sh"
client-connect "/usr/bin/sudo /etc/openvpn/tc.sh"
client-disconnect "/usr/bin/sudo /etc/openvpn/tc.sh"
Add an unprivileged user named openvpn
:
useradd -s /usr/sbin/nologin -r -M -d /dev/null openvpn
Edit /etc/sudoers
with command visudo
, add the following line:
# User privilege specification
openvpn ALL=NOPASSWD: /etc/openvpn/tc.sh
Save and quit with Ctrl+x, y
Make the script only writable by root:
chown root:root /etc/openvpn/tc.sh
chmod 700 /etc/openvpn/tc.sh
Please note that this might open a security hole and could possibly be comparable to running OpenVPN as root. Although it looks quite safe to me, but there are always people with better eyes :)
Troubleshooting
The script should be run as root now, you can troubleshoot it by adding the following lines to the beginning of your tc.sh
script:
#!/bin/bash
exec >>/tmp/ov.log 2>&1
chmod 666 /tmp/ov.log 2>/dev/null
echo
date
id
echo "PATH=$PATH"
printenv
As soon as the server is started for the first time, you can tail the logs:
tail -f /var/log/openvpn.log /tmp/ov.log
Best Answer
Sudo as a default require TTY. Once you run the script manually TTY is availbale. Once it is run in cron or (I guess also) by openvpn as called script there is no TTY available.
I see 2 options:
change configuration of sudo to not require tty Why does cron silently fail to run sudo stuff in my script? (@stackexchange):