计算机网络-TCP实战抓包分析

为了分析TCP的特性,我们借助转包工具来把TCP的‘衣服扒掉’来看清楚。

image.png

1. 隐形不可见的网络包

⽹络世界中的数据包交互我们⾁眼是看不⻅的,它们就好像隐形了⼀样,我们对着课本学习计算机⽹络的时候就会 觉得⾮常的抽象,加⼤了学习的难度。

直到⼯作后,认识了两⼤分析⽹络的利器:tcpdump 和 Wireshark,这两⼤利器把我们“看不⻅”的数据包,呈现 在我们眼前,⼀⽬了然。

  • tcpdump 和 Wireshark 有什么区别?

tcpdump 和 Wireshark 就是最常⽤的⽹络抓包和分析⼯具,更是分析⽹络性能必不可少的利器。

tcpdump 仅⽀持命令⾏格式使⽤,常⽤在 Linux 服务器中抓取和分析⽹络包。 Wireshark 除了可以抓包外,还提供了可视化分析⽹络包的图形⻚⾯。

所以,这两者实际上是搭配使⽤的,先⽤ tcpdump 命令在 Linux 服务器上抓包,接着把抓包的⽂件拖出到 Windows 电脑后,⽤ Wireshark 可视化分析

1.1 tcpdump 在 Linux 下如何抓包?

假设我们要抓取下⾯的 ping 的数据包:

image.png

要抓取上⾯的 ping 命令数据包,⾸先我们要知道 ping 的数据包是 icmp 协议,接着在使⽤ tcpdump 抓包的时候,就可以指定只抓 icmp 协议的数据包:

image.png

当 tcpdump 抓取到 icmp 数据包后, 输出格式如下:

image.png

image.png

从 tcpdump 抓取的 icmp 数据包,我们很清楚的看到 icmp echo 的交互过程了,⾸先发送⽅发起了 ICMP echo request 请求报⽂,接收⽅收到后回了⼀个 ICMP echo reply 响应报⽂,之后 seq 是递增的。

1.2 tcpdump的使用选项 & 过滤条件

image.png

image.png

说了这么多,你应该也发现了,tcpdump 虽然功能强⼤,但是输出的格式并不直观。

所以,在⼯作中 tcpdump 只是⽤来抓取数据包,不⽤来分析数据包,⽽是把 tcpdump 抓取的数据包保存成 pcap 后缀的⽂件,接着⽤ Wireshark ⼯具进⾏数据包分析

1.3 wireshark工具如何分析数据包?

wireshark的下载安装地址:www.wireshark.org/download.ht…

Wireshark 除了可以抓包外,还提供了可视化分析⽹络包的图形⻚⾯,同时,还内置了⼀系列的汇总分析⼯具。

⽐如,拿上⾯的 ping 例⼦来说,我们可以使⽤下⾯的命令,把抓取的数据包保存到 ping.pcap ⽂件。

image.png

接着把 ping.pcap ⽂件拖到电脑,再⽤ Wireshark 打开它。打开后,你就可以看到下⾯这个界⾯:

image.png

接着,在⽹络包列表中选择某⼀个⽹络包后,在其下⾯的⽹络包详情中,可以更清楚的看到,这个⽹络包在协议栈 各层的详细信息。⽐如,以编号 1 的⽹络包为例⼦:

image.png

1. 可以在数据链路层,看到 MAC 包头信息,如源 MAC 地址和⽬标 MAC 地址等字段; 

2. 可以在 IP 层,看到 IP 包头信息,如源 IP 地址和⽬标 IP 地址、TTL、IP 包⻓度、协议等 IP 协议各个字段的数 值和含义; 

3. 可以在 ICMP 层,看到 ICMP 包头信息,⽐如 Type、Code 等 ICMP 协议各个字段的数值和含义;
复制代码

从 ping 的例⼦中,我们可以看到⽹络分层就像有序的分⼯,每⼀层都有⾃⼰的责任范围和信息,上层协议完成⼯ 作后就交给下⼀层,最终形成⼀个完整的⽹络包。

image.png

2. 解密TCP的三次握手 和 四次挥手

既然学会了 tcpdump 和 Wireshark 两⼤⽹络分析利器,那我们快⻢加鞭,接下⽤它俩抓取和分析 HTTP 协议⽹络 包,并理解 TCP 三次握⼿和四次挥⼿的⼯作原理。

本次例⼦,我们将要访问的 http://192.168.3.200 服务端。在终端⼀⽤ tcpdump 命令抓取数据包:

image.png

接着,在终端⼆执⾏下⾯的 curl 命令:

image.png

最后,回到终端⼀,按下 Ctrl+C 停⽌ tcpdump,并把得到的 http.pcap 取出到电脑。

使⽤ Wireshark 打开 http.pcap 后,你就可以在 Wireshark 中,看到如下的界⾯:

image.png

我们都知道 HTTP 是基于 TCP 协议进⾏传输的,那么:

1. 最开始的 3 个包就是 TCP 三次握⼿建⽴连接的包 

2. 中间是 HTTP 请求和响应的包 

3. ⽽最后的 3 个包则是 TCP 断开连接的挥⼿包
复制代码

Wireshark 可以⽤时序图的⽅式显示数据包交互的过程,从菜单栏中,点击 统计 (Statistics) -> 流ᰁ图 (Flow Graph),然后,在弹出的界⾯中的「流ᰁ类型」选择 「TCP Flows」,你可以更清晰的看到,整个过程中 TCP 流 的执⾏过程:

image.png

  • 你可能会好奇,为什么三次握⼿连接过程的 Seq 是 0 ?

实际上是因为 Wireshark ⼯具帮我们做了优化,它默认显示的是序列号 seq 是相对值,⽽不是真实值。

如果你想看到实际的序列号的值,可以右键菜单, 然后找到「协议⾸选项」,接着找到「Relative Seq」后,把它 给取消,操作如下:

image.png

image.png

可⻅,客户端和服务端的序列号实际上是不同的,序列号是⼀个随机值

这其实跟我们书上看到的 TCP 三次握⼿和四次挥⼿很类似,作为对⽐,你通常看到的 TCP 三次握⼿和四次挥⼿的 流程,基本是这样的:

image.png

image.png

  • 为什么抓到的 TCP 挥⼿是三次,⽽不是书上说的四次?

因为服务器端收到客户端的 FIN 后,服务器端同时也要关闭连接,这样就可以把 ACK 和 FIN 合并到⼀起发 送,节省了⼀个包,变成了“三次挥⼿”。

⽽通常情况下,服务器端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端⼀个 ACK 包, 稍等⼀会⼉,完成所有数据包的发送后,才会发送 FIN 包,这也就是四次挥⼿了。

如下图,就是四次挥⼿的过程:

image.png

3. TCP三次握手异常情况实战分析

TCP 三次握⼿的过程相信⼤家都背的滚⽠烂熟,那么你有没有想过这三个异常情况:

  1. TCP 第⼀次握⼿的 SYN 丢包了,会发⽣了什么?
  2. TCP 第⼆次握⼿的 SYN、ACK 丢包了,会发⽣什么?
  3. TCP 第三次握⼿的 ACK 包丢了,会发⽣什么?

有的⼩伙伴可能说:“很简单呀,包丢了就会重传嘛。”

  1. 那会᯿传⼏次?
  2. 超时重传的时间 RTO 会如何变化?
  3. 在 Linux 下如何设置᯿传次数?
  4. ...

是不是哑⼝⽆⾔,⽆法回答?

接下⾥我⽤三个实验案例,带⼤家⼀起探究探究这三种异常。

  • 实验场景

本次实验⽤了两台虚拟机,⼀台作为服务端,⼀台作为客户端,它们的关系如下:

image.png

1. 客户端和服务端都是 CentOs 6.5 Linux,Linux 内核版本 2.6.32 

2. 服务端 192.168.12.36,apache web 服务 

3. 客户端 192.168.12.37
复制代码

3.1 TCP 第⼀次握⼿ SYN 丢包

为了模拟 TCP 第⼀次握⼿ SYN 丢包的情况,我是在拔掉服务器的⽹线后,⽴刻在客户端执⾏ curl 命令:

image.png

其间 tcpdump 抓包的命令如下:

image.png

过了⼀会, curl 返回了超时连接的错误:

image.png

从 date 返回的时间,可以发现在超时接近 1 分钟的时间后,curl 返回了错误。

接着,把 tcp_sys_timeout.pcap ⽂件⽤ Wireshark 打开分析,显示如下图:

image.png

从上图可以发现, 客户端发起了 SYN 包后,⼀直没有收到服务端的 ACK ,所以⼀直超时᯿传了 5 次,并且每次 RTO 超时时间是不同的:

1. 第⼀次是在 1 秒超时重传 

2. 第⼆次是在 3 秒超时重传 

3. 第三次是在 7 秒超时重传 

4. 第四次是在 15 秒超时重传 

5. 第五次是在 31 秒超时重传
复制代码

可以发现,每次超时时间 RTO 是指数(翻倍)上涨的,当超过最⼤᯿传次数后,客户端不再发送 SYN 包。

在 Linux 中,第⼀次握⼿的 SYN 超时᯿传次数,是如下内核参数指定的:

image.png tcp_syn_retries 默认值为 5,也就是 SYN 最⼤᯿传次数是 5 次。

接下来,我们继续做实验,把 tcp_syn_retries 设置为 2 次:

image.png

重传抓包后,⽤ Wireshark 打开分析,显示如下图:

image.png

  • 实验⼀的实验⼩结

通过实验⼀的实验结果,我们可以得知,当客户端发起的 TCP 第⼀次握⼿ SYN 包,在超时时间内没收到服务端的 ACK,就会在超时᯿传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的᯿传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。

image.png

3.2 TCP 第⼆次握⼿ SYN、ACK 丢包

为了模拟客户端收不到服务端第⼆次握⼿ SYN、ACK 包,我的做法是在客户端加上防⽕墙限制,直接粗暴的把来 ⾃服务端的数据都丢弃,防⽕墙的配置如下:

image.png

接着,在客户端执⾏ curl 命令:

image.png

从 date 返回的时间前后,可以算出⼤概 1 分钟后,curl 报错退出了。

客户端在这其间抓取的数据包,⽤ Wireshark 打开分析,显示的时序图如下:

image.png

从图中可以发现:

1. 客户端发起 SYN 后,由于防⽕墙屏蔽了服务端的所有数据包,所以 curl 是⽆法收到服务端的 SYN、ACK 包, 当发⽣超时后,就会重传 SYN 包 

2. 服务端收到客户的 SYN 包后,就会回 SYN、ACK 包,但是客户端⼀直没有回 ACK,服务端在超时后,重传 了 SYN、ACK 包。
接着⼀会,客户端超时重传的 SYN 包⼜抵达了服务端,服务端收到后,超时定时器就重新 计时,然后回了 SYN、ACK 包,所以相当于服务端的超时定时器只触发了⼀次,⼜被重置了。 

3. 最后,客户端 SYN 超时重传次数达到了 5 次(tcp_syn_retries 默认值 5 次),就不再继续发送 SYN 包了
复制代码

所以,我们可以发现,当第⼆次握⼿的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会超时重传 SYN、ACK 包

  • 客户端设置了防⽕墙,屏蔽了服务端的⽹络包,为什么 tcpdump 还能抓到服务端的⽹络包?
添加 iptables 限制后, tcpdump 是否能抓到包 ,这要看添加的 iptables 限制条件: 

1. 如果添加的是 INPUT 规则,则可以抓得到包 

2. 如果添加的是 OUTPUT 规则,则抓不到包
复制代码
  • ⽹络包进⼊主机后的顺序

进来的顺序 Wire -> NIC -> tcpdump -> netfilter/iptables

出去的顺序 iptables -> tcpdump -> NIC -> Wire

  • tcp_syn_retries 是限制 SYN 重传次数,那第⼆次握⼿ SYN、ACK 限制最⼤重传次数是多少?

