web-dev-qa-db-ja.com

OpenVPNクライアントごとにTCでトラフィックシェーピング(レート制限)を行う方法

この質問は 別の質問 に関連しています 素晴らしい回答とスクリプト@ Oliver から。

目的:この回答で提供されているスクリプト を私の要件に合わせて変更/拡張したいのですが、次のとおりです。

  1. 多数のクライアント(最大1000)があります。各クライアントには、サブスクリプションクラスと、そのCN(共通名)に基づいて対応する最大データレートが割り当てられます。これらのレート制限は、クライアントが接続したときに適用され、クライアントが切断したときに削除されます。

    • bronze:1メガビット
    • silver:10メガビット
    • gold:100メガビット
  2. クライアントがOpenVPNサーバーに接続されている間、各クライアントのサブスクリプションクラスと対応するアクティブなデータレート制限をオンザフライで調整したいと思います。クライアントはOpenVPNサーバーに再接続する必要はありません。これは可能ですか、または各クライアントを切断してOpenVPNに再接続し、tc構成を変更するためにスクリプトを再度呼び出す必要がありますか?

  3. シェルを使用してtc構成を手動で変更する代わりに、クライアントサブスクリプションクラスと対応するアクティブデータレート制限を別のコンピューターまたはアプリケーションから(つまり、PHPを介して)オンザフライで更新するにはどうすればよいですか?

どうもありがとう

3

これが解決策ですOpenVPNによって呼び出されるスクリプトを使用してtc(トラフィック制御)で個々のクライアントのデータレート制限のためのトラフィックシェーピングを行う方法

トラフィック制御設定は、次の機能を持つスクリプトtc.shで処理されます。

  • ディレクティブを使用してOpenVPNによって呼び出されます:updownclient-connectおよびclient-disconnect
  • すべての設定は環境変数を介して渡されます
  • 理論的には/16までのサブネットをサポート(最大65534クライアント)
  • ハッシュフィルター を使用したフィルタリングにより、非常に高速で大規模なフィルタリングを実現
  • フィルターとクラスは、現在接続されているクライアントに対してのみ設定され、一意の識別子(tchashtableshandles)を使用して他のclassids設定に影響を与えることなく個別に追加および削除されます。これらの識別子は、クライアントのリモートVPN IPの最後の16ビットから生成されます
  • CN名(クライアント証明書の共通名)に基づくクライアントの個別の制限/調整
  • クライアント設定は、「サブスクリプションクラス」(bronzesilvergold)を含むファイルに保存され、他のクラスを使用して、スクリプトを編集し、必要に応じて変更するだけです。
  • 「サブスクリプションクラス」と対応するデータレート(「帯域幅」)は、クライアントの接続中に外部アプリケーションからオンザフライで変更できます。

構成

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"

最後の2行のDNSサーバーを正しいIPアドレスに置き換えます。

トラフィック制御スクリプト/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=$1"
  echo "\$2=$2"
  echo "\$3=$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

実行可能にします。

chmod +x /etc/openvpn/tc/tc.sh

サブスクリプションデータベースディレクトリ/etc/openvpn/tc/db/

このディレクトリには、「サブスクリプションクラス」文字列を含むCN-nameにちなんで名付けられたクライアントごとのファイルが含まれています。次のように構成します。

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

IPデータベースディレクトリ/etc/openvpn/tc/ip/

このディレクトリには、実行時にCN-name <-> IP-addressリレーションとtun interfaceが含まれます。これらは、クライアントが接続されているときにtc設定を更新する外部アプリケーションに提供する必要があります。

mkdir -p /etc/openvpn/tc/ip

次のようになります。

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

IP転送を有効にする:

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

設定NAT(ネットワークアドレス変換):

静的外部IPアドレスがある場合は、SNATを使用します。

iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o <if> -j SNAT --to <ip>

または、動的に割り当てられたIPアドレスがある場合は、MASQUERADE(遅い)を使用します。

iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o <if> -j MASQUERADE

ながら

  • <if>は外部インターフェイスの名前です(つまり、eth0
  • <ip>は、外部インターフェースのIPアドレスです

スクリプトの使用とtc構成の表示

外部アプリケーションからの「サブスクリプションクラス」とtc設定の更新:

OpenVPNサーバーが起動し、クライアントが接続している間に、次のコマンドを発行します(client1"gold"サブスクリプションにアップグレードする例):

echo gold > /etc/openvpn/tc/db/client1
/etc/openvpn/tc/tc.sh update client1

tcコマンドで設定を表示:

tc -s qdisc show dev tun0
tc class show dev tun0
tc filter show dev tun0

追加情報

注意と可能な最適化:

  • スクリプトとtc設定は、少数のクライアントを使用してのみテストされました
  • 大規模な同時クライアントトラフィックを使用した大規模なテストを実行し、tc設定を最適化する必要がある
  • Ingressの設定がどのように機能するか完全に理解していません。 この答え で説明されているように、ifbインターフェースを使用して最適化する必要があります。

より深い理解のための関連ドキュメント:

10
rda