神兵利器:妙用 Linux 抓包工具 tcpdump 排查网络问题

序言

  作为开发人员,我们经常需要和第三方公司进行接口联调工作。

  要想联调成功,除了需要双方按照约定的接口文档进行代码开发外,还有个要求——双方网络得保持畅通。

  然鹅,网络这个家伙有时候非常调皮,偏偏喜欢搞事情,emmm,为了甩锅(啊不,为了定位问题),我们可以使用抓包工具去观察下数据包的出入流量情况,来确定到底是哪一方的问题。

  tcpdump是 Linux 自带的一个不错的抓包工具,那么,就跟随这篇文章来学习怎么使用它吧!

基础用法

监听第一块网卡往来的数据包

  不指定任何参数使用该命令,可以监听第一块网卡上往来的数据包。

1
tcpdump

监听特定网卡往来的数据包

  由于主机上大多不止一块网卡,所以经常需要监听特定网卡往来的数据包,此时增加-i参数即可指定特定网卡:

1
tcpdump -i en0

监听特定主机往来的数据包

  有时候,我们想监听本机跟其他某个主机之间往来的通信包,此时增加host参数指定对应主机即可:

1
tcpdump host 42.254.38.55

监听来自特定主机的数据包

  有时候,我们只想监听来自特定主机的数据包,此时在host参数上新增src即可:

1
tcpdump src host hostname

监听发往特定主机的数据包

  有时候,我们只想监听发往特定主机的数据包,此时在host参数上新增dst即可:

1
tcpdump dst host hostname

特定端口

  有时候,我们只想监听特定端口往来的数据包,此时使用port参数指定对应端口即可:

1
tcpdump port 3000

监听 TCP/UDP

  服务器上不同服务可能分别用了 TCP、UDP 作为传输层,假如只想监听 TCP 或 UDP 的数据包,可以使用以下命令:

1
2
tcpdump tcp
tcpdump udp

组合用法

来源主机+端口+TCP

  可以组合使用前面的命令,比如想监听来自主机123.207.116.169在端口22上的 TCP 数据包,可以使用如下命令:

1
tcpdump tcp port 8088 and src host 42.254.38.55

监听特定主机之间的通信

1
tcpdump ip host 210.27.48.1 and 210.27.48.2

210.27.48.1除了和210.27.48.2之外的主机之间的通信

1
tcpdump ip host 210.27.48.1 and ! 210.27.48.2

输出信息

  tcpdump总的的输出格式为:系统时间 来源主机.端口 > 目标主机.端口 数据包参数

  示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tcpdump host 120.76.246.190
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:03:29.143020 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [S], seq 2060160299, win 29200, options [mss 1460,sackOK,TS val 2563391267 ecr 0,nop,wscale 7], length 0
11:03:29.178998 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [S.], seq 322502808, ack 2060160300, win 28960, options [mss 1424,sackOK,TS val 3801373792 ecr 2563391267,nop,wscale 7], length 0
11:03:29.179029 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [.], ack 1, win 229, options [nop,nop,TS val 2563391303 ecr 3801373792], length 0
11:03:29.275934 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 1:223, ack 1, win 229, options [nop,nop,TS val 2563391400 ecr 3801373792], length 222
11:03:29.311967 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 223, win 235, options [nop,nop,TS val 3801373925 ecr 2563391400], length 0
11:03:29.313341 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 1:3196, ack 223, win 235, options [nop,nop,TS val 3801373926 ecr 2563391400], length 3195
11:03:29.313361 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [.], ack 3196, win 279, options [nop,nop,TS val 2563391438 ecr 3801373926], length 0
11:03:29.316801 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 223:349, ack 3196, win 279, options [nop,nop,TS val 2563391441 ecr 3801373926], length 126
11:03:29.352906 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3196:3247, ack 349, win 235, options [nop,nop,TS val 3801373966 ecr 2563391441], length 51
11:03:29.353136 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 349:1363, ack 3247, win 279, options [nop,nop,TS val 2563391477 ecr 3801373966], length 1014
11:03:29.444488 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 1363, win 251, options [nop,nop,TS val 3801374058 ecr 2563391477], length 0
11:03:29.590307 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3247:3790, ack 1363, win 251, options [nop,nop,TS val 3801374203 ecr 2563391477], length 543
11:03:29.590505 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 1363:1394, ack 3790, win 301, options [nop,nop,TS val 2563391715 ecr 3801374203], length 31
11:03:29.590524 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [F.], seq 1394, ack 3790, win 301, options [nop,nop,TS val 2563391715 ecr 3801374203], length 0
11:03:29.626439 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 1394, win 251, options [nop,nop,TS val 3801374239 ecr 2563391715], length 0
11:03:29.626483 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3790:3821, ack 1395, win 251, options [nop,nop,TS val 3801374240 ecr 2563391715], length 31
11:03:29.626500 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [R], seq 2060161694, win 0, length 0
11:03:29.626511 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [F.], seq 3821, ack 1395, win 251, options [nop,nop,TS val 3801374240 ecr 2563391715], length 0
11:03:29.626515 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [R], seq 2060161694, win 0, length 0

  上面输出信息的第一条数据,各部分内容含义:

  • 源地址主机(IP)是VM-0-12-centos,源端口是55650
  • 目的地址是 120.76.246.190,目的端口是 443(https端口)
  • >符号代表数据的方向
  • 具体报文

  对于上面的报文,包含了 TCP 协议的三次握手过程,下面是常见的 TCP 报文的 Flags:

  • [S]: SYN(开始连接)
  • [.]: 没有 Flag
  • [P]: PSH(推送数据)
  • [F]: FIN (结束连接)
  • [R]: RST(重置连接)

