五层模型
第五层:应用层,代表协议HTTP、SFTP、POP、RTMP、MYSQL
第四层:传输层,代表协议TCP、UDP
第三层:网络层,代表协议IP、ICMP
第二层:数据链路层,代表协议L2TP、ARP(MAC地址学习)
第一层:物理层,网络设备
TCP协议
TCP报文格式
固定首部大小为4Byte * 5 = 20Byte
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Sequence Number
为报文序号,在三次握手中的第一次作用为沟通本段发送序号,由ISN(initial sequence number)生成初始报文序号- ISN生成规则为
ISN=M + F(sip+sport+dip+dport+salt)
,其中M为全局递增的计数器,F为Hash散列函数。 - ISN主要用于防止RST攻击,RST攻击的方式一般为SEQ猜测,然后发送在滑动窗口内的RST报文,使服务器异常断开。
- ISN还有一个作用,在五元组冲突(五元组为源IP、源端口、目的IP、目的端口、协议类型)时,保证会话唯一。
- ISN生成规则为
Acknowledgement Number
为确认序号,用于标识在ack之前的包均以收到,并且下一个期望的包需要应该需要从ack开始。ACK主要用于报文保序、异常处理等场景。- 在三次握手场景中,如果由于网络拥塞导致
Sync
包未按时到达从而重传Sync
包,可通过ACK丢弃服务端历史的二次握手请求。 - SEQ回绕时如何判定包序?seq本身是无符号整型,新包seq2减旧包seq1都是正数,例如seq2(255) - seq1(200) = 5 > 0、seq2(5) - seq1(255) = 6 > 0
- 在三次握手场景中,如果由于网络拥塞导致
Data Offset
为首部长度,首部长度字段占用4位,所以首部长度最大值为$ 2 ^ {4} $ - 1个4字节(即60字节),除去首部固定的20字节后,剩余可用的首部长度为40字节,均为Options选项字段所用- 六小只
URG
用于标记是否启用紧急指针ACK
确认序号标志,为1时表示确认序号有效,一般只有在三次握手的第一次不会带上ACK标志位,其余时刻均会启用PSH
,为1时表示有需要push的数据,接收端在收到后应尽快推送至应用,而不是继续存在缓冲区RST
重置标志,表示当前发送端服务不可用拒接连接,接收端需要断开连接。- RST报文会被利用作为一种攻击手段,通过恶意发送RST报文,从而是服务端断开连接
- 由于有SEQ与ACK机制保证历史已确认包不会被接受,因此RST攻击的手段往往是猜测SEQ,然后发送窗口内的RST包给接收端。
- 防范可以开启ISN生成随机序号,使SEQ猜测难度增高
SYN
同步标志,在三次握手中的前两次握手中用到,目的是用于将发送端的SEQ同步至接收端,这样接收端就可以计算下一次收包的开始序号。FIN
结束标志,在四次挥手中会用到,用于通知接收端,本端的发送请求结束,可以关闭本段发送流。
Window
接收端消息窗口,用于接收端告知发送端可处理的数据报个数,一般是按MSS计算- 此处Window便是流量控制的表现,如果一次性将所有数据都发送出去,那势必会造成接收端接受不过来,通过滑动窗口的形式,可以很好得根据接收端的处理能力来发包。
Checksum
校验和,用于校验TCP报(包含头部+数据)是否完整。Urgent Pointer
紧急指针,指向需要紧急发送的数据内容。Options
可选字段- MSS,最大报文数据长度(不包含TCP头),一般在三次握手的第一次指定。
- SACK,快速重传中引入的字段,大小等同与SEQ,每组标识丢包的左右边界序号SEQ,发送端可根据SACK进行选择性发包。SACK最多有4组,计算方式: (60Byte - 20Byte - 4Byte) / 4Byte = 8个SACK。
三次握手
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
Basic 3-Way Handshake for Connection Synchronization
步骤:
- 客户端发出SYNC包
SYN=1, seq=x
,用于与服务端同步序号,发出后进入SYN_SENT
状态- 若SYNC包发出后经过超时时间未收到ACK,则会重发
- 服务端收到SYN包后,回发ACK包,同时也带上自己的序号与客户端同步,
SYN=1, ACK=1, seq=y, ack=x+1
,发出后进入SYN-RECVED
状态- SYNC标志位个人理解主要是为了告知对端开始序号
- ack表示
x+1
之前的报文均已确认接受,并且期待的下一个报文seq为x+1
- 该步骤容易被利用,知道SYNC泛洪水攻击。
- 攻击原理是伪造不存在的ip,发送大量SYN包,致使服务器创建大量状态为
SYN-RECEIVED
状态的连接,以消耗系统资源 - 防御手段可以采用中间代理、降低半连接存活时间,或网关拦截的方式,参考Link
- 攻击原理是伪造不存在的ip,发送大量SYN包,致使服务器创建大量状态为
- 客户端收到ACK包后,回发ACK包
ACK=1, seq=x+1, ack=y+1
,发出后进入ESTABLISH
状态 - 服务端收到客户端的ACK包后,也进入
ESTABLISH
状态
为什么是三次握手
- 反证法:如果改为两次握手,那就需要考虑网络拥塞和丢包的场景
- 场景一,网络拥塞导致客户端的SYNC包未及时到达服务端,客户端会触发重传机制,重新发送一个SYNC包,这样网络上便会同时存在两个SYNC包,假设第二个SYNC包发送后正常建立连接并关闭连接,此后网络中第一个SYNC包到达服务端,此时服务端会认为是一次正常的建联请求,返回ACK并进入
ESTABLISH
状态,建立了一个无效的链接 - 场景二,网络丢包导致服务端的SYNC包无法到达客户端,那边客户端会反复发送SYNC包,但是由于服务端已经进入
ESTABLISH
状态,会建立失败
- 场景一,网络拥塞导致客户端的SYNC包未及时到达服务端,客户端会触发重传机制,重新发送一个SYNC包,这样网络上便会同时存在两个SYNC包,假设第二个SYNC包发送后正常建立连接并关闭连接,此后网络中第一个SYNC包到达服务端,此时服务端会认为是一次正常的建联请求,返回ACK并进入
四次挥手
TCP A TCP B
1. ESTABLISHED ESTABLISHED
2. (Close)
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> --> CLOSE-WAIT
3. FIN-WAIT-2 <-- <SEQ=300><ACK=101><CTL=ACK> <-- CLOSE-WAIT
4. (Close)
TIME-WAIT <-- <SEQ=300><ACK=101><CTL=FIN,ACK> <-- LAST-ACK
5. TIME-WAIT --> <SEQ=101><ACK=301><CTL=ACK> --> CLOSED
6. (2 MSL)
CLOSED
Normal Close Sequence
步骤:
- 关闭连接一方(假设客户端)会发送FIN包至对端,
FIN=1, ACK=1, seq=x, ack=y
,并进入FIN-WAIT-1
状态 - 服务端收到客户端的FIN包后,返回ACK,
ACK=1, seq=x, ack=y+1
,并进入CLOSE-WAIT
状态 - 客户端收到服务端的ACK后,进入
FIN-WAIT-2
状态 - 服务端在处理完所有发送数据后,会发送FIN包至客户端,
FIN=1, ACK=1, seq=z, ack=x+1
,并进入LAST-ACK
状态 - 客户端在收到服务端的FIN包后,发送ACK,
ACK=1, seq=x+1, ack=z+1
,并进入TIME-WAIT
状态,在等待2MSL(Maximum Segment Lifetime)后将会进入CLOSED
状态- 为什么需要等待2MSL呢?首先MSL定义是一个报文能在网路上留存的最长时间,一般是配合IP报文头中的TTL(Time To Live,每过一条交换机便减一,直至0丢包)计算出来的,2MSL正好对应一去(客户端发出的ACK)一回(服务端超时重发的FIN包)的时间,主要是应对服务端未收到ACK重传的场景。一个MSL默认为60秒,远超出重传的时间RTO。
- 服务端在收到ACK后,进入
CLOSED
状态
为什么是四次挥手
每次一端发出的FIN包,仅仅是告知对端,本端的发送已经结束,可以关闭接收通道,因此需要4次。并且在只有一端关闭发送通道的情况下,另一端任然可以继续发送数据,直至数据传输完毕关闭发送通道。
TCP拥塞控制
拥塞控制是为了避免过多的数据注入网络中,从而导致网络负载大,出现频繁丢包的情况(交换机或路由器中的等待队列出现满负载的情况下,会出现丢包)
拥塞控制算法主要体现在对拥塞控制窗口的控制上,TCP实际发包流程中,会取发送窗口(swnd,由TCP报文中的window字段决定)与控制窗口(cwnd,由拥塞控制算法决定)两者的最小值,作为单次发包数据量
TCP的拥塞控制算法有多个版本
早期Tahoe版本提出了慢启动以及拥塞避免算法,具体流程:
- 慢启动,cwnd以较低的一个值启动(此为慢启动,慢特指启动时的窗口值低),在到达ssthresh(慢启动门限)前,以乘法趋势增加cwnd值
- 拥塞避免,逐步增加cwnd值超过ssthresh后,便会进入拥塞避免阶段,降低cwnd增速趋势,采用加法递增
- 当感知到网络出现拥塞后,便会将ssthresh值降为当前值的一般,而后重新进入慢启动阶段,cwnd值降为慢启动起始值
- 如何感知网络拥塞?TCP在发出数据包后,会为每个报文设置一个定时器,当某个报文在RTO(重传超时时间)时间内未返回ACK包,则会被判定为出现丢包或网络拥塞。
- RTO的取值很重要,太短会导致频繁重传,太长会导致等待时间过久,一般会以RTT(Round Trip Time)为基准,通过加权平均计算SRTT(Smooth Round Trip Time),最后得到RTO。参考
Tahoe版拥塞控制存在两个不足
- 拥塞感知滞后。通过计时器感知拥塞的方式效率较为低下,假设要传输的数据包
1,2,3,4,5
中,2
出现了丢包,但是1,3,4,5
均到达了接收端,接收端在能感知到2产生丢包的情况下,可以主动提醒发送端,2
这个包丢失了,而不需要发送端等待2
包的超时处理。 - 降速过于激进。受限于拥塞感知的能力,无法判断一个包的ACK接收超时是因为拥塞还是丢包导致的,若仅仅是受传输载体影响的丢包(无线网络波动等等),则只需要重传对应的包即可,无须降速。
以上的问题,均在后续提出的Reno
版拥塞控制算法中得到解决
- 快速重传,解决拥塞感知滞后问题。接收端在可以感知到某个中间包丢失时,会主动回发相同seq的ACK给客户端,例如还是
1,2,3,4,5
这个例子中,如果出现2
丢包,后续接收端又陆续收到了3,4,5
三个包,则每个包均会返回相同ACKACK=1, seq=x, ack=2
,用于告知发送端,我只确认了序号2
之前的包。当发送端收到三个相同的ACK时,便会立即重传丢失的包。 快速恢复
,解决降速过于激进的问题。发送端在感知到丢包后,不会重新进入慢启动阶段,而是先将ssthresh降为当前cwnd的一半,然后cwnd配置成ssthresh大小的值后,直接进入拥塞避免阶段。因为ACK都能很快回来,可能网络没那么拥塞。
还有其他拥塞控制算法,例如CUBIC
和谷歌的BBR
,参考
UDP协议
报文格式
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
User Datagram Header Format
固定首部长为8个字节
IP协议
报文格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
同TCP报文,固定首部长为20个字节
数据报是如何被转发的
当数据报在第三层经过IP协议填充目的IP封装好包后,便会交给第二层数据链路层。
数据链路层需要封装目的MAC地址后才能向下继续传递。首先主机会从ARP缓存表中查找目的IP对应的MAC地址
-
若找到,则会使用缓存表中的数据封装报文。
-
若未找到,则需要通过ARP协议获取目的IP对应的MAC地址。但由于ARP无法被广播至当前广播域外(广播消息不能出路由),因此还需要做一次目的IP的网段校验
- 若目的IP与当前主机IP处于同一网段(通过子网掩码匹配),则会广播ARP报文至当前广播域,然后等待目的主机返回带有目的MAC的ARP报文,当前主机收到后会存一份至ARP缓存表,然后继续封装收到的目的MAC地址,并发送报文
- 若目的IP与当前主机IP处于不同网段,则或通过ARP获取当前主机网关的MAC地址,然后封装报文发送给网关。后续的报文便会由网关发送至其他网络(路由器是网关的一种,网关与网关之间通过OSPF/BGP交换路由信息)
- 当源端处于局域网内,需要向处于广域网的目的端发送数据包时,IP报文头填写局域网IP就不合适了,这个时候就需要路由开启NAT/NAPT来做数据转发。
- NAT会将内网IP与公网IP在路由器中的NAT表中做映射关系,例如
8.8.8.8 ---> 192.168.2.2
,即外网的8.8.8.8
IP与内网的192.168.2.2
IP进行绑定,同时源端IP地址会被修改为8.8.8.8
,然后转发到广域网。当目的端收到报文,返回数据时,使用的目的IP也是8.8.8.8
,在报文抵达源端所连接的路由器时,路由器检查的有NAT映射,便会修改IP报文头中的目的IP为192.168.2.2
- NAPT是NAT的升级版,NAT的做法虽然可以将打通内网与外网的数据流,同时隐藏内网信息,但是太耗IP资源了,因此NAPT便是基于TCP报文的源端口以及IP报文的源IP做映射,这样支持的映射关系便可以增加很多
当数据报文跨过局域网,到达广域网时,二层数据报协议便会从以太网协议切换到PPP协议(我猜的,毕竟广域网的二层协议也不确定,可能会被切换成其他协议吧,待验证)- 在广域网中,不同的ISP(Internet Service Provider)拥有不同的自治系统(AS),要将数据报传到目的端,就需要穿越这些AS,AS内部会使用OSPF/IGP计算路由,AS与AS之间会使用BGP同步路由。BGP主要维护的是自治系统的可达性,同时隐藏自治系统内部的网络架构
以太网协议
以太帧格式
| Source Mac (6B) | Dest Mac (6B) | Type (2B) | Data (46B-MTU) | FCS (4B) |
目的MAC的第一个字节有讲究
- 当第一个字节的第八位为0时,表示单播
- 当第一个字节的第八位为1时,表示组播
- 当MAC地址为FF:FF:FF:FF:FF:FF时,表示广播
Type字段决定了二层使用什么协议
- 当Type值小于1500时,表示IEEE 802.3协议
- 当Type值大于1536时,表示Ethwenet II型协议,此时Type值的含义便是当前以太帧需要发送给上层那个协议处理,例如2048为IP协议、2054为ARP协议
RDMA
RDMA协议为什么会被提出?
这个就需要了解传统网卡数据处理的流程。
最初版
网卡与磁盘的IO操作都是需要从用户态态切换至内核态操作的(因为系统对用户操作的不信任,参考)。
拿网卡举例,每当网卡收到一组数据,都会发送中断至CPU,让CPU读取网卡中的数据并经过TCP协议栈的解析,才能交于应用层(也就是用户态),由于网络传输事件较频繁,因此会发送大量中断至CPU,导致CPU无暇处理其他事件,将全部精力都消耗在网卡数据读取与处理上
DMA改良
最初版已知的问题就是网卡发送中断、CPU读取数据这块对CPU性能影响较大,那么就可以通过第三方设备代替完成这一部分工作,这就是DMA诞生的原因。
DMA技术解决了CPU读取数据这块的时间成本,CPU只需要配置一些DMA参数,然后由DMA设备将网卡中的数据读取至CPU的缓存中,这期间CPU可以处理其他事件,等到DMA操作完成后,由DMA发送中断至CPU,再由CPU去解析这些数据。这里DMA只适用于设备与CPU之间的数据拷贝,CPU与用户态之间任然需要CPU拷贝。
零拷贝
以我们的应用从磁盘中读取数据发送至网卡为例,虽然DMA解决了CPU读取数据这块耗时的操作,但是CPU与用户态的数据交互,任然需要CPU参与拷贝。读取磁盘数据并通过网卡发出的大致过程如下:
- 用户态程序需要执行
read()
读取磁盘数据,由于是磁盘IO操作,因此会发生一次上下文切换,即从用户态切换至内核态(第一次切换)。 - 处于内核态后会检查磁盘中的数据是否在内核态buffer中存在,若存在则直接返回。
- 如果磁盘数据不存在内核态buffer中,即出现缺页(会触发缺页异常),CPU便会通过DMA将数据拷贝至内核态buffer的页缓存中(第一次拷贝)。
- 当磁盘中的数据已经加载至页缓存后,CPU便会将此部分数据拷贝至用户态buffer(第二次拷贝)
- CPU拷贝结束后,
read()
结束,会保存上下文,并恢复至用户态(第二次切换) - 用户态程序处理数据,在处理完成后,会调用
write()
将数据写入socket buffer,由于是对网卡操作,需要再次切换至内核态(第三次切换) - CPU将用户态数据拷贝至内核态buffer(第三次拷贝)
- CPU使用DMA机制将数据拷贝至socket buffer(第四次拷贝)
write()
返回,恢复至用户态(第四次切换)
假如有一种场景,数据不需要用户态额外处理,只需要将磁盘中的某一块数据通过网卡发送出去,那么就根本不需要经过那么多上下文切换开销,直接通过CPU配置需要传输的数据地址以及网络接口描述符,网卡通过DMA收集功能即可将数据拷贝至网卡协议栈,这就是零拷贝(其实这里的零指的是0次CPU拷贝,两次DMA拷贝还是存在的),参考、参考二。
CPU拷贝 | DMA拷贝 | 系统调用 | 上下文切换 | |
---|---|---|---|---|
传统方法 | 2 | 2 | read/write | 4 |
内存映射 | 1 | 2 | mmap/write | 4 |
sendfile | 1 | 2 | sendfile | 2 |
scatter/gather copy | 0 | 2 | sendfile | 2 |
splice | 0 | 2 | splice | 0 |
RDMA
TLS和SSL
TLS全称Transport Layer Security,SSL全称Secure Socket Layer。
select、poll、epoll
Pending…
参考链接
最后修改于 2022-02-24