在做网络服务器的时候,会碰到各种各样的网络问题比如说网络超时,通常一般的开发人员对于这种问题最常用的工具当然是tcpdump或者更先进的wireshark来进行抓包分析。通常这个工具能解决大部分的问题,但是比如说wireshark发现丢包,那深层次的原因就很难解释了。这不怪开发人员,要怪就怪linux网络协议栈太深。我们来看下:
这7层里面每个层都可能由于各种各样的原因,比如说缓冲区满,包非法等,把包丢掉,这样的问题就需要特殊的工具来发现了。 好了,主角dropwatch出场.
它的官方网站在这里
What is Dropwatch
Dropwatch is a project I am tinkering with to improve the visibility developers and sysadmins have into the Linux networking stack. Specifically I am aiming to improve our ability to detect and understand packets that get dropped within the stack.
Dropwatch定位很清晰,就是用来查看协议栈丢包的问题。
RHEL系的系统安装相当简单,yum安装下就好:
$ uname -r
2.6.32-131.21.1.tb477.el6.x86_64
$ sudo yum install dropwatch
man dropwatch下就可以得到使用的帮助,dropwatch支持交互模式, 方便随时启动和停止观测。
使用也是很简单:
Kernel monitoring activated. |
Issue Ctrl-C to stop monitoring |
1 drops at netlink_unicast+251 |
15 drops at unix_stream_recvmsg+32a |
3 drops at unix_stream_connect+1dc |
-l kas的意思是获取drop点的符号信息,这样的话针对源码就可以分析出来丢包的地方。
同学们可以参考这篇文章(Using netstat and dropwatch to observe packet loss on Linux servers):http://prefetch.net/blog/index.php/2011/07/11/using-netstat-and-dropwatch-to-observe-packet-loss-on-linux-servers/
那他的原理是什么呢?在解释原理之前,我们先看下这个工具的对等的stap脚本:
$ cat /usr/share/doc/systemtap-1.6/examples/network/dropwatch.stp |
probe begin { printf ( "Monitoring for dropped packets\n" ) } |
probe end { printf ( "Stopping dropped packet monitor\n" ) } |
probe kernel.trace( "kfree_skb" ) { locations[$location] <<< 1 } |
foreach (l in locations-) { |
printf ( "%d packets dropped at %s\n" , |
@count(locations[l]), symname(l)) |
这个脚本核心的地方就在于这行:
probe kernel.trace(“kfree_skb”) { locations[$location] <<< 1 }
当kfree_skb被调用的时候,内核就记录下这个drop协议栈的位置,同时透过netlink通知dropwatch用户态的部分收集这个位置,同时把它整理显示出来.以上就是dropwatch的工作流程。
现在的问题是内核什么地方,什么时候会调用kfree_skb这个函数呢? 我们继续追查下:
dropwatch需要对内核打patch,当然RHEL5U4以上的内核都已经打了patch。
patch在这里下载:https://fedorahosted.org/releases/d/r/dropwatch/dropwatch_kernel_patches.tbz2
通过查看里面的5个patch,我们知道drop主要修改了以下几个文件:
include/linux/netlink.h
include/trace/skb.h
net/core/Makefile
net/core/net-traces.c
include/linux/skbuff.h
net/core/datagram.c
include/linux/net_dropmon.h
net/core/drop_monitor.c
include/linux/Kbuild
net/Kconfig
net/core/Makefile
我们透过RHEL5U4的代码可以清楚的看到:
extern void kfree_skb( struct sk_buff *skb); |
extern void consume_skb( struct sk_buff *skb); |
这些patch的作用是使得支持dropwatch的内核把kfree_skb分成二类: 1. 人畜无害的调用consume_skb 2. 需要丢包的调用kfree_skb 同时提供基础的netlink通信往用户空间传递位置信息。
知道了这点,我们在RHEL 5U4的源码目录下: linux-2.6.18.x86_64/net/ipv4 或者 linux-2.6.18.x86_64/net/core下 grep下
./tcp_input.c:3122: __kfree_skb(skb); |
./tcp_input.c:3219: __kfree_skb(skb); |
./tcp_input.c:3234: __kfree_skb(skb); |
./tcp_input.c:3318: __kfree_skb(skb); |
./ip_fragment.c:166:static __inline__ void frag_kfree_skb(struct sk_buff *skb, int *work) |
./ip_fragment.c:171: kfree_skb(skb); |
./ip_fragment.c:211: frag_kfree_skb(fp, work); |
./ip_fragment.c:452: frag_kfree_skb(fp, NULL); |
./ip_fragment.c:578: frag_kfree_skb(free_it, NULL); |
./ip_fragment.c:607: kfree_skb(skb); |
./ip_fragment.c:732: kfree_skb(skb); |
./udp.c:1028: kfree_skb(skb); |
./udp.c:1049: kfree_skb(skb); |
./udp.c:1069: kfree_skb(skb); |
./udp.c:1083: kfree_skb(skb); |
./udp.c:1134: kfree_skb(skb1); |
./udp.c:1229: kfree_skb(skb); |
./udp.c:1242: kfree_skb(skb); |
./udp.c:1258: kfree_skb(skb); |
./udp.c:1418: kfree_skb(skb); |
./ip_sockglue.c:286: kfree_skb(skb); |
./ip_sockglue.c:322: kfree_skb(skb); |
./ip_sockglue.c:398: kfree_skb(skb); |
./devinet.c:1140: kfree_skb(skb); |
./xfrm4_ninput.c:29: kfree_skb(skb); |
./xfrm4_ninput.c:122: kfree_skb(skb); |
./tunnel4.c:85: kfree_skb(skb); |
./icmp.c:47: * and moved all kfree_skb() up to |
./icmp.c:1046: kfree_skb(skb); |
./ip_forward.c:121: kfree_skb(skb); |
./netfilter.c:73: kfree_skb(*pskb); |
./netfilter.c:114: kfree_skb(*pskb); |
./netfilter/ip_queue.c:277: kfree_skb(skb); |
./netfilter/ip_queue.c:335: kfree_skb(nskb); |
./netfilter/ip_queue.c:373: kfree_skb(e->skb); |
./netfilter/ip_queue.c:556: kfree_skb(skb); |
./netfilter/ipt_TCPMSS.c:153: kfree_skb(*pskb); |
./netfilter/ipt_ULOG.c:435: kfree_skb(ub->skb); |
./tcp.c:1458: kfree_skb(skb); |
./tcp.c:1577: __kfree_skb(skb); |
./ip_gre.c:482: kfree_skb(skb2); |
./ip_gre.c:497: kfree_skb(skb2); |
./ip_gre.c:504: kfree_skb(skb2); |
./ip_gre.c:892: dev_kfree_skb(skb); |
./raw.c:244: kfree_skb(skb); |
./raw.c:254: kfree_skb(skb); |
./raw.c:329: kfree_skb(skb); |
./tcp_ipv4.c:1039: kfree_skb(skb); |
./tcp_ipv4.c:1151: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:213: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:290: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:374: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:378: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:423: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:480: kfree_skb(skb); |
./ipvs/ip_vs_xmit.c:553: dev_kfree_skb(skb); |
./ipvs/ip_vs_core.c:197: kfree_skb(*pskb); |
./ipvs/ip_vs_core.c:829: kfree_skb(*pskb); |
./ipconfig.c:504: kfree_skb(skb); |
./ipconfig.c:1019: kfree_skb(skb); |
./xfrm4_input.c:50: kfree_skb(skb); |
./xfrm4_input.c:162: kfree_skb(skb); |
./fib_semantics.c:292: kfree_skb(skb); |
./ipip.c:414: kfree_skb(skb2); |
./ipip.c:429: kfree_skb(skb2); |
./ipip.c:436: kfree_skb(skb2); |
./ipip.c:444: kfree_skb(skb2); |
./ipip.c:458: kfree_skb(skb2); |
./ipip.c:482: kfree_skb(skb); |
./ipip.c:609: dev_kfree_skb(skb); |
./ipip.c:615: dev_kfree_skb(skb); |
./ipip.c:654: dev_kfree_skb(skb); |
./ipmr.c:185: kfree_skb(skb); |
./ip_output.c:763: kfree_skb(skb); |
./ip_output.c:964: kfree_skb(skb); |
./ip_output.c:1313: kfree_skb(skb); |
我们可以看到一堆可以丢包的点。
当我们观察到dropwatch发现丢包的时候,可以根据符号信息结合以上的源码轻松的分析出来问题所在。
小结: 工具是知识的积累。