tcpdump使用技巧

一般情况下,非HTTP协议的网络分析,在服务器端用tcpdump比较多,在客户端用wireshark比较多,两个抓包软件的语法是一样的。

一、基本语法

1、过滤主机

  • 抓取所有经过eth1,目的或源地址是192.168.1.1的网络数据
tcpdump -i eth1 host 192.168.1.1
  • 指定源地址
tcpdump -i eth1 src host 192.168.1.1
  • 指定目的地址
tcpdump -i eth1 dst host 192.168.1.1

2、过滤端口

  • 抓取所有经过eth1,目的或源端口是25的网络数据
tcpdump -i eth1 port 25
  • 指定源端口
tcpdump -i eth1 src port 25

指定目的端口

tcpdump -i eth1 dst port 25

3、网络过滤

tcpdump -i eth1 net 192.168
tcpdump -i eth1 src net 192.168
tcpdump -i eth1 dst net 192.168

4、协议过滤

tcpdump -i eth1 arp
tcpdump -i eth1 ip
tcpdump -i eth1 tcp
tcpdump -i eth1 udp
tcpdump -i eth1 icmp

5、常用表达式

非 : ! or "not" (去掉双引号)  且 : && or "and"  或 : || or "or"
  • 抓取所有经过eth1,目的地址是192.168.1.254或192.168.1.200端口是80的TCP数据
tcpdump -i eth1 '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.200)))'
  • 抓取所有经过eth1,目标MAC地址是00:01:02:03:04:05的ICMP数据
tcpdump -i eth1 '((icmp) and ((ether dst host 00:01:02:03:04:05)))'
  • 抓取所有经过eth1,目的网络是192.168,但目的主机不是192.168.1.200的TCP数据
tcpdump -i eth1 '((tcp) and ((dst net 192.168) and (not dst host 192.168.1.200)))'

二、高级包头过滤

首先了解如何从包头过滤信息

proto[x:y]          : 过滤从x字节开始的y字节数。比如ip[2:2]过滤出3、4字节(第一字节从0开始排)proto[x:y] & z = 0  : proto[x:y]和z的与操作为0proto[x:y] & z !=0  : proto[x:y]和z的与操作不为0proto[x:y] & z = z  : proto[x:y]和z的与操作为zproto[x:y] = z      : proto[x:y]等于z

操作符 : >, <, >=, <=, =, !=

1、IP头

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |Version|  IHL  |Type of Service|          Total Length         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |         Identification        |Flags|      Fragment Offset    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Time to Live |    Protocol   |         Header Checksum       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                       Source Address                          |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                    Destination Address                        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                    Options                    |    Padding    | <-- optional +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                            DATA ...                           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

本文只针对IPv4。

2、IP选项设置了吗?

“一般”的IP头是20字节,但IP头有选项设置,不能直接从偏移21字节处读取数据。IP头有个长度字段可以知道头长度是否大于20字节。

 +-+-+-+-+-+-+-+-+
 |Version|  IHL  |
 +-+-+-+-+-+-+-+-+

通常第一个字节的二进制值是:01000101,分成两个部分:

0100 = 4 表示IP版本 0101 = 5 表示IP头32 bit的块数,5 x 32 bits = 160 bits or 20 bytes

如果第一字节第二部分的值大于5,那么表示头有IP选项。

下面介绍两种过滤方法(第一种方法比较操蛋,可忽略):

a. 比较第一字节的值是否大于01000101,这可以判断IPv4带IP选项的数据和IPv6的数据。

01000101十进制等于69,计算方法如下(小提示:用计算器更方便)

0 : 0  1 : 2^6 = 64  第一部分 (IP版本)0 : 0   /0 : 0  /-0 : 0  1 : 2^2 = 4   第二部分 (头长度)0 : 0   /1 : 2^0 = 1 /

64 + 4 + 1 = 69

如果设置了IP选项,那么第一自己是01000110(十进制70),过滤规则:

tcpdump -i eth1 'ip[0] > 69'

IPv6的数据也会匹配,看看第二种方法。

b. 位操作

0100 0101 : 第一字节的二进制
0000 1111 : 与操作
<=========
0000 0101 : 结果

正确的过滤方法

tcpdump -i eth1 'ip[0] & 15 > 5'

或者

tcpdump -i eth1 'ip[0] & 0x0f > 5'

3、分片标记

当发送端的MTU大于到目的路径链路上的MTU时就会被分片,这段话有点拗口,权威的请参考《TCP/IP详解》。唉,32借我的书没还,只能凑合写,大家记得看书啊。

分片信息在IP头的第七和第八字节:

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |Flags|      Fragment Offset    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Bit 0: 保留,必须是0
Bit 1: (DF) 0 = 可能分片, 1 = 不分片
Bit 2: (MF) 0 = 最后的分片, 1 = 还有分片

Fragment Offset字段只有在分片的时候才使用。

要抓带DF位标记的不分片的包,第七字节的值应该是:

01000000 = 64

tcpdump -i eth1 'ip[6] = 64'

4、抓分片包

  • 匹配MF,分片包
tcpdump -i eth1 'ip[6] = 32'

最后分片包的开始3位是0,但是有Fragment Offset字段。

  • 匹配分片和最后分片
tcpdump -i eth1 '((ip[6:2] > 0) and (not ip[6] = 64))'

测试分片可以用下面的命令:

ping -M want -s 3000 192.168.1.1

5、匹配小TTL

TTL字段在第九字节,并且正好是完整的一个字节,TTL最大值是255,二进制为11111111。

可以用下面的命令验证一下:

$ ping -M want -s 3000 -t 256 192.168.1.200
ping: ttl 256 out of range
 +-+-+-+-+-+-+-+-+
 |  Time to Live |
 +-+-+-+-+-+-+-+-+
  • 在网关可以用下面的命令看看网络中谁在使用traceroute
tcpdump -i eth1 'ip[8] < 5'

6、抓大于X字节的包

  • 大于600字节
tcpdump -i eth1 'ip[2:2] > 600'

7、更多的IP过滤

首先还是需要知道TCP基本结构,再次推荐《TCP/IP详解》,卷一就够看的了,避免走火入魔。

  • TCP头
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |          Source Port          |       Destination Port        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        Sequence Number                        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                    Acknowledgment Number                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Data |       |C|E|U|A|P|R|S|F|                               |
 | Offset|  Res. |W|C|R|C|S|S|Y|I|            Window             |
 |       |       |R|E|G|K|H|T|N|N|                               |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Checksum            |         Urgent Pointer        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                    Options                    |    Padding    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                             data                              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 抓取源端口大于1024的TCP数据包
tcpdump -i eth1 'tcp[0:2] > 1024'
  • 匹配TCP数据包的特殊标记

TCP标记定义在TCP头的第十四个字节

 +-+-+-+-+-+-+-+-+
 |C|E|U|A|P|R|S|F|
 |W|C|R|C|S|S|Y|I|
 |R|E|G|K|H|T|N|N|
 +-+-+-+-+-+-+-+-+

重复一下TCP三次握手,两个主机是如何勾搭的:

  1. 源发送SYN
  2. 目标回答SYN, ACK
  3. 源发送ACK

没女朋友的童鞋要学习一下:

  1. MM,你的手有空吗?–
  2. 有空,你呢?~~
  3. 我也有空 _

失败的loser是酱紫的:

  1. MM,这是你掉的板砖吗?(SYN)  ̄▽ ̄
  2. 不是,找拍啊?(RST-ACK) ˋ﹏ˊ
  • 只抓SYN包,第十四字节是二进制的00000010,也就是十进制的2
tcpdump -i eth1 'tcp[13] = 2'
  • 抓SYN, ACK (00010010 or 18)
tcpdump -i eth1 'tcp[13] = 18'
  • 抓SYN或者SYN-ACK
tcpdump -i eth1 'tcp[13] & 2 = 2'

用到了位操作,就是不管ACK位是啥。

  • 抓PSH-ACK
tcpdump -i eth1 'tcp[13] = 24'
  • 抓所有包含FIN标记的包(FIN通常和ACK一起,表示幽会完了,回见)
tcpdump -i eth1 'tcp[13] & 1 = 1'
  • 抓RST(勾搭没成功,伟大的greatwall对她认为有敏感信息的连接发RST包,典型的棒打鸳鸯)