TCP 第⼆次握⼿ SYN、ACK 包的最⼤重传次数是通过 tcp_synack_retries 内核参数限制的,其默认值如下:

image.png

是的,TCP 第⼆次握⼿ SYN、ACK 包的最⼤重传次数默认值是 5 次.

  • 验证 SYN、ACK 包最⼤重传次数

我们继续做下实验,我们先把客户端的 tcp_syn_retries 设置为 1,表示客户端 SYN 最⼤超时次数是 1 次,⽬的是为了防⽌多次重传 SYN,把服务端 SYN、ACK 超时定时器重置。

接着,还是如上⾯的步骤:

1. 客户端配置防⽕墙屏蔽服务端的数据包 

2. 客户端 tcpdump 抓取 curl 执⾏时的数据包
复制代码

把抓取的数据包,⽤ Wireshark 打开分析,显示的时序图如下:

image.png

从上图,我们可以分析出:

1. 客户端的 SYN 只超时᯿传了 1 次,因为 tcp_syn_retries 值为 1 

2. 服务端应答了客户端超时重传的 SYN 包后,由于⼀直收不到客户端的 ACK 包,所以服务端⼀直在超时重传 SYN、ACK 包,每次的 RTO 也是指数上涨的,⼀共超时重传了 5 次,因为 tcp_synack_retries 值为 5
复制代码
  • 把 tcp_synack_retries 设置为 2, tcp_syn_retries 依然设置为 1:

image.png

依然保持⼀样的实验步骤进⾏操作,接着把抓取的数据包,⽤ Wireshark 打开分析,显示的时序图如下:

image.png

1. 客户端的 SYN 包只超时᯿传了 1 次,符合 tcp_syn_retries 设置的值; 

2. 服务端的 SYN、ACK 超时᯿传了 2 次,符合 tcp_synack_retries 设置的值
复制代码
  • 实验⼆的实验⼩结

通过实验⼆的实验结果,我们可以得知,当 TCP 第⼆次握⼿ SYN、ACK 包丢了后,客户端 SYN 包会发⽣超时重传,服务端 SYN、ACK 也会发⽣超时重传

客户端 SYN 包超时重传的最⼤次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最⼤次数,是由 tcp_synack_retries 决定的,默认值是 5 次

3.3 TCP 第三次握⼿ ACK 丢包

为了模拟 TCP 第三次握⼿ ACK 包丢,我的实验⽅法是在服务端配置防⽕墙,屏蔽客户端 TCP 报⽂中标志位是 ACK 的包,也就是当服务端收到客户端的 TCP ACK 的报⽂时就会丢弃,iptables 配置命令如下:

image.png

接着,在客户端执⾏如下 tcpdump 命令:

image.png

然后,客户端向服务端发起 telnet,因为 telnet 命令是会发起 TCP 连接,所以⽤此命令做测试:

image.png

此时,由于服务端收不到第三次握⼿的 ACK 包,所以⼀直处于 SYN_RECV 状态:

image.png

⽽客户端是已完成 TCP 连接建⽴,处于 ESTABLISHED 状态:

image.png

过了 1 分钟后,观察发现服务端的 TCP 连接不⻅了:

image.png

过了 30 分钟,客户端依然还是处于 ESTABLISHED 状态:

image.png

接着,在刚才客户端建⽴的 telnet 会话,输⼊ 123456 字符,进⾏发送:

image.png

持续「好⻓」⼀段时间,客户端的 telnet 才断开连接:

image.png

  • 以上就是本次的实现三的现象,这⾥存在两个疑点:
1. 为什么服务端原本处于 SYN_RECV 状态的连接,过 1 分钟后就消失了? 

2. 为什么客户端 telnet 输⼊ 123456 字符后,过了好⻓⼀段时间,telnet 才断开连接?
复制代码

不着急,我们把刚抓的数据包,⽤ Wireshark 打开分析,显示的时序图如下:

image.png

1. 客户端发送 SYN 包给服务端,服务端收到后,回了个 SYN、ACK 包给客户端,此时服务端的 TCP 连接处于 SYN_RECV 状态; 

