# TCP 基础
思考
- TCP 是什么?
- 三次握手?
- 四次挥手?
- 为什么是三次握手,四次挥手?
# TCP 是什么
# 定义
TCP 是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如 ip 地址、端口号等
TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要
TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用 4 次挥手来关闭一个连接
# 组成
一个 TCP 连接由一个4元组
构成,分别是两个IP地址
和两个端口号
。一个 TCP 连接通常分为三个阶段:启动
、数据传输
、退出(关闭)
。
头部
- 源端口:记录本机的端口
- 目的端口:记录目标主机的端口
- Sequence Number:是记录包的序号,TCP 会按照报文字节进行编号,它是用来解决包在网络中乱序的问题
- Acknowledgement Number:确认序列号,是用于向发送方确认已经收到了哪些包,用来解决不丢包的问题
- TCP Flag:就是包的类型,主要是用于操控 TCP 状态机的
- Window:滑动窗口,主要是用来解决流控的
SYN: 用于初如化一个连接的序列号
ACK: 确认,使得确认号有效
RST: 重置连接
FIN: 该报文段的发送方已经结束向对方发送数据
# TCP 连接
# 三次握手
客户端和服务端通信前要进行连接,“3 次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的
第一次握手:
客户端发送网络包,服务端收到了。服务端得出结论:客户端的发送能力、服务端的接收能力是正常的
第二次握手
服务端发包,客户端收到了。客户端得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的
第三次握手
客户端继续发包,服务端收到了。服务端得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。
- 客户端发送一个
SYN
段,并指明客户端的初始序列号,即ISN(c)
- 服务端发送自己的
SYN
段作为应答,同样指明自己的ISN(s)
。为了确认客户端的SYN
,将ISN(c)+1
作为ACK
数值。这样,每发送一个SYN
,序列号就会加 1
。 如果有丢失的情况,则会重传。 - 为了确认服务器端的
SYN
,客户端将ISN(s)+1
作为返回的ACK
数值
# 四次挥手
第一次挥手
客户端发起 FIN 包(FIN = 1)
,客户端进入FIN_WAIT_1
状态。TCP 规定,即使 FIN
包不携带数据,也要消耗一个序号。
第二次挥手
服务器端收到 FIN 包,发出确认包 ACK(ack = x + 1)
,并带上自己的序号 seq=y
,服务器端进入了CLOSE_WAIT
状态。这个时候客户端已经没有数据要发送了,不过服务器端有数据发送的话,客户端依然需要接收。客户端接收到服务器端发送的ACK
后,进入了FIN_WAIT_2
状态
第三次挥手
服务器端数据发送完毕后,向客户端发送FIN
包(seq=z ack=x+1
),半连接状态下服务器可能又发送了一些数据,假设发送 seq 为 w。服务器此时进入了LAST_ACK
状态
第四次挥手
客户端收到服务器的FIN
包后,发出确认包(ACK=1,ack=z+1
),此时客户端就进入了 TIME_WAIT
状态。注意此时 TCP 连接还没有释放,必须经过 2*MSL
后,才进入 CLOSED
状态。而服务器端收到客户端的确认包 ACK 后就进入了 CLOSED
状态,可以看出服务器端结束 TCP 连接的时间要比客户端早一些。
# TCP 高级
# ISN
三次握手的一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。
如果 ISN 是固定的,攻击者很容易猜出后续的确认号。
ISN = M + F(localhost, localport, remotehost, remoteport)
M 是一个计时器,每隔 4 毫秒加 1。 F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 hash 算法不能被外部轻易推算得出
# SYN flood 攻击
最基本的DoS
攻击就是利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。syn flood
属于Dos
攻击的一种。
如果恶意的向某个服务器端口发送大量的 SYN 包,则可以使服务器打开大量的半开连接,分配TCB(Transmission Control Block)
, 从而消耗大量的服务器资源,同时也使得正常的连接请求无法被相应。当开放了一个 TCP 端口后,该端口就处于 Listening 状态,不停地监视发到该端口的 Syn 报文,一旦接收到 Client 发来的 Syn 报文,就需要为该请求分配一个 TCB,通常一个 TCB 至少需要 280 个字节,在某些操作系统中 TCB 甚至需要 1300 个字节,并返回一个 SYN ACK 命令,立即转为 SYN-RECEIVED 即半开连接状态。系统会为此耗尽资源。
解决方案
1.无效连接的监视释放:
监视系统的半开连接和不活动连接,当达到一定阈值时拆除这些连接,从而释放系统资源。这种方法对于所有的连接一视同仁,而且由于 SYN Flood 造成的半开连接数量很大,正常连接请求也被淹没在其中被这种方式误释放掉,因此这种方法属于入门级的 SYN Flood 方法
2.延缓 TCB 分配方法:
消耗服务器资源主要是因为当 SYN 数据报文一到达,系统立即分配 TCB,从而占用了资源。而 SYN Flood 由于很难建立起正常连接,因此,当正常连接建立起来后再分配TCB则可以有效地减轻服务器资源的消耗
。常见的方法是使用Syn Cache
和Syn Cookie
技术
# Syn Cache 技术
系统在收到一个 SYN 报文时,在一个专用 HASH 表中保存这种半连接信息,直到收到正确的回应 ACK 报文再分配 TCB。这个开销远小于 TCB 的开销。当然还需要保存序列号
# Syn Cookie 技术
Syn Cookie 技术则完全不使用任何存储资源,这种方法比较巧妙,它使用一种特殊的算法生成 Sequence Number,这种算法考虑到了对方的 IP、端口、己方 IP、端口的固定信息,以及对方无法知道而己方比较固定的一些信息,如 MSS(Maximum Segment Size,最大报文段大小,指的是 TCP 报文的最大数据报长度,其中不包括 TCP 首部长度。)、时间等,在收到对方的 ACK 报文后,重新计算一遍,看其是否与对方回应报文中的(Sequence Number-1)相同,从而决定是否分配 TCB 资源
# 连接队列
在外部请求到达时,被服务程序最终感知到前,连接可能处于SYN_RCVD
状态或是ESTABLISHED
状态,但还未被应用程序接受。对应地,服务器端也会维护两种队列,处于SYN_RCVD
状态的半连接队列,而处于ESTABLISHED
状态但仍未被应用程序accept
的为全连接队列。如果这两个队列满了之后,就会出现各种丢包的情形
# 半连接队列
在三次握手协议中,服务器维护一个半连接队列
,该队列为每个客户端的 SYN 包开设一个条目(服务端在接收到 SYN 包的时候,就已经创建了request_sock
结构,存储在半连接队列中),该条目表明服务器已收到 SYN 包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于SYN_RECV
状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED
状态
# 全连接队列
当第三次握手时,当 server 接收到 ACK 包之后,会进入一个新的叫accept
的队列。
当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被响应,此时 ListenOverflows+1,同时 server 通过 tcp_abort_on_overflow 来决定如何返回,0 表示直接丢弃该 ACK,1 表示发送 RST 通知 client;
相应的,client 则会分别返回 read timeout 或者 connection reset by peer
。另外,tcp_abort_on_overflow
是 0 的话,server 过一段时间再次发送 syn+ack 给 client(也就是重新走握手的第二步),如果 client 超时等待比较短,就很容易异常了。而客户端收到多个 SYN ACK
包,则会认为之前的 ACK 丢包了。于是促使客户端再次发送ACK
,在 accept 队列有空闲的时候最终完成连接。若accept
队列始终满员,则最终客户端收到 RST
包(此时服务端发送 syn+ack 的次数超出了tcp_synack_retries
)
# 滑动窗口
# 拥塞控制
# 常见问题
# 为什么是三次握手,四次挥手?
其实在 TCP 握手的时候,接收端发送 SYN+ACK 的包是将一个 ACK 和一个 SYN 合并到一个包中,所以减少了一次包的发送,三次完成握手。
对于四次挥手,因为 TCP 是全双工通信,·在主动关闭方发送 FIN 包后,接收端可能还要发送数据,不能立即关闭服务器端到客户端的数据通道,所以也就不能将服务器端的FIN 包与对客户端的 ACK 包合并发送
,只能先确认 ACK,然后服务器待无需发送数据时再发送 FIN 包,所以四次挥手时必须是四次数据包的交互
# 为什么 TIME_WAIT 状态需要经过 2MSL 才能返回到 CLOSE 状态?
MSL 指的是报文在网络中最大生存时间。在客户端发送对服务器端的 FIN 的确认包 ACK 后,这个 ACK 包是有可能不可达的,服务器端如果收不到 ACK 的话需要重新发送 FIN 包。
所以客户端发送 ACK 后需要留出 2MSL 时间(ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回)等待确认服务器端确实收到了 ACK 包。
也就是说客户端如果等待 2MSL 时间也没有收到服务器端的重传包 FIN,说明可以确认服务器已经收到客户端发送的 ACK。
还有第 2 个理由,避免新旧连接混淆。
在客户端发送完最后一个 ACK 报文段后,在经过 2MSL 时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。
你要知道,有些自作主张的路由器会缓存 IP 数据包,如果连接重用了,那么这些延迟收到的包就有可能会跟新连接混在一起
# TCP和UDP的区别
# 词汇
MSS: Maximum Segment Size 最大报文段大小,指的是TCP报文的最大数据报长度,其中不包括TCP首部长度