文章轉自:使用 tc netem 模擬網絡異常(原文配色實在是對閱讀不太友好。。。)
在某些情況下,我們需要模擬網絡很差的狀態來測試軟件能夠正常工作,比如網絡延遲、丟包、亂序、重複等。linux 系統強大的流量控制工具 tc 能很輕鬆地完成,tc 命令行是 iproute2
軟件包中的軟件,可以根據系統版本自行安裝。
流量控制是個系統而複雜的話題,tc 能做的事情很多,除了本文介紹的還有帶寬控制、優先級控制等等,這些功能是通過類似的模塊組件實現的,這篇文章介紹的功能主要是通過 netem
這個組件實現的。netem
是 Network Emulator
的縮寫,關於更多功能以及參數的詳細解釋可以參閱 tc-netem
的 man page。
網絡狀況模擬
網絡狀況欠佳從用戶角度來說就是下載東西慢(網頁一直加載、視頻卡頓、圖片加載很久等),從網絡報文角度來看卻有很多情況:延遲(某個機器發送報文很慢)、丟包(發送的報文在網絡中丟失需要一直重傳)、亂序(報文順序錯亂,需要大量計算時間來重新排序)、重複(報文有大量重複,導致網絡擁堵)、錯誤(接收到的報文有誤只能丟棄重傳)等。
對於這些情況,都可以用 netem 來模擬。需要注意的是,netem 是直接添加到網卡上的,也就是說所有從網卡發送出去的包都會收到配置參數的影響,所以最好搭建臨時的虛擬機進行測試。
在下面的例子中 add
表示爲網卡添加 netem 配置,change
表示修改已經存在的 netem 配置到新的值,如果要刪除網卡上的配置可以使用 del
:
# tc qdisc del dev eth0 root
1. 模擬延遲傳輸
最簡單的例子是所有的報文延遲 100ms 發送:
# tc qdisc add dev eth0 root netem delay 100ms
如果你想在一個局域網裏模擬遠距離傳輸的延遲可以用這個方法,比如實際用戶會訪問外國網站,延遲爲 120ms,而你測試環境網絡交互只需要 10ms,那麼只要添加 110 ms 額外延遲就行。
在我本地的虛擬機中實驗結果:
[root@node02 ~]# tc qdisc replace dev enp0s8 root netem delay 100ms
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=101 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=100 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=102 ms
^C
--- 172.17.8.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 100.725/101.370/102.048/0.653 ms
如果在網絡中看到非常穩定的時延,很可能是某個地方加了定時器,因爲網絡線路很複雜,傳輸過程一定會有變化。因此實際情況網絡延遲一定會有變化的,netem
也考慮到這一點,提供了額外的參數來控制延遲的時間分佈,完整的參數列表爲:
DELAY := delay TIME [ JITTER [ CORRELATION ]]]
[ distribution { uniform | normal | pareto | paretonormal } ]
除了延遲時間 TIME
之外,還有三個可選參數:
JITTER
:抖動,增加一個隨機時間長度,讓延遲時間出現在某個範圍CORRELATION
:相關,下一個報文延遲時間和上一個報文的相關係數distribution
:分佈,延遲的分佈模式,可以選擇的值有uniform
、normal
、pareto
和paretonormal
先說說 JITTER
,如果設置爲 20ms
,那麼報文延遲的時間在 100ms ± 20ms 之間(90ms - 110ms),具體值隨機選擇:
[root@node02 ~]# tc qdisc replace dev enp0s8 root netem delay 100ms 20ms
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=112 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=89.7 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=114 ms
......
CORRELATION
指相關性,因爲網絡狀況是平滑變化的,短時間裏相鄰報文的延遲應該是近似的而不是完全隨機的。這個值是個百分比,如果爲 100%
,就退化到固定延遲的情況;如果是 0%
則退化到隨機延遲的情況
[root@node02 ~]# tc qdisc replace dev enp0s8 root netem delay 100ms 20ms 50%
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=116 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=89.7 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=90.8 ms
64 bytes from 172.17.8.100: icmp_seq=4 ttl=64 time=96.4 ms
64 bytes from 172.17.8.100: icmp_seq=5 ttl=64 time=90.5 ms
報文的分佈和很多現實事件一樣都滿足某種統計規律,比如最常用的正態分佈。因此爲了更逼近現實情況,可以使用 distribution
參數來限制它的延遲分佈模型。比如讓報文延遲時間滿足正態分佈:
# tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal
[root@node02 ~]# tc qdisc replace dev enp0s8 root netem delay 100ms 20ms distribution normal
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=119 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=102 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=115 ms
64 bytes from 172.17.8.100: icmp_seq=4 ttl=64 time=105 ms
64 bytes from 172.17.8.100: icmp_seq=5 ttl=64 time=119 ms
這樣的話,大部分的延遲會在平均值的一定範圍內,而很少接近出現最大值和最小值的延遲。
其他分佈方法包括:uniform)、pareto 和 paretonormal
,這些分佈我沒有深入去看它們的意思,感興趣的讀者可以自行了解。
對於大多數情況,隨機在某個時間範圍裏延遲就能滿足需求的。
2. 模擬丟包率
另一個常見的網絡異常是因爲丟包,丟包會導致重傳,從而增加網絡鏈路的流量和延遲。netem 的 loss
參數可以模擬丟包率,比如發送的報文有 50% 的丟包率(爲了容易用 ping 看出來,所以這個數字我選的很大,實際情況丟包率可能比這個小很多,比如 0.5%
):
[root@node02 ~]# tc qdisc change dev enp0s8 root netem loss 50%
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=0.716 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=0.713 ms
64 bytes from 172.17.8.100: icmp_seq=5 ttl=64 time=0.719 ms
64 bytes from 172.17.8.100: icmp_seq=7 ttl=64 time=0.938 ms
64 bytes from 172.17.8.100: icmp_seq=10 ttl=64 time=0.594 ms
64 bytes from 172.17.8.100: icmp_seq=11 ttl=64 time=0.698 ms
64 bytes from 172.17.8.100: icmp_seq=12 ttl=64 time=0.681 ms
可以從 icmp_seq
序號看出來大約有一半的報文丟掉了,和延遲類似,丟包率也可以增加一個相關係數,表示後一個報文丟包概率和它前一個報文的相關性:
# tc qdisc change dev eth0 root netem loss 0.3% 25%
這個命令表示,丟包率是 0.3%,並且當前報文丟棄的可能性和前一個報文 25% 相關。默認的丟包模型爲隨機,loss 也支持 state
(4-state Markov 模型) 和 gemodel
(Gilbert-Elliot 丟包模型) 兩種模型的丟包,因爲兩者都相對負責,這裏也不再介紹了。
需要注意的是,丟包信息會發送到上層協議,如果是 TCP 協議,那麼 TCP 會進行重傳,所以對應用來說看不到丟包。這時候要模擬丟包,需要把 loss 配置到王橋或者路由設備上。
3. 模擬包重複
報文重複和丟包的參數類似,就是重複率和相關性兩個參數,比如隨機產生 50% 重複的包:
[root@node02 ~]# tc qdisc change dev enp0s8 root netem duplicate 50%
[root@node02 ~]# ping 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=0.705 ms
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=1.03 ms (DUP!)
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=0.710 ms
......
4. 模擬包損壞
報文損壞和報文重複的參數也類似,比如隨機產生 2% 損壞的報文(在報文的隨機位置造成一個比特的錯誤):
# tc qdisc add dev eth0 root netem corrupt 2%
5. 模擬包亂序
網絡傳輸並不能保證順序,傳輸層 TCP 會對報文進行重組保證順序,所以報文亂序對應用的影響比上面的幾種問題要下。
報文亂序可前面的參數不太一樣,因爲上面的報文問題都是獨立的,針對單個報文做操作就行,而亂序則牽涉到多個報文的重組。模擬報亂序一定會用到延遲(因爲模擬亂序的本質就是把一些包延遲發送),netem 有兩種方法可以做。第一種是固定的每隔一定數量的報文就亂序一次:
每 5 個報文(第 5、10、15…報文)會正常發送,其他的報文延遲 100ms:
# tc qdisc change dev enp0s8 root netem reorder 50% gap 3 delay 100ms
[root@node02 ~]# ping -i 0.05 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=0.634 ms
64 bytes from 172.17.8.100: icmp_seq=4 ttl=64 time=0.765 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=102 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=100 ms
64 bytes from 172.17.8.100: icmp_seq=5 ttl=64 time=100 ms
64 bytes from 172.17.8.100: icmp_seq=7 ttl=64 time=50.3 ms
64 bytes from 172.17.8.100: icmp_seq=6 ttl=64 time=100 ms
64 bytes from 172.17.8.100: icmp_seq=8 ttl=64 time=100 ms
......
要想看到 ping 報文的亂序,我們要保證發送報文的間隔小於報文的延遲時間 100ms
,這裏用 -i 0.05
把發送間隔設置爲 50ms
。
第二種方法的亂序是相對隨機的,使用概率來選擇亂序的報文:
[root@node02 ~]# tc qdisc change dev enp0s8 root netem reorder 50% 15% delay 300ms
[root@node02 ~]# ping -i 0.05 172.17.8.100
PING 172.17.8.100 (172.17.8.100) 56(84) bytes of data.
64 bytes from 172.17.8.100: icmp_seq=1 ttl=64 time=0.545 ms
64 bytes from 172.17.8.100: icmp_seq=5 ttl=64 time=120 ms
64 bytes from 172.17.8.100: icmp_seq=2 ttl=64 time=300 ms
64 bytes from 172.17.8.100: icmp_seq=8 ttl=64 time=19.8 ms
64 bytes from 172.17.8.100: icmp_seq=3 ttl=64 time=301 ms
64 bytes from 172.17.8.100: icmp_seq=9 ttl=64 time=28.3 ms
64 bytes from 172.17.8.100: icmp_seq=4 ttl=64 time=300 ms
64 bytes from 172.17.8.100: icmp_seq=11 ttl=64 time=35.5 ms
......
50% 的報文會正常發送,其他報文(1-50%)延遲 300ms 發送,這裏選擇的延遲很大是爲了能夠明顯看出來亂序的結果。
兩個工具
netem 在 tc 中算是比較簡單的模塊,如果要實現流量控制或者精細化的過濾需要更復雜的配置。這裏推薦兩個小工具,它們共同的特點是用法簡單,能滿足特定的需求,而不用自己去倒騰 tc 的命令。
wondershaper
netem 只能模擬網絡狀況,不能控制帶寬,wondershaper 能完美解決這個問題。wondershaper 的使用非常簡單,只有三個參數:網卡名、下行限速、上行限速。比如要設置網卡下載速度爲 200kb/s,上傳速度爲 150kb/s
:
wondershaper enp0s8 200 150
comcast
comcast 是一個跨平臺的網絡模擬工具,旨在其他平臺(OSX、Windows、BSD)也提供類似網絡模擬的功能。
它的使用也相對簡單:
$ comcast --device=eth0 --latency=250 \
--target-bw=1000 --default-bw=1000000 \
--packet-loss=10% \
--target-addr=8.8.8.8,10.0.0.0/24 \
--target-proto=tcp,udp,icmp \
--target-port=80,22,1000:2000
--device
說明要控制的網卡爲eth0
--latency
指定 250ms 的延遲--target-bw
指定目標帶寬--default-bw
指定默認帶寬--packet-loss
是丟包率--target-addr
、--target-proto
、--target-port
參數指定在滿足這些條件的報文上實施上面的配置
總結
可以看出,tc 的 netem 模塊主要用來模擬各種網絡的異常狀況,本身並沒有提供寬帶限制的功能,而且一旦在網卡上配置了 netem,該網卡上所有的報文都會受影響,如果想精細地控制部分報文,需要用到 tc 的 filter 功能。