0%

TCP 协议抓包分析

通过抓包分析和学习 TCP 协议。

TCP

延迟确认

192.168.3.6192.168.3.10发送了一个数据报,192.168.3.10并没有马上回复 ACK,而是选择先等待一段时间…

捎带确认

tcp-piggybacking-client

piggybacking-server

刚好在 20 ms 后有数据报要发送给对方,于是就捎带上了这个 ACK。

累计确认

delayedack

很快它又收到了一个数据报,于是就回复了一个 ACK 来确认两个数据报。

超时之后

delayedack

过了 300 ms 后,即没有待发的数据报,也没有收到新的数据报,于是就回复了一个 ACK。

总结
一个 ACK 没有携带用户数据,却要发送至少 40 个字节长度的数据包,是对网络流量的浪费。不管捎带确认还是累计确认,目的都是尽量减少网络中的小包数量。

快速重传

192.168.3.6192.168.3.10连续发送数据。

fast_retransmit

其中序号3241的数据报丢失了,所以192.168.3.10对所有后续到来的数据报都立即回复ACK=3241,如78``79号包。TCP 协议规定在连续收到 3 个相同的 ACK 后要快速重传,此处只有 2 个相同的 ACK 所以192.168.3.6会继续发送数据报。

在第 3 个相同的 ACK 后(82号包),192.168.3.6开始快速重传序号3241的数据报(87号包)。

192.168.3.10收到序号3241的数据报后,应该回复ACK=3241+536=3777,但是事实上回复的是ACK=806588号包),为什么呢?因为虽然序号3241的数据报丢失了,但是序号37777529的数据报被192.168.3.10收到并且缓存了起来。所以当收到重传的序号3241的数据报后,192.168.3.10回复的是ACK=8065

再往下看94``95号包又是两个重复ACK=8065,这是为什么呢?因为快速重传只会重传丢失的那一个数据报,而不会把后面所有未确认的包全部重传。192.168.3.6只重传了序号3241的包,然后接着从序号11817的数据报开始发送了,但是192.168.3.10的期待接收的下个数据报序号是8065,所以回复了重复的 ACK。

超时重传

192.168.3.6192.168.3.10连续发送数据。

retransmit_timeout

在发送完序号13229的数据报后等了 300ms 没有收到 ACK(192.168.3.10异常掉线),于是192.168.3.6开始超时重传。

第一个重传间隔是RTO,后面的重传间隔都是前面的 2 倍,也就是说重传间隔是按 2 的指数方式增长(术语:backoff,退避算法),直到第 12 次重传仍然没有收到 ACK,于是192.168.3.6就认为192.168.3.10已经异常掉线,就向它发送了一个RST

零窗口探测

192.168.3.6192.168.3.10连续发送数据。

zero_window_probe

发送完序号22包后,192.168.3.10192.168.3.6回复了一个零窗口 ACK,表示自己的不能再接收数据了。然后192.168.3.6就会发送TCP ZeroWindowProbe

TCP 通过接收窗口来实现流量控制。发送方接到零窗口通告时,则会停止报文段的发送,直到接收方通告非零的窗口。非零窗口通告一般在一个不含任何数据的ACK报文中发送,但ACK的传输并不可靠(ACK报文段不会被确认和重传),假设一个非零窗口通告丢失了,接收方等待接收数据(因为它已通告了一个非零的窗口),而发送方在等待非零窗口更新,就会产生死锁。为了解决这个问题,发送方使用一个坚持定时器persist timer来周期性地向接收方窗口是否被非零,这样的报文段称为窗口探査报文window probe

在 LwIP 中,窗口探测报本质是个长度为 1,序号为待确认报文段(如果没有,则是待发送报文段)的第 1 个字节的 TCP 数据报,对方一定会返回一个 ACK。协议栈收到一个零窗口通告后就开始窗口探查,收到非零窗口通告后就停止窗口探查。窗口探测的间隔时间会递增,但达到一个值后就不再改变。

关于零窗口探测包的实现,RFC 793-Section 3.7-Page 42中描述如下:

The sending TCP must be prepared to accept from the user and send at least one octet of new data even if the send window is zero. The sending TCP must regularly retransmit to the receiving TCP even when the window is zero. Two minutes is recommended for the retransmission interval when the window is zero. This retransmission is essential to guarantee that when either TCP has a zero window the re-opening of the window will be reliably reported to the other.

When the receiving TCP has a zero window and a segment arrives it must still send an acknowledgment showing its next expected sequence number and current window (zero).

也就是说 TCP 发送方的零窗口探测报至少要包含一个字节的数据。但是在一些系统的实现中并没有严格遵守这个规范,比如吊吊的 Linux 中实际上发送的是长度为 0,序号为对方期望接收的序号减 1 的 TCP ACK 包(和 keep alive 报一毛一样)。

关于 Linux 上零窗口探测报的实现,老外的提问和解答 (原文链接):

Re: TCP zero window probing

From: Tauno Voipio (tauno.voipio_at_iki.fi.NOSPAM.invalid)
Date: 04/05/04

Louis Laborde wrote:

It seems that to probe a closed receive window,
linux TCP implementation sends an empty segment
with its sequence number set to SND.UNA-1.
I was wondering if this was compliant with RFC
793 which does not seem to describe precisely
what such a probe should contain.

Thanks,
Louis.

It is sensed as an extra retransmit/ACK for a byte
lready transferred. The peer should respond to
t with the current sequence & acknowledgement
alues - which also carries the current window
nformation.

This is, IMHO, easier to handle than the pure
FC version of sending one byte over the window
size.

Tauno Voipio
tauno voipio @ iki fi

还有一个(原文链接),这个没有解答,但是抓包可供参考:

Hi,

I am running a client-server program with client running on a linux machine with 2.4.18-14 kernel installed.

When the server announces zero-window to the client, client starts sending zero-window probes which are nothing but unacceptable segments.

A short trace obtained using tcpdump and interpreted using ethereal is shown below:

16:27:17.979349 e.f.g.h.33464 > a.b.c.d.40000: P Seq=76441951 Ack=802335667 Win 5840 len=1080
27:18.040407 a.b.c.d.40000 > e.f.g.h.33464: . Seq=802335667 Ack=764413031 Win 0 len=0
27:18.256213 e.f.g.h.33464 > a.b.c.d.40000: . Seq=76443030 Ack=802335667 Win 5840 len=0

This sequence continues as per retransmission algorithm.

It can be seen above that unacceptable zero-length packets with a sequence no. already unacknowledged is being used as zero-window probes.

Zero window probes are defined in RFC 793 and RFC1122 to be a data segment containing atleast one byte of data beyond the window of the receiver who has closed the window.

This seems to be a bug. Has it been already fixed in later kernel versions or is this how it is intended to remain?

Regards,

Praveen

从 Linux 的观点来看,不管使用什么方法,只要能让对方返回 ACK 就行了。

规范也是人定出来的,并不是真理,要不然也不会有那么多的修订版了,所以不要一昧迷信书本,须知尽信书不如无书,黑猫白猫抓到老鼠就是好猫。

保活机制

客户端192.168.3.6连接服务器192.168.3.10

keep_alive

客户端发送序号14的数据报后,过了很久(一般是 2 个小时)也没有新的数据发送,服务器为了确认客户端是否仍然正常运行,开始周期性(一般是 75 秒)的发送Keep-Alive探测包,当发送了 N 次(一般为 9 次)探测包客户端仍然没有相应时,就认为客户端已经异常掉线了,发送一个 RST 包。

在 LwIP 中,保活探查报本质上是长度为 0,序号为对方期望接收的序号减 1 的 TCP ACK 包,对方认为这是个失序的 TCP 报,就一定会返回一个 ACK。协议栈中 Keep alive 默认是关闭的,打开和配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int optval;

/* Enable keep alive */
optval = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));

/* Idle time before first KEEPALIVE probe is sent */
optval = 10;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval));

/* Interval between probes */
optval = 5;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval));

/* Total number of probes sent */
optval = 5;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval));

伪重传

在 wireshark 抓去 Wi-Fi 包时经常遇到spurious retransmission的情况:

spurious_retransmission

三次重传的间隔只有几毫秒,远小于超时重传的间隔,事实上这是 Wi-Fi 层的重传,并不是伪重传,只是 wireshark 显示的问题。

判断是 Wi-Fi 重传还是 TCP 重传的方法就是看 IP 包的 ID,如果一样那就是 Wi-Fi 重传,否则就是 TCP 重传,因为 TCP 重传包是新的 IP 包,所以 ID 也是不同。

ARP

ARP 的本质

ARP 的本质是IP 和 MAC 的映射,核心是ARP 缓存表

ARP 表更新的途径

  • 收到发给自己的 ARP 回复
  • 收到 ARP 请求
  • 收到 IP 包

