使用 tc netem 模擬網絡異常

原文鏈接:https://cizixs.com/2017/10/23/tc-netem-for-terrible-network/

文章轉自:使用 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:分佈,延遲的分佈模式,可以選擇的值有 uniformnormalpareto 和 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 功能。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章