歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 在Linux上為指定IP端口模擬網絡收發包延遲

在Linux上為指定IP端口模擬網絡收發包延遲

日期:2017/2/28 14:24:21   编辑:Linux教程

  編寫網絡應用程序時,我們一般都是在網絡狀況良好的局域網甚至是本機內進行測試調試。有沒有辦法在網絡狀況良好的內網環境中,在不改動程序自身代碼的前提下,為應用程序模擬復雜的外網環境——尤其是網絡延遲呢?這是我在學校寫網絡程序時就有過的想法,只是一直沒認真研究,直到最近在公司編寫跨服代碼。

  跨服涉及多台服務器之間,還有服務器與客戶端之間的通訊,流程很復雜,其中每一步都要正確處理網絡異常延遲與斷開的情況。測試人員通過改代碼或下斷點的方式來測試網絡延遲是極麻煩的,而且能模擬的延遲用例也很有限。因此如果有一個第三方工具為應用程序使用的某個socket(IP端口)模擬網絡延遲,那測試人員應該會非常喜歡的。

  最初找到的工具有Linux自帶的tc命令(需要配合tc自帶的模塊netem)和一個第三方工具dummynet。前者概念很復雜,命令行參數也很復雜。後者跨平台,在Windows上也可用;但在Linux上安裝非常麻煩,為了編譯dummynet提供的內核模塊,需要編譯正確版本的Linux內核源代碼——我在這一步卡了很久,一直沒搞定。最終還是決定用tc。計劃的方案是用tc為服務端端口分別設置收包和發包的網絡延遲,這樣可解決tc只能工作在Linux中的問題。tc手冊和網上很多文章都提到tc只能設置發包延遲,而無法設置收包延遲。但只要配合Linux自帶的ifb(Intermediate Functional Block device)內核模塊和一點小技巧,tc就可以設置收包延遲。

  其實tc可以很簡單地為一塊網卡設置網絡延遲:

# tc qdisc add dev wlan0 root netem delay 1s

  這條命令給無線網卡wlan0發送的包設置了1秒網絡延遲。

  可以通過ping局域網中的其它機器來驗證:

# ping -c 4 192.168.1.5

PING 192.168.1.5 (192.168.1.5) 56(84) bytes of data.

64 bytes from 192.168.1.5: icmp_req=1 ttl=64 time=1002 ms

64 bytes from 192.168.1.5: icmp_req=2 ttl=64 time=1001 ms

64 bytes from 192.168.1.5: icmp_req=3 ttl=64 time=1002 ms

64 bytes from 192.168.1.5: icmp_req=4 ttl=64 time=1004 ms

---192.168.1.5 ping statistics ---

4 packets transmitted, 4 received, 0% packet loss, time 3006 ms

rtt min/avg/max/mdev = 1001.446/1002.830/1004.967/1.642 ms

  但是這樣會影響所有通過該網卡發送的包。這不是我想要的。我只想給服務器上指定的端口設置網絡延遲,不想影響其它端口,所以還是把這條延遲規則去掉吧:

# tc qdisc del dev wlan0 root

  為了只給指定的IP端口設置延遲,我們需要使用tc中的三個有點復雜的概念:qdisc(排隊規則)、class(類)和filter(過濾器)。我花了很多天才基本理解它們是如何組合在一起工作的。這裡我不打算詳細解釋這些概念(想詳細了解的可查看文末列出的參考資料),只寫下我是怎麼做的。

  假設現在本機上有兩個相互通訊的應用程序在運行,程序A在端口14100監聽,程序B和A的14100端口之間建立了TCP連接。我想在B到A的通訊方向上設置延遲,方法是在本地環回網卡的發送端設置qdisc與filter,過濾所有發給本地14100端口的包,並給這些包設置延遲。

  首先在本地環回網卡lo添加一條root qdisc:

# tc qdisc add dev lo root handle 1: prio bands 4

  這條qdisc下設4個class,handle id為1:。在沒有filter的情況下,tc從IP協議層收到的包會根據IP包頭的TOS(Type of Service)字段進入第1~第3個class(與pfifo_fast規則相同),第4個class是沒用的。現在給第4個class添加一個5秒延遲的qdisc:

