Openvpn
如何對每個 OpenVPN 客戶端使用 TC 進行流量整形(速率限制)
這個問題與來自@Oliver的一個很好的答案和腳本的另一個問題有關。
**目標:**我想修改/擴展此答案中提供的腳本 以滿足我的要求,如下所示:
- 我有大量客戶(最多 1000 個)。每個客戶端應根據其 CN(通用名稱)分配一個訂閱類別和相應的最大數據速率。這些速率限制應在客戶端連接時應用,並在其斷開連接時刪除:
bronze
: 1 兆位silver
: 10 兆比特gold
: 100 兆位
- 我想在客戶端連接到 OpenVPN 伺服器時即時調整每個客戶端的訂閱類別和相應的活動數據速率限制。客戶端不必重新連接到 OpenVPN 伺服器。這是可能的還是我們必須斷開每個客戶端並將其重新連接到 OpenVPN 以導致再次呼叫腳本來更改
tc
配置?tc
我們將如何從另一台電腦或應用程序(即通過 PHP)即時更新客戶端訂閱類和相應的活動數據速率限制,而不是使用 shell 手動修改配置?非常感謝
這是一個解決方案,如何
tc
使用 OpenVPN 呼叫的腳本為單個客戶端的數據速率限制(流量控制)進行流量整形。流量控制設置在
tc.sh
具有以下功能的腳本中處理:
- 由 OpenVPN 使用指令呼叫:
up
、、down
和client-connect``client-disconnect
- 所有設置都通過環境變數傳遞
- 理論上最多支持
/16
子網(最多 65534 個客戶端)- 使用散列過濾器進行過濾以實現非常快速的大規模過濾
- 過濾器和類僅針對目前連接的客戶端設置,並單獨添加和刪除,而不會影響
tc
使用唯一標識符(hashtables
、handles
、classids
)的其他設置。這些標識符是從客戶端遠端 vpn IP 的最後 16 位生成的- 基於 CN-name(客戶端證書公用名)對客戶端的個別限制/節流
- 客戶端設置儲存在包含其“訂閱類”( 和 )的文件中
bronze
,silver
要gold
使用其他類,只需編輯腳本並根據需要進行修改。- “訂閱類”和相應的數據速率(“頻寬”)可以在連接客戶端時從外部應用程序動態修改。
配置
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 名稱命名,包含“訂閱類”字元串,配置如下:
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
必須優化設置- 我不完全理解入口設置是如何工作的。如本答案中所述,它們可能應該使用
ifb
介面進行優化。深入了解的相關文件: