Aqui está uma solução, como configurar o tráfego para limitar a taxa de dados de clientes individuais com tc
(controle de tráfego) usando um script chamado pelo OpenVPN .
As configurações de controle de tráfego são processadas em um script tc.sh
com os seguintes recursos:
- Chamado pelo OpenVPN usando diretivas:
up
,down
,client-connect
eclient-disconnect
- Todas as configurações são transmitidas por meio de variáveis de ambiente
- Suporta teoricamente até
/16
sub-redes (até 65534 clientes) - Filtrar usando filtros de hash para filtragem massiva muito rápida
- Os filtros e as turmas são definidos apenas para clientes atualmente conectados e são adicionados e removidos individualmente sem afetar outras configurações de
tc
usando identificadores exclusivos (hashtables
,handles
,classids
). Esses identificadores são gerados a partir dos últimos 16 bits do IP da VPN remoto do cliente - Limitação / limitação individual de clientes com base no nome do CN (nome comum do certificado de cliente)
- As configurações do cliente são armazenadas em arquivos que contêm a "classe de assinatura" (
bronze
,silver
egold
), para usar outras classes. Basta editar o script e modificar conforme necessário. - "Classe de assinatura" e a taxa de dados correspondente ("largura de banda") podem ser modificadas dinamicamente a partir de aplicativos externos enquanto um cliente estiver conectado.
Configuração
Configuração do servidor OpenVPN /etc/openvpn/tc/conf
:
port 1194
proto udp
dev tun
sndbuf 0
rcvbuf 0
ca ca.crt
cert server.crt
key server.key
dh dh.pem
tls-auth ta.key 0
topology subnet
server 10.8.0.0 255.255.0.0
keepalive 10 60
comp-lzo
persist-key
persist-tun
status /var/log/openvpn-tc-status.log
log /var/log/openvpn-tc.log
verb 3
script-security 2
down-pre
up /etc/openvpn/tc/tc.sh
down /etc/openvpn/tc/tc.sh
client-connect /etc/openvpn/tc/tc.sh
client-disconnect /etc/openvpn/tc/tc.sh
push "redirect-gateway def1"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
Substitua os servidores DNS nas últimas 2 linhas pelos endereços IP corretos.
Script de controle de tráfego /etc/openvpn/tc/tc.sh
:
#!/bin/bash
ipdir=/etc/openvpn/tc/ip
dbdir=/etc/openvpn/tc/db
ip="$ifconfig_pool_remote_ip"
cn="$common_name"
ip_local="$ifconfig_local"
debug=0
log=/tmp/tc.log
if [[ "$debug" > 0 ]]; then
exec >>"$log" 2>&1
chmod 666 "$log" 2>/dev/null
if [[ "$debug" > 1 ]]; then
date
id
echo "PATH=$PATH"
[[ "$debug" > 2 ]] && printenv
fi
echo
echo "script_type=$script_type"
echo "dev=$dev"
echo "ip=$ip"
echo "user=$cn"
echo "\=$1"
echo "\=$2"
echo "\=$3"
fi
cut_ip_local() {
if [ -n "$ip_local" ]; then
ip_local_byte1='echo "$ip_local" | cut -d. -f1'
ip_local_byte2='echo "$ip_local" | cut -d. -f2'
fi
[[ "$debug" > 0 ]] && echo "ip_local_byte1=$ip_local_byte1"
[[ "$debug" > 0 ]] && echo "ip_local_byte2=$ip_local_byte2"
}
create_identifiers() {
if [ -n "$ip" ]; then
ip_byte3='echo "$ip" | cut -d. -f3'
handle='printf "%x\n" "$ip_byte3"'
ip_byte4='echo "$ip" | cut -d. -f4'
hash='printf "%x\n" "$ip_byte4"'
classid='printf "%x\n" $((256*ip_byte3+ip_byte4))'
fi
[[ "$debug" > 0 ]] && echo "ip_byte3=$ip_byte3"
[[ "$debug" > 0 ]] && echo "ip_byte4=$ip_byte4"
[[ "$debug" > 0 ]] && echo "handle=$handle"
[[ "$debug" > 0 ]] && echo "hash=$hash"
}
start_tc() {
[[ "$debug" > 1 ]] && echo "start_tc()"
cut_ip_local
echo "$dev" > "$ipdir"/dev
tc qdisc add dev "$dev" root handle 1: htb
tc qdisc add dev "$dev" handle ffff: ingress
tc filter add dev "$dev" parent 1:0 prio 1 protocol ip u32
tc filter add dev "$dev" parent 1:0 prio 1 handle 2: protocol ip u32 divisor 256
tc filter add dev "$dev" parent 1:0 prio 1 protocol ip u32 ht 800:: \
match ip dst "${ip_local_byte1}"."${ip_local_byte2}".0.0/16 \
hashkey mask 0x000000ff at 16 link 2:
tc filter add dev "$dev" parent ffff:0 prio 1 protocol ip u32
tc filter add dev "$dev" parent ffff:0 prio 1 handle 3: protocol ip u32 divisor 256
tc filter add dev "$dev" parent ffff:0 prio 1 protocol ip u32 ht 800:: \
match ip src "${ip_local_byte1}"."${ip_local_byte2}".0.0/16 \
hashkey mask 0x000000ff at 12 link 3:
}
stop_tc() {
[[ "$debug" > 1 ]] && echo "stop_tc()"
tc qdisc del dev "$dev" root
tc qdisc del dev "$dev" handle ffff: ingress
[ -e "$ipdir"/dev ] && rm "$ipdir"/dev
}
function bwlimit-enable() {
[[ "$debug" > 1 ]] && echo "bwlimit-enable()"
create_identifiers
echo "$ip" > "$ipdir"/"$cn".ip
# Find this user's bandwidth limit
[[ "$debug" > 0 ]] && echo "userdbfile=${dbdir}/${cn}"
user='cat "${dbdir}/${cn}"'
[[ "$debug" > 0 ]] && echo "subscription=$user"
if [ "$user" == "gold" ]; then
downrate=100mbit
uprate=100mbit
elif [ "$user" == "silver" ]; then
downrate=10mbit
uprate=10mbit
elif [ "$user" == "bronze" ]; then
downrate=1mbit
uprate=1mbit
else
downrate=10kbit
uprate=10kbit
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" parent 1:0 protocol ip prio 1 \
handle 2:"${hash}":"${handle}" \
u32 ht 2:"${hash}": match ip dst "$ip"/32 flowid 1:"$classid"
# Limit traffic from client to VPN server
# Maybe better use ifb for ingress? See: https://serverfault.com/a/386791/209089
tc filter add dev "$dev" parent ffff:0 protocol ip prio 1 \
handle 3:"${hash}":"${handle}" \
u32 ht 3:"${hash}": match ip src "$ip"/32 \
police rate "$uprate" burst 80k drop flowid :"$classid"
}
function bwlimit-disable() {
[[ "$debug" > 1 ]] && echo "bwlimit-disable()"
create_identifiers
tc filter del dev "$dev" parent 1:0 protocol ip prio 1 \
handle 2:"${hash}":"${handle}" u32 ht 2:"${hash}":
tc class del dev "$dev" classid 1:"$classid"
tc filter del dev "$dev" parent ffff:0 protocol ip prio 1 \
handle 3:"${hash}":"${handle}" u32 ht 3:"${hash}":
# Remove .ip
[ -e "$ipdir"/"$cn".ip ] && rm "$ipdir"/"$cn".ip
}
case "$script_type" in
up)
start_tc
;;
down)
stop_tc
;;
client-connect)
bwlimit-enable
;;
client-disconnect)
bwlimit-disable
;;
*)
case "$1" in
update)
[ -z "$2" ] && echo "$0 $1: missing argument [client-CN]" >&2 && exit 1
[ ! -e "$ipdir"/"$2".ip ] && \
echo "$0 $1 $2: file $ipdir/$2.ip not found" >&2 && exit 1
[ ! -e "$ipdir"/dev ] && \
echo "$0 $1: file $ipdir/dev not found" >&2 && exit 1
ip='cat "$ipdir/$2.ip"'
dev='cat "$ipdir/dev"'
cn="$2"
bwlimit-disable
bwlimit-enable
;;
*)
echo "$0: unknown operation [$1]" >&2
exit 1
;;
esac
;;
esac
exit 0
Torne-o executável:
chmod +x /etc/openvpn/tc/tc.sh
Diretório do banco de dados de inscrição /etc/openvpn/tc/db/
:
Este diretório contém um arquivo por cliente com o nome do seu Nome CN contendo a string "subscription class", configure da seguinte forma:
mkdir -p /etc/openvpn/tc/db
echo bronze > /etc/openvpn/tc/db/client1
echo silver > /etc/openvpn/tc/db/client2
echo gold > /etc/openvpn/tc/db/client3
Diretório do banco de dados de IP /etc/openvpn/tc/ip/
:
Este diretório conterá a relação CN-name <-> IP-address
e o tun interface
durante o tempo de execução, que deve ser fornecido para um aplicativo externo atualizando as configurações de tc
enquanto os clientes estiverem conectados.
mkdir -p /etc/openvpn/tc/ip
Será da seguinte forma:
root@ubuntu:/etc/openvpn/tc/ip# ls -l
-rw-r--r-- 1 root root 9 Jun 1 08:31 client1.ip
-rw-r--r-- 1 root root 9 Jun 1 08:30 client2.ip
-rw-r--r-- 1 root root 9 Jun 1 08:30 client3.ip
-rw-r--r-- 1 root root 5 Jun 1 08:25 dev
root@ubuntu:/etc/openvpn/tc/ip# cat *
10.8.0.2
10.8.1.0
10.8.2.123
tun0
Ativar encaminhamento de IP:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
Configurando o NAT (conversão de endereço de rede):
Se você tiver um endereço IP externo estático, use SNAT
:
iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o <if> -j SNAT --to <ip>
Ou, se você tiver um endereço IP atribuído dinamicamente, use MASQUERADE
(mais lento):
iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o <if> -j MASQUERADE
enquanto
-
<if>
é o nome da interface externa (por exemplo,eth0
) -
<ip>
é o endereço IP da interface externa
Uso de script e exibição da configuração tc
Atualizando "classe de assinatura" e tc
configurações do aplicativo externo:
Enquanto o servidor OpenVPN está ativo e o cliente conectado emite os seguintes comandos (exemplo para atualizar client1
para "gold"
subscription):
echo gold > /etc/openvpn/tc/db/client1
/etc/openvpn/tc/tc.sh update client1
Comandos tc
para mostrar as configurações:
tc -s qdisc show dev tun0
tc class show dev tun0
tc filter show dev tun0
Informações adicionais
Notas e possíveis otimizações:
- O script e as configurações
tc
foram testados apenas usando um pequeno número de clientes - Testes em grande escala com tráfego massivo de clientes simultâneos precisam ser feitos e, possivelmente, as configurações de
tc
precisam ser otimizadas - Eu não entendo completamente como funcionam as configurações de entrada. Eles provavelmente devem ser otimizados com o uso da interface
ifb
, conforme explicado em esta resposta .
Documentação relacionada para um entendimento mais profundo:
- Traffic Control HOWTO
- Roteamento avançado & HOWTO de Controle de Tráfego (especialmente capítulo 9-12)
-
Manual de disciplina de enfileiramento HTB Linux - guia do usuário (muito boa explicação sobre
htb
qdisc ) - página de manual do TC
- Identificando filtros tc para
add
edel
operações - página de manual do OpenVPN 2.3