目标:我想修改/扩展此答案中提供的脚本 以满足我的要求,如下所示:
我有大量客户端(最多 1000 个)。应根据每个客户端的 CN(通用名称)为其分配一个订阅类别和相应的最大数据速率。这些速率限制应在客户端连接时应用,并在客户端断开连接时删除:
bronze
:1兆位silver
:10兆位gold
:100兆位
我想在客户端连接到 OpenVPN 服务器时动态调整每个客户端的订阅类别和相应的活动数据速率限制。客户端不必重新连接到 OpenVPN 服务器。这可行吗?还是我们必须断开每个客户端与 OpenVPN 的连接并重新连接,以再次调用脚本来更改配置
tc
?我们不需要
tc
使用 shell 手动修改配置,如何从另一台计算机或应用程序(即通过 PHP)动态更新客户端订阅类别和相应的活动数据速率限制?
非常感谢
答案1
这是一个解决方案,tc
如何使用 OpenVPN 调用的脚本对单个客户端的数据速率进行流量整形(流量控制)。
流量控制设置在脚本中处理,tc.sh
具有以下特点:
- 由OpenVPN 使用指令调用:
up
、down
和client-connect
client-disconnect
- 所有设置都通过环境变量传递
- 理论上支持最多
/16
子网(最多 65534 个客户端) - 使用以下方式进行过滤哈希过滤器用于非常快速的大规模过滤
- 过滤器和类别仅针对当前连接的客户端进行设置,并单独添加和删除,而不会影响
tc
使用唯一标识符(hashtables
、handles
、classids
)的其他设置。这些标识符由客户端的远程 VPN IP 的最后 16 位生成 - 根据 CN 名称(客户端证书通用名称)对客户端进行单独限制/节流
- 客户端设置存储在包含其“订阅类”(
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"
将最后两行中的 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
界面进行优化,如这个答案。
更深入理解的相关文档:
- 交通管制指南
- Linux 高级路由和流量控制指南(特别是第 9-12 章)
- HTB Linux 排队规则手册 - 用户指南(对 qdisc 的非常好的解释
htb
) - TC 手册页
- 识别 tc 过滤器的
add
和del
操作 - OpenVPN 2.3 手册页