AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / server / 问题 / 777875
Accepted
Server Programmer
Server Programmer
Asked: 2016-05-20 13:42:14 +0800 CST2016-05-20 13:42:14 +0800 CST 2016-05-20 13:42:14 +0800 CST

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

  • 772

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

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

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

    • bronze: 1 兆位
    • silver: 10 兆比特
    • gold: 100 兆位
  2. 我想在客户端连接到 OpenVPN 服务器时即时调整每个客户端的订阅类别和相应的活动数据速率限制。客户端不必重新连接到 OpenVPN 服务器。这是可能的还是我们必须断开每个客户端并将其重新连接到 OpenVPN 以导致再次调用脚本来更改tc配置?

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

非常感谢

openvpn bandwidth rate-limiting tc
  • 1 1 个回答
  • 14554 Views

1 个回答

  • Voted
  1. Best Answer
    rda
    2016-06-02T02:17:50+08:002016-06-02T02:17:50+08:00

    这是一个解决方案,如何tc使用 OpenVPN 调用的脚本为单个客户端的数据速率限制(流量控制)进行流量整形。

    流量控制设置在tc.sh具有以下功能的脚本中处理:

    • 由 OpenVPN 使用指令调用:up、、down和client-connectclient-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接口进行优化。

    深入了解的相关文档:

    • 交通管制 HOWTO
    • Linux Advanced Routing & Traffic Control HOWTO(特别是第 9-12 章)
    • HTB Linux排队纪律手册-用户指南(qdisc很好的解释htb)
    • TC 手册页
    • 识别 tc 过滤器add和del操作
    • OpenVPN 2.3 手册页
    • 10

相关问题

  • OpenVPN:在哪里生成私钥?

  • 将 iPhone 连接到 OpenVPN

  • OpenVPN 的 Linux IP 转发 - 正确的防火墙设置?

  • 最好的点对点 VPN?

  • 通过 VPN 使您的打印机可用的最佳方法是什么?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    新安装后 postgres 的默认超级用户用户名/密码是什么?

    • 5 个回答
  • Marko Smith

    SFTP 使用什么端口?

    • 6 个回答
  • Marko Smith

    命令行列出 Windows Active Directory 组中的用户?

    • 9 个回答
  • Marko Smith

    什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同?

    • 3 个回答
  • Marko Smith

    如何确定bash变量是否为空?

    • 15 个回答
  • Martin Hope
    Tom Feiner 如何按大小对 du -h 输出进行排序 2009-02-26 05:42:42 +0800 CST
  • Martin Hope
    Noah Goodrich 什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同? 2009-05-19 18:24:42 +0800 CST
  • Martin Hope
    Brent 如何确定bash变量是否为空? 2009-05-13 09:54:48 +0800 CST
  • Martin Hope
    cletus 您如何找到在 Windows 中打开文件的进程? 2009-05-01 16:47:16 +0800 CST

热门标签

linux nginx windows networking ubuntu domain-name-system amazon-web-services active-directory apache-2.4 ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve