跨平台应用开发进阶(四十三)一文走近网络层抓包工具:WhireShark

一、前言

网络是移动应用生命线,网络层面的各种问题会给移动应用带来许多迷惑的行为和症状。通过抓取网络包数据,可以针对性地分析由网络层面问题引起的各种症状,包括连接中断TLS 握手失败DNS 解析失败等错误。

CharlesFiddler 可以帮助捕获和分析 HTTP 层面的问题,如果问题发生在 TCP/IP 层面,则需要 TCP 报文的捕获和分析工具。Wireshark(支持 Mac/Windows 平台)、Network Monitor(Windows 平台)和 TCPDUMP 是常用的三种网络层抓包工具。比较常见的网络层问题包括 SSL 握手失败TCP 链接中断重发等。

本节介绍 Wireshark基本使用方法。

wireshark是非常流行的网络封包分析软件,功能十分强大。可以截取各种网络封包,显示网络封包的详细信息。使用wireshark的人必须了解网络协议,否则就看不懂wireshark抓包信息。

为了安全考虑,wireshark只能查看封包,而不能修改封包的内容或者发送封包。 wireshark能获取HTTP,也能获取HTTPS,但是不能解密HTTPS。所以wireshark看不懂HTTPS中的内容。如果是处理HTTPHTTPS 还是用Fiddler,其他协议比如TCPUDP 就用wireshark

二、条件过滤

  • 过滤源ip、目的ip

    shell 复制代码
    ip.dst==192.168.101.8 #查找目的地址为192.168.101.8的包
    
    ip.src==1.1.1.1 #查找源地址为1.1.1.1 的包
  • 端口过滤

    shell 复制代码
    tcp.port==80 #把源端口和目的端口为80的都过滤出来
    
    tcp.dstport==80 #只过滤目的端口为80的
    
    tcp.srcport==80 #只过滤源端口为80的包
  • 协议过滤

    shell 复制代码
    arp #ARP协议
    
    icmp || icmpv6 #ICMP协议
    
    smb || nbss || nbns || nbipx || ipxsap || netbios #Server Message Block类协议
    
    http || tcp.port == 80 || http2 #HTTP协议,这是很简陋的识别方法
    
    tcp.flags & 0x02 || tcp.flags.fin == 1 #TCP连接的起始和关闭报文
    
    hsrp || eigrp || ospf || bgp || cdp || vrrp || carp || gvrp || igmp || ismp #路由类协议
    
    eth.fcs_bad==1 || ip.checksum_bad==1 || tcp.checksum_bad==1 || udp.checksum_bad==1 || sctp.checksum_bad==1 || mstp.checksum_bad==1 || cdp.checksum_bad==1 || edp.checksum_bad==1 || wlan.fcs_bad==1 #条件中的各类协议的checksum异常
    
    ! ip.dst == 224.0.0.0/4 && ip.ttl < 5 && !pim) || (ip.dst == 224.0.0.0/24 && ip.dst != 224.0.0.251 && ip.ttl != 1 && !(vrrp || carp) #TTL异常报文
    
    tcp.flags.reset eq 1 #TCP流被RESET报文
    
    icmp.type eq 3 || icmp.type eq 4 || icmp.type eq 5 || icmp.type eq 11 || icmpv6.type eq 1 || icmpv6.type eq 2 || icmpv6.type eq 3 || icmpv6.type eq 4 #ICMP协议错误,协议的type字段值错误报文
  • http模式过滤

    shell 复制代码
    http.request.method=="GET" #过滤get请求的包
    
    http.request.method=="POST" #过滤post请求的包
  • 两个过滤条件连接符and的使用

    shell 复制代码
    ip.src==192.168.101.8 and http #过滤ip为192.168.101.8并且为http协议
  • 过滤http握手的空报文

    使用wireshark打开pcap包,通过条件过滤数据长度为0的包,命令如下:

    shell 复制代码
    tcp.len > 0
  • 过滤重复数据包

    使用cmd命令窗口,进入wireshark安装目录,找到editcap.exe程序。执行editcap.exe -d命令,指定源文件(d:\input.pcap)和目标文件(d:\output.pcap),命令如下:

    shell 复制代码
    C:\Program Files\Wireshark>editcap.exe -d d:\input.pcap d:\output.pcap
  • 过滤TCP重传数据包

    使用wireshark打开pcap包,通过条件过滤tcp.analysis.retransmission的包,命令如下:

    shell 复制代码
    http && !(tcp.analysis.retransmission)
  • 过滤TCP解析错误数据包

    使用wireshark打开pcap包,通过条件过滤TCP重传,乱序,丢包,重复响应的包,命令如下:

    shell 复制代码
    tcp.analysis.flags && !tcp.analysis.window_update

三、字段含义

在TCP层,字段有以下⼏个标识:SYN、FIN、ACK、PSH、RST、URG。

它们的含义是:

  • SYN表⽰建⽴连接;
  • FIN表⽰关闭连接;
  • ACK表⽰响应;
  • PSH表⽰有数据传输;
  • RST表⽰连接重置。
  • URG:Urget pointer is valid (紧急指针字段值有效)

其中,ACK是可能与SYNFIN等同时使⽤的,⽐如SYNACK可能同时为1,它表⽰的就是建⽴连接之后的响应,如果只是单个的⼀个SYN,它表⽰的只是建⽴连接。TCP的⼏次握⼿就是通过这样的ACK表现出来的。但SYN与FIN是不会同时为1的,因为前者表⽰的是建⽴连接,⽽后者表⽰的是断开连接。

RST⼀般是在FIN之后才会出现为1的情况,表⽰的是连接重置。⼀般地,当出现FIN包或RST包时,我们便认为客户端与服务器端断开了连接;⽽当出现SYNSYN+ACK包时,我们认为客户端与服务器建⽴了⼀个连接。PSH为1的情况,⼀般只出现在DATA内容不为0的包中,也就是说PSH为1表⽰的是有真正的TCP数据包内容被传递。

wireshark抓包过滤条件:

shell 复制代码
(ip.src == 127.0.0.1) && (tcp.srcport == 27015 || tcp.dstport == 27015) && (tcp.flags.push == 1)

四、TCP连接

三次握手Three-way Handshake

一个虚拟连接的建立是通过三次握手来实现。

  1. (B) --> [SYN] --> (A)

    假如服务器A和客户机B通讯. 当A要和B通信时,B首先向A发一个SYN (Synchronize) 标记的包,告诉A请求建立连接.。

    注意⚠️: 一个 SYN包就是仅SYN标记设为1的TCP包(参见TCP包头Resources)。认识到这点很重要,只有当A受到B发来的SYN包,才可建立连接,除此之外别无他法。因此,如果你的防火墙丢弃所有的发往外网接口的SYN包,那么你将不 能让外部任何主机主动建立连接。

  2. (B) <-- [SYN/ACK] <--(A)

    接着,A收到后会发一个对SYN包的确认包(SYN/ACK)回去,表示对第一个SYN包的确认,并继续握手操作。

    注意⚠️: SYN/ACK包是仅SYN 和 ACK 标记为1的包。

  3. (B) --> [ACK] --> (A)

    B收到SYN/ACK 包,B发一个确认包(ACK),通知A连接已建立。至此,三次握手完成,一个TCP连接完成。

    注意⚠️: ACK包就是仅ACK 标记设为1的TCP包。需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位。

这就是为何连接跟踪很重要的原因了。没有连接跟踪,防火墙将无法判断收到的ACK包是否属于一个已经建立的连接。一般的包过滤(Ipchains)收到ACK包时,会让它通过(这绝对不是个好主意)。而当状态型防火墙收到此种包时,它会先在连接表中查找是否属于哪个已建连接,否则丢弃该包。

4.1 三次握手示例

通过TCP三次握手:SYN-SYN ACK-ACK,建立连接 以抓取443为例(加密,端口不一定为443):

先:DNS请求

DNS请求:

DNS response:

客户端发起TCP三次握手:

第一次:SYN=1,ACK=0,端口61020--443

第二次:SYN=1,ACK=0+1,端口443-61020,确认序号=序列号+1

第三次:ACK=1,端口61020--443

Client发送hello包:

  • Random:随机生成数,用于生成最终密钥
  • Session ID:会话标识符
  • Cipher Suites:加密套件,
  • Compression Merhods:压缩方法 Server hello:

服务器也生成了一个随机数发送给客户端,双方同时拥有两个随机数 服务器返回证书,客户端收到后根据证书链辨别真伪,服务器中有公钥,用于加密后面生成的Prenaster secret(会话密钥) 安全连接建立,发送数据:Application Data

TLS传输过程:

①-④:握手阶段 ⑤:握手后双方使用协商好的密钥进行通讯 ②中有多个类型,是因为它是一个多握手信息,一次性发送多个握手协议包 SNI:TLS的扩展,用来解决一个服务器拥有多个域名的情况 TLS握手信息中并不携带客户端要访问的目标地址,若一台服务器用多个虚拟主机,且域名不同,使用了不一样的证书,TLS使用添加host的方法识别访问那台虚拟主机,在握手第一阶段ClientHello的报文中添加SNI中包含 Server NAME,即Host内容。

五、色彩规则

黑色:报文错误(TCP解析错误、重传、乱序、丢包、重复响应)

  • TCP dup ack:重复应答

  • TCP Retransmission:TCP重传,TCP有超时重传机制

  • TCP Otu-of-Order:乱序,网络拥塞导致包到达时间不同,时延长,导致包丢失

  • TCP Previous segment not captured:前一段未捕获,丢失

  • TCP Dup ACK:TCP重复应答,#前表示丢失序号,后表示丢失次数

  • TCP Retransmission:TCP重传

  • TCP ACKed unseen segment:报文没抓全,此报文是ACK报文

  • TCP ZeroWindow与TCP Window Full:

  • TCP ZeroWindow:告诉对方,我的接收窗口的大小,即出现时告诉对方不要在发送数据

  • TCP Window Full:当待发送数据为0,出现Full,表示我不能再发送数据了

  • HSRP State Change:HSRP(热备份协议),表示状态非active和standby

  • Spanning Tree Topology Change:生成树协议状态为0x80,拓扑发生变化

  • OSPF State Chang:OSPF的msg类型不是hello

  • ICMP errors:ICMP协议错误,协议type字段值错误

红色:各类异常

  • TCP RST:TCP流被RESET,出现原因:1、端口未打开2、请求超时3、提前关闭连接4、在一个已关闭的socket上收数据。断开连接,远端服务器尝试打开链接但无结果时,也会初心RST信号,这是防火墙阻隔连接的情况,每个SYN都返回一个RST。

  • SCTP ABORT:串流控制协议的chunk_type为ABORT

  • TTL low or unexpected:TTL异常

  • Checksum Errors:条件中的各类checksum异常,在PC上抓包时网卡的一些设置经常会使Wireskark显示此错误

其他:正常

  • SMB:Server Message Block类协议
  • IPX:互联网数据包交换类协议
  • TCP SYN/FIN:TCP连接的起始和关闭
  • TCP/ARP/ICMP/UDP/HTTP/Routing/Broadcast:TCP/ARP/ICMP/UDP/HTTP/路由协议/广播数据

六、分析方法

6.1 Packet size limited during capture

当你看到这个提示,说明被标记的那个包没有抓全。以下图的4号包为例,它全长有171字节,但只有前96个字节被抓到了,因此Wireshark给了此提示。

这种情况一般是由抓包方式引起的。在有些操作系统中,tcpdump默认只抓每个帧的前96个字节,我们可以用"-s"参数来指定想要抓到的字节数,比如下面这条命令可以抓到1000字节。

shell 复制代码
[root@my_server /]# tcpdump -i eth0 -s 1000 -w /tmp/tcpdump.cap

6.2 TCP Previous segment not captured

在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的Seq号等于前一个包的Seq Len(三次握手和四次挥手是例外)。如果Wireshark发现后一个包的Seq号大于前一个包的Seq Len,就知道中间缺失了一段数据。假如缺失的那段数据在整个网络包中都找不到(即排除了乱序),就会提示[TCP Previous segment not captured]。比如在下图这个例子中,6号包的Seq号1449大于5号包的Seq Len=1 0=1,说明中间有个携带1448字节的包没被抓到,它就是"Seq=1, Len=1448"。

网络包没被抓到还分两种情况:一种是真的丢了;另一种是实际上没有丢,但被抓包工具漏掉了。在Wireshark中如何区分这两种情况呢?只要看对方回复的确认(Ack)就行了。如果该确认包含了没抓到的那个包,那就是抓包工具漏掉而已,否则就是真的丢了。

顺便分析一下图中这个网络包,它是HTTPS传输异常时在客户端抓的。因为"Len: 667"的小包(即6号包)可以送达,但"Len: 1448"的大包却丢了,说明路径上可能有个网络设备的MTU比较小,会丢弃大包。后来的解决方式证实了这个猜测,只要把整个网络路径的MTU保持一致,问题就消失了。

6.3 TCP ACKed unseen segment

当Wireshark发现被Ack的那个包没被抓到,就会提示 [TCP ACKed unseen segment]。这可能是最常见的Wireshark提示了,幸好它几乎是永远可以忽略的。以图3为例,32号包的Seq Len=6889 1448=8337,说明服务器发出的下一个包应该是Seq=8337。而我们看到的却是35号包的Seq=11233,这意味着8337~11232这段数据没有被抓到。这段数据本应该出现在34号之前,所以Wireshark提示了[TCP ACKed unseen segment]。

不难想象,在一个网络包的开头会经常看到这个提示,因为只抓到了后面的Ack但没抓到前面的数据包。

6.4 TCP Out-of-Order

在TCP传输过程中(不包括三次握手和四次挥手),同一台主机发出的数据包应该是连续的,即后一个包的Seq号等于前一个包的Seq + Len。也可以说,后一个包的Seq会大于或等于前一个包的Seq。当Wireshark发现后一个包的Seq号小于前一个包的Seq + Len时,就会认为是乱序了,因此提示 [TCP Out-of-Order] 。如下图所示,3362号包的Seq=2685642小于3360号包的Seq=2712622,所以就是乱序。

小跨度的乱序影响不大,比如原本顺序为1、2、3、4、5号包被打乱成2、1、3、4、5就没事。但跨度大的乱序却可能触发快速重传,比如打乱成2、3、4、5、1时,就会触发足够多的Dup ACK,从而导致1号包的重传。

6.5 TCP Dup ACK

当乱序或者丢包发生时,接收方会收到一些Seq号比期望值大的包。它每收到一个这种包就会Ack一次期望的Seq值,以此方式来提醒发送方,于是就产生了一些重复的Ack。Wireshark会在这种重复的Ack上标记[TCP Dup ACK] 。

以下图为例,服务器收到的7号包为"Seq=29303, Len=1460",所以它期望下一个包应该是Seq Len=29303 +1460=30763,没想到实际收到的却是8号包Seq=32223,说明Seq=30763那个包可能丢失了。因此服务器立即在9号包发了Ack=30763,表示"我要的是Seq=30763"。由于接下来服务器收到的10号、12号、14号也都是大于Seq=30763的,因此它每收到一个就回复一次Ack=30763,从图中可见Wireshark在这些回复上都标记了[TCP Dup ACK]。

6.6 TCP Fast Retransmission

当发送方收到3个或以上[TCP Dup ACK],就意识到之前发的包可能丢了,于是快速重传它(这是RFC的规定)。以图6为例,客户端收到了4个Ack=991851,于是在1177号包重传了Seq=991851。

6.7 TCP Retransmission

如果一个包真的丢了,又没有后续包可以在接收方触发[Dup Ack],就不会快速重传。这种情况下发送方只好等到超时了再重传,此类重传包就会被Wireshark标上[TCP Retransmission]。以下图为例,客户端发了原始包(包号1053)之后,一直等不到相应的Ack,于是只能在100多毫秒之后重传了(包号1225)。

6.8 TCP zerowindow

TCP包中的"win="代表接收窗口的大小,即表示这个包的发送方当前还有多少缓存区可以接收数据。当Wireshark在一个包中发现"win=0"时,就会给它打上"TCP zerowindow"的标志,表示缓存区已满,不能再接受数据了。比如下图就是服务器的缓存区已满,所以通知客户端不要再发数据了。我们甚至可以在3258~3263这几个包中看出它的窗口逐渐减少的过程,即从win=15872减小到win=1472。

6.9 TCP window Full

当Wireshark在一个包中打上[TCP window Full]标志时,就表示这个包的发送方已经把对方所声明的接收窗口耗尽了。以下图为例,Britain一直声明它的接收窗口只有65535,意味着Middle East最多能给它发送65535字节的数据而无需确认,即"在途字节数"最多为65535字节。当Wireshark在包中计算出Middle East已经有65535字节未被确认时,就会发出此提示。

[TCP window Full]很容易跟[TCP zerowindow]混淆,实际上它们也有相似之处。前者表示这个包的发送方暂时没办法再发送数据了,后者表示这个包的发送方暂时没办法再接收数据了,也就是说两者都意味着传输暂停,都必须引起重视。

6.10 TCP segment of a reassembled PDU

当你收到这个提示,肯定已经在EditàPreferencesààTCP菜单里启用了Allow sub dissector to reassemble TCP streams。它表示Wireshark可以把属于同一个应用层PDU(比如SMB的Read Response和Write Request之类)的TCP包虚拟地集中起来。如下图所示,这一个SMB Read Response由39~48号包共同完成,因此Wireshark在最后一个包中虚拟地把所有包集中起来。这样做有个好处,就是可以右键点击下图底部的方框,选择CopyàBytesàPrintable Text Only,从而复制整个应用层的PDU。做研发的同学可能比较需要这个功能。

6.11 Continuation to

看到这个提示,说明已经在EditàPreferencesàProtocolsàTCP菜单里关闭了Allow sub dissector to reassemble TCP streams。比如上图的那些包,一关闭就变成下图这样。

仔细对比2图,你会发现Read Response在图10中被算在了48号包头上,而在图11中被算到了39号包头上。这样会带来一个诡异的结果:图10的读响应时间为2.528毫秒(38号包和48号包的时间差),而图11的读响应时间为2.476毫秒(38号包和39号包的时间差)。究竟哪个算正确呢?这个问题很难回答,如果在乎的是实际的总性能,那就看前者;如果想忽略TCP/IP协议的损耗,单看服务器的响应速度,那就看后者。在某些特殊情况下,这两者相差非常大,所以必须搞清楚。

6.12 Time-to-live exceeded (Fragment reassembly time exceeded)

ICMP的报错有好多种,大都不难理解,所以我们只举其中的一种为例。 [Fragment reassembly time exceeded]表示这个包的发送方之前收到了一些分片,但是由于某些原因迟迟无法组装起来。比如在图12中,由于上海发往北京的一些包被分片传输,且有一部分在路上丢失了,所以北京方无法组装起来,便只好用这个ICMP报错告知上海方。

七、问题追踪

从上述案例的分享和实践中可以看到,TLS 层面的问题在客户端的症状表现上有相似之处,但是问题的根因却大相径庭。这里例举的问题虽不能覆盖所有的问题场景,但可以看到基本的排查思路如下:

  1. 判断问题是否属于 TLS/SSL 层面的问题。

  2. 抓取网络包;有条件的情况下,可以针对正常和异常情况抓取两份网络包,以便后续进行对比分析。

  3. 根据网络包探寻问题发生的直接原因,进而进一步探究问题的根本原因。

  4. 根据分析结论并结合业务场景,选择合适的解决方案。

这类问题的排查基础是对 HTTPS 和 TLS/SSL 协议的理解以及对分析工具的掌握。在移动领域,这类问题存在一定的共性,直接了解上述结论和分析方法可以帮助开发者快速"出坑"。

7.1 案例分析

抓取的应用报文如下: 其中,TCP 报文的一些主要信息如下:

shell 复制代码
35973 → 8888 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1 TSval=15986187 TSecr=0 WS=256
  • 源端口和目的端口,35973->8888。
  • 序号,Seq=0,这是一个相对值而非绝对值,相对第一个包的序列号。因为是整个 TCP 流的第一包,所以 Wireshark 认定该包的序列号为 0。
  • 窗口大小,win=65535,也就是发送端的当前窗口最多容纳 65535 个字节。
  • 数据部分大小,Len=0,不带数据。
  • 最大报文大小选择,MSS=1460,数据部分最多有 1460 个字节。
  • 选择确认选项,SACK_PERM=1。
  • 发送时间戳,TSval=15986187,发出这个数据包的时候的时间戳。
  • 应答时间戳,TSecr=0,当前要发送的包应答的那个包的发送时间戳,因为是第一个包,应答的时间戳为 0。
  • 窗口扩大,WS=256。

八、拓展阅读

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax