找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

478

积分

0

好友

66

主题
发表于 5 天前 | 查看: 20| 回复: 0

Linux TCP 数据接收路径示意图

Linux内核网络协议栈 的实现中,TCP数据接收被设计为两条处理路径:快路径慢路径。这种设计旨在高效处理最常见的数据流场景。快路径专门用于处理预期的、理想情况下的数据段,力求以最小的开销完成数据处理。而慢路径则负责处理所有非标准或复杂情况,例如乱序报文、套接字缓冲区管理以及紧急数据等。

当TCP连接建立后,数据包的接收处理流程始于 tcp_v4_rcv,经过 tcp_v4_do_rcv,最终到达处理已连接状态包的核心函数 tcp_rcv_established。正是在这个函数中,系统会根据一个名为 pred_flags 的预测字段,来决定当前数据包应该走快路径还是慢路径。

一、首部预测字段 pred_flags

预测字段 pred_flagsstruct tcp_sock 结构体的一个成员,它是一个32位的大端序(网络字节序)整数。当其值为0时,表示关闭首部预测功能,强制使用慢速路径;非0值则为开启快速路径提供了前提。

struct tcp_sock {
  ......
  /* Header prediction flags * 0x5?10 << 16 + snd_wnd in net byte order */
  __be32 pred_flags;
  ......
}

这个32位的 pred_flags 需要与TCP报文头部的第3个32位字(即第13到16字节,包含数据偏移、标志位和窗口大小字段)进行匹配。其值通过 tcp_fast_path_on__tcp_fast_path_on 函数来设置。

static inline void tcp_fast_path_on(struct tcp_sock *tp)
{
  __tcp_fast_path_on(tp, tp->snd_wnd >> tp->rx_opt.snd_wscale);
}

static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{
  //pred_flags由三部分构成:数据偏移、ACK标志、对端接收窗口(即本端发送窗口)
  tp->pred_flags = htonl((tp->tcp_header_len << 26) |
             ntohl(TCP_FLAG_ACK) |
             snd_wnd);
}

pred_flags 由三个关键部分按位或运算组成:

  1. TCP首部长度 (tp->tcp_header_len << 26):tcp_header_len 是以字节为单位的TCP头部长度。将其左移26位,是为了将“数据偏移”这个4位字段放置到TCP头部第3个32位字中的正确比特位。
  2. ACK标志位 (ntohl(TCP_FLAG_ACK)):即常量 0x00100000,对应ACK标志位为1。
  3. 发送窗口大小 (snd_wnd):即对端通告的接收窗口大小。

内核注释清晰地解释了其结构:

/* pred_flags is 0xS?10 << 16 + snd_wnd
 * if header_prediction is to be made
 * ‘S’ will always be tp->tcp_header_len >> 2
 * ‘?’ will be 0 for the fast path, otherwise pred_flags is 0 to
 *  turn it off (when there are holes in the receive
 *  space for instance)
 * PSH flag is ignored.
 */

TCP头部字段与第3个32位字
图1:TCP头部字段分布与第3个32位字

pred_flags字段结构
图2:pred_flags字段的构成

二、pred_flags 的设定时机

预测字段的设定逻辑分散在连接生命周期的不同阶段,主要分为直接设置和条件检验后设置。

1. 直接调用 __tcp_fast_path_on

  • 时机:在客户端完成三次握手,调用 tcp_finish_connect 时。
  • 逻辑:如果对端没有启用窗口扩大选项(wscale),则直接开启快速路径。由于此时服务端可能还未收到最终的ACK,客户端暂不会收到数据,因此快速路径的开启是安全的预备操作。
    void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
    {
    ......
    if (!tp->rx_opt.snd_wscale) //对端没有开启wscale,则开启快速路径
    __tcp_fast_path_on(tp, tp->snd_wnd);
    else
    tp->pred_flags = 0;
    }

2. 直接调用 tcp_fast_path_on

  • 时机:服务器端在 TCP_SYN_RECV 状态处理SYN报文时。
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
    {
    ......
    switch (sk->sk_state) {
    case TCP_SYN_RECV:
    ......
    tcp_fast_path_on(tp);
    break;
    ......
    }

3. 条件检验后调用 tcp_fast_path_on
这是更常见的情况。检验由 tcp_fast_path_check 函数完成,它检查四个条件,全部满足才会设置 pred_flags

  • 条件1:乱序队列为空 (tp->out_of_order_queue 为空)。
  • 条件2:接收窗口还有剩余空间 (tp->rcv_wnd 非零)。
  • 条件3:接收内存没有耗尽 (已分配内存小于接收缓冲区大小)。
  • 条件4:没有待处理的紧急数据 (tp->urg_data 为假)。
static inline void tcp_fast_path_check(struct sock *sk)
{
 struct tcp_sock *tp = tcp_sk(sk);
 if (RB_EMPTY_ROOT(&tp->out_of_order_queue) && //乱序队列为空
     tp->rcv_wnd &&                           //接收窗口有空间
     atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf && //接收内存未耗尽
     !tp->urg_data)                          //无紧急数据
   tcp_fast_path_on(tp);
}

调用 tcp_fast_path_check 的关键时机包括:

  • 读完紧急数据后:紧急数据处理在慢路径完成,处理完毕后尝试恢复快路径。
  • 更新发送窗口后:当收到ACK并调用 tcp_ack_update_window 发现窗口变化时,必须重置并重新计算预测标记,以免后续报文因窗口不匹配误入慢路径。
  • 数据入接收队列后:在 tcp_data_queue 函数中,当接收到一个按序(in-order)报文并成功放入接收队列后,会尝试检查并开启快速路径。

三、快路径与慢路径的抉择与处理

是否执行快路径,pred_flags 匹配仅是第一个关卡。在 tcp_rcv_established 函数中,有一系列严格的检查,任何一项不满足都会落入慢路径。

内核文档列举了导致快路径被禁用的条件:

  • 本端通告过零窗口(需慢路径处理零窗口探测)。
  • 收到乱序报文。
  • 期望接收紧急数据。
  • 接收缓冲区空间不足。
  • 收到不符合 pred_flags 预测的TCP标志、窗口值或头部长度。
  • 连接是双向数据流(快路径仅支持纯发送或纯接收方)。
  • 存在意外的TCP选项。

快路径判断核心代码:

void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
    const struct tcphdr *th){
 ...
 // 关键的三重检查
 if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && // 1. 预测标记匹配
     TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&                // 2. 序列号是期望值
     !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {     // 3. ACK序号未超前
     // 进入快路径处理流程
     ...
     // 处理时间戳、校验和、将数据加入接收队列、发送ACK等
     ...
     return;
 }
slow_path:
 // 不符合快路径条件,转入慢路径
 ...
}

其中,tcp_flag_word(th) 获取TCP头部第3个32位字,TCP_HP_BITS 是一个掩码,用于屏蔽保留位和PSH标志(#define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH)))。

慢路径处理流程
落入 slow_path 后,内核会执行标准的、符合RFC 793的完整接收流程:

  1. 基础校验:检查报文长度、校验和。
  2. 标志位检查:对ACK、RST、SYN等标志进行合法性验证。
  3. 验证输入报文:调用 tcp_validate_incoming 进行序列号、窗口等更细致的检查。
  4. 处理ACK:调用 tcp_ack 处理确认信息。
  5. 处理紧急数据:调用 tcp_urg
  6. 队列化数据段:调用 tcp_data_queue 处理数据。
  7. 触发数据发送与ACK发送:检查是否需要发送数据或ACK。

TCP 数据接收快慢路径处理流程图
图3:TCP数据接收快慢路径处理流程示意图

通过理解快路径与慢路径的机制,开发者可以更好地优化应用程序与 TCP/IP协议栈 的交互,例如通过避免不必要的乱序发送、合理设置套接字缓冲区大小等方式,促使连接更多地运行在高效快路径上,从而提升网络吞吐性能。




上一篇:Android IoT僵尸网络Kimwolf技术分析:DNS over TLS与EtherHiding绕过检测
下一篇:Vue3插槽完整指南:从基础语法到高级组件化开发实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:21 , Processed in 0.153927 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表