如果不会在Linux命令行方式下抓包,永远不会成为服务端开发的高手;
当一个技术系统变得越来越庞大复杂的时候,用抓包的方式来掌握和理解其中的网络交互,就变得尤为重要;
抓包是掌握生产系统的一种方式,这种方式不依赖于具体的业务逻辑
最近发现身边很多Java程序员和PHP程序员不会在服务器上抓包,还是蛮吃惊的,这里结合多年的经验,讲解如何在服务器上抓包
1. Problem
这里列举几个典型的技术问题,这些问题使用其他方法比较难以解决,或者解决的效率比较低,但是使用抓包的方式就可以很好的处理。
1.1 后端系统间的通讯可靠性
假设两个后端系统A和B,A发了一个请求给B,但是B没有收到。A系统的业务日志显示已经发送请求,但是B系统的业务日志却没有收到相关请求的日志,如何定位问题
假如这个问题不是必现,而是偶尔出现,又该如何解决
1.2 如何确定请求方
有的时候,一个技术系统会突然收到很多莫名的请求,这些请求占用了蛮多的服务器资源,如何确定请求来自于哪里
2. Solution
在服务器上抓包,观察网络流可以解决上述问题,常用的抓包命令如下:
root@:~# tcpdump -iany -Xn -s0 port 443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
17:04:11.888152 IP 36.24.158.122.27603 > 10.133.206.234.https: Flags [S], seq 608876073, win 65535, options [mss 1412,nop,wscale 6,nop,nop,TS val 286050563 ecr 0,sackOK,eol], length 0
0x0000: 4500 0040 0000 4000 3306 abb6 2418 9e7a E..@..@.3...$..z
0x0010: 0a85 ceea 6bd3 01bb 244a b629 0000 0000 ....k...$J.)....
0x0020: b002 ffff 7918 0000 0204 0584 0103 0306 ....y...........
0x0030: 0101 080a 110c c903 0000 0000 0402 0000 ................
0x0040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
17:04:11.888235 IP 10.133.206.234.https > 36.24.158.122.27603: Flags [S.], seq 539909370, ack 608876074, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
0x0000: 4500 0034 0000 4000 4006 9ec2 0a85 ceea E..4..@.@.......
0x0010: 2418 9e7a 01bb 6bd3 202e 5cfa 244a b62a $..z..k...\.$J.*
0x0020: 8012 7210 9c28 0000 0204 05b4 0101 0402 ..r..(..........
0x0030: 0103 0307 0000 0000 0000 0000 0000 0000 ................
0x0040: 0000 0000 ....
2.1 tcpdump的工作原理
当网卡收到一个网络报文后,会将该报文发给所有能处理该报文的网络协议模块来进行解析处理。tcpdump通过注册一种虚拟的底层网络协议来获得对相关报文的处理权,同时将报文完整的复制一份,根据用户的过滤条件和展示选项进行报文的处理。
2.2 tcpdump常用选项
-
-i
: 选定网卡的interface进行抓包,可取值为any(所有接口)、eth0(eth0接口)等 -
-n
: 不要对主机名进行解析,即直接显示IP地址 -
-nn
: 不要对主机名和端口进行解析,即直接显示IP地址和数字形式的端口号 -
-X
: 抓到的包展示内容的时候,既包括Hex的样式,也包括ASCII码的样式 -
-s
: 对每一个抓到的包,限制包的大小(以字节为单位):-s0
表示不限制大小,全部抓取和展示,-s 128
表示展示128个字节
2.3 tcpdump常用的过滤表达式
在一个繁忙的服务器上抓包的时候,会有大量的traffic,为了过滤出来你关心的报文,经常使用Expression
进行过滤。
-
host
: 对主机进行过滤,host
后面跟服务器的IP,比如host 192.168.100.1
表示只抓取源IP或者目的IP为192.168.100.1的报文 -
port
: 对端口进行过滤,port
后面跟服务器的端口号,比如port 80
表示只抓取源端口号或者目的端口号为80的报文 -
src
和dst
: 这两个用户控制网络流的方向过滤,比如dst port 80
表示只抓取目的端口号为80的报文,src ip 192.168.100.1
表示只抓取源IP为192.168.100.1的报文 - 将过滤条件进行组合
可以使用and or not和小括号(需要\
来转义)来组合过滤条件,如下所示:
tcpdump -iany -Xn -s0 port 443 and \( not host 192.168.100.1 or host 192.168.100.2 \)
2.4 将tcpdump抓到的包保存到文件上
可以将tcpdump抓到的包保存到文件上,格式可以是二进制的(PCAP格式,可以下载到Windows用wireshark打卡),也可以是文本格式。
举例A. 将tcpdump抓到的包以二进制形式保存到文件上,使用-w
选项
tcpdump -iany -Xn -s0 port 443 -w /tmp/aaa.pcap
举例B. 将tcpdump抓到的包以文本形式保存到文件上,使用重定向
tcpdump -iany -Xn -s0 port 443 > /tmp/aaa.txt 2>&1
3. Discussion
3.1 TCP的连接建立
TCP的连接建立是通过收发双方的三次握手进行的
每个packet第一行里的Flags [S]
中表示握手阶段(S为synchronize的缩写),双方握手主要是在交换收发双方当前的序列号、窗口大小和其他TCP的选项;每个packet第一行里的ack 2335101116
表示接收方对发送方内容的ack,以序列号为标志。
14:26:52.511678 IP 10.133.206.234.36018 > 10.66.149.55.3306: Flags [S.], seq 2335101115, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
0x0000: 4500 0034 e5e4 4000 4006 dbf6 0a85 ceea E..4..@.@.......
0x0010: 0a42 9537 8cb2 0cea 8b2e d0bb 0000 0000 .B.7............
0x0020: 8002 7210 790f 0000 0204 05b4 0101 0402 ..r.y...........
0x0030: 0103 0307 0000 0000 6f6d 2060 745f 7573 ........om.`t_us
0x0040: 725f 7072 r_pr
14:26:52.511960 IP 10.66.149.55.3306 > 10.133.206.234.36018: Flags [S.], seq 3990271169, ack 2335101116, win 5760, options [mss 1404,nop,nop,sackOK,nop,wscale 7], length 0
0x0000: 4500 0034 0000 4000 3f06 c2db 0a42 9537 E..4..@.?....B.7
0x0010: 0a85 ceea 0cea 8cb2 edd6 b4c1 8b2e d0bc ................
0x0020: 8012 1680 47b0 0000 0204 057c 0101 0402 ....G......|....
0x0030: 0103 0307 7369 6f6e 2073 716c 5f6d 6f64 ....sion.sql_mod
0x0040: 653d 274f e='O
14:26:52.511991 IP 10.133.206.234.36018 > 10.66.149.55.3306: Flags [.], ack 1, win 229, length 0
0x0000: 4500 0028 e5e5 4000 4006 dc01 0a85 ceea E..(..@.@.......
0x0010: 0a42 9537 8cb2 0cea 8b2e d0bc edd6 b4c2 .B.7............
0x0020: 5010 00e5 7903 0000 0100 0001 1944 0000 P...y........D..
0x0030: 0203 6465 6608 6175 ..def.au
3.2 TCP的连接释放
TCP的连接释放是通过收发双方的四次释放完成的
每个packet第一行里的Flags [F]
中表示开始释放(F表示Finish)。
下述抓包是端口为20103的进程,主动关闭MySQL(端口号为3306):
- 20103的进程首先发送Fin包给MySQL
- MySQL对此Fin包进行ack
- MySQL再对20103的进程发Fin包
- 20103的进程对MySQL的Fin包进行确认
10:10:06.468550 IP 10.133.206.234.20103 > 10.66.149.55.3306: Flags [F.], seq 604, ack 4107, win 318, length 0
0x0000: 4500 0028 db7d 4000 4006 e669 0a85 ceea E..(.}@.@..i....
0x0010: 0a42 9537 4e87 0cea 35e7 a877 21a4 a45e .B.7N...5..w!..^
0x0020: 5011 013e 7903 0000 0204 057c 0101 0402 P..>y......|....
0x0030: 0103 0307 0000 0072 .......r
10:10:06.468853 IP 10.66.149.55.3306 > 10.133.206.234.20103: Flags [.], ack 604, win 62, length 0
0x0000: 4508 0028 b918 4000 3f06 09c7 0a42 9537 E..(..@.?....B.7
0x0010: 0a85 ceea 0cea 4e87 21a4 a45e 35e7 a877 ......N.!..^5..w
0x0020: 5010 003e 36db 0000 1000 0000 0375 7365 P..>6........use
0x0030: 2060 6175 6374 696f .`auctio
10:10:06.468856 IP 10.66.149.55.3306 > 10.133.206.234.20103: Flags [F.], seq 4107, ack 604, win 62, length 0
0x0000: 4508 0028 b919 4000 3f06 09c6 0a42 9537 E..(..@.?....B.7
0x0010: 0a85 ceea 0cea 4e87 21a4 a45e 35e7 a877 ......N.!..^5..w
0x0020: 5011 003e 36da 0000 5800 0000 0a35 2e35 P..>6...X....5.5
0x0030: 2e32 342d 4344 422d .24-CDB-
10:10:06.468886 IP 10.133.206.234.20103 > 10.66.149.55.3306: Flags [.], ack 4108, win 318, length 0
0x0000: 4500 0028 db7e 4000 4006 e668 0a85 ceea E..(.~@.@..h....
0x0010: 0a42 9537 4e87 0cea 35e7 a878 21a4 a45f .B.7N...5..x!.._
0x0020: 5010 013e 7903 0000 3100 0000 1673 6574 P..>y...1....set
0x0030: 206e 616d 6573 2027 .names.'
3.3 TCP的数据传输
一般而言,TCP建立连接后开始进行数据传输,第一行里的length字段表示传输的payload的长度。
Flags [P.]
表示发送方已经将当前发送buffer里的所有数据发给接收方,接收方需要尽快将此数据上报给顶层应用,P表示Push。如果没有Flags [P.]
,则表示此packet发完之后,发送buffer里还有待发送的数据。
示例A:MySQL应答了1424字节的数据,但是MySQL对应的tcp里的发送buffer,还有数据。
10:10:06.458574 IP 10.66.149.55.3306 > 10.133.206.234.20103: Flags [.], seq 169:1593, ack 522, win 62, length 1424
0x0000: 4508 05b8 b914 4000 3f06 043b 0a42 9537 E.....@.?..;.B.7
0x0010: 0a85 ceea 0cea 4e87 21a4 94fc 35e7 a825 ......N.!...5..%
......
示例B:MySQL应答了618字节的数据,MySQL对应的tcp里的发送buffer里已经没有数据,接收方需要尽快将此数据上浮给高层应用。
10:10:06.458574 IP 10.66.149.55.3306 > 10.133.206.234.20103: Flags [P.], seq 1593:2211, ack 522, win 62, length 618
0x0000: 4508 0292 b915 4000 3f06 0760 0a42 9537 E.....@.?..`.B.7
0x0010: 0a85 ceea 0cea 4e87 21a4 9a8c 35e7 a825 ......N.!...5..%
0x0020: 5018 003e 4bc7 0000 725f 6e61 6d65 0e64 P..>K...r_name.d
0x0030: 6561 6c5f 6164 6472 5f6e 616d 650c e000 eal_addr_name...
3.4 TCP的包头格式
1530845376699.jpg常见标记:
- S ( SYN synchronize ):Synchronize sequence numbers to initiate a connection
- A ( ACK acknowledgement ): Acknowledge sender packet with sequence number
- F ( FIN finish ):The sender of the segment is finished sending data to its peer
-
R
( RST reset ): Reset the connection (connection abort, usually because of an error) - P ( PSH push ): The receiver should pass this data to the application as soon as possible
备注:R标记的包,在网络排故的时候很有用,如果tcpdump抓出来的包有大量的reset标记,大概率意味着连接请求被防火墙或者其他安全措施挡住啦。
3.5 二进制协议
TCP/IP协议族中的大部分协议,其Header都组织为二进制格式,即每个bit或者bit串需要当成数值直接定义;另外很多Application也将自己的协议(例如PC QQ)组织为二进制的格式,二进制协议不易读,但是占用空间小,因而传输效率高。
下图为一个应用程序发起连接MySQL的请求,报文里0x0012字节的值为0x4e87 = 20103,即为发送方的端口号;报文里0x0013字节的值为0x0cea = 3306,即为服务方的端口号。
WechatIMG642.jpeg对于二进制协议,经常接合包里某个偏移位置上的内容进行抓包,比如下边的命令可以抓取所有和MySQL交互的报文:
root@:~# tcpdump -iany -Xn -nn -s0 ip[0x0014:2] = 0x0cea or ip[0x0016:2] = 0x0cea
3.6 字符串协议
包括MySQL、RabbitMQ在内,很多Application也会将自己的协议组织为字符串的格式,字符串格式的协议比较human-readable,可以对抓到的报文做很多二次操作。
对SQL(select * from t_usr_profile
where mobile_phone
= '13155668877')的抓包结果如下:
11:34:38.933455 IP 10.133.206.234.33166 > 10.66.149.55.3306: Flags [P.], seq 2397:2488, ack 148577, win 1475, length 91
0x0000: 4500 0083 bdf5 4000 4006 0397 0a85 ceea E.....@.@.......
0x0010: 0a42 9537 818e 0cea daa3 0567 8b16 d9f5 .B.7.......g....
0x0020: 5018 05c3 795e 0000 5400 0000 0000 0050 P...y^..T......P
0x0030: 0000 0003 5345 4c45 4354 202a 2046 524f ....SELECT.*.FRO
0x0040: 4d20 6074 5f75 7372 5f70 726f 6669 6c65 M.`t_usr_profile
0x0050: 6020 5748 4552 4520 606d 6f62 696c 655f `.WHERE.`mobile_
0x0060: 7068 6f6e 6560 203d 2027 3133 3135 3536 phone`.=.'131556
0x0070: 3638 3837 3727 204c 494d 4954 2030 2c31 68877'.LIMIT.0,1
0x0080: 3030 3041 5445 2c45 5252 4f52 5f46 4f52 000ATE,ERROR_FOR
0x0090: 5f44 49 _DI
我们可以把这类抓包保存到文本文件上,结合Linux的搜索命令,快速定位出操作手机号131556的请求(主要是搜索抓包结果最右侧的文本内容,因此注意折行)
3.7 其他方式的抓包
HTTPS的抓包:HTTPS因为加密的缘由,已经无法查看抓到的包的内容啦,如果大伙有高招(服务端),请告诉我。
另外,tcpdump的兄弟工具,wireshark以插件的形式对外提供了很多parse包的定制化能力,结合Lua语言可以构建出强大的二次分析能力。
4. Answer
4.1 请求的不可达(必现)
假设两个后端系统A和B,A发了一个请求给B,但是B没有收到。A系统的业务日志显示已经发送请求,但是B系统的业务日志却没有收到相关请求的日志,如何定位问题
在B的服务器上抓包,过滤主机A的IP,观察相关的报文是否达到服务器B上
tcpdump -iany -Xn -s0 host 192.168.100.1
4.2 请求的不可达(不是必现)
假如4.1的问题不是必现,而是偶尔出现,又该如何解决
在后台抓一段时间的包,之后再分析抓包的结果(特别注意:如果抓包量比较大的话,一定要避免把磁盘打满,此时需要增加更多的过滤表达式)
//将抓到的包输出到/tmp/1里,并且在后台运行
root@:~# nohup tcpdump -iany -Xn -nn -s0 host 10.66.149.55 > /tmp/1 2>&1 &
//可以看到存储包的文件大小在不断的涨
root@:~# ls -lh /tmp/1
-rw-r--r-- 1 root root 2.4M 7月 6 17:05 /tmp/1
//查看后台运行的job
root@:~# jobs
[2]+ 运行中 nohup tcpdump -iany -Xn -nn -s0 host 10.66.149.55 > /tmp/1 2>&1 &
//将后台运行的job切换到前台
root@:~# fg 2
nohup tcpdump -iany -Xn -nn -s0 host 10.66.149.55 > /tmp/1 2>&1
//结束抓包 Ctrl + C
//用其他命令来分析抓包结果,进而判断究竟是包没有达到B,还是A没有发出,还是被防火墙拦住啦
root@:~# ls -lh /tmp/1
-rw-r--r-- 1 root root 11M 7月 6 17:08 /tmp/1
4.3 确定请求方
有的时候,一个技术系统会突然收到很多莫名的请求,这些请求占用了蛮多的服务器资源,如何确定请求来自于哪里。
假设这个请求有固定的特征,比如说有固定的订单号或其他标识串,这个特征可以在抓包结果的最右侧捕获到。
解决这类问题的总思路是分析请求方服务器IP的分布,根据IP分布确定原因,一般而言原因分为几类:
- 服务方系统自身的bug,导致了ping-pang(A系统发请求给B,B请求了C,C又请求了A,也就是说同一个请求在系统间不停的路由转发而不总结),ping-pang是最为严重的bug,很容易把系统压瘫痪
- 系统配置有误,导致请求方不断在重试该请求
- 请求方底层依赖的框架或组件有bug(重发策略或路由分发策略等),导致不断重发一些过期的请求
下面以MySQL不断收到如下SQL的查询为例,列举一些思路和使用到的命令,这里的核心是熟练掌握Linux的常用命令。
select * from `t_usr_profile` where `mobile_phone` = '13155668877'
- 在服务方的机器上根据服务端口抓一段时间包,假设服务端是MySQL,则使用
dst port 3306
来过滤抓到的包,将抓到的包重定向到临时文件上(aaa),形如:
nohup tcpdump -iany -Xn -s0 dst port 3306 > /tmp/aaa 2>&1 &
; /tmp/aaa内容如下:
11:34:38.933455 IP 10.133.206.234.33166 > 10.66.149.55.3306: Flags [P.], seq 2397:2488, ack 148577, win 1475, length 91
0x0000: 4500 0083 bdf5 4000 4006 0397 0a85 ceea E.....@.@.......
0x0010: 0a42 9537 818e 0cea daa3 0567 8b16 d9f5 .B.7.......g....
0x0020: 5018 05c3 795e 0000 5400 0000 0000 0050 P...y^..T......P
0x0030: 0000 0003 5345 4c45 4354 202a 2046 524f ....SELECT.*.FRO
0x0040: 4d20 6074 5f75 7372 5f70 726f 6669 6c65 M.`t_usr_profile
0x0050: 6020 5748 4552 4520 606d 6f62 696c 655f `.WHERE.`mobile_
0x0060: 7068 6f6e 6560 203d 2027 3133 3135 3536 phone`.=.'131556
0x0070: 3638 3837 3727 204c 494d 4954 2030 2c31 68877'.LIMIT.0,1
0x0080: 3030 3041 5445 2c45 5252 4f52 5f46 4f52 000ATE,ERROR_FOR
0x0090: 5f44 49 _DI
- 搜索标识串(标识串为131556),并将所抓包的摘要行一起展示。注意grep命令的-B选项
root@:/tmp# grep "131556" aaa -B7
aaa-10:44:03.460160 IP 10.133.206.234.62790 > 10.66.149.55.mysql: Flags [P.], seq 514:580, ack 329, win 237, length 66
aaa- 0x0000: 4500 006a 3705 4000 4006 8aa0 0a85 ceea E..j7.@.@.......
aaa- 0x0010: 0a42 9537 f546 0cea f4ed dac4 03dc 4e32 .B.7.F........N2
aaa- 0x0020: 5018 00ed 7945 0000 3e00 0000 1703 0000 P...yE..>.......
aaa- 0x0030: 0000 0100 0000 0001 0800 0800 0800 0800 ................
aaa- 0x0040: 0800 0200 0000 0000 0000 f328 405b 0000 ...........(@[..
aaa- 0x0050: 0000 0092 3f5b 0000 0000 f328 405b 0000 ....?[.....(@[..
aaa: 0x0060: 0000 0000 0000 0000 0000 3133 3135 3536 ..........131556
--
- 将抓到的包的首行grep出来:
grep " > "
root@:/tmp# grep "131556" aaa -B7 | grep " >"
10:43:17.476791 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2208:2286, ack 10677, win 1030, length 78
10:43:17.642296 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2286:2364, ack 11057, win 1052, length 78
10:43:17.808716 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2364:2442, ack 11437, win 1074, length 78
10:43:17.935525 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2442:2520, ack 11817, win 1096, length 78
10:43:18.077713 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2520:2598, ack 12197, win 1119, length 78
10:43:18.264376 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2598:2676, ack 12577, win 1141, length 78
10:43:18.396421 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2676:2754, ack 12957, win 1163, length 78
10:43:18.555735 IP 10.133.206.234.62672 > 10.66.149.55.mysql: Flags [P.], seq 2754:2832, ack 13337, win 1185, length 78
- 将请求方IP列出来:
awk '{print $3}'
root@:/tmp# grep "131556" aaa -B7 | grep " >" | awk '{print $3}'
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
10.133.206.234.62672
- 去掉端口号:使用cut命令,
-d .
标识以.进行分割,-f 1,2,3,4
表示显示1 2 3 4四列
root@:/tmp# grep "131556" aaa -B7 | grep " >" | awk '{print $3}' | cut -d . -f 1,2,3,4
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
- 将请求方IP的排序: sort
root@:/tmp# grep "131556" aaa -B7 | grep " >" | awk '{print $3}' | cut -d . -f 1,2,3,4 | sort
10.133.206.234
10.133.206.234
10.133.206.234
10.133.206.234
- 统计请求方IP的出现次数: uniq -c
root@:/tmp# grep "131556" aaa -B7 | grep " >" | awk '{print $3}' | cut -d . -f 1,2,3,4 | sort | uniq -c
61 10.133.206.234
说明10.133.206.234请求了61次
- 依据出现次数排序: sort -n
root@:/tmp# grep "131556" aaa -B7 | grep " >" | awk '{print $3}' | cut -d . -f 1,2,3,4 | sort | uniq -c | sort -n
61 10.133.206.234
网友评论