1.TCP 报文格式
TCP(Transmission Control Protocol) 传输控制协议. TCP 是主机对主机层的传输控制协议, 提供可靠的连接服务, 采用三次握手确认建立一个连接.
我们需要知道 TCP 在网络 OSI 的七层模型中的第四层(Transport 层),IP 在第三层(Network 层), 第二层(Data Link 层), 在第二层上的数据, 我们叫 Frame, 在第三层上的数据叫 Packet, 第四层的数据叫 Segment.
TCP 传输控制协议是面向连接的可靠的传输层协议, 在进行数据传输之前, 需要在传输数据的两端 (客户端和服务器端) 创建一个连接, 这个连接由一对插口地址唯一标识, 即是在 IP 报文首部的源 IP 地址, 目的 IP 地址, 以及 TCP 数据报首部的源端口地址和目的端口地址.
上图中有几个字段需要重点介绍下:
(1)Sequence Number 序号: Seq 序号, 占 32 位, 用来标识从 TCP 源端向目的端发送的字节流, 发起方发送数据时对此进行标记. 是包的序号, 用来解决网络包乱序 (reordering) 问题.
(2)Acknowledgement Number 确认序号: Ack 序号, 占 32 位, 只有 ACK 标志位为 1 时, 确认序号字段才有效, Ack=Seq+1.
(3)标志位: 共 6 个, 即 URG,ACK,PSH,RST,SYN,FIN 等, 具体含义如下:
a. URG(urgent 紧急): 紧急指针 (urgent pointer) 有效.
b.ACK(acknowledgement 确认): 确认序号有效.
c.PSH(push 传送): 接收方应该尽快将这个报文交给应用层.
d.RST(reset 重置): 重置连接.
e.SYN (synchronous 建立联机): 发起一个新连接.
f.FIN(finish 结束): 释放一个连接.
需要注意的是:
(A)不要将确认序号 Ack 与标志位中的 ACK 搞混了.
(B)确认方 Ack = 发起方 Seq+1, 两端配对.
2.TCP 三次握手
TCP 是主机对主机层的传输控制协议, 提供可靠的连接服务, 采用三次握手确认建立一个连接.
所谓三次握手 (Three-Way Handshake) 即建立 TCP 连接, 是指建立一个 TCP 连接时, 需要客户端和服务端总共发送 3 个包以确认连接的建立.
使用 wireshark 抓包工具分析如下:
可以得出下图:
解释如下:
第一次握手: Client 将标志位 SYN 置为 1, 随机产生一个值 seq=x, 并将该数据包发送给 Server,Client 进入 SYN_SENT 状态, 等待 Server 确认.
第二次握手: Server 收到数据包后由标志位 SYN=1 知道 Client 请求建立连接, Server 将标志位 SYN 和 ACK 都置为 1,ack (number )=x+1, 随机产生一个值 seq=y, 并将该数据包发送给 Client 以确认连接请求, Server 进入 SYN_RCVD 状态.
第三次握手: Client 收到确认后, 检查 ack 是否为 x+1, 标志位 ACK 是否为 1, 如果正确则将标志位 ACK 置为 1,ack=y+1, 并将该数据包发送给 Server,Server 检查 ack 是否为 y+1,ACK 是否为 1, 如果正确则连接建立成功, Client 和 Server 进入 ESTABLISHED 状态, 完成三次握手, 随后 Client 与 Server 之间可以开始传输数据了.
3.TCP 四次挥手
所谓四次挥手 (Four-Way Wavehand) 即终止 TCP 连接, 就是指断开一个 TCP 连接时, 需要客户端和服务端总共发送 4 个包以确认连接的断开.
连接双方在完成数据传输之后就需要断开连接. 由于 TCP 连接是属于全双工的, 即连接双方可以在一条 TCP 连接上互相传输数据, 因此在断开时存在一个半关闭状态, 即有有一方失去发送数据的能力, 却还能接收数据. 因此, 断开连接需要分为四次
使用 wireshark 抓包工具分析如下:
流程如下:
由于 TCP 连接时全双工的, 因此, 每个方向都必须要单独进行关闭, 这一原则是当一方完成数据发送任务后, 发送一个 FIN 来终止这一方向的连接, 收到一个 FIN 只是意味着这一方向上没有数据流动了, 即不会再收到数据了, 但是在这个 TCP 连接上仍然能够发送数据, 直到这一方向也发送了 FIN. 首先进行关闭的一方将执行主动关闭, 而另一方则执行被动关闭.
第一次挥手: Client 将标志位 FIN 和 ACK 置为 1 并且发送发送一个 FIN 和 ACK, 用来关闭 Client 到 Server 的数据传送, Client 进入 FIN_WAIT_1 状态.
第二次挥手: Server 收到 FIN 后, 发送一个 ACK 给 Client, 确认序号 Ack 为收到 Seq+1(与 SYN 相同, 一个 FIN 占用一个序号), 序号 Seq=1,Server 进入 CLOSE_WAIT 状态.
第三次挥手: Server 发送一个 FIN, 标志位 FIN 和 ACK 置为 1, 用来关闭 Server 到 Client 的数据传送, Seq=y,Ack = 上次的 Seq+1,Server 进入 LAST_ACK 状态.
第四次挥手: Client 收到 FIN 后, Client 进入 TIME_WAIT 状态, 接着发送一个 ACK 给 Server, 确认序号 Ack 为收到序号 Seq+1,Server 进入 CLOSED 状态, 完成四次挥手.
4. 为什么建立连接需要三次握手?
TCP 是主机对主机层的传输控制协议, 提供可靠的连接服务, 采用三次握手确认建立一个连接. 确保数据能够完整传输.
这是因为服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的建连请求后, 它可以把 ACK 和 SYN(ACK 起应答作用, 而 SYN 起同步作用)放在一个报文里来发
送.
假设如下异常情况:
客户端向服务器发送了第一条请求报文, 但是该报文并未在网络中被丢弃, 而是长时间阻滞在某处, 而客户端收不到服务器确认, 以为该报文丢失, 于是重新发送该报文, 这次的报文成功到达服务器, 如果不使用三次握手, 则服务器只需对该报文发出确认, 就建立了一个连接. 而在这个连接建立, 并释放后, 第一次发送的, 阻滞在网络中的报文到达了服务器, 服务器以为是客户端又重新发送了一个连接请求(实际上在客户端那里, 该连接早已失效), 就又向客户端发送一个确认, 但客户端认为他没有发送该请求报文, 因此不理睬服务器发送的确认, 而服务器以为又建立了一个新的连接, 于是一直等待 A 发来数据, 造成了服务器资源的浪费, 并且会产生安全隐患. 因此, 若使用三次握手机制, 服务器发送了该确认后, 收不到客户端的确认, 也就知道并没有建立连接, 因此不会将资源浪费在这种没有意义的等待上.
5. 为什么断开连接需要四次挥手?
当被动方收到主动方的 FIN 报文通知时, 它仅仅表示主动方没有数据再发送给被动方了.
但未必被动方所有的数据都完整的发送给了主动方, 所以被动方不会马上关闭 SOCKET, 它可能还需要发送一些数据给主动方后, 再发送 FIN 报文给主动方, 告诉主动方同意关闭连接, 所以这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的.
6. 为什么 TIME_WAIT 状态还需要等 2MSL 后才能返回到 CLOSED 状态?
这是因为虽然双方都同意关闭连接了, 而且握手的 4 个报文也都协调和发送完毕, 按理可以直接回到 CLOSED 状态(就好比从 SYN_SEND 状态到 ESTABLISH 状态那样); 但是因为我们必须要假想网络是不可靠的, 你无法保证你最后发送的 ACK 报文会一定被对方收到, 因此对方处于 LAST_ACK 状态下的 SOCKET 可能会因为超时未收到 ACK 报文, 而重发 FIN 报文, 所以这个 TIME_WAIT 状态的作用就是用来重发可能丢失的 ACK 报文.
7.SYN 攻击原理
在三次握手过程中, Server 发送 SYN-ACK 之后, 收到 Client 的 ACK 之前的 TCP 连接称为半连接(half-open connect), 此时 Server 处于 SYN_RCVD 状态, 当收到 ACK 后, Server 转入 ESTABLISHED 状态.
SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址, 并向 Server 不断地发送 SYN 包, Server 回复确认包, 并等待 Client 的确认, 由于源地址是不存在的, 因此, Server 需要不断重发直至超时, 这些伪造的 SYN 包将长时间占用未连接队列, 导致正常的 SYN 请求因为队列满而被丢弃, 从而引起网络堵塞甚至系统瘫痪.
SYN 攻击时一种典型的 DDOS 攻击, 检测 SYN 攻击的方式非常简单, 即当 Server 上有大量半连接状态且源 IP 地址是随机的, 则可以断定遭到 SYN 攻击了, 使用如下命令可以可以查看 SYN_RECV 状态:
# netstat -nap | grep SYN_RECV
关于建连接时 SYN 超时: 试想一下, 如果 server 端接到了 clien 发的 SYN 后回了 SYN-ACK 后 client 掉线了, server 端没有收到 client 回来的 ACK, 那么, 这个连接处于一个中间状态, 即没成功, 也没失败. 于是, server 端如果在一定时间内没有收到的 TCP 会重发 SYN-ACK. 在 Linux 下, 默认重试次数为 5 次, 重试的间隔时间从 1s 开始每次都翻售, 5 次的重试时间间隔为 1s, 2s, 4s, 8s, 16s, 总共 31s, 第 5 次发出后还要等 32s 都知道第 5 次也超时了, 所以, 总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP 才会把断开这个连接.
关于 SYN Flood 攻击: 一些恶意的人就为此制造了 SYN Flood 攻击 -- 给服务器发了一个 SYN 后, 就下线了, 于是服务器需要默认等 63s 才会断开连接, 这样, 攻击者就可以把服务器的 syn 连接的队列耗尽, 让正常的连接请求不能处理. 于是, Linux 下给了一个叫 tcp_syncookies 的参数来应对这个事 -- 当 SYN 队列满了后, TCP 会通过源地址端口, 目标地址端口和时间戳打造出一个特别的 Sequence Number 发回去(又叫 cookie), 如果是攻击者则不会有响应, 如果是正常连接, 则会把这个 SYN Cookie 发回来, 然后服务端可以通过 cookie 建连接(即使你不在 SYN 队列中). 请注意, 请先千万别用 tcp_syncookies 来处理正常的大负载的连接的情况. 因为, synccookies 是妥协版的 TCP 协议, 并不严谨. 对于正常的请求, 你应该调整三个 TCP 参数可供你选择, 第一个是: tcp_synack_retries 可以用他来减少重试次数; 第二个是: tcp_max_syn_backlog, 可以增大 SYN 连接数; 第三个是: tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了.
来源: http://www.bubuko.com/infodetail-3301570.html