摘要
本博文主要是将计算机网络中的利用的概念进行的验证核测试。
网络实战工具
- linux :tcpdump
- window:Wireshark
这两者实际上是搭配使用的,先用 tcpdump 命令在 Linux 服务器上抓包,接着把抓包的文件拖
出到 Windows 电脑后,用 Wireshark 可视化分析。当然,如果你是在 Windows 上抓包,只需要用 Wireshark 工具就可以。
tcpdump使用方法
tcpdump 在 Linux 下如何抓包?
tcpdump 提供了大量的选项以及各式各样的过滤表达式,来帮助你抓取指定的数据包,不过不要担心,只需要掌握一些常用选项和过滤表达式,就可以满足大部分场景的需要了。假设我们要抓取下面的 ping 的数据包:
要抓取上面的 ping 命令数据包,首先我们要知道 ping 的数据包是 icmp 协议,接着在使用 tcpdump抓包的时候,就可以指定只抓 icmp 协议的数据包:
那么当 tcpdump 抓取到 icmp 数据包后, 输出格式如下:
时间戳协议源地址.源端口 > 目的地址.目的端口网络包详细信息
从 tcpdump 抓取的 icmp 数据包,我们很清楚的看到 icmp echo 的交互过程了,首先发送方发起了ICMP echo request 请求报文,接收方收到后回了一个 ICMP echo reply 响应报文,之后 seq 是递增的。
Wireshark 工具如何分析数据包?
接着把 ping.pcap 文件拖到电脑,再用 Wireshark 打开它。打开后,你就可以看到下面这个界面:
在 Wireshark 的页面里,可以更加直观的分析数据包,不仅展示各个网络包的头部信息,还会用
不同的颜色来区分不同的协议,由于这次抓包只有 ICMP 协议,所以只有紫色的条目。接着,在网络包列表中选择某一个网络包后,在其下面的网络包详情中,可以更清楚的看到,这个网络包在协议栈各层的详细信息。
从 ping 的例子中,我们可以看到网络分层就像有序的分工,每一层都有自己的责任范围和信息,上层协议完成工作后就交给下一层,最终形成一个完整的网络包。
TCP三次握手抓包
本次例子,我们将要访问的 http://192.168.3.200 服务端。在终端一用 tcpdump 命令抓取数据包:
使用 Wireshark 打开 http.pcap 后,你就可以在 Wireshark 中,看到如下的界面:
Wireshark 可以用时序图的方式显示数据包交互的过程,从菜单栏中,点击 统计 (Statistics) -> 流量图(Flow Graph),然后,在弹出的界面中的「流量类型」选择 「TCP Flows」,你可以更清晰的看到,整个过程中 TCP 流的执行过程:
你可能会好奇,为什么三次握手连接过程的 Seq 是 0 ?
实际上是因为 Wireshark 工具帮我们做了优化,它默认显示的是序列号 seq 是相对值,而不是真实值。如果你想看到实际的序列号的值,可以右键菜单, 然后找到「协议首选项」,接着找到「RelativeSeq」后,把它给取消,操作如下:
TCP四次挥手抓包
为什么抓到的 TCP 挥手是三次,而不是书上说的四次?
因为服务器端收到客户端的 FIN 后,服务器端同时也要关闭连接,这样就可以把 ACK 和 FIN 合
并到一起发送,节省了一个包,变成了“三次挥手”。而通常情况下,服务器端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端一个ACK 包,稍等一会儿,完成所有数据包的发送后,才会发送 FIN 包,这也就是四次挥手了。
如下图,就是四次挥手的过程:
TCP 三次握手异常情况实战分析
本次实验用了两台虚拟机,一台作为服务端,一台作为客户端,它们的关系如下:
实验一:TCP 第一次握手 SYN 丢包
为了模拟 TCP 第一次握手 SYN 丢包的情况,我是在拔掉服务器的网线后,立刻在客户端执行 curl 命令:
其间 tcpdump 抓包的命令如下:
过了一会, curl 返回了超时连接的错误:
从 date 返回的时间,可以发现在超时接近 1 分钟的时间后,curl 返回了错误。接着,把 tcp_sys_timeout.pcap 文件用 Wireshark 打开分析,显示如下图:
从上图可以发现, 客户端发起了 SYN 包后,一直没有收到服务端的 ACK ,所以一直超时重传了
次,并且每次 RTO 超时时间是不同的:
- 第一次是在 1 秒超时重传
- 第二次是在 3 秒超时重传
- 第三次是在 7 秒超时重传
- 第四次是在 15 秒超时重传
- 第五次是在 31 秒超时重传
可以发现,每次超时时间 RTO 是指数(翻倍)上涨的,当超过最大重传次数后,客户端不再发送 SYN包。
在 Linux 中,第一次握手的 SYN 超时重传次数,是如下内核参数指定的:
$ cat /proc/sys/net/ipv4/tcp_syn_retries
5
tcp_syn_retries 默认值为 5,也就是 SYN 最大重传次数是 5 次。
接下来,我们继续做实验,把 tcp_syn_retries 设置为 2 次:
$ echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
重传抓包后,用 Wireshark 打开分析,显示如下图:
实验一总结
通过实验一的实验结果,我们可以得知,当客户端发起的 TCP 第一次握手 SYN 包,在超时时间内没收到服务端的 ACK,就会在超时重传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。
实验二:TCP 第二次握手 SYN、ACK 丢包
为了模拟客户端收不到服务端第二次握手 SYN、ACK 包,我的做法是在客户端加上防火墙限制,直接粗暴的把来自服务端的数据都丢弃,防火墙的配置如下:
接着,在客户端执行 curl 命令:
从 date 返回的时间前后,可以算出大概 1 分钟后,curl 报错退出了。客户端在这其间抓取的数据包,用 Wireshark 打开分析,显示的时序图如下:
从图中可以发现:
- 客户端发起 SYN 后,由于防火墙屏蔽了服务端的所有数据包,所以 curl 是无法收到服务端的SYN、ACK 包,当发生超时后,就会重传 SYN 包。
- 服务端收到客户的 SYN 包后,就会回 SYN、ACK 包,但是客户端一直没有回 ACK,服务端在超时后,重传了 SYN、ACK 包,接着一会,客户端超时重传的 SYN 包又抵达了服务端,服务端收到后,超时定时器就重新计时,然后回了 SYN、ACK 包,所以相当于服务端的超时定时器只触发了一次,又被重置了。
- 最后,客户端 SYN 超时重传次数达到了 5 次(tcp_syn_retries 默认值 5 次),就不再继续发送SYN 包了。
所以,我们可以发现,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会超时重传 SYN、ACK 包。
客户端设置了防火墙,屏蔽了服务端的网络包,为什么 tcpdump 还能抓到服务端的网络包?
添加 iptables 限制后, tcpdump 是否能抓到包 ,这要看添加的 iptables 限制条件:
- 如果添加的是 INPUT 规则,则可以抓得到包
- 如果添加的是 OUTPUT 规则,则抓不到包
网络包进入主机后的顺序如下:
- 进来的顺序 Wire -> NIC -> tcpdump -> netfilter/iptables
- 出去的顺序 iptables -> tcpdump -> NIC -> Wire
tcp_syn_retries 是限制 SYN 重传次数,那第二次握手 SYN、ACK 限制最大重传次数是多少?
TCP 第二次握手 SYN、ACK 包的最大重传次数是通过 tcp_synack_retries 内核参数限制的,其默认值如下:
$ cat /proc/sys/net/ipv4/tcp_synack_retries
5
是的,TCP 第二次握手 SYN、ACK 包的最大重传次数默认值是 5 次。
为了验证 SYN、ACK 包最大重传次数是 5 次,我们继续做下实验,我们先把客户端的 tcp_syn_retries
设置为 1,表示客户端 SYN 最大超时次数是 1 次,目的是为了防止多次重传 SYN,把服务端 SYN、
ACK 超时定时器重置。
接着,还是如上面的步骤:
- 1. 客户端配置防火墙屏蔽服务端的数据包
- 2. 客户端 tcpdump 抓取 curl 执行时的数据包
把抓取的数据包,用 Wireshark 打开分析,显示的时序图如下:
从上图,我们可以分析出:
- 客户端的 SYN 只超时重传了 1 次,因为 tcp_syn_retries 值为 1
- 服务端应答了客户端超时重传的 SYN 包后,由于一直收不到客户端的 ACK 包,所以服务端一直在超时重传 SYN、ACK 包,每次的 RTO 也是指数上涨的,一共超时重传了 5 次,因为tcp_synack_retries 值为 5
接着,我把 tcp_synack_retries 设置为 2, tcp_syn_retries 依然设置为 1:
$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
$ echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
依然保持一样的实验步骤进行操作,接着把抓取的数据包,用 Wireshark 打开分析,显示的时序图如下:
由上图可见:
- 客户端的 SYN 包只超时重传了 1 次,符合 tcp_syn_retries 设置的值;
- 服务端的 SYN、ACK 超时重传了 2 次,符合 tcp_synack_retries 设置的值
实验二总结
通过实验二的实验结果,我们可以得知,当 TCP 第二次握手 SYN、ACK 包丢了后,客户端 SYN 包会发生超时重传,服务端 SYN、ACK 也会发生超时重传。客户端 SYN 包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是 5 次。
实验三:TCP 第三次握手 ACK 丢包
为了模拟 TCP 第三次握手 ACK 包丢,我的实验方法是在服务端配置防火墙,屏蔽客户端 TCP 报文中标志位是 ACK 的包,也就是当服务端收到客户端的 TCP ACK 的报文时就会丢弃,iptables 配置命令如下:
接着,在客户端执行如下 tcpdump 命令:
然后,客户端向服务端发起 telnet,因为 telnet 命令是会发起 TCP 连接,所以用此命令做测试:
此时,由于服务端收不到第三次握手的 ACK 包,所以一直处于 SYN_RECV 状态:
而客户端是已完成 TCP 连接建立,处于 ESTABLISHED 状态:
过了 1 分钟后,观察发现服务端的 TCP 连接不见了:
过了 30 分别,客户端依然还是处于 ESTABLISHED 状态:
接着,在刚才客户端建立的 telnet 会话,输入 123456 字符,进行发送:
持续「好长」一段时间,客户端的 telnet 才断开连接:
以上就是本次的实现三的现象,这里存在两个疑点:
- 为什么服务端原本处于 SYN_RECV 状态的连接,过 1 分钟后就消失了?
原因:服务端在重传 SYN、ACK 包时,超过了最大重传次数 tcp_synack_retries ,于是服务端的 TCP
连接主动断开了。
- 为什么客户端 telnet 输入 123456 字符后,过了好长一段时间,telnet 才断开连接?
原因:客户端向服务端发送数据包时,由于服务端的 TCP 连接已经退出了,所以数据包一直在超时重传,共重传了 15 次, telnet 就断开了连接。
上图的流程:
- 客户端发送 SYN 包给服务端,服务端收到后,回了个 SYN、ACK 包给客户端,此时服务端的TCP 连接处于 SYN_RECV 状态;
- 客户端收到服务端的 SYN、ACK 包后,给服务端回了个 ACK 包,此时客户端的 TCP 连接处于ESTABLISHED 状态;
- 由于服务端配置了防火墙,屏蔽了客户端的 ACK 包,所以服务端一直处于 SYN_RECV 状态,没有进入 ESTABLISHED 状态,tcpdump 之所以能抓到客户端的 ACK 包,是因为数据包进入系统的顺序是先进入 tcpudmp,后经过 iptables;
- 接着,服务端超时重传了 SYN、ACK 包,重传了 5 次后,也就是超过 tcp_synack_retries 的值(默认值是 5),然后就没有继续重传了,此时服务端的 TCP 连接主动中止了,所以刚才处于SYN_RECV 状态的 TCP 连接断开了,而客户端依然处于ESTABLISHED 状态;
- 虽然服务端 TCP 断开了,但过了一段时间,发现客户端依然处于ESTABLISHED 状态,于是就在客户端的 telnet 会话输入了 123456 字符;
- 此时由于服务端已经断开连接,客户端发送的数据报文,一直在超时重传,每一次重传,RTO 的值是指数增长的,所以持续了好长一段时间,客户端的 telnet 才报错退出了,此时共重传了 15次。
博文参考