2. 客户端收到服务端的 SYN、ACK 包后,给服务端回了个 ACK 包,此时客户端的 TCP 连接处于 ESTABLISHED 状态; 

3. 由于服务端配置了防⽕墙,屏蔽了客户端的 ACK 包,所以服务端⼀直处于 SYN_RECV 状态,没有进⼊ ESTABLISHED 状态,tcpdump 之所以能抓到客户端的 ACK 包,是因为数据包进⼊系统的顺序是先进⼊ tcpudmp,后经过 iptables; 

4. 接着,服务端超时᯿传了 SYN、ACK 包,᯿传了 5 次后,也就是超过 tcp_synack_retries 的值(默认值是 5),然后就没有继续重传了,此时服务端的 TCP 连接主动中⽌了,所以刚才处于 SYN_RECV 状态的 TCP 连接断开了,⽽客户端依然处于 ESTABLISHED 状态; 

5. 虽然服务端 TCP 断开了,但过了⼀段时间,发现客户端依然处于 ESTABLISHED 状态,于是就在客户端的 telnet 会话输⼊了 123456 字符; 

6. 此时由于服务端已经断开连接,客户端发送的数据报⽂,⼀直在超时重传,每⼀次重传,RTO 的值是指数增⻓ 的,所以持续了好⻓⼀段时间,客户端的 telnet 才报错退出了,此时共重传了 15 次。
复制代码
  • 通过这⼀波分析,刚才的两个疑点已经解除了
1. 服务端在重传 SYN、ACK 包时,超过了最⼤᯿传次数 tcp_synack_retries ,于是服务端的 TCP 连接主动断 开了。 

2. 客户端向服务端发送数据包时,由于服务端的 TCP 连接已经退出了,所以数据包⼀直在超时᯿传,共重传了 15 次, telnet 就断开了连接。
复制代码
  • 那 TCP 建⽴连接后的数据包最⼤超时᯿传次数是由 什么参数指定呢?

TCP 建⽴连接后的数据包传输,最⼤超时᯿传次数是由 tcp_retries2 指定,默认值是 15 次.

image.png

  • 如果客户端不发送数据,什么时候才会断开处于 ESTABLISHED 状态的连接?

这⾥就需要提到 TCP 的 保活机制: 定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作⽤,每隔⼀个时间间 隔,发送⼀个「探测报⽂」,该探测报⽂包含的数据⾮常少,如果连续⼏个探测报⽂都没有得到响应,则认为当前 的 TCP 连接已经死亡,系统内核将错误信息通知给上层应⽤程序。

在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200 
net.ipv4.tcp_keepalive_intvl=75 
net.ipv4.tcp_keepalive_probes=9

1. tcp_keepalive_time=7200:表示保活时间是 7200 秒(2⼩时),也就 2 ⼩时内如果没有任何连接相关的活动,则会启动保活机制;
2. tcp_keepalive_intvl=75:表示每次检测间隔 75 秒; 
3. tcp_keepalive_probes=9:表示检测 9 次⽆响应,认为对⽅是不可达的,从⽽中断本次的连接;
复制代码

也就是说在 Linux 系统中,最少需要经过 2 ⼩时 11 分 15 秒才可以发现⼀个「死亡」连接。

image.png

  • 实验三的实验⼩结

在建⽴ TCP 连接时,如果第三次握⼿的 ACK,服务端⽆法收到,则服务端就会短暂处于 SYN_RECV 状态,⽽客户端会处于 ESTABLISHED 状态

一、服务端情况:
由于服务端⼀直收不到 TCP 第三次握⼿的 ACK,则会⼀直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。

二、客户端情况
1. 如果客户端没发送数据包,⼀直处于 ESTABLISHED 状态,然后经过 2 ⼩时 11 分 15 秒才可以发现⼀个「死 亡」连接,于是客户端连接就会断开连接。 
2. 如果客户端发送了数据包,⼀直没有收到服务端对该数据包的确认报⽂,则会⼀直᯿传该数据包,直到᯿传次 数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接
复制代码

猜你喜欢

转载自juejin.im/post/7051119430020890632