如何使用每个OpenVPN客户端的TC进行stream量整形(速率限制)

这个问题与@Oliver的一个很好的答案和脚本 有关 。

目标:我想修改/扩展此答案中提供的脚本以适应我的要求,如下所示:

  1. 我有大量的客户(高达1000)。 每个客户端应根据其CN(通用名称)分配一个订阅类别和相应的最大数据速率。 这些速率限制应在客户端连接时应用,并在断开连接时移除:

    • bronze :1 mbit
    • silver :10兆位
    • gold :100兆位
  2. 当客户端连接到OpenVPN服务器时,我想调整每个客户端的订阅类别和相应的活动数据速率限制。 客户端不应该重新连接到OpenVPN服务器。 这是可能的,还是我们必须断开连接并重新连接到OpenVPN的每个客户端,导致脚本被再次调用来改变tcconfiguration?

  3. 而不是使用shell手动修改tcconfiguration,我们将如何从另一台计算机或应用程序(即通过PHP)更新客户端订阅类和相应的活动数据速率限制?

非常感谢

这是一个解决scheme, 如何使用OpenVPN所调用的脚本,通过tc (stream量控制)为个别客户端进行stream量整形

stream量控制设置在脚本tc.sh中处理,具有以下function:

  • 由OpenVPN使用指令调用: updownclient-connectclient-disconnect
  • 所有设置都通过环境variables传递
  • 理论上支持多达/16子网(最多65534个客户端)
  • 使用哈希filter进行过滤以实现非常快速的大量过滤
  • 筛选器和类仅为当前连接的客户端设置,并且使用唯一标识符( hashtableshandlesclassids )单独添加和删除,而不会影响其他的tc设置。 这些标识符是从客户端的远程vpn IP的最后16位生成的
  • 基于CN名称(客户端证书通用名称)对客户端进行个别限制/限制
  • 客户端设置存储在包含“订阅类”( bronzesilvergold )的文件中,以便使用其他类来简单编辑脚本并根据需要进行修改。
  • 当客户端连接时,“订购类”和相应的数据速率(“带宽”)可以从外部应用程序进行修改。

组态

OpenVPN服务器configuration/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" 

将最后两行中的DNS服务器replace为正确的IP地址。

stream量控制脚本/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/

这个目录包含一个每个客户端的文件,其名称都是包含“订阅类”string的CN名称 ,configuration如下:

 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 

configurationNAT(networking地址转换):

如果你有一个静态的外部IP地址使用SNAT

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

或者,如果您有dynamic分配的IP地址,请使用MASQUERADE (较慢):

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

  • <if>是外部接口的名称(即eth0
  • <ip>是外部接口的IP地址

脚本使用情况并显示tcconfiguration

从外部应用程序更新“订阅类”和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设置仅使用less量的客户端进行testing
  • 大规模同时进行客户端stream量的大规模testing必须完成,并且可能必须优化tc设置
  • 我不完全了解入口设置如何工作。 他们可能应该使用ifb接口进行优化,如本答案所述 。

有关更深入了解的相关文档:

  • 交通pipe制HOWTO
  • Linux高级路由和stream量控制HOWTO (特别是第9-12章)
  • HTB Linux排队纪律手册 – 用户指南 (非常好的解释htb
  • TC联机手册
  • 识别adddel操作的tcfilter
  • OpenVPN 2.3的手册页