前言
最近工作中会发现有超时的问题,还有就是在面试的时候很多都要求深入理解TCP/IP协议。突然感觉TCP/IP协议是一个既熟悉,又陌生的技术。又想到上大学的时候,老师说过
网络的圣经:“TCP/IP详解” 卷一 卷二 卷三,三本书。所以这次写一篇文档,好好学习一下。以下文档是根据TCP/IP详解 卷一和网络文章综合所得
一、tcpdump准备工作
1.操作系统版本
[root@web-server ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
2.tcpdump安装
yum -y install tcpdump
3.tcpdump版本
[root@web-server ~]# tcpdump --version
tcpdump version 4.9.2
libpcap version 1.5.3
OpenSSL 1.0.2k-fips 26 Jan 2017
二、tcpdump用法
1. 网卡参数 -i
指定网卡的选项是必须的。我看网上很多资料不用指定网卡,可以直接过滤,不知道是不是tcpdump版本过低的问题
可以使用tcpdump -D 打印出可以监控的网络接口有哪些
1.1 指定固定网卡
指定接收ens32网卡的所有收发数据包,
tcpdump -i ens32 -c 10
1.2 指定所有网卡
tcpdump -i any -c 10
2.指定协议
查看icmp协议的收发数据包.可以通过客户端ping服务器,来查看数据包的情况
tcpdump -i ens32 icmp
tcpdump -i ens32 arp
tcpdump -i ens32 tcp
3.指定端口 port
要想抓取应用层的访问数据包,就应该通过端口来抓取了。
服务端安装nginx,进行测试
tcpdump -i ens32 port 80
3.1 指定目标端口
tcpdump -i ens32 dst port 80
3.2 区别
如果不指定目标端口,会显示通过80端口上所有接收和发送的数据包
如果指定目标端口,只会显示访问80的数据包,通过80发送出去的数据包不会显示
4.过滤主机 host
4.1 过滤源主机IP地址
tcpdump -i ens32 src host 192.168.1.3
5.组合使用
以上条件组合起来一起使用必须使用and进行连接.
例子1:获取源主机为1.3 在80端口上所有收发的数据包
tcpdump -i ens32 src host 192.168.1.3 and port 80
例子2:查看正在Ping服务器(1.102)的所有主机
tcpdump -i ens32 icmp and dst host 192.168.1.102
例子3:查看某台主机是否在ping服务器(1.20)
tcpdump -i ens32 icmp and dst 192.168.1.102 and src 192.168.1.103
6.tcpdump数据格式
格式如下:
22:57:09.763679 IP 192.168.1.102.80 > 192.168.1.103.57296: Flags [S.], seq 1928661013, ack 1195573118, win 28960, options [mss 1460,sackOK,TS val 8656013 ecr 8681178,nop,wscale 7], length 0
格式:
1. 22:57:09.763679 :数据请求的时间
2. IP: 表示网络协议
3. 192.168.1.102.80: 源地址IP和端口号
4. > : 数据走向,也就说谁发给谁的数据包
5. 192.168.1.103.57296:目标IP地址和端口号
6. Flags [P.]: 标识位
其它: seq号 ack号 win窗口 数据长度length
7、其他参数
7.1 -v和-vv
显示更详细的信息
7.2 -nn
以数字的形式显示端口号和IP地址
7.3 -P
查看进出口数据包。-P (in|out)
tcpdump -i ens32 -P out dst 192.168.1.103 -nn
7.4 -w
将捕获到的数据包保存到文件当中,文件后缀需要使用.pcap。 然后方便使用wireshark软件进行查看
7.5 -S
将TCP的序列号,以绝对值的形式输出。如果不使用-S参数,在抓包过程中ack的值会这样显示:ack=1,这样不便于观察。
7.6 -t
不显示时间戳
三、TCP协议
1.简介
tcp提供了一种面向连接的,可靠的的字节流服务。
1.1 面向连接
面向连接指的是:客户端和服务端要交互数组,必须要先建立连接。
1.2 可靠性
TCP协议的可靠性主要靠以下几个方面:
(1)应用数据会被TCP分割成 TCP认为最合适的数据块
(2)TCP发出一个数据段后,会启动一个定时器,等待目的端确认是否收到这个数据段,如果不能及时收到一个确认。将重新发送这个数据段
(3)目的端收到数据后,发送一个确认,这个确认不会立即发送,通常会推迟几分之一秒
(4)TCP协议会重新对数据段进行排序
(5)TCP会丢弃"重复"、"首部和数据的校验和有差错"的数据段
2.TCP报文
2.1 源端口和目标端口
源端口:表示是哪个进程发来的数据包
目标端口:表示这个数据包要发送给哪个进程
2.2 序号和确认号
(1)序号:指定了当前数据分片中分配给第一字节数据的序列号。在TCP传输流中每一个字节为一个序号。如果TCP报文中flags标志位为SYN,该序列号表示初始化序列号(ISN),此时第一个数据应该是从序列号ISN+1开始。
(2)确认号:表示TCP发送者期望接受下一个数据分片的序列号。该序号在TCP分片中Flags标志位为ACK时生效。
作用1:确认对端发来的包收到了。
作用2:期望对端开始传输数据时候,序列号从这个值开始
此值一般是在对端的序列号上加1
3.标志位
标志位 | 含义 | 备注 |
---|---|---|
SYN | 连接请求或者接受连接请求 | 在tcpdump中的表现形式为: 【S】 |
FIN | 连接结束,断开连接 | 在tcpdump中的表现形式为: 【F】 |
RST | 重置连接 | 在tcpdump中的表现形式为: 【R】 |
ACK | 确认标记位 | 【.】代表ACK的确认包 或者URG标志位。其它的标志位均为0.一般情况下就ACK包 |
PSH | 催促标志位 | 在tcpdump中的表现形式为: 【P】 推送数据 |
URG | 紧急标志位 | 【.】代表ACK的确认包 或者URG标志位.其它的标志位均为0 |
注意:
1.PSH标志位:提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间。如果为1,则表示对方应当立即把数据提交给上层应用,而不是缓存起来,如果应用程序不将接收到的数据读走,就会一直停留在TCP接收缓冲区中
2.RST标志位:如果收到一个RST=1的报文,说明与主机的连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。或者说明上次发送给主机的数据有问题,主机拒绝响应,带RST标志的TCP报文段称为复位报文段即连接重置
四、TCP连接的建立
1.简介
TCP的连接也就是"三次握手"的过程。
所谓三次握手就是指:建立一个 TCP 连接时需要客户端和服务器端总共发送三个包来确认连接的建立
2.三次握手的过程
3.抓包实例
3.1 抓包命令
在server启动nginx,开始进行抓包,命令如下
tcpdump -i any -nn -S port 80
3.2 抓包结果
使用telnet进行连接nginx的80端口
telnet 192.168.1.16 80
查看抓包内容,结果如下:
01:16:08.055233 IP 192.168.1.1.57853 > 192.168.1.16.80: Flags [S], seq 1599595106, win 64240,
01:16:08.055284 IP 192.168.1.16.80 > 192.168.1.1.57853: Flags [S.], seq 4257773931, ack 1599595107, win 29200,
01:16:08.055804 IP 192.168.1.1.57853 > 192.168.1.16.80: Flags [.], ack 4257773932, win 4106, length 0
4.结果分析
4.1 第一次握手
客户端发送给服务器端
192.168.1.1.57853 > 192.168.1.16.80
[S]: 发送SYN 标志位位1的 数据包,
seq: 初始的随机序列号值为1599595106
总结: SYN=1,seq=1599595106
4.2 第二次握手
服务端回应客户端
192.168.1.16.80 > 192.168.1.1.57853
[S.]: SYN和ACK标志位都为1,
ack: 确认序号为 1599595107,在客户端发来的序号之上 + 1
seq: 产生和客户端建立连接的初始化序列号4257773931
总结:
SYN=1,seq=4257773931
ACK=1,ack=1599595107
4.3 第三次握手
客户端确认
192.168.1.1.57853 > 192.168.1.16.80
[.] ACK标志位为1
ack:确认号4257773932
总结:
ACK=1,ack=4257773932
就第三次握手的情况来看,第三的包中并没有携带seq 序列号值。这是因为默认没有显示,可以使用-vv参数来查看
但是在windows中使用wireshark抓包,可以看到seq这个序列号。
5.状态验证
5.1 查看SYN_SENT
在服务端启动防火墙,客户端就无法连接nginx.
查看服务器抓包情况,可以看出客户端一直在发送建立连接的包
18:13:31.336551 IP 192.168.1.1.61419 > 192.168.1.16.80: Flags [S], seq 1643209252, win 64240
18:13:31.341627 IP 192.168.1.1.61420 > 192.168.1.16.80: Flags [S], seq 142056919, win 64240
18:13:31.587559 IP 192.168.1.1.61422 > 192.168.1.16.80: Flags [S], seq 3305175899, win 64240
18:13:34.336935 IP 192.168.1.1.61419 > 192.168.1.16.80: Flags [S], seq 1643209252, win 64240
18:13:34.344128 IP 192.168.1.1.61420 > 192.168.1.16.80: Flags [S], seq 142056919, win 64240
查看客户端状态,处于SYN_SENT状态。这里的连接数量不重要,主要是看状态
C:\Users\pangbb>netstat -an |findstr /i 1.16|findstr /i 80
TCP 192.168.1.1:61419 192.168.1.16:80 SYN_SENT
TCP 192.168.1.1:61420 192.168.1.16:80 SYN_SENT
TCP 192.168.1.1:61422 192.168.1.16:80 SYN_SENT
5.2 查看SYN_RECV
环境:
server: nc -l 16868
在服务端的抓包命令:
tcpdump -i any -nn -S port 16868
此时开启客户端(linux)的设置iptables策略。1.16为服务端地址。含义就是不接受服务端返回
iptables -A INPUT -s 192.168.1.16 -j DROP
然后发起连接
nc -v 192.168.1.16 16868
查看服务端状态
[root@node1 ~]# netstat -antp |grep 16868
tcp 0 0 0.0.0.0:16868 0.0.0.0:* LISTEN 7308/nc
tcp 0 0 192.168.1.16:16868 192.168.1.18:34786 SYN_RECV -
tcp6 0 0 :::16868 :::* LISTEN 7308/nc
五、TCP连接的断开
1.简介
在TCP连接中的断开 常被称为"四次断开"或者"四次挥手"
之所以断开需要四次:
这是因为TCP的"半关闭"造成的.TCP是全双工的,因此每个方向必须单独地进行关闭
2.四次断开过程
2.1 过程图
这里的过程图,没有过多些回复包的seq序列号。这里主要是看状态的转换
2.2 端口状态
(1)主动断开方:
FIN_WAIT1
FIN_WAIT2
TIME_WAIT
(2)被动断开方
CLOSE_WAIT
LAST_ACK
3.nc实现标准4次断开
3.1 启动抓包命令
tcpdump -i any -S -nn port 16868
3.2 启动nc
在server上使用以下命令启动16868端口
nc -l 16868
3.3 客户端连接
在客户端cmd中使用telnet连接server的16868端口
telnet 192.168.1.16 16868
nc命令不会主动断开连接。不手动断开是不会断开连接的。
3.4 断开连接
使用telnet连接nc端口后,可以在客户端输入点数据。也可以不输入数据。
然后在"服务端"端开始断开连接,注意:一定要在服务端主动断开。也就是停止nc命令。
3.5 查看抓包结果
这里展现了标准的tcp 4次断开结果
# 服务端主动断开nc命令,服务端向客户端发起断开请求 第一次
192.168.1.16.16868 > 192.168.1.1.50907: Flags [F.], seq 2801815419, ack 3112729795, win 229, length 0
# 客户端收到服务端断开连接的请求,回复ACK 进行确认。 第二次
192.168.1.1.50907 > 192.168.1.16.16868: Flags [.], ack 2801815420, win 4106, length 0
# 客户端开始向服务端发起断开连接请求, 第三次
192.168.1.1.50907 > 192.168.1.16.16868: Flags [F.], seq 3112729795, ack 2801815420, win 4106, length 0
# 服务端确认客户端的请求。第四次
192.168.1.16.16868 > 192.168.1.1.50907: Flags [.], ack 3112729796, win 229, length 0
4.nginx断开实例
3.1 启动nginx
nginx
3.2 开始抓包
tcpdump -i any -nn -S port 80
使用浏览器访问nginx,得到以下抓包数据
# nginx主动发起 第一次断开连接的请求
01:50:13.124555 IP 192.168.1.16.80 > 192.168.1.1.55320: Flags [F.], seq 3869309450, ack 202349151, win 254, length 0
01:50:13.124900 IP 192.168.1.1.55320 > 192.168.1.16.80: Flags [.], ack 3869309451, win 4102, length 0
# 后续的包不通场景有些不同,这里只是想表达 nginx会主动发断开的FIN包
3.3 状态转换
在建立完连接之后,nginx在 “keepalive_timeout” 时间内,客户端没有传输数据,nginx就会自动断开连接。发送FIN包,进行断开.
(1)当nginx主动发起FIN断开连接后,服务端将进入 “FIN_WAIT2” 状态
(2)此时客户端进入"CLOSE_WAIT"
(3)当客户端端像nginx发起FIN后,nginx的连接进入"TIME_WAIT"
5.断开得状态复现
1、主动关闭方发送FIN,此时主动关闭方状态为:FIN_WAIT_1
2、被动关闭方收到后,回复ack,此时被动关闭方状态为:CLOSE_WAIT
3、主动关闭方收到ack后,状态为FIN_WAIT_2
4、被动关闭方发送FIN,此时状态为LAST_ACK
5、主动关闭方收到后,回复ack,此时状态为TIME_WAIT 。
6、被动关闭方收到ack后,状态为CLOSED
为了方便看到TCP断开状态得转换和查看。server和client都使用"nc"进行实现
server端使用linux 192.168.1.16 使用nc
client端使用linux 192.168.1.18 也使用nc
5.1.FIN_WAIT_1
5.1.1 启动server
nc -l 16868
#开始启动抓包
tcpdump -nn -S -i any port 16868
5.1.2 client开始连接
nc -v 192.168.1.16 16868
5.1.3 设置客户端防火墙策略
此策略得目的就是不接受服务端发来得FIN 断开请求包
ptables -A INPUT -s 192.168.1.16 -j DROP
5.1.4 断开服务端
直接ctrl + c 断开即可
5.1.4 查看服务端状态
[root@node1 ~]# netstat -antp |grep 16868
tcp 0 1 192.168.1.16:16868 192.168.1.18:34826 FIN_WAIT1 -
5.2 FIN_WAIT_2
先还原正常建立连接得正常状态. 清除所有防火墙策略
5.2.1 启动server
nc -l 16868
#开始启动抓包
tcpdump -nn -S -i any port 16868
5.2.1 client开始连接
nc -v 192.168.1.16 16868
5.2.3 断开服务端
直接ctrl + c 断开即可。此时 client得nc 不会自动断开。必须手动断开才可以
5.2.4 查看抓包结果
这里发现客户端已经回复了ACK得的确认包
18:43:08.639049 IP 192.168.1.16.16868 > 192.168.1.18.34828: Flags [F.], seq 1714549503, ack 38581695, win 227, options [nop,nop,TS val 77246674 ecr 77160729], length 0
18:43:08.641121 IP 192.168.1.18.34828 > 192.168.1.16.16868: Flags [.], ack 1714549504, win 229, options [nop,nop,TS val 77241592 ecr 77246674], length 0
5.2.5 查看服务端状态
此时服务端得连接状态已经变成了FIN_WAIT2
[root@node1 ~]# netstat -antp |grep 16868
tcp 0 0 192.168.1.16:16868 192.168.1.18:34836 FIN_WAIT2 -
5.2.6 查看客户端状态
此时发现客户端已经进入到CLOSE_WAIT状态
[root@node4 ~]# netstat -antp |grep 16868
tcp 0 0 192.168.1.18:34836 192.168.1.16:16868 CLOSE_WAIT 4308/nc
5.3 TIME_WAIT
TIME_WAIT状态 是为了等待足够的时间以确保对端能够收到自己发送的断开确认包。
5.3.1 启动server
nc -l 16868
#开始启动抓包
tcpdump -nn -S -i any port 16868
5.3.2 client开始连接
nc -v 192.168.1.16 16868
5.3.3 断开服务端
查看服务端已经处于FIN_WAIT2状态
5.3.4 断开客户端
此时发现服务端已经变成了TIMW_WAIT状态了
[root@node1 ~]# netstat -antp |grep -i 16868
tcp 0 0 192.168.1.16:16868 192.168.1.18:34836 TIME_WAIT -