HACK#27 Network Drop Monitor

本节介绍dropwatch的使用方法,并以UDP为例介绍网络的调整方法。

从Linux 2.6.30开始具有Network Drop Monitor(dropwatch)功能。

dropwatch是监控内核的网络栈丢弃的数据包。

接收数据包后,数据从网络驱动程序传输到内核的网络层。在这里进行校验和或IP地址的确认等数据包验证处理。在这个过程中,如果是非法数据包,则依据RFC丢弃,即使是合法的数据包,在超过接收缓冲区大小等情况也会有意识地丢弃。

由于上述这些理由,接收数据包在内核内丢弃。这些不会在日志等中输出,因此一般难以发现。

如果是非法数据包,那么丢弃也没问题,但如果内存有空闲却因接收缓冲区较小而在网络负载较高时丢弃大量数据包,就需要通过调整来进行改善。

本节以UDP的网络负载为例,使用dropwatch来确认丢弃数据包的情况。使用proc文件系统调整接收缓冲区的大小,并使用dropwatch来确认调整结果。使用的操作系统为RHEL6。

dropwatch的使用方法

使用上游内核时,需要启用Network packet drop alerting service(NET_DROP_MONITOR=y)并编译内核。

dropwatch是使用Kernel Tracepoint API安装的。具体来说,就是各协议释放socket缓冲区时(kfree_skb函数)收集信息。可以应对IP、ARP、ICMP、IGMP、UDP协议的数据包等。

因此,即使不使用proc文件系统或工具针对各个协议收集信息,也可以通过dropwatch将数据集中到一起进行确认。

dropwatch是作为内核功能使用,因此不需要为了调查潜在的网络性能而改变已有的应用程序。便于使用也是其优点之一。

下面开始实际使用dropwatch。无须进行特别的设置。执行dropwatch命令,显示出dropwatch和提示符后,输入start。


dropwatch

Initalizing null lookup method

dropwatch>start<—-输入start

Enabling monitoring……

Kernel monitoring activated.

Issue Ctrl-C to stop monitoring

1 drops at location 0xffffffff81433719

16 drops at location 0xffffffff81440849

4 drops at location 0xffffffff81440849

4 drops at location 0xffffffff81440849

4 drops at location 0xffffffff81440849

1 drops at location 0xffffffff8149a8ed

1 drops at location 0xffffffff8149a8ed

……


使用Ctrl+C返回提示符。输入stop或exit就可以结束。最左边的数值就是废弃的数据包数。location后面的数值是内核内的地址。在-l选项中使用/proc/kallsyms,将地址转换成函数名输出。按照下列方法执行-l命令。kas为kernel all symbols的缩写。


dropwatch-l kas

Initalizing kallsyms db

dropwatch>start

Enabling monitoring……

Kernel monitoring activated.

Issue Ctrl-C to stop monitoring

12 drops at ip_rcv_finish+199—-①

2 drops at ip_forward+288

1 drops at ip_forward+288

5 drops at netlink_broadcast+180

4 drops at ip_rcv_finish+199

1 drops at igmp_rcv+cb

8 drops at ip_rcv_finish+199

1 drops at__brk_limit+1e8a3284

……


①表示在ip_rcvz-finish函数的偏移量0×199中检测出废弃了12个数据包。这个信息为每隔1秒输出一次,或者在废弃的数据包数达到64时输出。在当前已安装的版本中,这些最小值是不能改变的。

ip_rcv_finish函数的偏移量0×194是下列代码中的①,实际检测出的内容为上一行的ip_rcv_finishi+0×194<kfree_skb>。

例4-5<内核2.6.32-71.el6.×86_64>


0xffffffff8144082d<ip_rcv_finish+0x17d>:mov 0xe0(%rax),%rax;

0 x f f f f f f f f 8 1 4 4 0 8 3 4<i p_r c v_f i n i s h+0 x 1 8 4>:a d d

-0x7e767c40(,%rdx,8),%rax

0xffffffff8144083c<ip_rcv_finish+0x18c>:addq$0x1,0x40(%rax)

0xffffffff81440841<ip_rcv_finish+0x191>:mov%rbx,%rdi

0xffffffff81440844<ip_rcv_finish+0x194>:callq 0xffffffff81405560

<kfree_skb>

0xffffffff81440849<ip_rcv_finish+0x199>:mov$0x1,%eax—-①

0xffffffff8144084e<iprcv_finish+0x19e>:jmp 0xffffffff814407dd<ip

rcv_finish+0x12d>

0xffffffff81440850<ip_rcv_finish+0x1a0>:mov 0xcc(%rbx),%ecx


具体调整的例

在大量发送UDP数据包的测试程序udp-stress.c中进行具体的验证。

例4-6<udp-stress.c>


include<stdio.h>

include<string.h>

include<arpa/inet.h>

include<unistd.h>

define DADDR"192.168.0.112"

define PORT 5000

int

main(int argc, char**argv)

