重拾 Linux 网络(五):TCP/IP 协议栈回顾

工作中遇到的那些协议栈相关的知识点汇总(基于 google-netstack 的应用开发)

Posted by pandaychen on October 2, 2023

0x00 前言

本文汇总下笔者在近期工作中遇到的与 TCP/IP 协议栈相关的知识点汇总

Linux内核网络数据收发流程

1、Linux网络接收数据包流程如下,在Linux内核中当数据包到达网卡的时候,通过DMA方式将数据映射到内存,然后硬中断通知CPU有数据到来,调用硬中断处理函数,之后交给软中断去处理。通过ksoftirq调用软中断处理函数,收包的软中断处理函数是net_rx_action函数,主要将Ring Buffer缓冲区数据做成sk_buff送给上层协议栈进行处理,之后数据包经过层层解包最终将数据放入套接字缓冲区中,CPU通过将数据拷贝给应用程序

recv

2、Linux网络接收数据包流程如下,首先,应用层应用程序通过CPU将数据拷贝到套接字缓冲区,然后数据包经过层层处理封装好数据包,经过软中断处理函数,通过建立DMA映射,将数据放到发送缓冲区中,最后经过一个物理网卡发送出去

send

0x01 链路层

  • 目的 mac 地址:6 字节物理地址
  • 源 mac 地址:6 字节物理地址
  • 数据包协议类型: 为 0x8000 时为 IPv4 协议包,为 0x8060 时,后面为 ARP 协议包
  • 数据包:网卡输送能力上限 MTU(1500 字节), 对网络层 ip 协议对封装
0               1               2               3               4               5               6
0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          DESTINATION      MAC    6 字节目的 mac 地址                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          ORIGINALSRC      MAC    6 字节源 mac 地址                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       2 字节 网络层协议类型      |     46 - 1500 字节(ip 包头 + 传输层包头 + 应用层数据)           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

0x02 网络层

0x03 传输层 - TCP

TCP协议格式

TCP的状态机

TCP options

1、tcp timestamp option 在三次握手中有什么作用?

TCP Timestamp Option(时间戳选项)在三次握手中的作用是提供一个可靠的方法来测量网络延迟和计算往返时间(RTT)。在三次握手中,发送方在 SYN 包中添加一个时间戳选项,接收方在 SYN-ACK 包中将该选项返回给发送方。发送方可以使用这个时间戳来计算 RTT,并根据计算结果调整数据包的发送时间,从而优化网络性能。此外,时间戳选项还可以防止一些网络攻击,如 TCP 序列号预测攻击。可以通过 sysctl -w net.ipv4.tcp_timestamps=0 禁用 TCP 时间戳选项,禁用 TCP 时间戳选项可以减少网络带宽和 CPU 资源的消耗,因为 TCP 时间戳选项需要在每个数据包中添加额外的数据,好处是提升安全性

拥塞控制算法

TCP 滑动窗口机制

SEQ && ACK

  • 序列号(SEQ):在建立连接时由内核生成的随机数作为其初始值,通过 SYN 报文传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小,用来解决网络包乱序问题。累加方式为:序列号 = 上一次发送的序列号 + len(data),若上一次发送的报文是 SYN/FIN 报文,则改为上一次发送的序列号 + 1
  • 确认号(ACK):指下一次期望收到的数据的序列号,发送端收到接收方发来的 ACK 确认报文以后,可认为在这个序号以前的数据都已经被正常接收;用来解决丢包的问题。确认号 = 上一次收到的报文中的序列号 + len(data)。若收到的是 SYN/FIN 报文,则改为上一次收到的报文中的序列号 + 1

在 TCP 重组中,依赖于这两个关键参数

MTU And MSS

MTU: Maxitum Transmission Unit 最大传输单元 MSS: Maxitum Segment Size 最大分段大小

TCP重传机制

1、超时重传机制

2、快速重传机制

3、SACK 方法

4、Duplicate SACK:重复收到数据的问题

TCP - 流重组

SEQ 是为了保证 TCP 数据包的按顺序传输来设计的,可以有效的实现 TCP 数据的完整传输,特别是在数据传送过程中出现错误的时候可以有效的进行错误修正。在 TCP 会话的重新组合过程中需要按照数据包的序列号对接收到的数据包进行排序

1、BSD 的实现思路

BSD 以 SSH 连接的服务端(被动接收)出发,实现涉及两个队列,队列 A 存放顺序到来的数据包,队列 B 存放失序到来的数据包;假设队列 A 最后的缓存数据记录为 seq=100/len=100,下一个到达的数据包可能如下:

  • case1:顺序到来的数据包,数据包 2 的序列号为 seq2 = seq1+len1,由此数据包的 seq 可知,这个报文预期后续报文,将此报文追加到正常报文队列
  • case2:重复数据包,如数据包 3/4/5,都包含在队列 A 已经缓存的数据包 buffer 之中,应该被丢弃
  • case3:重叠数据包,如数据包 6 的前部分 seq=150len=50 与已缓存的数据包 buffer 重叠,而后部分 len=50 则是新数据,此时应该对这个报文作如下处理
    • 计算重复字节数:(seq1+len1)-seq2=100+100-150=50,即这个报文段前 50 个字节是重复的,需要丢弃
    • 截取报文段新数据,即只保留字节序号段 200~249
    • 重新设置这个报文段的 seq=200
    • 重新设置这个报文段的数据长度 len2=50
    • 将重新设置后报文段加入顺序队列 A
  • case4:提前到达的报文,如数据包 7seq2>seq1+len1,是提前到来的报文,此时应该将这个报文放置到失序报文队列 B 缓存起来,以备后续重组使用

这样直到客户端断开此 TCP 连接(FIN OR RST),此时将正常报文队列和失序报文队列中的数据合并起来,完成重组。取出正常报文队列最后一个报文的 seqlen,在失序报文队列中查找属于它的后续报文,该报文是否可以作为正常报文队列的后续报文,至此重组完成

2、cs144的tcp assembler

3、gvisor 的实现
参考 代码

gopacket 中的 tcp 重组实现

TCP:拥塞控制

0x04 传输层 - UDP

0x05 应用层

0x06 杂项

0x07 参考