Centos

在 CentOS 7 中限制特定埠的頻寬?

  • April 12, 2020

我在我的 VPS 上執行 CentOS 7,我想限制特定埠的頻寬。我環顧四周,在我能找到的解決方案中,要麼是對界面的限制,要麼是模糊描述的 iptable 設置,似乎只在 CentOS 6 上嘗試過。

就我而言,我的 Shadowsocks(代理應用程序)伺服器端正在偵聽 port 108010811082on eth0。我想允許1080無限頻寬,但同時限制108110821MBps 左右。由於它是代理應用程序,因此入站和出站流量大致相等。請注意,它是一個監聽 3 個埠的 Shadowsocks 實例,而不是每個監聽 1 個埠的 3 個實例,因此按程序限制頻寬不適用。

但除此之外,任何解決方案對我來說都是可行的,無論是 CentOS 開箱即用的支持,還是某種中間監控層。只要它完成工作,我就願意接受。

提前致謝。

只能使用Linux 的 Traffic Control來限制流量。

為了澄清起見,shadowsocks創建了一個隧道,一側作為 SOCKS5 代理(sslocal考慮到給定的埠,我假設這是在 OP 伺服器上執行的),與遠端端點(ssserver)通信,該端點本身將與實際目標通信伺服器。shadowsocks 處理 SOCKS5 UDP ASSOCIATE,然後在與 (SOCKS5) TCP 埠相同的埠上使用 (SOCKS5) UDP。

此解決方案對 TCP 和 UDP 均按原樣(見註 1)工作,但 UDP 可能會帶來額外的挑戰:如果源正在創建“大於 MTU”大小的 UDP 數據包(這可能不應該由行為良好的客戶端或伺服器),它們會變得支離破碎。tc,它ingress中比netfilter更早工作,在egress中比**netfilter晚,將看到這些片段。UDP 埠在片段中不可用,因此沒有過濾器能夠擷取它們,並且幾乎不會發生限制。TCP 自然地使用 MTU 來限制數據包大小(並且無論如何都要進行路徑 MTU 發現)在大多數設置中不會遇到這個問題。

這是一個數據包流 ascii 圖片(整個圖片通常表示一個客戶端活動導致兩個流,一個在代理的左側,一個在代理的右側):

             traffic controlled      TCP self-adjusting / no UDP control
            ------------->               <-------------
          /                \           /                \
 clients |                  |  proxy  |                  |  remote  ====== real servers
          \                / (sslocal) \                / (ssserver)
            <-------------               ------------->
             traffic controlled       already rate limited

無需擔心與遠端伺服器的流量:

  • 從代理到遠端伺服器的傳出當然會受到客戶端傳入的限制,

  • 從遠端/伺服器傳入代理

    • TCP 通常會調整併表現得像客戶端的流量。
    • UDP不會有這種可能性,除非應用協議可以做到。例如:如果通過簡單 UDP 的兩個影片源從伺服器端到達並超過客戶端的限制,則兩個客戶端流都可能被破壞。應該有一個應用程序回饋來減少頻寬,這超出了這個範圍。

無論如何,將遠端/伺服器端流量連結到客戶端以供tc使用會變得更加複雜,可能涉及 shadowsocks 內部的更改。

對於只發送數據的 SOCKS5 客戶端,需要限制來自它們的入口以限制頻寬,對於只接收數據的 SOCKS5 客戶端,需要限制它們的出口以限制頻寬:除非正在使用的應用程序是眾所周知的,否則兩種方式都應該是流量控制的.

交通控制是一個複雜的話題,我幾乎無法觸及。我將給出兩種答案:一種簡單粗暴的只做監管(丟棄過量),一種更複雜的,做整形(包括在不得不丟棄之前延遲),使用 IFB 介面來解決入口限制.

應閱讀以下文件以了解概念和 Linux 實現:

http://www.tldp.org/HOWTO/Traffic-Control-HOWTO/

此外,在 shell 腳本中實現的這個命令(並使用與此答案中類似的機制)也確實可以創造奇蹟:

https://github.com/magnific0/wondershaper

簡陋

策略操作用於丟棄任何多餘的數據包匹配埠(這是一種粗略的方法)。它通常用於入口,但也適用於出口。流量是速率受限的,但在各種速率受限的客戶端之間可能存在波動和不公平的共享(尤其是在涉及 UDP 與 TCP 的情況下)。

  • 出口(傳出數據包)

允許附加過濾器的最簡單的qdisc是prio qdisc *,*它的特定功能不會真正被使用。

tc qdisc add dev eth0 root handle 1: prio

只需為每個埠添加一個以下過濾器(8mbits/s <=> 1MBytes/s)(u16 at 0 layer transport表示“源埠”),即可完成 TCP 和 UDP (另請參見註釋 2):

tc filter add dev eth0 parent 1: protocol ip basic match 'cmp(u16 at 0 layer transport eq 1081)' action police rate 8mibit burst 256k
tc filter add dev eth0 parent 1: protocol ip basic match 'cmp(u16 at 0 layer transport eq 1082)' action police rate 8mibit burst 256k

萬一我誤解了 1081 和 1082 應該只有一個共同限制,請使用它而不是上面的兩個,將它們分組在同一個操作中(使用basic / ematch過濾器很容易),然後將處理它們單個令牌桶:

tc filter add dev eth0 parent 1: protocol ip basic match 'cmp(u16 at 0 layer transport eq 1081) or cmp(u16 at 0 layer transport eq 1082)' action police rate 8mibit burst 256k
  • 入口(傳入數據包)

Ingressegress更受限制(不能進行整形),但無論如何都沒有在簡單的情況下完成。使用它只需要添加一個ingressqdisc (見註 3):

tc qdisc add dev eth0 ingress

等效過濾器(u16 at 2 layer transport表示“目標埠”):

tc filter add dev eth0 ingress protocol ip basic match 'cmp(u16 at 2 layer transport eq 1081)' action police rate 8mibit burst 256k
tc filter add dev eth0 ingress protocol ip basic match 'cmp(u16 at 2 layer transport eq 1082)' action police rate 8mibit burst 256k

或單個限制,而不是上述兩個:

tc filter add dev eth0 ingress protocol ip basic match 'cmp(u16 at 2 layer transport eq 1081) or cmp(u16 at 2 layer transport eq 1082)' action police rate 8mibit burst 256k

清潔 tc

egressingress或這兩種設置都可以替換為下面的改進版本。應先清理以前的設置。

要刪除以前應用的 tc 設置,只需刪除rootingress qdiscs。它們下方的所有內容,包括過濾器,也將被刪除。保留句柄 0:的預設介面根qdisc將被放回。

tc qdisc del dev eth0 root
tc qdisc del dev eth0 ingress

使用有類 qdiscs 和 IFB 介面進行更複雜的設置

使用整形可以在必須丟棄數據包之前延遲數據包,這應該會改善整體結果。Hierarchy Token Bucket ( HTB ),一個有類的 qdisc 將處理頻寬,而在它之下,隨機公平隊列 ( SFQ ) 將提高客戶端之間在受限頻寬內競爭時的公​​平性。

  • 出口

這是描述下一個設置的 ascii 圖片:

                   root 1:   HTB classful qdisc
                     |
                   / | \
                  /  |  \
                 /   |   \
                /    |    \
               /    1:20  1:30  HTB classes
              /    8mibit  8mibit
             /       |       \
            /        |        \
           /        20:       30:
          /         SFQ       SFQ
    still 1:
    default         port         port
incl. port 1080      1081         1082

有限的頻寬不會藉用額外的可用流量(OP 沒有要求):這就是為什麼它們不是“整個可用頻寬”預設類的子類。剩下的預設流量,包括 1080 埠,只是停留在 1:,不做特殊處理。在允許類借用可用頻寬的不同設置中,應將這些類放在其速率設置為最大可用頻寬的準確值的父類之下,以了解借用什麼。因此,配置需要針對每種情況進行微調。我保持簡單。

htb 有類 qdisc:

tc qdisc add dev eth0 root handle 1: htb

htb 類、附加的 sfq 和指向它們的過濾器:

tc class add dev eth0 parent 1: classid 1:20 htb rate 8mibit
tc class add dev eth0 parent 1: classid 1:30 htb rate 8mibit

tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10

tc filter add dev eth0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 0 layer transport eq 1081)' flowid 1:20
tc filter add dev eth0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 0 layer transport eq 1082)' flowid 1:30

或單個限制,而不是上面的 6 個命令:

tc class add dev eth0 parent 1: classid 1:20 htb rate 8mibit
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc filter add dev eth0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 0 layer transport eq 1081)' flowid 1:20
tc filter add dev eth0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 0 layer transport eq 1082)' flowid 1:20
  • 入口

入口qdisc 不能用於整形(例如延遲數據包),而只能使用過濾器將它們丟棄,就像在簡單情況下一樣。為了獲得更好的控制,可以使用一個技巧:中間功能塊,它顯示為人工出口介面,入口流量可以通過過濾器重定向,但與網路堆棧的其餘部分幾乎沒有互動。一旦到位,就可以在其上應用出口功能,即使其中一些可能並不總是有幫助,考慮到對傳入流量的真正控制不在接收系統的手中。所以在這裡我設置了ifb0界面然後在上面複製(出口) 設置,以使某種入口整形表現得比監管更好。

創建ifb0 (參見註釋 4)並應用與之前的egress相同的設置:

ip link add name ifb0 type ifb 2&gt;/dev/null || :
ip link set dev ifb0 up

tc qdisc add dev ifb0 root handle 1: htb

指向它們的類和過濾器:

tc class add dev ifb0 parent 1: classid 1:20 htb rate 8mibit
tc class add dev ifb0 parent 1: classid 1:30 htb rate 8mibit

tc qdisc add dev ifb0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev ifb0 parent 1:30 handle 30: sfq perturb 10

tc filter add dev ifb0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 2 layer transport eq 1081)' flowid 1:20
tc filter add dev ifb0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 2 layer transport eq 1082)' flowid 1:30

或單個限制,而不是上面的 6 個命令:

tc class add dev ifb0 parent 1: classid 1:20 htb rate 8mibit     
tc qdisc add dev ifb0 parent 1:20 handle 20: sfq perturb 10
tc filter add dev ifb0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 2 layer transport eq 1081)' flowid 1:20
tc filter add dev ifb0 parent 1: protocol ip prio 1 basic match 'cmp(u16 at 2 layer transport eq 1082)' flowid 1:20

eth0入口ifb0 出口的重定向在下面完成。為了優化,只重定向目標埠而不是所有流量。無論如何,實際的過濾和整形都是在ifb0中完成的。

tc qdisc add dev eth0 ingress
tc filter add dev eth0 ingress protocol ip basic match 'cmp(u16 at 2 layer transport eq 1081)' action mirred egress redirect dev ifb0
tc filter add dev eth0 ingress protocol ip basic match 'cmp(u16 at 2 layer transport eq 1081)' action mirred egress redirect dev ifb0

筆記:

  1. 在 Debian 10 / 核心 5.3 上使用一些網路命名空間進行了測試。命令語法也在 CentOS 7.6 容器/核心 5.3(而不是 3.10)上進行了測試。

2.u32 match ip sport 1081 0xffff本來可以用來匹配源埠 1081。但它不能處理 IP 選項的存在。可以處理它,但它實際上需要三個**u32過濾器u32 match tcp src 1081 0xffff的複雜使用,如手冊頁中所述。所以我最終選擇了。 basic match

3.是否指定ingress了保留句柄ffff:(指定的句柄值被忽略),所以我寧願不指定它。引用 ingress byparent ffff:可以替換為 just ingressso 這就是我選擇的。

4.第一次創建IFB介面時,會載入ifb模組,預設會在初始命名空間中自動創建ifb0ifb1介面,如果詢問介面名稱ifb0會報錯,而實際創建為命令的結果。同時,如果只是載入模組,這個介面不會出現在網路命名空間(例如:容器)中,所以仍然需要在那裡。因此,添加2&gt;/dev/null || :解決了這兩種情況。當然,我假設 IFB 支持確實可用。

引用自:https://unix.stackexchange.com/questions/545033