tcpdump -i eth1 'tcp[13] & 4 = 4'

8、大叔注

tcpdump考虑了一些数字恐惧症者的需求,提供了部分常用的字段偏移名字:

icmptype (ICMP类型字段)
icmpcode (ICMP符号字段)
tcpflags (TCP标记字段)

ICMP类型值有:

icmp-echoreply, icmp-unreach, icmp-sourcequench, icmp-redirect, icmp-echo, icmp-routeradvert, icmp-routersolicit, icmp-timxceed, icmp-paramprob, icmp-tstamp, icmp-tstampreply, icmp-ireq, icmp-ireqreply, icmp-maskreq, icmp-maskreply

TCP标记值:

tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-push, tcp-ack, tcp-urg

这样上面按照TCP标记位抓包的就可以写直观的表达式了:

  • 只抓SYN包
tcpdump -i eth1 'tcp[tcpflags] = tcp-syn'
  • 抓SYN, ACK
tcpdump -i eth1 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack != 0'

9、抓SMTP数据

tcpdump -i eth1 '((port 25) and (tcp[(tcp[12]>>2):4] = 0x4d41494c))'

抓取数据区开始为”MAIL”的包,”MAIL”的十六进制为0x4d41494c。

10、抓HTTP GET数据

tcpdump -i eth1 'tcp[(tcp[12]>>2):4] = 0x47455420'

“GET “的十六进制是47455420

11、抓SSH返回

tcpdump -i eth1 'tcp[(tcp[12]>>2):4] = 0x5353482D'

“SSH-“的十六进制是0x5353482D

tcpdump -i eth1 '(tcp[(tcp[12]>>2):4] = 0x5353482D) and (tcp[((tcp[12]>>2)+4):2] = 0x312E)'

抓老版本的SSH返回信息,如”SSH-1.99..”

三、大叔注

如果是为了查看数据内容,建议用tcpdump -s 0 -w filename把数据包都保存下来,然后用wireshark的Follow TCP Stream/Follow UDP Stream来查看整个会话的内容。

-s 0是抓取完整数据包,否则默认只抓68字节。

另外,用tcpflow也可以方便的获取TCP会话内容,支持tcpdump的各种表达式。

1、UDP头

  0      7 8     15 16    23 24    31
 +--------+--------+--------+--------+
 |     Source      |   Destination   |
 |      Port       |      Port       |
 +--------+--------+--------+--------+
 |                 |                 |
 |     Length      |    Checksum     |
 +--------+--------+--------+--------+
 |                                   |
 |              DATA ...             |
 +-----------------------------------+
  • 抓DNS请求数据
tcpdump -i eth1 udp dst port 53

2、其他

-c参数对于运维人员来说也比较常用,因为流量比较大的服务器,靠人工CTRL+C还是抓的太多,甚至导致服务器宕机,于是可以用-c参数指定抓多少个包。

time tcpdump -nn -i eth0 'tcp[tcpflags] = tcp-syn' -c 10000 > /dev/null

上面的命令计算抓10000个SYN包花费多少时间,可以判断访问量大概是多少。

tcpdump学习与实践

在分析非HTTP协议网络时,在服务端一般使用tcpdump,客户端是wireshark(有一本书《wireshark网络分析就是这么简单》),利用空闲时间学习一下如何使用。

为了结合实践,这里用了swoole_server的一个简单的echo服务器。

<?php
$serv = new swoole_server("0.0.0.0", 9501);

$port = $serv->listen('127.0.0.1', 9501, SWOOLE_SOCK_UDP);
$port->on('packet', function($serv, $data, $addr){
    var_dump($serv, $data, $addr);
});

$serv->on('connect', function ($serv, $fd, $from_id){
   echo "[#".posix_getpid()."]tClient@[$fd:$from_id]: Connect.n";
});

$serv->on('receive', function (swoole_server $serv, $fd, $from_id, $data) {
   echo "[#".$serv->worker_id."]tClient[$fd]: $datan";
    if ($serv->send($fd, "hellon") == false)
    {
        echo "errorn";
    }
});

$serv->on('close', function ($serv, $fd, $from_id) {
   echo "[#".posix_getpid()."]tClient@[$fd:$from_id]: Close.n";
});

$serv->start();

客户端使用telnet

$ telnet 127.0.0.1 9501

实验开始

(由于在命令行下操作,所以需要启动多个窗口)

窗口1:

$ php echo.php # 开启服务

窗口2:

$ sudo tcpdump -i any tcp port 9501 # 开启tcpdump

窗口3:

$ telnet 127.0.0.1 9501 # 客户端连接

观察窗口2:

首先,tcpdump需要在root用户下运行,
-i 参数制定了网卡,any表示所有网卡,监听tcp协议,端口为9501
可以发现,倒数三行是一个完整的TCP三次握手,
客户端发送SYN[S]
服务端回答SYN,ACK[S.]
客户端发送ACK[.]

列参数含义

  • 11:24:18.062098 时间带有精确到微妙
  • localhost.8816 > localhost.9501 表示通信的流向,8816是客户端,9501是服务器端
  • [S] 表示这是一个SYN请求
  • [.] 表示这是一个ACK确认包,(client)SYN->(server)SYN->(client)ACK 就是3次握手过程
  • [P] 表示这个是一个数据推送,可以是从服务器端向客户端推送,也可以从客户端向服务器端推
  • [F] 表示这是一个FIN包,是关闭连接操作,client/server都有可能发起
  • [R] 表示这是一个RST包,与F包作用相同,但RST表示连接关闭时,仍然有数据未被处理。可以理解为是强制切断连接
  • win 342是指滑动窗口大小
  • length 12指数据包的大小

发送数据

客户端 》服务端

未分类

服务端 》客户端

未分类

关闭连接(客户端主动关闭,四次挥手)

未分类

关闭连接(服务端退出,三次挥手)

未分类

tcpdump抓包实战分享

网络抓包,是运维工程师很一项重要,也需要的技能。但实现生活中却少有运维工程师能掌握。因为一来学习比较难,二来工作中用得少,一段时间没用就忘光光了~~

但是这个技能可以大大减少运维人员背锅的机率。有好几次,开发都说我:我的代码没问题,是请求没有进来; 或是说网络有问题;或者说是你负载均衡转发时把参数搞丢了;等*4。最后都被我用抓包技能成功反杀。也就是说在实际工作中遇到某些问题,它是可以做为解决问题的一种手段!
我相信,应该有些朋友在遇到问题时,也想过要抓包,但是却不知道从哪里入手好,希望听过这个分享后,能帮你形成一个思路。

至于抓包的工具,在windows平台下,一般我用wireshark,这个工具很叼的,抓了包后还能帮你把数据包整理得一清二楚,GUI界面的用起来也比较方便。而linux下的一般用的是tcpdump,这个工具也是非常叼的,不过它是个命令行工具,用起来可不是一般地难,但是做为运维工程师,面对最多的系统就是linux了。所以今天的分享,不难的就算了,要讲就讲点有难度的。所以这次分享用的就是tcpdump。又因为是实战性的分享,所以就没有那么多的文字材料可以给大家,不过分享之后,我回头会做个总结性的东西放共享里,方便大家记忆。目前群空间里有两个本次分享要用到的图片,可以先下载一来。一边听一边看最好了。

然后我会以http流量为示例进行抓包讲解,因为在实际工作环境下,需要用到抓包分析的,十有八九是http流量。了解所以然后,其实其他类型的流量均是换汤不换药的。

OK。用tcpdump抓包,有两个东西要先学。一个是工作参数(废话),另一个是BPF(全称Berkeley Packet Filter 伯克利封包过滤器,没错,就是著名的加州大学伯克利分校,创造的一种专门过滤网络数据包的过滤语言)

参数多的不讲,讲几个实用的。大家记住就好。

  • -X:很重要:以16进制和ASCII码显示包数据。现场分析数据包内容必备

  • -nnn:比较重要:让tcpdump不要把IP地址转化为网络名称,不要把端口显示为服务名

  • -s0:一般重要:抓包最好带上,-s指定数据包截断的长度,0表示不截断,tcpdump默认截断68字节

  • -i :一般重要 :指定要抓包的网络接口,eth0/eth1/wlan0/lo等等,BPF明明没写错,就是抓不到包,就要注意一下这里是不是写错了。还有,强调一点127.0.0.1是属于lo环回接口

  • -w:记得就好:保存到文件里,可以后期结合wireshark来分析。屏幕不会输出。

  • -c:记得就好:-c 10 抓满10个包就退出