# tc qdisc add dev lo parent 1:4 handle 40: netem delay 5s

  給root qdisc添加一個filter,將發給14100端口的包都送到第4個class:

# tc filter add dev lo protocol ip parent 1:0 prio 4 u32 \

match ip dport 14100 0xffff flowid 1:4

  這樣就可以了。

  如果要撤銷網絡延遲,可以把filter刪掉。先列出filter的信息:

# tc -s filter show dev lo

filter parent 1: protocol ip pref 4 u32

filter parent 1: protocol ip pref 4 u32 fh 800: ht divisor 1

filter parent 1: protocol ip pref 4 u32 fh 800::800 order 2048 key ht 800bkt 0 flowid 1:4 (rule hit 672 success 76)

match 00003714/0000ffff at 20 (success 76 )

  上面的信息顯示有76個包被filter過濾了出來,這些包都是由本地環回網卡發給14100端口的。現在刪除filter:

# tc filter del dev lo pref 4

  不過,上面的情景是兩個應用程序都在本地,因此可以通過設置環回網卡的發送端來變相控制14100端口(在環回網卡上)的收包速度。如果程序B在另一台機器上,那就需要ifb的配合了。ifb會在系統中開辟出一塊虛擬網卡。如果我們將wlan0(實際網卡)收到的包重定向到ifb,ifb就會將收到的包又發回給wlan0,最後仍然通過wlan0送給IP層,上層協議毫不知情。因此通過設置ifb的發包延遲就可以實現wlan0的收包延遲。

  為了使用ifb,首先需要載入ifb內核模塊,這個模塊在Debian 7中是自帶的:

# modprobe ifb

  通過ip命令可看到系統中多出了ifb0和ifb1兩塊網卡:

# ip link list

1:lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc prio state UNKNOWN mode DEFAULT

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2:eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000

link/ether 60:eb:69:99:66:54 brd ff:ff:ff:ff:ff:ff

3:wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mqstate UP mode DORMANT qlen 1000

link/ether 1c:65:9d:a9:db:01 brd ff:ff:ff:ff:ff:ff

10:ifb0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 32

link/ether 7a:2a:33:6b:e7:f7 brd ff:ff:ff:ff:ff:ff

11:ifb1: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 32

link/ether ce:ba:f4:38:df:6c brd ff:ff:ff:ff:ff:ff

  啟動ifb0網卡:

# ip link set ifb0 up

  確認ifb0網卡已啟動:

# ip link list

1:lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc prio state UNKNOWN mode DEFAULT

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2:eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000

link/ether 60:eb:69:99:66:54 brd ff:ff:ff:ff:ff:ff

3:wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT qlen 1000

link/ether 1c:65:9d:a9:db:01 brd ff:ff:ff:ff:ff:ff

10:ifb0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 32

link/ether 7a:2a:33:6b:e7:f7 brd ff:ff:ff:ff:ff:ff

11:ifb1: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 32

link/ether ce:ba:f4:38:df:6c brd ff:ff:ff:ff:ff:ff

  在wlan0添加ingress qdisc,即收包的排隊規則:

# tc qdisc add dev wlan0 ingress

  將wlan0收到的包重定向到ifb0:

# tc filter add dev wlan0 parent ffff: \

protocol ip u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0

  接下來像之前一樣,在ifb0的發送端設置qdisc和filter,為發送到14100端口的包設置5秒延遲:

# tc qdisc add dev ifb0 root handle 1: prio bands 4

# tc qdisc add dev ifb0 parent 1:4 handle 40: netem delay 5s

# tc filter add dev ifb0 protocol ip parent 1:0 prio 4 u32 \

match ip dport 14100 0xffff flowid 1:4

  大功告成!從頭到尾整個過程都沒有對應用程序本身做任何修改,也沒有改變網絡協議的行為,也沒有影響機器上其它正在運行的程序。

  不過這些tc命令對於測試人員來說仍然太復雜了,畢竟tc的目標用戶似乎是專業網管和系統管理員。本來只想簡單地模擬網絡延遲,卻沒想到最後發現這涉及一個很大的課題——流量控制Orz。屆時我還要把它們封裝成簡單的命令才行。

Copyright © Linux教程網 All Rights Reserved