网络知识大杂烩
瞎写一通网络知识

五层模型

第五层:应用层,代表协议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、目的端口、协议类型)时,保证会话唯一。
  • 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
步骤:
  1. 客户端发出SYNC包SYN=1, seq=x,用于与服务端同步序号,发出后进入SYN_SENT状态
    1. 若SYNC包发出后经过超时时间未收到ACK,则会重发
  2. 服务端收到SYN包后,回发ACK包,同时也带上自己的序号与客户端同步,SYN=1, ACK=1, seq=y, ack=x+1,发出后进入SYN-RECVED状态
    1. SYNC标志位个人理解主要是为了告知对端开始序号
    2. ack表示x+1之前的报文均已确认接受,并且期待的下一个报文seq为x+1
    3. 该步骤容易被利用,知道SYNC泛洪水攻击。
      1. 攻击原理是伪造不存在的ip,发送大量SYN包,致使服务器创建大量状态为SYN-RECEIVED状态的连接,以消耗系统资源
      2. 防御手段可以采用中间代理、降低半连接存活时间,或网关拦截的方式,参考Link
  3. 客户端收到ACK包后,回发ACK包ACK=1, seq=x+1, ack=y+1,发出后进入ESTABLISH状态
  4. 服务端收到客户端的ACK包后,也进入ESTABLISH状态
为什么是三次握手
  • 反证法:如果改为两次握手,那就需要考虑网络拥塞和丢包的场景
    • 场景一,网络拥塞导致客户端的SYNC包未及时到达服务端,客户端会触发重传机制,重新发送一个SYNC包,这样网络上便会同时存在两个SYNC包,假设第二个SYNC包发送后正常建立连接并关闭连接,此后网络中第一个SYNC包到达服务端,此时服务端会认为是一次正常的建联请求,返回ACK并进入ESTABLISH状态,建立了一个无效的链接
    • 场景二,网络丢包导致服务端的SYNC包无法到达客户端,那边客户端会反复发送SYNC包,但是由于服务端已经进入ESTABLISH状态,会建立失败

四次挥手

      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
步骤:
  1. 关闭连接一方(假设客户端)会发送FIN包至对端,FIN=1, ACK=1, seq=x, ack=y,并进入FIN-WAIT-1状态
  2. 服务端收到客户端的FIN包后,返回ACK,ACK=1, seq=x, ack=y+1,并进入CLOSE-WAIT状态
  3. 客户端收到服务端的ACK后,进入FIN-WAIT-2状态
  4. 服务端在处理完所有发送数据后,会发送FIN包至客户端,FIN=1, ACK=1, seq=z, ack=x+1,并进入LAST-ACK状态
  5. 客户端在收到服务端的FIN包后,发送ACK,ACK=1, seq=x+1, ack=z+1,并进入TIME-WAIT状态,在等待2MSL(Maximum Segment Lifetime)后将会进入CLOSED状态
    1. 为什么需要等待2MSL呢?首先MSL定义是一个报文能在网路上留存的最长时间,一般是配合IP报文头中的TTL(Time To Live,每过一条交换机便减一,直至0丢包)计算出来的,2MSL正好对应一去(客户端发出的ACK)一回(服务端超时重发的FIN包)的时间,主要是应对服务端未收到ACK重传的场景。一个MSL默认为60秒,远超出重传的时间RTO。
  6. 服务端在收到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协议

RFC791

报文格式

    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.8IP与内网的192.168.2.2IP进行绑定,同时源端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