在做网络服务器的时候,会碰到各种各样的网络问题比如说网络超时,通常一般的开发人员对于这种问题最常用的工具当然是tcpdump或者更先进的wireshark来进行抓包分析。通常这个工具能解决大部分的问题,但是比如说wireshark发现丢包,那深层次的原因就很难解释了。这不怪开发人员,要怪就怪linux网络协议栈太深。我们来看下:
network-stack

这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支持交互模式, 方便随时启动和停止观测。

使用也是很简单:

$ sudo dropwatch -l kas
Initalizing kallsymsa db
dropwatch> start
Enabling monitoring...
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
#!/usr/bin/stap
 
############################################################
# Dropwatch.stp
# Author: Neil Horman <nhorman@redhat.com>
# An example script to mimic the behavior of the dropwatch utility
############################################################
 
# Array to hold the list of drop points we find
global locations
 
# Note when we turn the monitor on and off
probe begin { printf("Monitoring for dropped packets\n") }
probe end { printf("Stopping dropped packet monitor\n") }
 
# increment a drop counter for every location we drop at
probe kernel.trace("kfree_skb") { locations[$location] <<< 1 }
 
# Every 1 seconds report our drop locations
probe timer.sec(1)
{
  printf("\n")
  foreach (l in locations-) {
    printf("%d packets dropped at %s\n",
           @count(locations[l]), symname(l))
  }
  delete locations
}

这个脚本核心的地方就在于这行:
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的代码可以清楚的看到:

//include/linux/skbuff.h
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下

$ grep -rin kfree_skb  .
./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发现丢包的时候,可以根据符号信息结合以上的源码轻松的分析出来问题所在。

小结: 工具是知识的积累。


Logo

更多推荐