{

int sock=0;

struct sockaddr_in dest;

char tmpbuf[]={"stress"};

sock=socket(PF_INET, SOCK_DGRAM,0);

if(!sock){

perror("socket");

return 1;

}

dest.sin_family=AF_INET;

dest.sin_port=htons(PORT);

dest.sin_addr.s_addr=inet_addr(DADDR);

if(connect(sock,(struct sockaddr*)&dest, sizeof(dest))==-1){

perror("connect");

return 1;

}

while(1)

sendto(sock, tmpbuf, strlen(tmpbuf)+1,0,

(struct sockaddr*)&dest, sizeof(dest));

close(sock);

return 0;

}


编译udp-stress.c。


gcc-Wall udp-stress.c-o udp-stress


在接收UDP数据包的机器(RHEL6)上执行nc命令。nc命令只是接收UDP的数据包并显示数据的程序。


nc-u-l 192.168.0.112 5000


对执行nc命令的服务器机器执行udp-stress,并发送UDP数据包。在服务器机器上执行dropwatch,在笔者的环境下首先会输出如下内容,并未能进行UDP通信。


64 drops at nf_hook_slow+e0

14 drops at ip_rcv_finish+176

64 drops at nf_hook_slow+e0

2 drops at ip_rcv_finish+176

64 drops at nf_hook_slow+e0

64 drops at nf_hook_slow+e0

8 drops at ip_rcv_finish+176

64 drops at nf_hook_slow+e0

64 drops at nf_hook_slow+e0

1 drops at unix_stream_recvmsg+2f3

6 drops at ip_rcv_finish+176

64 drops at nf_hook_slow+e0


nf_hook_slow是netfilter的函数。这表示根据DROP的规则已废弃。

下面暂时删除所有iptables的规则。


iptables-F


这样就不会输出nf_hook_slow+e0。也就是说,由于删除了netfilter的规则,因此nf_hook_slow+e0不存在数据包丢失。

这样就可以进行UDP通信,因此再次执行dropwatch。


2 drops at ip_forward+288

6 drops at ip_rcv_finish+199

2 drops at ip_rcv_finish+199

1 drops at ip_forward+288

1 drops at unix_stream_recvmsg+30d

1 drops at unix_stream_recvmsg+30d

4 drops at ip_rcv_finish+199

6 drops at ip_rcv_finish+199

2 drops at unix_stream_recvmsg+30d

14 drops at ip_rcv_finish+199

64 drops at__udp_queue_rcv_skb+79<—-执行udp-stress的时刻

1 drops at unix_stream_recvmsg+30d

64 drops at__udp_queue_rcv_skb+79

4 drops at ip_rcv_finish+199

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

64 drops at__udp_queue_rcv_skb+79

1 drops at igmp_rcv+cb

64 drops at__udp_queue_rcv_skb+79


从执行udp-stressd的时刻开始,输出变成64 drops at__udp_queue_rcv_skb+79。这就是下列代码中的。


0xffffffff81467691<__udp_queue_rcv_skb+0x61>:movslq%edx,%rdx

0xffffffff81467694<__udp_queue_rcv_skb+0x64>:mov%r12,%rdi

0xffffffff81467697<__udp_queue_rcv_skb+0x67>:add-0x7e767c40(,%rdx,8),%rax

0xffffffff8146769f<__udp_queue_rcv_skb+0x6f>:addq$0x1,0x18(%rax)

0xffffffff814676a4<__udp_queue_rcv_skb+0x74>:callq 0xffffffff81405560<kfree_skb>

0xffffffff814676a9<__udp_queue_rcv_skb+0x79>:mov$0xffffffff,%eax—-

0xffffffff814676ae<udp_queue_rcv_skb+0x7e>:jmp 0xffffffff81467662<udp_queue_rcv_skb+0x32>


通过__udp_queue_rcv_skb()调出kfree_skb()的只有一处(②)。要让②通过编译,③处的sock_queue_rcv_skb()必须是个错误。

例4-7<net/ipv4/udp.c>


static int__udp_queue_rcv_skb(struct socksk, struct sk_buffskb)

{

int is_udplite=IS_UDPLITE(sk);

int rc;

if((rc=sock_queue_rcv_skb(sk, skb))<0){—-③

/Note that an ENOMEM error is charged twice/

if(rc==-ENOMEM){

UDP_INC_STATS_BH(sock_net(sk),UDP_MIB_RCVBUFERRORS,


is_udplite);


atomic_inc(&sk->sk_drops);

}

goto drop;

}

return 0;

drop:

UDP_INC_STATS_BH(sock_net(sk),UDP_MIB_INERRORS, is_udplite);

kfree_skb(skb);—-②

return-1;

}


使用SystemTap确认sock_queue_rcv_skb()返回的地方,结果为③。<net/core/sock.c>


281 int sock_queue_rcv_skb(struct socksk, struct sk_buffskb)

282{

283 int err=0;

284 int skb_len;

285 unsigned long flags;


HACK#27 Network Drop Monitor - 图1

sk_rcvbuf就是/proc/sys/net/core/rmem_default,在这里不作详细说明。初始值为124 928。


cat/proc/sys/net/core/rmem_default

124928


如果增大这个值,废弃的数据包就会减少,因此可以尝试进行下列设置。


echo 4194304>/proc/sys/net/core/rmem_default


这时如果执行udp-stress,就不会输出__udp_queue_rcv_skb+79。