参数介绍

  • ip/icmp/arp/rarp/tcp/udp/icmp:这些协议可以放到第一个参数的位置,用来指定报文的协议类型
  • -t:不显示时间戳
  • -c 100:只抓取100个数据包
  • -w ./target.cap: 保存成cap文件,方便用其他抓包工具,如 wireshark 分析

保存到本地

备注:tcpdump默认会将输出写到缓冲区,只有缓冲区内容达到一定的大小,或者tcpdump退出时,才会将输出写到本地磁盘

1
tcpdump -n -vvv -c 1000 -w /tmp/tcpdump_save.cap

扩展——Wireshark 分析 tcpdump 文件

  通常情况下,使用客户端的抓包工具,比如Wireshark,会比 tcpdump 更容易分析应用层协议。

  那么,如何用Wireshark来分析tcpdump输出相关报文呢?

  一般的做法是这样:在远程服务器上先使用 tcpdump 抓取数据并写入文件,然后再将文件拷贝到本地,最后借助 Wireshark 分析。

  其实,还有一种更高效的方法,我们可以直接通过ssh连接将抓取到的数据实时发送给Wireshark进行分析。
  以 MacOS 系统为例,可以通过 brew cask install wireshark 来安装Wireshark,然后通过下面的命令来分析:

1
ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i

  如果想分析 DNS 协议,则可以使用下面的命令:

1
ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i

实战

  一个比较常见的部署方式:

  • 在服务器上部署了 Java 应用,应用使用8080端口
  • 在服务器上部署了 Nginx,Nginx 反向代理监听80端口,将外部的接口请求转发给 Java 应用(127.0.0.1:8080)。

  假设外部调用方向调用这边服务器上的 Java 应用接口,那么此时调用关系如下:

  外部调用方 -> Nginx 反向代理 -> Java Server

场景

  假设外部调用方(IP地址:202.14.122.107)访问服务器(IP地址:42.36.146.33),发现请求出现了异常的结果,那么,此问题该如何排查呢?

分析

  其实,这个问题核心就两点:

  • 确定调用方数据包有没有发出去
  • 确定我方服务器有没有接收来自调用方的数据包

解决方案

  我们当然可以通过查看各个应用的日志来确认请求是否到达,若各个应用都没有日志,说明对方的数据包应该没有发到服务器。

  不过,有时候外部调用方客户技术不行,人都是懵的,我明明调了你接口啊!你怎么说我没调?

  这个时候,你就要帮客户分析如何查问题,最通用的做法就是抓包看下!

  首先,我们要明确让客户意识到这边是没有接收到数据包的,此时我们可以在自己服务器抓入口包看下。

本方抓包

  若想在自己服务器抓某个端口的流量包,首先你想的是使用以下命令:

1
tcpdump port 8080

  但是,需要注意的是,当调用 8080 端口后,你会发现没有任何输出,即使 Java 应用已经收到了请求。

  为什么呢?
  由于 Nginx 转发到的地址是127.0.0.1,用的不是默认的网卡,因此,此时需要指定特定网卡。

1
tcpdump port 8080 -i lo

  执行上面命令后,要求客户再次调用下我方服务器接口,若这边控制台没有观测到任何数据,则说明——我方服务器确实没有收到客户的请求包,不是我们这边的问题

注意:上述结论的前提是 Nginx 层面没有拦截请求报文,比如限制了请求方报文大小导致不再进行转发。

外方抓包

  通过上面的本方抓包,我们确定了问题不在于接收端,因为接收端根本没有接收到数据报文,那么,此时问题很可能出在外部调用方的服务器了。

  那么,客户到底有没有数据包发过来呢,是不是被某个地方拦截了?

  假设现在的服务调用链路是:客户 A 服务 –> 客户 B 服务 –> 我方 S 服务。

  那么很可能,数据包在客户 B 服务截断了,这个时候,可以要求客户在自己的 B 服务器上查看下自己的出口流量,确定 B 服务调用 S 服务的包是否有发出去:

1
tcpdump dst host 42.36.146.33

  如果 A 服务调用 B 服务,而 B 服务做了一些限制:比如 B 服务的 Nginx 限制了报文大小,而 A 服务的报文太大,不被允许转发。那么,此时 B 服务不会再去调用 S 服务,上面的命令也观测不到任何结果了。

  这代表,客户自己的服务出问题了,要求客户自己去解决去。

文章信息

时间 说明
2022-09-06 初稿
0%