程序员人生 网站导航

在Linux上为指定IP端口模拟网络收发包延迟

栏目:服务器时间:2015-01-10 08:14:09

  编写网络利用程序时,我们1般都是在网络状态良好的局域网乃至是本机内进行测试调试。有无办法在网络状态良好的内网环境中,在不改动程序本身代码的条件下,为利用程序摹拟复杂的外网环境――特别是网络延迟呢?这是我在学校写网络程序时就有过的想法,只是1直没认真研究,直到最近在公司编写跨服代码。


  跨服触及多台服务器之间,还有服务器与客户端之间的通讯,流程很复杂,其中每步都要正确处理网络异常延迟与断开的情况。测试人员通过改代码或下断点的方式来测试网络延迟是极麻烦的,而且能摹拟的延迟用例也很有限。因此如果有1个第3方工具为利用程序使用的某个socketIP端口)摹拟网络延迟,那测试人员应当会非常喜欢的。


  最初找到的工具有Linux自带的tc命令(需要配合tc自带的模块netem)和1个第3方工具dummynet。前者概念很复杂,命令行参数也很复杂。后者跨平台,在Windows上也可用;但在Linux上安装非常麻烦,为了编译dummynet提供的内核模块,需要编译正确版本的Linux内核源代码――我在这1步卡了很久,1直没弄定。终究还是决定用tc。计划的方案是用tc为服务端端口分别设置收包和发包的网络延迟,这样可解决tc只能工作在Linux中的问题。tc手册和网上很多文章都提到tc只能设置发包延迟,而没法设置收包延迟。但只要配合Linux自带的ifbIntermediate Functional Block device)内核模块和1点小技能,tc就能够设置收包延迟。


  其实tc可以很简单地为1块网卡设置网络延迟:


# 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


--⑴92.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中的3个有点复杂的概念:qdisc(排队规则)、class(类)和filter(过滤器)。我花了很多天才基本理解它们是如何组合在1起工作的。这里我不打算详细解释这些概念(想详细了解的可查看文末列出的参考资料),只写下我是怎样做的。


  假定现在本机上有两个相互通讯的利用程序在运行,程序A在端口14100监听,程序BA14100端口之间建立了TCP连接。我想在BA的通讯方向上设置延迟,方法是在本地环回网卡的发送端设置qdiscfilter,过滤所有发给本地14100端口的包,并给这些包设置延迟。


  首先在本地环回网卡lo添加1条root qdisc


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


  这条qdisc下设4classhandle id1:。在没有filter的情况下,tcIP协议层收到的包会根据IP包头的TOSType of Service)字段进入第1~第3class(与pfifo_fast规则相同),第4class是没用的。现在给第4class添加1个5秒延迟的qdisc


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


  给root qdisc添加1个filter,将发给14100端口的包都送到第4class


# 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会在系统中开辟出1块虚拟网卡。如果我们将wlan0(实际网卡)收到的包重定向到ifbifb就会将收到的包又发回给wlan0,最后依然通过wlan0送给IP层,上层协议绝不知情。因此通过设置ifb的发包延迟就能够实现wlan0的收包延迟。


  为了使用ifb,首先需要载入ifb内核模块,这个模块在Debian 7中是自带的:


# modprobe ifb


  通过ip命令可看到系统中多出了ifb0ifb1两块网卡:


# 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


  接下来像之前1样,在ifb0的发送端设置qdiscfilter,为发送到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的目标用户似乎是专业网管和系统管理员。本来只想简单地摹拟网络延迟,却没想到最后发现这触及1个很大的课题――流量控制Orz。届时我还要把它们封装成简单的命令才行。


参考资料


  1. Network emulation (by Wikipedia)

  2. man tc

  3. man netem

  4. netem(by Linux Foundation)。这篇文章讲到了如作甚指定的IP设置网络延迟,和如何用ifb设置收包延迟。

  5. Linux Advanced Routing & Traffic Control HOWTO。比较相干的是第3Introduction to iproute2、第9Queueing Disciplines for Bandwidth Management(其中讲到的IMQ就是ifb的前身),和第12Advanced filters for (re-)classifying packets

  6. Deleting filters in tc

  7. Linux TCifb原理和ingress流控

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