在 CentOS 7 中限制特定埠的頻寬?
我在我的 VPS 上執行 CentOS 7,我想限制特定埠的頻寬。我環顧四周,在我能找到的解決方案中,要麼是對界面的限制,要麼是模糊描述的 iptable 設置,似乎只在 CentOS 6 上嘗試過。
就我而言,我的 Shadowsocks(代理應用程序)伺服器端正在偵聽 port
1080
、1081
和1082
oneth0
。我想允許1080
無限頻寬,但同時限制1081
在1082
1MBps 左右。由於它是代理應用程序,因此入站和出站流量大致相等。請注意,它是一個監聽 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
- 入口(傳入數據包)
Ingress比egress更受限制(不能進行整形),但無論如何都沒有在簡單的情況下完成。使用它只需要添加一個
ingress
qdisc (見註 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
egress,ingress或這兩種設置都可以替換為下面的改進版本。應先清理以前的設置。
要刪除以前應用的 tc 設置,只需刪除root和ingress 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>/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
筆記:
- 在 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:
可以替換為 justingress
so 這就是我選擇的。4.第一次創建IFB介面時,會載入ifb模組,預設會在初始命名空間中自動創建ifb0和ifb1介面,如果詢問介面名稱ifb0會報錯,而實際創建為命令的結果。同時,如果只是載入模組,這個介面不會出現在網路命名空間(例如:容器)中,所以仍然需要在那裡。因此,添加
2>/dev/null || :
解決了這兩種情況。當然,我假設 IFB 支持確實可用。