(这时可以现场演示一下各个参数)

BPF:

讲BPF包过滤语言前,大家先了解一下网络五元组是什么。源IP地址,目的IP地址,源端口,目的端口,协议 这五个。网络请求的发起方(源),用一个随机端口去连接远端计算机一个具体的端口,远端计算机响应此请求时,会把源IP端口和目的IP端口反过来,把响应报文发回去。

有几点要先让大家记得的:

  • BPF支持加减乘除,包括移位运算
  • BPF支持and(&&) or(||) not(!)逻辑运算
  • BPF可以用括号等改变运算顺序
  • 使用时请用单引号包起来防止shell误读

OK。进入正式BPF内容:

请住以下三段中的单词或字母:

一,tcp(tcp协议) udp(udp协议) icmp(icmp协议,就是ping用的)
二,src(源source缩写) dst(目的destination缩写)
三,host(主机IP/网络名) port(端口) portrange(端口范围) net(网段)

这三段可以自由组合,但是要注意,
一是协议类型放在第一位置。二是icmp协议没有端口一说。
比如tcp src host 192.168.0.1 ,表示源IP地址为192.168.0.1的TCP数据包
udp dst port 53,表示发到远端服务器53端口(目的端口为53)的udp数据包
icmp src 192.168.1.0/24,表示来自(源地址)为192.168.1.0网段(192.168.1.0~192.168.1.255)的ICMP数据包

加上逻辑判断的:

  • tcp dst port 80 and dst host 192.168.0.100 目的IP为192.168.0.100,目的端口为80的TCP数据包
  • dst host 192.168.0.100 and ! udp port 53 目的IP为192.168.0.100,但不是udp53端口的所有数据包

OK。到这里,其实就可以满足大部分工作需求了。
今天就讲到这里的话,那也太LOW了~~~

接下来是BPF的高级部分,一定要认真听了。

因为IP包头的东西比较简单,上面讲的东西会用了,也没IP包头什么事了。难点重点在于TCP报文,因为TCP报文是带了数据的,TCP头之后就是数据的开始。要玩高端的抓包,就是要针对TCP头或是数据内容进行抓包,比如只抓握手包,只抓POST提交的包,比如只抓某个URL的包,特别后面两个,直接从四层捕获七层的内容,很爽吧!所以大家一定要打开TCP数据包分解图,一边听我讲,一边看。

有些群友是不是一看这个图头都大了?

不过我们不是网工,我们不用去记那么多东西的。让我们来过滤过滤
首先橙色和浅蓝色的两段number不用看它,蓝色的window size,绿色的tcp checksum跟它旁边的紫色不用看它。现在剩下三段半的东西而已。

现在大家看到的都是是计算机基础知识。图中的一段长度是32bit,就是32比特,比特是计算机最小的基础单位了。每个比特就只能表示0或者1,8个比特表示一字节。就是图中黑格白格的那些,每格表示一个字节。一段就是4个字节。一个字符占用一个字节,所以每一段32比特的长度只能表示4个字符。

先看第一段的源端口和目的端口,各占16个比特的长度,16比特范围能表示多大呢,也就是二进制的0000000000000000到1111111111111111,打开你们电脑的科学计算器,二进制16个1转为10进制是多少?65535啊亲,这就是为什么系统的端口最大只能是65535的原因。

红白黄一会再聊,看白色的option,这个是可变长的选项字段,长度从0到40字节,且必须是4字节的整倍数,直白点就是要加就是一段一段加。
最后我们第七层应用层的数据就跟在option后面以ascii码的形式出现了。所以要找到数据位置,就得先确定tcp头部长度,但是option长度是可变的,那要怎么得知位置呢?

这里就要靠上面的红格子啦。我先直接给你们bpf,再解释为什么。

tcp[12] >> 2

在tcpdump里。tcp[12]表示tcp报文中的第12个字节,看图,第12字节就是红白格子。事实上,有用的只有红色格子,它只占4比特,半个字节。白色格子是预留的空位,不起作用的,恒定为0.

看回tcp[12] >> 2,这里是将12字节的值做右移位处理,这里其实是做了数字上的简化,原操作应该是这样的
( tcp[12] >> 4 ) << 2,先做4位右移,再做2位左移,用乘除的方式表达就是( tcp[12] / 16 ) × 4
为什么要这么计算,因为红格子里面记录的是TCP报名头的长度,是一个偏移量,单位为4字节(1段),所以我们用长度×4,就能计数出整个个TCP头的字节了。那么除以16又是什么鬼?我们来看一下数据包例子。包的tcp[12]是整个字节,例子中是80(注意:这是十六进制)。刚才说了,有用的红色格子只占了半个字节,也就是只有前面的8是要用到的,0是无用的。
在10进制里,要把一个整数最后的0去掉,是做除法,除以10,在十六进制里也是做除法,不过是除以16。

所以简化一下就是tcp[12] / 4,正好是右移2位(也是因为计算机做移位操作比除法快,所以换成移位操作)

确定TCP头长度后,后面的就是数据了。那我们现在就开始针对数据内容来抓包。

我们先来抓POST请求的包。

让我们先来看一下HTTP协议是怎么做HTTP请求的。
http请求由三部分组成,分别是:请求行、消息报头、请求正文

POST /index.html HTTP/1.1
Host: www.xxx.com
Content-Length:22
Connection:Keep-Alive
Cache-Control:no-cache

user=jeffrey&pwd=1234

POST这几个字符正好是出现在数据最前面的四个字节。

在tcpdump里tcp[x:n]表示tcp报文,偏移x个字节后,长度为n字节的数据。
所以我们用tcp[(tcp[12] >> 2):4]表示数据开始的前4个字节。注意:数据内容是用16进制ascii码来表示的。

POST的十六进制ascii为0x504f534(python中用ord(‘P’)获取P的十进制ascii码,用hex(ord(‘P’))获取P的十六进制ascii码)

所以我们的BPF可以这样写:

tcp[(tcp[12] >> 2):4] = 0x504f5354

注意,这里的:4不是随便填的。tcpdump只支持1,2,4这三种长度

之后可以再举例抓路径名的,介绍+的使用

最后看喜好介绍TCP标志位的抓包方法
外国友人记忆标记位的方法:

Unskilled Attackers Pester Real Security Folks

蹩脚的攻击者缠住了真正的安全人员
(不知道是不是可以翻译成:盲拳打死老师傅)

Unskilled = URG
Attackers = ACK
Pester = PSH
Real = RST
Security = SYN
Folks = FIN

