美文网首页web知识库
服务端的抓包

服务端的抓包

作者: hongqi_b715 | 来源:发表于2018-07-06 17:23 被阅读745次

    如果不会在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常用选项

    1. -i: 选定网卡的interface进行抓包,可取值为any(所有接口)、eth0(eth0接口)等
    2. -n: 不要对主机名进行解析,即直接显示IP地址
    3. -nn: 不要对主机名和端口进行解析,即直接显示IP地址和数字形式的端口号
    4. -X: 抓到的包展示内容的时候,既包括Hex的样式,也包括ASCII码的样式
    5. -s: 对每一个抓到的包,限制包的大小(以字节为单位):-s0表示不限制大小,全部抓取和展示,-s 128 表示展示128个字节

    2.3 tcpdump常用的过滤表达式

    在一个繁忙的服务器上抓包的时候,会有大量的traffic,为了过滤出来你关心的报文,经常使用Expression进行过滤。

    1. host: 对主机进行过滤,host后面跟服务器的IP,比如host 192.168.100.1表示只抓取源IP或者目的IP为192.168.100.1的报文
    2. port: 对端口进行过滤,port后面跟服务器的端口号,比如port 80表示只抓取源端口号或者目的端口号为80的报文
    3. srcdst: 这两个用户控制网络流的方向过滤,比如dst port 80表示只抓取目的端口号为80的报文,src ip 192.168.100.1表示只抓取源IP为192.168.100.1的报文
    4. 将过滤条件进行组合
      可以使用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):

    1. 20103的进程首先发送Fin包给MySQL
    2. MySQL对此Fin包进行ack
    3. MySQL再对20103的进程发Fin包
    4. 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

    常见标记:

    1. S ( SYN synchronize ):Synchronize sequence numbers to initiate a connection
    2. A ( ACK acknowledgement ): Acknowledge sender packet with sequence number
    3. F ( FIN finish ):The sender of the segment is finished sending data to its peer
    4. R ( RST reset ): Reset the connection (connection abort, usually because of an error)
    5. 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分布确定原因,一般而言原因分为几类:

    1. 服务方系统自身的bug,导致了ping-pang(A系统发请求给B,B请求了C,C又请求了A,也就是说同一个请求在系统间不停的路由转发而不总结),ping-pang是最为严重的bug,很容易把系统压瘫痪
    2. 系统配置有误,导致请求方不断在重试该请求
    3. 请求方底层依赖的框架或组件有bug(重发策略或路由分发策略等),导致不断重发一些过期的请求

    下面以MySQL不断收到如下SQL的查询为例,列举一些思路和使用到的命令,这里的核心是熟练掌握Linux的常用命令

    select * from `t_usr_profile` where `mobile_phone` = '13155668877'
    
    1. 在服务方的机器上根据服务端口抓一段时间包,假设服务端是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
    
    1. 搜索标识串(标识串为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
    --
    
    1. 将抓到的包的首行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
    
    1. 将请求方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
    
    1. 去掉端口号:使用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
    
    1. 将请求方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
    
    1. 统计请求方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次

    1. 依据出现次数排序: 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
    

    相关文章

      网友评论

        本文标题:服务端的抓包

        本文链接:https://www.haomeiwen.com/subject/efrluftx.html