Como fazer a modelagem de tráfego (limitação de taxa) com TC por cliente OpenVPN

2

Esta questão está relacionada com outra questão com um excelente resposta e script em @ Oliver .

O objetivo: eu quero modificar / estender o script fornecido nesta resposta para atender às minhas necessidades, que são as seguintes:

  1. Eu tenho um grande número de clientes (até 1000). Cada cliente deve receber uma classe de assinatura e uma taxa de dados máxima correspondente com base em seu CN (nome comum). Estes limites de taxa devem ser aplicados quando o cliente conecta e deve ser removido quando desconectar:

    • bronze : 1 mbit
    • silver : 10 mbit
    • gold : 100 mbit
  2. Gostaria de ajustar a classe de assinatura de cada cliente e o limite de taxa de dados ativo correspondente em tempo real, enquanto o cliente está conectado ao servidor OpenVPN. O cliente não deve ter que se reconectar ao servidor OpenVPN. Isso é possível ou temos que desconectar e reconectar cada cliente ao OpenVPN para fazer com que o script seja chamado novamente para alterar a configuração tc ?

  3. Em vez de modificar manualmente a configuração tc usando o shell, como atualizaríamos a classe de assinatura do cliente e o limite de taxa de dados ativo correspondente em tempo real de outro computador ou aplicativo (por exemplo, via PHP)?

Muito obrigado

    
por Server Programmer 19.05.2016 / 23:42

1 resposta

5

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 e client-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 e gold ), 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:

por 01.06.2016 / 12:17