19:53:32.320702 IP 10.0.0.91.49808 > 202.104.101.103.http: Flags [P.], seq 0:582, ack 1, win 115, options [nop,nop,TS val 3307698431 ecr 10667605], length 582
0x0000: 4500 027a a33d 4000 4006 5b16 0a00 005b E..z.=@.@.[….[
0x0010: ca68 6567 c290 0050 cc09 dbc4 da97 f8e2 .heg…P……..
0x0020: 8018 0073 6aad 0000 0101 080a c527 78ff …sj……..’x.
0x0030: 00a2 c655 504f 5354 202f 696e 6465 782e …UPOST./index.
0x0040: 7068 703f 633d 7363 6826 613d 6765 7453 php?c=sch&a=getS
0x0050: 6368 6564 756c 6520 4854 5450 2f31 2e31 chedule.HTTP/1.1
0x0060: 0d0a 582d 5265 616c 2d69 703a 2032 3131 ..X-Real-ip:.211
0x0070: 2e31 3534 2e31 3536 2e33 380d 0a58 2d46 .154.156.38..X-F
0x0080: 6f72 7761 7264 6564 2d46 6f72 3a20 3231 orwarded-For:.21
0x0090: 312e 3135 342e 3135 362e 3338 0d0a 4854 1.154.156.38..HT
0x00a0: 5450 5f43 4c49 454e 545f 4950 3a20 3231 TP_CLIENT_IP:.21
0x00b0: 312e 3135 342e 3135 362e 3338 0d0a 4854 1.154.156.38..HT
0x00c0: 5450 5f58 5f46 4f52 5741 5244 4544 5f46 TP_X_FORWARDED_F
0x00d0: 4f52 3a20 3231 312e 3135 342e 3135 362e OR:.211.154.156.
0x00e0: 3338 0d0a 5245 4d4f 5445 5f41 4444 523a 38..REMOTE_ADDR:
0x00f0: 2032 3131 2e31 3534 2e31 3536 2e33 380d .211.154.156.38.
0x0100: 0a57 4c2d 5072 6f78 792d 436c 6965 6e74 .WL-Proxy-Client
0x0110: 2d49 503a 2032 3131 2e31 3534 2e31 3536 -IP:.211.154.156
0x0120: 2e33 380d 0a50 726f 7879 2d43 6c69 656e .38..Proxy-Clien
0x0130: 742d 4950 3a20 3231 312e 3135 342e 3135 t-IP:.211.154.15
0x0140: 362e 3338 0d0a 436f 6e74 656e 742d 4c65 6.38..Content-Le
0x0150: 6e67 7468 3a20 3933 0d0a 436f 6e74 656e ngth:.93..Conten
0x0160: 742d 5479 7065 3a20 6170 706c 6963 6174 t-Type:.applicat
0x0170: 696f 6e2f 782d 7777 772d 666f 726d 2d75 ion/x-www-form-u
0x0180: 726c 656e 636f 6465 643b 2063 6861 7273 rlencoded;.chars
0x0190: 6574 3d55 5446 2d38 0d0a 486f 7374 3a20 et=UTF-8..Host:.
0x01a0: 7773 6170 692e 3931 3136 302e 636f 6d0d wsapi.91160.com.
0x01b0: 0a43 6f6e 6e65 6374 696f 6e3a 204b 6565 .Connection:.Kee
0x01c0: 702d 416c 6976 650d 0a55 7365 722d 4167 p-Alive..User-Ag
0x01d0: 656e 743a 2041 7061 6368 652d 4874 7470 ent:.Apache-Http
0x01e0: 436c 6965 6e74 2f34 2e35 2028 4a61 7661 Client/4.5.(Java
0x01f0: 2f31 2e37 2e30 5f37 3929 0d0a 4163 6365 /1.7.0_79)..Acce
0x0200: 7074 2d45 6e63 6f64 696e 673a 2067 7a69 pt-Encoding:.gzi
0x0210: 702c 6465 666c 6174 650d 0a0d 0a75 6964 p,deflate….uid
0x0220: 3d33 2664 6f63 6964 3d32 3336 3726 656e =3&docid=2367&en
0x0230: 645f 6461 7465 3d32 3031 362d 3037 2d31 d_date=2016-07-1
0x0240: 3326 746f 6b65 6e3d 686a 2a71 6b2d 3538 3&token=hj*qk-58
0x0250: 3536 3326 6265 6769 6e5f 6461 7465 3d32 563&begin_date=2
0x0260: 3031 362d 3036 2d32 3926 6465 7069 643d 016-06-29&depid=
0x0270: 3436 3126 6369 643d 3237 461&cid=27

印象笔记,让记忆永存。下载印象笔记

快速找到Tomcat中最耗CPU的线程

1、找出TOMCAT的JVM的进程ID

[work@112542000 ~]$ jps                                                                                                                                         
290 Bootstrap                                                                                                                                                   
61213 Jps                                                                                                                                                       

2、查看该进程中,最耗费CPU的线程

[work@112542000 ~]$ ps -mp 290 -o THREAD,tid,time | sort -k2 -r | head -n 20                                                                                    
USER     %CPU PRI SCNT WCHAN  USER SYSTEM   TID     TIME                                                                                                        
work     15.3   -    - -         -      -     - 03:11:58                                                                                                        
work      1.2  19    - -         -      -   326 00:15:45                                                                                                        
work      0.2  19    - -         -      -   872 00:03:44                                                                                                        
work      0.1  19    - -         -      -   992 00:01:44                                                                                                        
work      0.1  19    - -         -      -   972 00:01:16                                                                                                        
work      0.1  19    - -         -      -   870 00:01:19                                                                                                        
work      0.1  19    - -         -      -   869 00:01:34                                                                                                        
work      0.0  19    - -         -      -  9993 00:00:00                                                                                                        
work      0.0  19    - -         -      -   997 00:00:06                                                                                                        
work      0.0  19    - -         -      -  9969 00:00:00                                                                                                        
work      0.0  19    - -         -      -  9968 00:00:00                                                                                                        
work      0.0  19    - -         -      -   996 00:00:34                                                                                                        
work      0.0  19    - -         -      -  9960 00:00:00                                                                                                        
work      0.0  19    - -         -      -   995 00:00:00                                                                                                        
work      0.0  19    - -         -      -  9944 00:00:00                                                                                                        
work      0.0  19    - -         -      -   994 00:00:31                                                                                                        
work      0.0  19    - -         -      -  9936 00:00:00                                                                                                        
work      0.0  19    - -         -      -  9934 00:00:26                                                                                                        
work      0.0  19    - -         -      -  9933 00:00:21                                                                                                        

3、以TID==326为例,查看该线程的堆栈

首先,将十进制的326转换成十六进制,可以在线转换:

http://tool.oschina.net/hexconvert

结果等于146

使用jstack查询该线程堆栈:

[work@112542000 ~]$ jstack 290 | grep "0x160" -A 10                                                                                                             
"Timer-2955" daemon prio=10 tid=0x00007f39d3ecd000 nid=0x1602 in Object.wait() [0x00007f38fb273000]                                                             
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       
        - locked <0x00000007c2a0fc40> (a java.util.TaskQueue)                                                                                                   
        at java.util.TimerThread.run(Timer.java:505)                                                                                                            

"Timer-2954" daemon prio=10 tid=0x00007f392481e000 nid=0x15e4 in Object.wait() [0x00007f38fbe7f000]                                                             
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       
--                                                                                                                                                              
"Timer-2132" daemon prio=10 tid=0x00007f39d367d000 nid=0x1603 in Object.wait() [0x00007f3933f46000]                                                             
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       
        - locked <0x00000007b44ef3c8> (a java.util.TaskQueue)                                                                                                   
        at java.util.TimerThread.run(Timer.java:505)                                                                                                            

"Timer-2131" daemon prio=10 tid=0x00007f39d3b61800 nid=0x15e6 in Object.wait() [0x00007f3934d54000]                                                             
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       
--                                                                                                                                                              
"Timer-450" daemon prio=10 tid=0x00007f39d0728800 nid=0x160 in Object.wait() [0x00007f39b1312000]                                                               
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       
        - locked <0x00000007bddf8788> (a java.util.TaskQueue)                                                                                                   
        at java.util.TimerThread.run(Timer.java:505)                                                                                                            

"Timer-449" daemon prio=10 tid=0x00007f39ec01f000 nid=0xffc7 in Object.wait() [0x00007f39af5f5000]                                                              
   java.lang.Thread.State: TIMED_WAITING (on object monitor)                                                                                                    
        at java.lang.Object.wait(Native Method)                                                                                                                 
        at java.util.TimerThread.mainLoop(Timer.java:552)                                                                                                       

tcpdump在bond环境下抓到重复报文的问题说明

近期在排查问题时, 使用 tcpdump 抓包发现一个很奇怪的现象, 所有抓到的包都有重复的一份, 使用 wireshark 查看则表现为 tcp out of order, tcp dup ack 以及 tcp retransmission 这三种提示特别多, 如下图所示:

未分类

我们在 centos 6 和 centos 7 系统中验证都有这样的问题, 类似情况见 redhat-1260733

下面开始详细介绍该问题的描述及处理细节:

环境说明

10.0.21.5  -  web 主机 - centos6
10.0.21.7  -  redis 主机 - centos7

网络结构

bond 网络结构大致如下:

未分类

两台机器的 eth2, eth3 网口分别连接两个交换机, 选择模式 1 进行设置组成了 bond1 网卡. web 主机 eth2 为当前活跃的网卡, redis 主机 eth3 位当前活跃的网卡.

问题说明

在 bond 模式为 1(active-backup 模式) 的网络环境下, 使用 tcpdump 及选项 -i any 抓取 web 主机上所有网卡的报文, 使用 wireshark 发现所有的接收和发送都有重复的报文, 而对端的 redis 主机则一切正常.出现的现象如上图所示, 使用以下命令进行抓包测试:

  • web 主机上抓包:
tcpdump -S -s 0 -n -n -i any port 6380 -w 6380.pcap
  • 在 web 主机的另一个会话中, 执行 telnet
telnet 10.0.21.7 6380
Trying 10.0.21.7...
Connected to 10.0.21.7.
Escape character is '^]'.
^]
telnet> quit
Connection closed.

web 主机上抓到的包类似下面的内容:

11:18:02.056270 IP 10.0.21.5.24399 > 10.0.21.7.6380: Flags [S], seq 3834591311, win 14600, options [mss 1460,sackOK,TS val 4281235630 ecr 0,nop,wscale 8], length 0
11:18:02.056275 IP 10.0.21.5.24399 > 10.0.21.7.6380: Flags [S], seq 3834591311, win 14600, options [mss 1460,sackOK,TS val 4281235630 ecr 0,nop,wscale 8], length 0
11:18:02.056405 IP 10.0.21.7.3302 > 10.0.21.5.24399: Flags [S.], seq 522913168, ack 3834591312, win 14480, options [mss 1460,sackOK,TS val 3589524071 ecr 4281235630,nop,wscale 8], length 0
11:18:02.056405 IP 10.0.21.7.3302 > 10.0.21.5.24399: Flags [S.], seq 522913168, ack 3834591312, win 14480, options [mss 1460,sackOK,TS val 3589524071 ecr 4281235630,nop,wscale 8], length 0
11:18:02.056425 IP 10.0.21.5.24399 > 10.0.21.7.6380: Flags [.], ack 1, win 58, options [nop,nop,TS val 4281235631 ecr 3589524071], length 0
11:18:02.056426 IP 10.0.21.5.24399 > 10.0.21.7.6380: Flags [.], ack 1, win 58, options [nop,nop,TS val 4281235631 ecr 3589524071], length 0
11:18:02.056560 IP 10.0.21.7.3302 > 10.0.21.5.24399: Flags [P.], seq 1:91, ack 1, win 57, options [nop,nop,TS val 3589524071 ecr 4281235631], length 90
11:18:02.056560 IP 10.0.21.7.3302 > 10.0.21.5.24399: Flags [P.], seq 1:91, ack 1, win 57, options [nop,nop,TS val 3589524071 ecr 4281235631], length 90

可以看到几乎每个包都有重复的条目, 包括 tcp 的三次握手及数据传输等. 所以这里就出现了几个问题:

  • tcpdump 是从哪个网卡抓到的接收的报文(eth2 还是 bond1)?
  • 为什么 web 主机的出方向的包也有重复的条目?

分析处理

有趣的是在 redis 主机上单独监听 bond1 网卡, 数据报文都是正常的, web 主机上独立监听 bond1 网卡也是正常的, 这说明 redis 主机并没有向 web 主机重复发送报文, 也说明 web 主机没有重复向 redis 主机发送报文.

对于第一个问题, 很遗憾 tcpdump 并没有类似的功能能够显示来源数据对应的网卡, 所以我们难以区别到底是 eth2 还是 bond1 网卡引起了重复报文. 第二个问题则比较奇特, 从抓出的报文来看 wen 主机重复发送请求但是 redis 主机并没有收到, 这说明重复发送可能只是 web 主机内部的原因, 同样重复接收报文可能也是 web 主机的内部原因.

我们从内核文档bonding 找到以下重复报文相关的内容

10. 小节
 For the active-backup, balance-tlb and balance-alb modes, the
promiscuous mode setting is propagated only to the active slave.
......
For the active-backup, balance-tlb and balance-alb modes, when
the active slave changes (e.g., due to a link failure), the
promiscuous setting will be propagated to the new active slave.
......
......
13.2 小节:
This is not due to an error in the bonding driver, rather, it
is a side effect of how many switches update their MAC forwarding
tables.  Initially, the switch does not associate the MAC address in
the packet with a particular switch port, and so it may send the
traffic to all ports until its MAC forwarding table is updated.  Since
the interfaces attached to the bond may occupy multiple ports on a
single switch, when the switch (temporarily) floods the traffic to all
ports, the bond device receives multiple copies of the same packet
(one per slave device).

    The duplicated packet behavior is switch dependent, some
switches exhibit this, and some do not.  On switches that display this
behavior, it can be induced by clearing the MAC forwarding table (on
most Cisco switches, the privileged command "clear mac address-table
dynamic" will accomplish this).

从这点看, tcpdump 仅将混杂模式传播到当前活动的网卡中, 另外在交换机进行 MAC 地址学习的时候可能会出现这种重复报文的情况, 这种情况还依赖于交换机的功能. 从这点来看内核文档的解释和我们的问题并没有关系.

由此我们继续追查别的方面, 从底向上的分析, 在 web 主机收到报文的时候, 会对整个报文进行解封装, 如下图所示为数据报文的封装和解封装过程:

未分类

从右边的解封装过程来看, 主机的 MAC 地址经过交换机的学习后不会再出现上述内核文档的现象, 而且数据经过传输肯定会先传到 web 主机的 eth2 物理网卡上, 而不是我们认为的直接到 bond1 网卡上. 我们参考文章 tcpdump-work-with-bonding-interface 了解到在 centos 5 系统中, 内核接收到数据报文之后会直接将 skb-dev 从 eth2 更新为 bond1, 最后再将报文通过 bond1 网口发送给 ptye_all, 由于这个工作模式, 直接通过 tcpdump -i eth2 是抓取不到任何包的, 因为报文数据不会经过 eth2.

但是在 linux 内核 3.16 之后, 内核作者 torvalds 修改了工作模式, skb->dev 首先为 eth2 物理网卡, 数据先通过 eth2, 经过 rx_handler 进行处理, 最后找到 bond1 网卡, 再将数据发送到 bond1 进行处理. 如下:

bond related process is moved to dev->rx_handler, Just like the bridge or openvswitch.

Packet will first be processed by ptype_all with skb->dev is eth0 and then rx_handler(bond handler for eth0,eth1). if the rx handler return RX_HANDLER_ANOTHER, the packet arrive by ptye_all again with differentskb->dev (bond0).

从这点来看, web 主机接收的数据报文会先经过 eth2, 再经过 bond1, 而发送的报文则会先经过 bond1, 再经过 eth2 网卡. 而 tcpdump 使用 -i any 选项抓包, 则意味着不管接收还是发送都会出现重复的条目. 为了验证此猜测我们同时监听 eth2 和 bond1 网卡:

在两个终端界面输入下面命令:

tcpdump -S -s 0 -n -n -i eth2 port 6380 -w eth2_6380.pcap
tcpdump -S -s 0 -n -n -i eth2 port 6380 -w bond1_6380.pcap

可以看到如下输出:

eth2 抓包的数据

# tcpdump -n -n -r eth2_6380.pcap 
reading from file eth2_6380.pcap, link-type EN10MB (Ethernet)
11:40:59.309086 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [S], seq 275391682, win 14600, options [mss 1460,sackOK,TS val 70624316 ecr 0,nop,wscale 8], length 0
11:40:59.309122 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [S.], seq 969430611, ack 275391683, win 14480, options [mss 1460,sackOK,TS val 3674159462 ecr 70624316,nop,wscale 8], length 0
11:40:59.309137 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 1, win 58, options [nop,nop,TS val 70624316 ecr 3674159462], length 0
11:41:01.130610 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [P.], seq 1:7, ack 1, win 58, options [nop,nop,TS val 70626138 ecr 3674159462], length 6
11:41:01.130643 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [.], ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 0
11:41:01.130728 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [.], seq 1:1449, ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 1448
11:41:01.130739 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 1449, win 69, options [nop,nop,TS val 70626138 ecr 3674161283], length 0
11:41:01.130776 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [P.], seq 1449:2193, ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 744
11:41:01.130784 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 2193, win 80, options [nop,nop,TS val 70626138 ecr 3674161283], length 0
11:41:04.453604 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [F.], seq 7, ack 2193, win 80, options [nop,nop,TS val 70629461 ecr 3674161283], length 0
11:41:04.453694 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [F.], seq 2193, ack 8, win 57, options [nop,nop,TS val 3674164606 ecr 70629461], length 0
11:41:04.453709 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 2194, win 80, options [nop,nop,TS val 70629461 ecr 3674164606], length 0

bond1 抓包的数据:

# tcpdump -n -n -r bond1_6380.pcap 
reading from file bond1_6380.pcap, link-type EN10MB (Ethernet)
11:40:59.309081 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [S], seq 275391682, win 14600, options [mss 1460,sackOK,TS val 70624316 ecr 0,nop,wscale 8], length 0
11:40:59.309122 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [S.], seq 969430611, ack 275391683, win 14480, options [mss 1460,sackOK,TS val 3674159462 ecr 70624316,nop,wscale 8], length 0
11:40:59.309136 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 1, win 58, options [nop,nop,TS val 70624316 ecr 3674159462], length 0
11:41:01.130605 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [P.], seq 1:7, ack 1, win 58, options [nop,nop,TS val 70626138 ecr 3674159462], length 6
11:41:01.130643 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [.], ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 0
11:41:01.130728 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [.], seq 1:1449, ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 1448
11:41:01.130737 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 1449, win 69, options [nop,nop,TS val 70626138 ecr 3674161283], length 0
11:41:01.130776 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [P.], seq 1449:2193, ack 7, win 57, options [nop,nop,TS val 3674161283 ecr 70626138], length 744
11:41:01.130782 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 2193, win 80, options [nop,nop,TS val 70626138 ecr 3674161283], length 0
11:41:04.453600 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [F.], seq 7, ack 2193, win 80, options [nop,nop,TS val 70629461 ecr 3674161283], length 0
11:41:04.453694 IP 10.0.21.7.6380 > 10.0.21.5.42953: Flags [F.], seq 2193, ack 8, win 57, options [nop,nop,TS val 3674164606 ecr 70629461], length 0
11:41:04.453707 IP 10.0.21.5.42953 > 10.0.21.7.6380: Flags [.], ack 2194, win 80, options [nop,nop,TS val 70629461 ecr 3674164606], length 0

可以看到, eth2 和 bond1 抓到了同样的报文, 只是时间戳稍有不同, 这是正常的因为 redis 主机网 web 主机发的包的时间戳是固定的, 上面可以看到从 10.0.21.7 到 10.0.21.5 主机的时间戳都不变化, 而 web 主机到 redis 主机的时间戳则有一些不同, 基本都相差几微秒.

由此可以猜测出, tcpdump 使用 -i any 选项会把 eth2 和 bond1 的报文都抓取, 而两个网卡报文的时间戳有特别相近, 所以使用 wireshark 进行查看的时候会出现各种各样的提示. 以上述的截图为例进行说明:

1、tcp out of order

第一条为三次握手的第一个包, 因为是从 web 主机发出去的, 所以第一条应该是 bond1 网卡的 syn 报文, 紧跟的第二条则为 eth2 网卡的 syn 报文, 而且两个条目还相差 5 微秒, 对于 wireshark 而言第二条报文不是期待的 syn + ack, 时间戳也不一样, 所以就提示乱序. 事实上在 tcp 三次握手和四次回收的过程中是不会出现乱序的, 乱序只在传输数据报文的时候发生.

2、tcp Dup Ack

当乱序或丢包发生时,web 会收到一些 seq 号比期望大的包, web 每收到这种包就会 ack 一次期望的值提醒发送方, 于是就产生 Dup Ack, wireshark 则提示 tcp Dup Ack, 截图中则主要由于时间戳乱序引起了该提示.

3、tcp retransmission

正常情况下, 一个包丢失以及没有后续的 Dup Ack 包, 则会进行超时重传操作. 而在本文的示例中, 由于传输的数据包(P 标志)有重复的而且seq, ack 这些都一样所以 wireshark 会直接提示该包为重传的包.

总结

总体来看, 出现重复报文算是虚惊一场, 不是内核的 bug 也不是 tcpdump 的 bug, 而是 bond 的机制需要经过多次转发处理, 这也能解释为什么 redis 主机上的报文都是正常的. 如上所述, 如果在 centos 5 中抓包应该就不会出现本文的问题. 所以出现重复报文只是一种现象, 不会影响本地网卡的流量, 也不会影响业务的交互. 或许 tcpdump 会在将来修复这个不算错误的漏洞, linux 也可能换一种机制来完成数据的传输.

通过Tcpdump抓包 Wireshark分析 找出Wget文件下载失败的原因

在实际工作的过程中,服务程序每日开盘前需要依赖一下静态的文件进行初始化操作,类似版块文件、财务文件、除权文件等文件。由于下载工作比较简单,我们使用linux系统自带的wget,通过shell脚本的方式进行下载,脚本编写完成在测试过程中发现,随着下载频率的增加出现下载不成功的次数会增加,下载到的内容类似:

未分类

从上面的信息可以看到,等到的信息中有类似hs_cc_cookie,初步得出两个怀疑方向:

  • 难道是异常使用cookie导致请求直接返回?
  • 或者是请求被认为是CC攻击,安全设备直接返回了上面的上面的HTML内容??

基于上面的怀疑我们开始问题的排查。

问题排查

1、对wget的请求增加参数

在请求中增加 -S参数,打印详细的响应头,正常下载:

未分类

错误下载:

未分类

然后对请求 增加–no-cookies 参数 ,不使用 cookies ,增加参数后结果一样,多次下载继续出现问题,关于cookies可能性排除,难道真的是安全设备的问题?涉及到跨部门协调,需要拿出证据,那么只能出最后的绝招–网络抓包 ,结果绝对公平公正,童叟无欺。

抓包分析

说到抓包,不得不说说TCP的建立连接的“三次握手”和断开连接的“四次挥手”。
如果觉得有必要回顾一下的同学可以先看看“附录一”,里面会有详细的接收。了然于胸的同学我们继续问题排查。
通过TCPDUMP (LINUX 网络抓包工具)抓取正常下载和异常下载的网络包进行对比分析。

正常下载:

未分类

下载异常:

未分类

下面我们对下载异常的网络包进行分析:

  • 665号包说明服务器认为自己已经完成了服务请求,但客户端却没有主动关闭连接,于是在 20s 后只好主动将连接关闭。665号包显示 “TCP Previous segment not captured” 并且包中 “Seq=640” 而上一个 “Seq=1”我们确实有包没有抓到。

  • 666号包客户端在收到 “Seq=640” 后认为自己收到了一个乱序包,因此试图通过 “TCP Dup ACK 663#1” 和 “Ack=1” 试图让服务器重传缺失的 639 字节。

  • 667号包 出现 “TCP Out-Of-Order” ,产生该问题的原因一般:Packet 可能 Lost,所以重新传送造成。或存在Load Balance 之类的架构,晚送的封包却比早送的到达。

  • 671 号包开始说明客户端由于某种原因还进行了多次针对 “FIN” 的==“TCP Spurious Retransmission”== 。

TCP Previous segment not captured的问题一般有以下几种情况,我们来一一排查。
服务器CPU或网络压力导致上述问题(服务器没有压力)

未分类

  • 杀毒软件、恶意软件监测程序等导致问题(无此问题)。

  • 交换机、路由器和防火墙等网络层问题。

排查到这里,基本可以判断问题是由于网络层相关问题导致,接下来我们从以下两个方向进行问题排查。

1、查看防火墙出口带宽,整个出口我有200M 现在看看只有50M的峰值,和出口带宽无关

未分类

2、查看防火墙关于CC攻击的配置发现开启了IPS防御以及防病毒配置,对于单IP的访问频次过高时,认为是攻击行为会产生丢弃操作,当出现错误请求时有如下报错信息:

未分类

3、调整防火墙配置关闭CC防护,问题解决:

未分类

通过以上的排查我们得出结论:wget在频繁被执行时触发了防火墙的CC攻击防护机制,导致请求被截断从而出现了文章开头的问题。

解决方案:

  • 针对wget下载脚本:
    在增加 中wget 时增加–limit-rate=2048k (限制下载速度为2M)参数,限制下载速度,并增长两次下载过程中的sleep 时间间隔。

  • 针对防火墙:
    适当调高防火墙CC防护的rps值,避免正常请求被防火墙作为CC攻击屏蔽。

tcpdump命令的使用与示例

网络数据采集分析工具TcpDump的简介

  顾名思义,TcpDump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。tcpdump就是一种免费的网络分析工具,尤其其提供了源代码,公开了接口,因此具备很强的可扩展性,对于网络维护和入侵者都是非常有用的工具。tcpdump存在于基本的FreeBSD系统中,由于它需要将网络界面设置为混杂模式,普通用户不能正常执行,但具备root权限的用户可以直接执行它来获取网络上的信息。因此系统中存在网络分析工具主要不是对本机安全的威胁,而是对网络上的其他计算机的安全存在威胁。

  我们用尽量简单的话来定义tcpdump,就是:dump the traffice on a network.,根据使用者的定义对网络上的数据包进行截获的包分析工具。作为互联网上经典的的系统管理员必备工具,tcpdump以其强大的功能,灵活的截取策略,成为每个高级的系统管理员分析网络,排查问题等所必备的东西之一。tcpdump提供了源代码,公开了接口,因此具备很强的可扩展性,对于网络维护和入侵者都是非常有用的工具。tcpdump存在于基本的FreeBSD系统中,由于它需要将网络界面设置为混杂模式,普通用户不能正常执行,但具备root权限的用户可以直接执行它来获取网络上的信息。因此系统中存在网络分析工具主要不是对本机安全的威胁,而是对网络上的其他计算机的安全存在威胁。

  网络数据采集分析工具TcpDump的使用

普通情况下,直接启动tcpdump将监视第一个网络界面上所有流过的数据包。

# tcpdump 
tcpdump: listening on fxp0
11:58:47.873028 202.102.245.40.netbios-ns > 202.102.245.127.netbios-ns: udp 50
11:58:47.974331 0:10:7b:8:3a:56 > 1:80:c2:0:0:0 802.1d ui/C len=43
                         0000 0000 0080 0000 1007 cf08 0900 0000
                         0e80 0000 902b 4695 0980 8701 0014 0002
                         000f 0000 902b 4695 0008 00
11:58:48.373134 0:0:e8:5b:6d:85 > Broadcast sap e0 ui/C len=97
                         ffff 0060 0004 ffff ffff ffff ffff ffff
                         0452 ffff ffff 0000 e85b 6d85 4008 0002
                         0640 4d41 5354 4552 5f57 4542 0000 0000
                         0000 00
^C

  tcpdump支持相当多的不同参数,如使用-i参数指定tcpdump监听的网络界面,这在计算机具有多个网络界面时非常有用,使用-c参数指定要监听的数据包数量,使用-w参数指定将监听到的数据包写入文件中保存,等等。

  然而更复杂的tcpdump参数是用于过滤目的,这是因为网络中流量很大,如果不加分辨将所有的数据包都截留下来,数据量太大,反而不容易发现需要的数据包。使用这些参数定义的过滤规则可以截留特定的数据包,以缩小目标,才能更好的分析网络中存在的问题。tcpdump使用参数指定要监视数据包的类型、地址、端口等,根据具体的网络问题,充分利用这些过滤规则就能达到迅速定位故障的目的。请使用man tcpdump查看这些过滤规则的具体用法。

  显然为了安全起见,不用作网络管理用途的计算机上不应该运行这一类的网络分析软件,为了屏蔽它们,可以屏蔽内核中的bpfilter伪设备。一般情况下网络硬件和TCP/IP堆栈不支持接收或发送与本计算机无关的数据包,为了接收这些数据包,就必须使用网卡的混杂模式,并绕过标准的TCP/IP堆栈才行。在FreeBSD下,这就需要内核支持伪设备bpfilter。因此,在内核中取消bpfilter支持,就能屏蔽tcpdump之类的网络分析工具。

  并且当网卡被设置为混杂模式时,系统会在控制台和日志文件中留下记录,提醒管理员留意这台系统是否被用作攻击同网络的其他计算机的跳板。

May 15 16:27:20 host1 /kernel: fxp0: promiscuous mode enabled

  虽然网络分析工具能将网络中传送的数据记录下来,但是网络中的数据流量相当大,如何对这些数据进行分析、分类统计、发现并报告错误却是更关键的问题。网络中的数据包属于不同的协议,而不同协议数据包的格式也不同。因此对捕获的数据进行解码,将包中的信息尽可能的展示出来,对于协议分析工具来讲更为重要。昂贵的商业分析工具的优势就在于它们能支持很多种类的应用层协议,而不仅仅只支持tcp、udp等低层协议。

  从上面tcpdump的输出可以看出,tcpdump对截获的数据并没有进行彻底解码,数据包内的大部分内容是使用十六进制的形式直接打印输出的。显然这不利于分析网络故障,通常的解决办法是先使用带-w参数的tcpdump 截获数据并保存到文件中,然后再使用其他程序进行解码分析。当然也应该定义过滤规则,以避免捕获的数据包填满整个硬盘。FreeBSD提供的一个有效的解码程序为tcpshow,它可以通过Packages Collection来安装。

# pkg_add /cdrom/packages/security/tcpshow*
# tcpdump -c 3 -w tcpdump.out
tcpdump: listening on fxp0
# tcpshow < tcpdump.out
---------------------------------------------------------------------------
Packet 1
TIME:12:00:59.984829
LINK:00:10:7B:08:3A:56 -> 01:80:C2:00:00:00 type=0026
<*** No decode support for encapsulated protocol ***>
---------------------------------------------------------------------------
Packet 2
TIME:12:01:01.074513 (1.089684)
LINK:00:A0:C9:AB:3C:DF -> FF:FF:FF:FF:FF:FF type=ARP
ARP:htype=Ethernet ptype=IP hlen=6 plen=4 op=request
sender-MAC-addr=00:A0:C9:AB:3C:DF sender-IP-address=202.102.245.3
target-MAC-addr=00:00:00:00:00:00 target-IP-address=202.102.245.3
---------------------------------------------------------------------------
Packet 3
TIME:12:01:01.985023 (0.910510)
LINK:00:10:7B:08:3A:56 -> 01:80:C2:00:00:00 type=0026
<*** No decode support for encapsulated protocol ***>

  tcpshow能以不同方式对数据包进行解码,并以不同的方式显示解码数据,使用者可以根据其手册来选择最合适的参数对截获的数据包进行分析。从上面的例子中可以看出,tcpshow支持的协议也并不丰富,对于它不支持的协议就无法进行解码。

  除了tcpdump之外,FreeBSD的Packages Collecion中还提供了Ethereal和Sniffit两个网络分析工具,以及其他一些基于网络分析方式的安全工具。其中Ethereal运行在X Window 下,具有不错的图形界面,Sniffit使用字符窗口形式,同样也易于操作。然而由于tcpdump对过滤规则的支持能力更强大,因此系统管理员仍然更喜欢使用它。对于有经验的网络管理员,使用这些网络分析工具不但能用来了解网络到底是如何运行的,故障出现在何处,还能进行有效的统计工作,如那种协议产生的通信量占主要地位,那个主机最繁忙,网络瓶颈位于何处等等问题。因此网络分析工具是用于网络管理的宝贵系统工具。为了防止数据被滥用的网络分析工具截获,关键还是要在网络的物理结构上解决。常用的方法是使用交换机或网桥将信任网络和不信任网络分隔开,可以防止外部网段窃听内部数据传输,但仍然不能解决内部网络与外部网络相互通信时的数据安全问题。如果没有足够的经费将网络上的共享集线器升级为以太网交换机,可以使用FreeBSD系统执行网桥任务。这需要使用option BRIDGE编译选项重新定制内核,此后使用bridge命令启动网桥功能。

  tcpdump采用命令行方式,它的命令格式为:

tcpdump [ -adeflnNOpqStvx ] [ -c 数量 ] [ -F 文件名 ]
    [ -i 网络接口 ] [ -r 文件名] [ -s snaplen ]
    [ -T 类型 ] [ -w 文件名 ] [表达式 ]

  (1). tcpdump的选项介绍

  • -a    将网络地址和广播地址转变成名字;
  • -d    将匹配信息包的代码以人们能够理解的汇编格式给出;
  • -dd    将匹配信息包的代码以c语言程序段的格式给出;
  • -ddd   将匹配信息包的代码以十进制的形式给出;
  • -e    在输出行打印出数据链路层的头部信息;
  • -f    将外部的Internet地址以数字的形式打印出来;
  • -l    使标准输出变为缓冲行形式;
  • -n    不把网络地址转换成名字;
  • -t    在输出的每一行不打印时间戳;
  • -v    输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息;
  • -vv    输出详细的报文信息;
  • -c    在收到指定的包的数目后,tcpdump就会停止;
  • -F    从指定的文件中读取表达式,忽略其它的表达式;
  • -i    指定监听的网络接口;
  • -r    从指定的文件中读取包(这些包一般通过-w选项产生);
  • -w    直接将包写入文件中,并不分析和打印出来;
  • -T    将监听到的包直接解释为指定的类型的报文,常见的类型有rpc (远程过程调用)和snmp(简单网络管理协议;)

  (2). tcpdump的表达式介绍

  表达式是一个正则表达式,tcpdump利用它作为过滤报文的条件,如果一个报文满足表达式的条件,则这个报文将会被捕获。如果没有给出任何条件,则网络上所有的信息包将会被截获。在表达式中一般如下几种类型的关键字。

  第一种是关于类型的关键字,主要包括host,net,port, 例如 host 210.27.48.2,指明 210.27.48.2是一台主机,net 202.0.0.0 指明 202.0.0.0是一个网络地址,port 23 指明端口号是23。如果没有指定类型,缺省的类型是host.

  第二种是确定传输方向的关键字,主要包括src , dst ,dst or src, dst and src ,这些关键字指明了传输的方向。举例说明,src 210.27.48.2 ,指明ip包中源地址是210.27.48.2 , dst net 202.0.0.0 指明目的网络地址是202.0.0.0 。如果没有指明方向关键字,则缺省是src or dst关键字。

  第三种是协议的关键字,主要包括fddi,ip,arp,rarp,tcp,udp等类型。Fddi指明是在FDDI(分布式光纤数据接口网络)上的特定的网络协议,实际上它是”ether”的别名,fddi和ether具有类似的源地址和目的地址,所以可以将fddi协议包当作ether的包进行处理和分析。其他的几个关键字就是指明了监听的包的协议内容。如果没有指定任何协议,则tcpdump将会监听所有协议的信息包。
  除了这三种类型的关键字之外,其他重要的关键字如下:gateway, broadcast,less,greater,还有三种逻辑运算,取非运算是 ‘not ‘ ‘! ‘, 与运算是’and’,’&&’;或运算 是’or’ ,’││’;这些关键字可以组合起来构成强大的组合条件来满足人们的需要,下面举几个例子来说明。

  A、想要截获所有210.27.48.1 的主机收到的和发出的所有的数据包:

#tcpdump host 210.27.48.1

  B、想要截获主机210.27.48.1 和主机210.27.48.2 或210.27.48.3的通信,使用命令:(在命令行中适用   括号时,一定要

#tcpdump host 210.27.48.1 and  (210.27.48.2 or 210.27.48.3 )

  C、如果想要获取主机210.27.48.1除了和主机210.27.48.2之外所有主机通信的ip包,使用命令:

#tcpdump ip host 210.27.48.1 and ! 210.27.48.2

  D、如果想要获取主机210.27.48.1接收或发出的telnet包,使用如下命令:

#tcpdump tcp port 23 host 210.27.48.1

  (3). tcpdump的输出结果介绍

  下面我们介绍几种典型的tcpdump命令的输出信息

  A、数据链路层头信息
使用命令

#tcpdump --e host ice

ice 是一台装有linux的主机,她的MAC地址是0:90:27:58:AF:1A
H219是一台装有SOLARIC的SUN工作站,它的MAC地址是8:0:20:79:5B:46;上一条命令的输出结果如下所示:

21:50:12.847509 eth0 < 8:0:20:79:5b:46 0:90:27:58:af:1a ip 60: h219.33357 > ice.telne
t 0:0(0) ack 22535 win 8760 (DF)

  分析:21:50:12是显示的时间, 847509是ID号,eth0 表示从网络接口设备发送数据包, 8:0:20:79:5b:46是主机H219的MAC地址,它表明是从源地址H219发来的数据包. 0:90:27:58:af:1a是主机ICE的MAC地址,表示该数据包的目的地址是ICE . ip 是表明该数据包是IP数据包,60 是数据包的长度, h219.33357 > ice.telnet 表明该数据包是从主机H219的33357端口发往主机ICE的TELNET(23)端口. ack 22535 表明对序列号是222535的包进行响应. win 8760表明发送窗口的大小是8760.

  B、ARP包的TCPDUMP输出信息
  使用命令

#tcpdump arp

得到的输出结果是:

22:32:42.802509 eth0 > arp who-has route tell ice (0:90:27:58:af:1a)
22:32:42.802902 eth0 < arp reply route is-at 0:90:27:12:10:66 (0:90:27:58:af:1a) 分析: 22:32:42是时间戳, 802509是ID号, eth0 >表明从主机发出该数据包, arp表明是ARP请求包, who-has route tell ice表明是主机ICE请求主机ROUTE的MAC地址。 0:90:27:58:af:1a是主机ICE的MAC地址。

  C、TCP包的输出信息

  用TCPDUMP捕获的TCP包的一般输出信息是:

src > dst: flags data-seqno ack window urgent options
src > dst:表明从源地址到目的地址, flags是TCP包中的标志信息,S 是SYN标志, F (FIN), P (PUSH) , R (RST) "." (没有标记); data-seqno是数据包中的数据的顺序号, ack是下次期望的顺序号, window是接收缓存的窗口大小, urgent表明数据包中是否有紧急指针. Options是选项.

  D、UDP包的输出信息

  用TCPDUMP捕获的UDP包的一般输出信息是:

route.port1 > ice.port2: udp lenth

  UDP十分简单,上面的输出行表明从主机ROUTE的port1端口发出的一个UDP数据包到主机ICE的port2端口,类型是UDP, 包的长度是lenth

借助tcpdump统计http请求

这里所说的统计http请求,是指统计QPS(每秒请求数),统计前十条被访问最多的url。一般做这样的统计时,我们经常会使用网站访问日志来统计。当我们来到一个陌生的服务器环境,需要立即统计当前前十条被访问最多的url,来初步确定是否存在攻击行为,使用tcpdump则简单得多,因为我们不需要关心网站日志在哪,不需要考虑网站日志有没有开启之类的问题,直接用tcpdump捕捉当前的http包,再进一步过滤,就会得出我们想要的统计。此功能已集成到EZHTTP,下面是效果图:
Shell
下面介绍其统计方法。
1、捕捉10秒的数据包。

  1. tcpdump -i eth0 tcp[20:2]=0x4745 or tcp[20:2]=0x504f -w /tmp/tcp.cap -s 512 2>&1 &
  2. sleep 10
  3. kill `ps aux | grep tcpdump | grep -v grep | awk ‘{print $2}’`

此命令表示监控网卡eth0,捕捉tcp,且21-22字节字符为GE或者PO,表示匹配GET或者POST请求的数据包,并写到/tmp/tcp.cap文件。
2、这时候我们得到最新10秒的二进制数据包文件,我们下一步就是通过strings命令来找出GET/POST的url以及Host。

  1. strings /tmp/tcp.cap | grep -E "GET /|POST /|Host:" | grep –no-group-separator -B 1 "Host:" | grep –no-group-separator -A 1 -E "GET /|POST /" | awk ‘{url=$2;getline;host=$2;printf ("%sn",host""url)}’ > url.txt

此命令是本文的关键,通过strings显示二进制文件tcp.cap所有可打印字符,然后通过grep和awk过滤出http请求,并把拼接得到的url(包括域名+uri)写进一个文件url.txt。
3、这时我们拿到了近10秒钟所有的访问url,接下来的统计就容易得出,比如:
统计QPS:

  1. (( qps=$(wc -l /tmp/url.txt | cut -d’ ‘ -f 1) / 10 ))

排除静态文件统计前10访问url:

  1. grep -v -i -E ".(gif|png|jpg|jpeg|ico|js|swf|css)" /tmp/url.txt | sort | uniq -c | sort -nr | head -n 10