💡 有理想,但不妄想, 💭 有希望,但不奢望|

navyum

园龄:4个月粉丝:0关注:0

05.*传输层、TCP(四层)

传输:

数据传输格式:

  • MAC层定义了本地局域网设备的传输行为
  • IP层定义了整个网络端到端的传输行为
  • 传输层定义应用程序到应用程序的传输,基于端口区分
  • 这两层基本定义了包的特性:
    • 网络传输是以包为单位的,MAC层叫帧Frame,IP层叫包Packet,包被分片后叫Fragment,传输层叫段Segment
    • MAC层和IP层的我们笼统地称为包。包单独传输,自行选路,在不同的设备封装、解封装,不保证到达

UDP:

特性:

  • UDP 不提供可靠交付,不保证不丢失,不保证按顺序到达
  • UDP 不提供拥塞控制,无论网络情况,应用层想发就发
  • UDP 是面向数据报的,一个完整报文得进行收发(无论多大不分片)
  • UDP 是无状态的服务,只管发出去

协议格式:

Img
  • 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和

  • UDP 数据最大值:

    • 最大1472 字节
    • 计算:1500(MTU) - IP头(20) - UDP头(8)
    • 注意事项:UDP不会对上层数据做分段,而是直接交给IP层处理
      • 如果IP层设置了DF,则UDP数据会被丢弃,IP层发ICMP报文通知发送端
      • 如果IP层未设置DF,则UDP会被IP层分成多片,Packet乱序到达后,由IP层进行重组

UDP 应用场景:

  • 需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用
  • 不需要一对一沟通建立连接,而是可以广播或多播的应用
  • 需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩

具体应用:

  • QUIC(Quick UDP Internet Connections):
    • 在应用层的协议,会自己实现快速连接建立、减少重传时延,自适应拥塞控制
  • 包总量较少的通信:如 DNS 、SNMP等
  • 视频直播
    • 基于UDP实现自己的协议
  • 实时游戏
  • IoT领域
    • 物联网通信协议 Thread
  • 移动通信领域:GTP 协议
  • 广播通信

TCP:

TCP 连接:

  • 建立连接的目的是为了用一定的数据结构来维护双方交互的状态
  • TCP连接不是桥,是在码头上增加了记录人员,核查人员和督导人员
  • TCP用四个元组来表示是同一个连接 Img

特性:

  • TCP 提供可靠交付(无差错、不丢失、不重复、并且按序到达)
  • TCP 是面向字节流的(发送的时候发的是一个流,流大小不超过MSS,流可以重组为报文)
  • TCP 是可以有拥塞控制的,根据网络情况调整发送策略
  • TCP 是一个有状态服务(记录发送和接收状态)

TCP 基础概念:

  • MSL( Maximum Segment Lifetime):报文最大生存时间
  • RTO(Retransmission TimeOut):触发重传的超时时间
  • RTT(Round Trip Time):一个数据包从发出去到回来的时间
  • CWND( congestion Window):拥塞控制窗口
  • SWND(Sliding Window):滑动窗口

TCP 协议格式:

Img
  • 序号 Sequence Number:用来解决网络包乱序问题
  • 确认序号Ack Acknowledgement Number:用于确认收到,可以发现是否出现丢包
    • Ack确认的是收到的最后一个连续的包
    • SeqNum和Ack以字节数为单位
    • Ack = Seq + Len(包大小)
  • 窗口大小:滑动窗口大小
  • TCP Flags类型:
    • SYN:希望建立连接。握手阶段使用
    • ACK:确认应答,最初建立连接时的 SYN 包之外的包该值都为1
    • RST:连接出现异常,必须强制断开
    • FIN:数据发送结束,希望断开连接。挥手阶段使用。
  • TCP Options:
    • MSS设置
    • SACK设置
    • 其他
  • MSS
    • Max Segment Size,TCP最大分段长度
    • TCP对应用层的数据按照TCP认为最合适的大小进行分段,不一定取最大值
    • 范围:1 ~ 1420/1460 字节
    • 计算:1500 - IP头(20) - TCP头(20) - TCP 选项(0~40)

TCP 可靠性的依赖:

