前言
TCP三次握手和四次挥手大概是计算机网络里面的最经典的名词之一了,刚进小组时就听说了手把手教学妹TCP三次握手 的事迹,当然TCP中的流量控制,差错控制,拥塞控制都很重要,今天主要从三次握手 来初步的理解TCP协议。
一些基础
我们以谢希仁老师的《计算机网络》中的五层模型(物理层、数据链路层、网络层、传输层、应用层)来看,TCP位于从上数第二层,接受网络层 尽最大努力交付的的数据报,也就是可能丢失、失序的数据,同时向应用层提供可靠的数据,实现逻辑上的进程端到端通信,并且是全双工的。
TCP的三次握手代表了TCP的连接建立过程,同时,这里的设计也体现了TCP的上述特点。
抓包分析 + 一个简单的echo服务
我们通过一个具体的例子来说明,因为在真实的网络通信中(比如浏览网页的HTTP,SSH登陆)中掺杂了大量了无关包,所以我通过使用自己实现的echo服务器来抓包,这样就很简单(我才不会说是我懒得过滤呢),而且真实情况确实比较。。(想起了朱学长给我们抓包某个夜晚)。
echo代码如下
Server端(制作成守护进程可以放在腾讯云上一直跑)
Client端
下文中Client和Server发送的echo字符串我们都将用数据指代。(如果你大概了解TCP,那么你定然知道我这里的用意–ACK报文不携带数据不消耗序号)
TCP报头讲解(部分)
首先,TCP的也是通过报头来控制,结构如下(随便找的)
端口
首先4个字节标记了源(发送进程)和目的(接受进程)的端口,范围当然是0-65535.
端口对应进程,IP对应主机。在网络层已经通过IP地址找到目标主机,所以TCP作为传输层需要找到目标进程,将数据送给指定进程。
这里需要注意的是端口号分为三个范围:
(1) 0-1023这1024个端口号叫做熟知端口号,已经被指派给了TCP/IP里重要的应用程序了,如SSH的22端口,HTTP的80端口。
(2) 1024-49151 登记端口号,使用这些端口号需要在IANA登记,以防止重复。 PS:感觉这个没啥用。。毕竟(1)(2)都是对于服务器端的限制,我自己写的服务器只要随意绑定的端口被客户端知道就行,当然0-1023还是不用的好。
(3) 49152-65535 短暂端口号 ,对于客户端进程,操作系统会在其运行时为它动态分配一个端口号,这个端口号对客户进程其实没有用处,只是为了服务器端向客户端发包时,操作系统知道将这个包送给本机上的哪个进程,依然体现了端口对应进程。
seq & ack
然后是序列号 (下文用seq指代) 和确认号(下文用ack指代)。
在TCP三次握手也就是连接建立之后,双方开始全双工的通信。
每方每次发送的时候 seq代表我方已经成功发送的字节数+1+ISN (书上描述与我这里完全不同,我只说我的理解,下面也是这样)。
比如Client第一次发送数据时,假设ISN为0,因为已经成功发送0字节,所以第一个包的seq自然是1,如果Client从建立连接后成功发送了一个“hello” 那么下一次发送时seq为7,(“hello”字符串长度为6,包括’\0’,成功发送6字节,自然此次为7)。
而ack则代表我方确认成功接收到(你方发送)的字节数+1+ISN。
依然是Client第一次发送数据时,假设ISN为0,由于成功接受对方(server)0字节,所以第一个包的ack也是1,后面的就和seq一样了。
偏移&保留位
偏移指的是TCP报头的长度,4位最大表示60字节。4位范围是0-15,怎么表示60字节?? 以4字节作为计算单位,也就是每次要乘4,比如1010代表长度为40字节(10*4)。
保留位有6位。
标志位
这里标志位有6种(下文中相关标志一律全大写)
1. URG—不展开,本文不涉及。
2. ACK—确认。 当ACK标志 =1 时,ack才有效,TCP规定,建立连接之后,所有报文都必须将ACK置为1.
3. PSH—不是很懂,不涉及不展开。
4. RST—同上
5. SYN—同步。用于在连接建立时同步序号(seq&ack)。同时,报文中SYN=1就表明这是一个请求连接 或者 接受连接 报文
6. FIN—终止。当报文中FIN=1,表明发送方要发送的数据已经全部发送,请求释放连接。
三次握手
(看到这里真不容易,毕竟需要的基础太多)
意义
三次握手,也就是建立连接的阶段,我认为主要做了两件事,通知 和 开始。
图 (随便找的x2)
流程
三次握手主要是通知双方的seq和MSS(最大报文段长度,以就不展开)可能还有其他的(我也才刚看)。
第一次握手(SYN =1—请求建立连接)seq=ISN_Client
第二次握手(SYN =1 — 接受请求 ACK = 1—确认成功收到) seq = ISN_Server ack = ISN_Client + 1
第三次握手(ACK =1 — 确认成功收到)seq = ISN_Client + 1 ack = ISN_Server + 1
ISN是什么
如果通过Wireshark抓包,显示的seq和ack都是相对值 也就是seq-ISN之后的值。
ISN(Init Sequence Number)是一个由特定算法得出的随机值,用于生成每次的seq和ack。
为什么要有ISN
如果没有ISN 每次seq的开始必然是一个大家都知道的特定值,那么就会存在漏洞。
一个攻击的例子:
某机密数据服务器Server只和信任客户Client通信,且只验证IP地址。
(1)攻击者首先拿到Client IP地址,同时让Client网络瘫痪(各类DDos)。
(2)伪造IP 与Server进行第一次握手:发送seq = X 的SYN报
(3) Server验证IP无误,进行第二次握手,向Client IP发送seq = 特定值,ack = X+1 的SYN+ACK报文
(4)攻击者并没有受到第二次握手的报文,但由于他知道seq是一个已知的特定值依然可以完成第三次握手,发送seq = X +1 ack = 特定值 + 1的ACK报文,使得连接建立。
虽然不知道建立连接有什么用,但起码可以DDos吧。。。(对这些安全的知识确实不懂)。而且在连接建立之后,如果是以特定值生成的seq很容易被攻击者猜出来吧。
为什么是三次不是两次
书上的解答是,如果经过两次握手即认为连接建立,那么有可能Client发起连接的SYN包在网络过程中发生了延迟,即Client发送了两个SYN包,先后到达Server,Server收到第一个SYN,发出ACK+SYN,认为连接开始,之后再受到SYN,又发出一次ACK+SYN,认为再次建立连接,但是Client并不知情,导致产生各种差错和浪费。。
为什么是三次不是更多
没有看到特别满意的解答,我自己的理解是TCP三次握手实际上已经屏蔽了很多种异常情况,至于某些更小几率的异常(毕竟网络环境越来越好)在和实际的效率消耗(三次握手还是消耗很大的)的权衡之中,倾向于更高的效率,而不是百分百的完美通信。
实例 echo服务
Client 192.168.30.151 临时端口 41608
Server 123.206.89.123 端口5473
我们的Client分别输入了“hello” “world”之后 主动close。
首先是加黑的三次握手。
第一次 Client 发送 SYN seq = 0(相对ISN_Client)
第二次 Server 回复 SYN+ACK seq = 0 (相对ISN_Server) ack = 1(相对ISN_Client)
第三次 Client 发送 ACK seq = 1(相对ISN_Client)ack = 1(相对ISN_Server)
然后是Client的hello:与上一个66字节相比多了一个6字节的“hello”字符串 seq ack同上。
Server 回复ACK(不带数据)seq = 1 ack = 7(这里不知道参照前面的ack)
Server 按照echo服务回复“hello”字符串 seq依然是1,ack依然是7.(ACK报文不消耗序号),所以这里Server的两个报文seq 和ack是一样的没有变化。
Client 收到这个“hello” 回复ACK(不带数据) seq变为7,ack变为7。表示知道自己成功发送了6个字节,也成功受到对方的6个字节。
Client发送 “world” 过程就同上了。
最后
还记得我们前面说的seq和ack是成功发送/收到字节数+1+ISN 吗,这个1其实就是三次握手中的将两个SYN看作是逻辑上成功发送的1字节,纯ACK是不占序号的,当然了,也可以按照书上的seq为发送报文第一个字节在整个字节流中的序号,你觉得哪个好就是那个咯