Wireshark 抓包

  • Request

    arp-request

  • Response

    arp-reponse

  • Gratuitous

    arp-gratuitous

无回报 (Gratuitous) ARP请求

主机在刚启动后一般会向局域网广播一个自己的<IP : MAC>信息,称为无回报(gratuitous)ARP请求
无回报 ARP 请求其实就是目的 IP 是自己的 ARP 请求

ARP 欺骗:发送伪造的网关 Gratuitous ARP 包

比如一个局域网的网关地址是192.168.31.1,在此局域网内不停地发送伪造的 Gratuitous ARP 包,那么局域网内的所有的主机 ARP 表中的192.168.31.1就会被更新为伪造的 MAC 值,进而导致主机发送的数据包都到不了网关。

DHCP

频繁重连后DHCP无法获取地址或获取地址慢

摘要:WIFI上网时,有时会出现无线用户接入后获取地址慢,而且经常无线网卡提示连接受限的现象。该情况将直接影响到用户的使用感受。本文主要分析了该问题发生的主要原因以及解决方案。

关键词:WIFI 连接受限 DHCP获取地址慢

正文:

在无锡市区的一些热点区域,经常会有用户反映出现无线接入后获取地址慢,而且有时会出现无线网卡提示受限连接的现象。该情况将直接影响到用户的使用感受。

针对上面的现象,我们进行了现场的分析和定位,确定了最终的原因:该现象的最终原因不是WLAN接入造成,而是DHCP server所引起的。

DHCP Server进行了一定的保护,也就是当DHCP server成功分配出一个地址以后,对于再次来自于客户端设备的DHCP请求将不作处理,只有原来的表项老化以后,才可能继续重新为客户端设备分配地址。

由于无线的特殊性,网卡在信号不稳的时候会出现重新连接,或者最终用户在使用过程中可能直接拔插网卡的情况,这样相当于链路异常断开,最终导致DHCP server不知道用户已经下线。而当用户再次申请地址的时候,DHCP server可能认为报文非法而不进行处理,最终出现了获取地址慢的现象。

通过有线进行测试也验证同样存在该问题。先使用有线网卡连接,保证成功获取地址,之后直接将网卡禁用后在使能,可以发现该网卡同样无法在短时间内获取地址。

下面是用户DHCP申请地址的流程:

dhcp-dhcp_flow

造成获取地址慢的原因:当DHCP server成功发送DHCP ACK报文之后,DHCP server将认为它已经成功为Client分配了一个IP地址,在没有接收到Release报文之前,或者自己的表项没有老化之前,再次收到来自于Client的报文,DHCP server将作为非法报文处理。

  • 获取地址慢的出现情况一:

如果客户端出现异常断开(也就是客户端虽然断开连接,但是没有发送DHCP Release报文),当该客户端再次连接的时候会出现无法获取地址,只有等待足够的一段时间后,才可以获取地址的问题。

能够造成该种情况的操作:

  1. 直接将有线网卡禁用;
  2. 直接将无线网卡禁用;
  3. 直接拔插无线网卡;
  • 获取地址慢的出现情况二:

由于客户端和服务器经过了大量的有线网络,所以在获取地址的瞬间,有可能出现报文延时的问题,特别当DHCP server回复的DHCP ACK报文延时到达客户端的时候,此时客户端会认为它已经发送的DHCP request报文超时,状态机回到初始位置,重新发送DHCP discovery报文。但是由于DHCP服务器已经发送了ACK,所以认为已经成功给客户端分配了地址,所以会忽略掉客户端的新的DHCP discovery请求。造成客户端获取不到地址或者需要经过一段时间后才可以获取地址。

下面是在AP上行端口抓包,第110条报文,AP将客户端的DHCP request报文成功发送出去,但是等待了10ms之后没有收到服务器的DHCP ACK报文,所以重新发送DHCP discovery报文申请地址;但是此时服务器实际上已经发送了一个DHCP ACK报文(只是该报文在60ms之后到达)。这样就造成了客户端和服务器的状态机不一致,服务器不再处理新的DHCP Discovery请求。

dhcp-dhcp_timeout

这个现象只能通过DHCP server上面的优化,目前该现象与WLAN网络和有线网络都没有关系,WLAN设备没有问题。由于DHCP服务器管理了很多的接入服务,需要仔细考虑如何进行优化。

坚持原创技术分享,您的支持将鼓励我继续创作!