1. 维护连接状态:

  • TCP状态机Img
  • TCP 握手、挥手过程在内核协议栈进行,TLS在应用层
  • 三次握手
    • Img
    • 握手为什么至少三次:
      • 原因一:避免历史连接
      • 原因二:需要同步双方初始序列号
      • 原因三:避免资源浪费
        • 四次握手的浪费: Img
  • 四次挥手
    • Img
    • TCP是全双工的,发送Fin之后还可接收数据。所以发送方和接收方都需要做FinAck
    • TIME_WAIT
      • 作用:
        • 防止历史连接中的数据,被后续相同的四元组连接错误的接收
        • 保证「被动关闭连接」的一方接收到ACK,被正确的关闭
      • 一般设为:2MSL
      • 2MSL原因:让原连接中在网络上的数据包都被正常处理或者丢弃,避免被后续连接收到且被确认(因为Seq 的循环使用)
    • 四次挥手变成三次挥手
    • 挥手特殊说明:
      • 挥手过程在内核协议栈进行
        • 机器宕机,无法正常挥手
        • 进程崩溃,正常挥手

2. 顺序问题:

  • 初始ISN(Init Sequence Number)
    • 每次握手都不一样的原因:
      • 避免误收
      • 防止篡改
    • 如何随机:
      • ISN = M + F(localhost, localport, remotehost, remoteport)
      • M 是一个计时器,这个计时器每隔 4 微秒加 1
      • F 是一个 Hash 算法
  • 每个包都带有SEQ number 和ACK number

3. 丢包问题:

  • 问题描述:确保发出去的包最终一定被收到,如果丢失,通过重传机制再次发送
  • 重传机制
    • 1.超时重传
      • 定义:以时间驱动重传。发送端设置定时器
      • RTO(Retransmission TimeOut)基于网络环境,RTT是动态计算的
      • 内核参数设置次数:tcp_retries2
      • TCP RTO 算法:
        • 核心:采样 RTT数据,以及 RTT 的波动范围
        • SRTT:平滑的 RTT
        • DevRTR:平滑的 RTT 与 最新 RTT 的差距(波动)
        • 具体算法:
          • 经典算法:
            • SRTT = ( α * SRTT ) + ((1- α) * RTT)) // α [0.8, 0.9]
            • RTO = min [ UBOUND,  max [ LBOUND, (β * SRTT) ]] // β [1.3, 2.0]
            • UBOUND是最大的timeout时间、LBOUND是最小的timeout时
          • Karn / Partridge 算法:
            • 忽略重传,不把重传时的RTT 用作采样数据
            • 存在问题:重传时的网络情况被忽略导致出现问题无法发现
          • Jacobson / Karels 算法(最优解):
            • 解决:因为平滑导致RTT的波动导致网络波动问题被忽略
            • SRTT = SRTT + α (RTT – SRTT)
            • DevRTT = (1-β)DevRTT + β(|RTT-SRTT|)
            • RTO = µ  SRTT + ∂ DevRTT
            • 其中 α = 0.125,β = 0.25,μ = 1,∂ = 4
    • 2.快速重传(Fast Retransmit):
      • 定义:以数据驱动重传。接收端因为数据没有收到,会重复ACK可能丢失的包(累计确认),发送端连续收到3次就重传。 Img
      • 过程:
        • 接收端 Seq 1 到达,回复Ack 2
        • 接收端 Seq 2 没收到,Seq 3 到达了,于是还是 回复 Ack 2
        • 接收端 Seq 4 和 Seq 5 都到了,因为 Seq 2 还没有收到,还是回复 Ack 2
        • 发送端收到了三个 Ack 2 的确认,知道了 Seq 2 还没有收到,就会在定时器过期之前,重传丢失的 Seq 2(在开启SACK情况下)
        • 最后,接收端收到了 Seq 2,此时因为 Seq3,Seq4,Seq5 都收到了,于是回复 Ack 6
    • 重传发生时,应该重传哪些数据:
      • 问题:如果只重传ack的那个包,效率低;如果重传ack之后的所有包,资源浪费;
      • SACK 选择性确认:
        • 接收方额外返回已收到的数据区间,让发送端有选择的重传,而不必重传已收到的包 Img
      • D-SACK Duplicate SACK:
        • 接收方使用SACK告诉发送方自己重复接收到的数据,用于发送方优化
        • DSACK 可以让发送方知道:
          • 是发出去的包丢了,还是回来的ACK包丢了
          • 是不是自己的timeout太小了,导致重传
          • 网络上出现了先发的包后到的情况(又称reordering)
          • 网络上是否把包做了复制 Img

4.流量控制问题(照顾通信对象):

  • 问题描述: 无法改变接收者的接收能力,只能调整自身发送速度
  • 滑动窗口 SlidingImg -Window:
    • 作用:协调双端的发送能力和接收能力。让发送端提升发送效率,接收端避免压垮
    • 原理:
      • 滑动窗口是一个缓存空间,发送方在等到确认应答返回之前,必须在缓冲区中保留已发送的数据
      • 如果按期收到确认应答,此时数据就可以从缓存区清除
      • 如果未收到确认应答,此时需要重传数据。
    • Window字段:定义了接收端的接收能力
    • 发送方滑动窗口示意图:
      • 向右滑动 Img

      • 可用窗口计算:Img

        • SND.WND发送窗口的大小(接收方指定)
        • SND.UNA已发送但未收到确认的第一个字节的序列号
        • SND.NXT未发送但可发送范围的第一个字节的序列号
        • 可用窗口大小 = SND.WND -(SND.NXT - SND.UNA
    • 接收方滑动窗口示意图:
      • 计算:Img
        • RCV.WND接收窗口的大小
        • RCV.NXT期望从发送方发送来的下一个数据字节的序列号
    • 接收窗口的大小约等于发送窗口的大小
  • Zero Window 窗口关闭:
    • 作用:
      • 接收端通过ACK告诉发送端自己的可用窗口大小为0,让发送端不发数据
    • 如何结束不可发送状态:
      • 收到接收端的ACK告知window不为0
      • 发送端发ZWP (Zero Window Probe)包主动询问
        • 具体做法:
          1. 发送端主动发送3次ZWP包给接收端询问window
          2. 如果不为0,则发送端进行发送
          3. 如果一直为0,则发送端触发RST
  • Silly Window Syndrome 糊涂窗口综合症:
    • 描述:发送端/接收端因为可用window过小,发送小于MSS的数据,导致的效率低的问题
    • 解决方案:必须同时满足
      • 接收端当window如果小于 min(MSS,缓存空间/2),直接发送window=0关闭窗口
      • 发送端开启Nagle’s算法,将包堆积到一定大小后再发
        • Nagle’s算法:(再次发送满足以下任意一个)
            1. 可用Window>=MSS && 累积的数据 >= MSS
            1. 收到之前发的包的ACK回复
          • 注意事项:
            1. Nagle算法没有禁止小包发送,只是禁止了大量的小包发送
            2. 在发送大量小包的场景,需要关闭该算法 #### 5.拥塞控制问题(照顾通信环境):
  • 问题描述:无法改变网络状况,但可以控制自己发送的速度,自我牺牲(在发送端)
  • 拥塞窗口 cwnd
    • 发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的
    • 发送窗口 = min(拥塞窗口, 发送窗口)
  • 拥塞控制主要过程:
    • 慢启动门限 ssthresh (slow start threshold)
    1. 慢启动
      • 核心思想:刚刚加入网络的连接,一点一点地提速
      • 慢启动的算法:
        1. 初始化cwnd = 1(表明可以传一个MSS大小的数据)
        2. 每收到一个ACK,cwnd++;呈线性上升
        3. 每经过一个RTT,cwnd = cwnd *2 ;呈指数让升
        4. 当cwnd >= ssthresh时,就会进入拥塞避免算法(一般来说ssthresh的值是65535字节)
    2. 拥塞避免
      • 核心思想:避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。是一个线性上升的算法
      • 具体算法:
        1. 每收到一个ACK时,cwnd = cwnd + 1/cwnd
        2. 当每过一个RTT时,cwnd++
    3. 拥塞发生
      • 核心思想:通过强烈地震荡快速而小心得找到在拥塞时流量的平衡点
      • 基于重传类型分为两种
        • 如果出现超时重传
          1. 修改 sshthresh = cwnd /2
          2. cwnd 重置为初始值 1
          3. 进入慢启动过程` Img
        • 如果出现快速重传
          1. TCP Tahoe
            1. 修改 sshthresh = cwnd /2
            2. cwnd 重置为初始值 1
            3. 进入快速恢复算法
          2. TCP Reno:(更优)
            1. cwnd = cwnd/2
            2. sshthresh = cwnd
            3. 进入快速恢复算法
    4. 快速恢复
      • 核心思想:如果收到3个Duplicated Acks说明网络也不那么糟糕,从而开启恢复。快速恢复是对拥塞发生后直接进入慢启动的一个优化。
      • 快速重传和快速恢复算法一般同时使用
      • TCP Reno 快速恢复
        • 过程:
          1. cwnd = sshthresh + 3(3 的意思是3个确认包被收到了)
          2. 重传duplicated ACK指定的丢失的数据包
          3. 如果再收到 duplicated Acks,那么cwnd ++
          4. 当收到了重传数据的Ack,那么cwnd = sshthresh,进入拥塞避免的算法(收到新的Ack意味着恢复阶段结束,此时需要重新进入之前的拥塞避免状态)
        • 和超时重传不一样,没有重置cwnd=1,而是还在比较高的值,后续呈线性增长
        • 局限性:
          • 该算法依赖3个重复的acks,如果丢失多个包且未开启SACK,接收方只会重传duplicated ACK指定这一个包。其他丢失的包会触发超时重传,这样会将cwnd降低到1/2。
      • TCP New Reno 快速恢复
        • 对TCP Reno的改进,在没有SACK的支持下改进快速恢复算法
        • 过程:
          1. 当接收到3个Duplicated ACKs,进行指定包的快速重传
          2. 判断重传的包返回的Ack信息能否覆盖发送端已发送未确认的所有数据
            1. 如果不能覆盖则表示不止一个包丢失,继续重传滑动窗口内未被ack的包,直到所有包正常发出
            2. 如果可以覆盖,则快速恢复结束
        • 示意图: 图片
      • FACK 算法:
        • 作用:对重传过程做拥塞流控,防止重传很多数据包,导致本来就很忙的网络就更忙
      • 其他快速恢复算法:
        • TCP Vegas 拥塞控制算法
        • HSTCP(High Speed TCP) 算法
        • TCP BIC 算法
        • TCP WestWood算法

TCP 半连接、全连接队列:

  • 半连接队列(SYN 队列):
    • 服务端收到第一次握手后,会将 sock`加入到这个队列中
    • 队列内的sock都是 SYN_RECV 状态
  • 全连接队列(ACCEPT 队列):
    • 服务端收到第三次握手后,将半连接队列的 sock取出,放到全连接队列中
    • 队列里的sock都是 ESTABLISHED 状态
    • 这里面的连接,就等着服务端执行 accept() 后被取出
  • 图例: Img
  • 底层结构:
    • 全连接队列:链表
    • 半连接队列:哈希表、hashkey(sourceip+port)
    • Img
  • 查看队列大小:
    • 全连接:ss -lnt
    • 半连接:netstat -nt | grep -i ‘127.0.0.1:8080’ | grep -i ‘SYN_RECV’ | wc -l

TCP SYN攻击:

Img * 说明: - 当服务端接收到客户端的 SYN 报文时,会创建一个半连接的对象,然后将其加入到内核的「SYN 队列」; - 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文; - 服务端接收到 ACK 报文后,从「SYN 队列」取出一个半连接对象,然后创建一个新的连接对象放入到「Accept 队列」; - 应用通过调用 accpet() socket 接口,从「Accept 队列」取出连接对象。 * 攻击原理: * SYN 攻击方式最直接的表现就会把TCP半连接队列打满,后续正常SYN报文就会被丢弃,导致客户端无法和服务端建立连接 * 避免方式: - 调大 netdev_max_backlog - 增大 TCP 半连接队列´ - 开启 tcp_syncookies(资源分配后置) - 减少/合并 SYN+ACK 重传次数

TCP 延迟确认机制:

  • 背景:接收方做ACK相应时,没有携带数据报文,协议头却占40字节,传输效率低。
  • 作用:TCP 延迟确认,通过延迟发送Ack,将延迟时间内的多个Ack合并,提升传输效率
  • 具体策略:
    • 接收方有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
    • 没有响应数据要发送,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
    • 但是如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK
    • Img
  • Nagle 算法

TCP 累计确认机制:

Img * 解决:接收方的响应Ack是对之前已收到的包的累计确认。当接收方给发送方的某个ACK丢包时,发送方可以通过后续的收到的ACK知道数据已被接收方确认。 * 图中的 ACK 600 确认应答报文丢失,但发送方收到了ACK 700 确认应答,这意味着 700 之前的所有数据接收方都收到了。这个模式就叫累计确认或者累计应答 * 存在的问题: * 如果接收方接受的数据包发生丢失,接收方无法使用累计确认Ack来确认后续的数据包。在这种情况下,接收方需要使用选择性确认SACK来确认非连续的数据包

TCP 选择性确认机制:SACK

  • 问题:发给接收方的包出现丢失,如果只重传ack的那个包,效率低;如果重传ack之后的所有包,资源浪费;
  • 接收方额外返回已收到的数据区间,让发送端有选择的重传,而不必重传已收到的包 Img

TCP 快速建立连接:TCP Fast Open

  • 作用:
    • 减少建立TCP连接所需的往返时间,在首次的SYN包中就携带数据
  • 机制说明:
    1. 客户端首次连接时进行正常的三次握手。在这个过程中,服务器会生成特殊的cookie,在SYN-ACK包中一并发送给客户端
    2. 当客户端再次与服务器建立连接时,在初始的SYN包中携带这个cookie用户数据
    3. 服务器在收到这个SYN包,会检查cookie的有效性:
      1. 如果cookie有效,正常处理用户数据,并通过SYN-ACK包对数据和SYN做确认响应
      2. 无效,则丢弃用户数据,只对SYN做确认
    4. 客户端根据SYN-ACK包,决定是否需要重发用户数据
  • TFO 存在的问题:
    • 安全性问题
    • 重放攻击

TCP 保活机制:TCP Keepalive

  • 作用:用于主动检测一个TCP连接是否仍然有效的机制
  • 机制说明:
    • 默认不开启
    • 需要长连接,数据发送不频繁的场景使用更好
    • net.ipv4.tcp_keepalive_time=7200 //没有活动的时间
    • net.ipv4.tcp_keepalive_intvl=75 //探测报文发送频率
    • net.ipv4.tcp_keepalive_probes=9 //探测报文重试次数
  • 过程:
    • 当一个TCP连接在一段时间内没有任何活动,TCP会发送保活探测报文(Keepalive probe)来检查连接是否仍然有效
    • 探测失败会按照一定频率进行重试,如果多次探测报文都没有得到响应,TCP会认为连接已经断开,并关闭连接

TCP 挑战确认: TCP Challenge ACK

  • 作用:应对TCP序列号预测攻击
  • 机制:
    • 当TCP连接的某些行为引发怀疑时(如收到不符合预期的SYN包、ACK包),接收方会发送一个挑战ACK包。这个包包含当前正确的序列号和一个新的确认号,要求发送方确认它们
  • 具体流程:
    • 客户端和服务端处于连接状态
    • 客户端异常中断,服务端仍处于连接状态
    • 在服务端TCP保活机制未发现客户端断开前,客户端又使用相同的端口号发送SYN包给服务端进行握手
    • 连接状态的服务端,收到这个SYN握手报文,会回复一个Ack。Ack携带的是历史连接的Seq num和Ack num而不是SYN包的。这个包就叫 Challenge ACK
    • 客户端收到这个ACK,发现确认号并不是自己期望收到的,于是回复RST报文关闭连接 Img
  • 实际应用:
    • killcx:
      • 伪造客户端发送 SYN 报文,服务端收到后就会回复正确「序列号和确认号」的 ACK 报文(Challenge ACK)
      • 利用 确认号伪造 RST 报文给服务端
      • 利用 序列号伪造 RST 报文给客户端

TCP 总结:

  • 顺序问题,稳重不乱
  • 丢包问题,承诺靠谱
  • 连接维护,有始有终
  • 流量控制,把握分寸
  • 拥塞控制,知进知退

参考 * https://mp.weixin.qq.com/s/Tc09ovdNacOtnMOMeRc_uA

本文作者:navyum

本文链接:https://www.cnblogs.com/navyum/p/18509329

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   navyum  阅读(26)  评论(0编辑  收藏  举报
//自己上传到博客园的js
点击右上角即可分享
微信分享提示