第9章:网络协议栈

章节大纲

9.1 引言

  • Linux 网络栈架构概述
  • 从网卡到应用程序的数据流
  • 内核网络子系统的演进

9.2 sk_buff 与网络数据流

  • 9.2.1 sk_buff 结构体详解
  • 9.2.2 sk_buff 的生命周期管理
  • 9.2.3 数据包在各层的处理流程
  • 9.2.4 零拷贝与 scatter-gather I/O

9.3 TCP/IP 协议栈实现

  • 9.3.1 网络层:IP 协议处理
  • 9.3.2 传输层:TCP 状态机与拥塞控制
  • 9.3.3 套接字层:socket API 实现
  • 9.3.4 连接管理与 TIME_WAIT 优化

9.4 Netfilter 框架与 iptables

  • 9.4.1 Netfilter 钩子机制
  • 9.4.2 连接跟踪(conntrack)
  • 9.4.3 NAT 实现原理
  • 9.4.4 iptables 规则匹配引擎

9.5 高性能网络技术

  • 9.5.1 NAPI:中断缓解机制
  • 9.5.2 XDP:内核快速数据路径
  • 9.5.3 io_uring 网络编程
  • 9.5.4 eBPF 网络应用

9.6 本章小结

9.7 练习题

9.8 常见陷阱与错误

9.9 最佳实践检查清单


9.1 引言

Linux 网络协议栈是内核中最复杂、最精妙的子系统之一。从早期的简单 TCP/IP 实现,到今天支持每秒处理千万级数据包的高性能架构,Linux 网络栈经历了翻天覆地的变化。本章将深入剖析网络栈的核心数据结构、关键算法和性能优化技术,帮助读者理解从网卡硬件中断到应用程序 socket 调用的完整数据流。

学习目标

完成本章学习后,您将能够:

  1. 理解 sk_buff 核心数据结构:掌握网络数据包在内核中的表示和管理方式
  2. 分析 TCP/IP 协议栈实现:深入理解各层协议的处理流程和状态机制
  3. 掌握 Netfilter 框架:理解防火墙规则匹配、NAT 转换和连接跟踪原理
  4. 应用高性能网络技术:使用 NAPI、XDP、eBPF 等技术优化网络性能
  5. 诊断网络性能问题:定位和解决常见的网络瓶颈和延迟问题

架构概览

Linux 网络栈采用分层架构,每层都有明确的职责:

应用层     ┌─────────────────────────────────────┐
          │     用户空间应用程序(socket API)      │
          └─────────────────────────────────────┘
                           ↑↓
═══════════════════ 系统调用界面 ═══════════════════
                           ↑↓
套接字层   ┌─────────────────────────────────────┐
          │    BSD Socket 层(struct socket)     │
          │         协议无关的接口抽象             │
          └─────────────────────────────────────┘
                           ↑↓
传输层     ┌─────────────────────────────────────┐
          │    TCP / UDP / SCTP / DCCP          │
          │    拥塞控制、流量控制、可靠传输         │
          └─────────────────────────────────────┘
                           ↑↓
网络层     ┌─────────────────────────────────────┐
          │         IPv4 / IPv6 / ICMP           │
          │      路由选择、分片重组、转发           │
          └─────────────────────────────────────┘
                           ↑↓
Netfilter ┌─────────────────────────────────────┐
          │   防火墙钩子、NAT、连接跟踪            │
          └─────────────────────────────────────┘
                           ↑↓
链路层     ┌─────────────────────────────────────┐
          │    ARP / Neighbor Discovery          │
          │      网络设备抽象(net_device)        │
          └─────────────────────────────────────┘
                           ↑↓
驱动层     ┌─────────────────────────────────────┐
          │      网卡驱动(e1000、ixgbe等)       │
          │         DMA、中断处理、队列管理        │
          └─────────────────────────────────────┘

关键创新

Linux 网络栈的几个重要创新点:

  1. sk_buff 设计:高效的零拷贝数据结构,支持数据包在各层间传递而无需复制
  2. NAPI 机制:自适应的中断缓解技术,在高负载时切换到轮询模式
  3. RCU 在网络栈的应用:无锁的路由表和连接表查找
  4. XDP 框架:在驱动层提供可编程的快速数据路径
  5. eBPF 网络程序:安全的内核态可编程网络处理

性能演进

网络栈性能的几个里程碑:

  • 2.4 内核时代:基本的 TCP/IP 实现,单核性能约 100Mbps
  • 2.6 内核引入 NAPI:中断缓解技术,千兆网卡成为可能
  • 3.x 内核多队列支持:利用多核并行处理,达到 10Gbps
  • 4.x 内核 XDP 出现:内核旁路技术,单核 14Mpps
  • 5.x 内核 io_uring:异步 I/O 革命,接近硬件极限性能

9.3 TCP/IP 协议栈实现

Linux 的 TCP/IP 协议栈是一个完整的、符合标准的实现,支持 IPv4、IPv6、TCP、UDP 以及众多扩展协议。本节深入分析协议栈的核心实现,包括网络层的路由机制、传输层的状态机管理、套接字层的 API 实现,以及连接管理的优化策略。

9.3.1 网络层:IP 协议处理

IP 协议架构

Linux 的 IP 层负责数据包的路由、转发、分片和重组。其核心数据结构和处理流程经过精心设计,支持高效的查找和转发:

接收处理流程:
netif_receive_skb() → ip_rcv() → ip_rcv_finish() → ip_local_deliver() / ip_forward()
                                        ↓
                                  路由查找(FIB)
                                        ↓
                            本地接收 / 转发 / 丢弃

发送处理流程:
ip_queue_xmit() → ip_output() → ip_finish_output() → ip_finish_output2()
        ↓              ↓                ↓                    ↓
    路由查找      Netfilter钩子     分片处理            邻居子系统

路由子系统

Linux 使用 FIB(Forwarding Information Base)和路由缓存实现高效的路由查找:

/* 路由表项 */
struct fib_info {
    struct hlist_node    fib_hash;      /* 哈希链表 */
    struct list_head     fib_lhash;     /* 链表头 */
    struct net          *fib_net;       /* 网络命名空间 */
    int                  fib_treeref;   /* 引用计数 */
    atomic_t             fib_clntref;   /* 客户端引用 */
    unsigned int         fib_flags;     /* 标志位 */
    unsigned char        fib_dead;      /* 删除标记 */
    unsigned char        fib_protocol;  /* 路由协议 */
    unsigned char        fib_scope;     /* 路由范围 */
    unsigned char        fib_type;      /* 路由类型 */
    __be32               fib_prefsrc;   /* 首选源地址 */
    u32                  fib_priority;  /* 路由优先级 */
    struct dst_metrics  *fib_metrics;   /* 路由度量 */
    int                  fib_nhs;       /* 下一跳数量 */
    struct fib_nh        fib_nh[0];     /* 下一跳数组 */
};

/* 路由查找核心函数 */
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, 
                                __be32 saddr, u8 tos, struct net_device *dev)
{
    struct fib_result res;
    struct flowi4 fl4;

    /* 构造查找键 */
    fl4.daddr = daddr;
    fl4.saddr = saddr;
    fl4.flowi4_tos = tos;
    fl4.flowi4_scope = RT_SCOPE_UNIVERSE;

    /* FIB 查找 */
    err = fib_lookup(net, &fl4, &res, 0);

    /* 创建路由缓存项 */
    rth = rt_dst_alloc(dev, flags);
    rth->dst.input = ip_local_deliver;
    rth->dst.output = ip_output;
}

路由缓存优化

Linux 使用多级缓存加速路由查找:

  1. DST 缓存:每个 sk_buff 携带 dst_entry 指针
  2. FIB Trie:基于 LC-Trie 的路由表查找,O(W) 复杂度
  3. 路由异常缓存:处理 PMTU、重定向等异常
/* LC-Trie 节点 */
struct tnode {
    struct key_vector kv[1];
    /* LC-Trie 使用路径压缩和级别压缩优化 */
};

/* 查找算法 */
static struct key_vector *fib_find_node(struct trie *t, 
                                         struct key_vector **tp, u32 key)
{
    struct key_vector *pn, *n = t->kv;
    unsigned long index;

    do {
        pn = n;
        index = get_index(key, pn);
        n = get_child_rcu(pn, index);

        if (!n)
            break;

        /* 路径压缩:跳过只有一个子节点的内部节点 */
        if (IS_TNODE(n) && (n->bits < KEYLENGTH)) {
            if (tkey_sub_equals(n->key, n->pos, n->bits, key))
                continue;
            break;
        }
    } while (IS_TNODE(n));

    return n;
}

IP 分片与重组

当数据包大小超过 MTU 时,IP 层负责分片和重组:

/* IP 分片 */
static int ip_fragment(struct net *net, struct sock *sk, 
                       struct sk_buff *skb,
                       unsigned int mtu,
                       int (*output)(struct net *, struct sock *, 
                                     struct sk_buff *))
{
    struct iphdr *iph = ip_hdr(skb);
    unsigned int hlen = iph->ihl * 4;
    unsigned int ll_rs = LL_RESERVED_SPACE(skb->dev);
    unsigned int mtu = dst_mtu(&rt->dst);

    /* 计算每个分片的大小 */
    unsigned int frag_size = mtu - hlen;
    frag_size &= ~7;  /* 8 字节对齐 */

    /* 创建分片 */
    while (left > 0) {
        struct sk_buff *skb2;

        /* 分配新的 sk_buff */
        skb2 = alloc_skb(len + hlen + ll_rs, GFP_ATOMIC);

        /* 复制 IP 头部 */
        ip_copy_metadata(skb2, skb);

        /* 设置分片标志和偏移 */
        iph2 = ip_hdr(skb2);
        iph2->frag_off = htons((offset >> 3));
        if (left > 0)
            iph2->frag_off |= htons(IP_MF);

        /* 发送分片 */
        err = output(net, sk, skb2);
    }
}

/* IP 重组 */
struct ipq {
    struct inet_frag_queue q;
    u8         ecn;        /* ECN 标记 */
    u16        max_df_size; /* 最大不分片大小 */
    int        iif;        /* 输入接口 */
    unsigned int rid;      /* 路由 ID */
    struct inet_peer *peer;
};

static int ip_frag_reasm(struct ipq *qp, struct sk_buff *skb,
                         struct sk_buff *prev_tail, struct net_device *dev)
{
    struct net *net = qp->q.fqdir->net;
    struct iphdr *iph;
    void *reasm_data;
    int len, err;

    /* 检查所有分片是否到齐 */
    if (!(qp->q.flags & INET_FRAG_COMPLETE))
        goto out_fail;

    /* 重组分片 */
    reasm_data = inet_frag_reasm_prepare(&qp->q, skb, prev_tail);

    /* 合并所有分片 */
    inet_frag_reasm_finish(&qp->q, skb, reasm_data,
                           ip_frag_ecn_table[qp->ecn]);

    /* 更新 IP 头部 */
    iph = ip_hdr(skb);
    iph->tot_len = htons(len);
    iph->frag_off = 0;
}

9.3.2 传输层:TCP 状态机与拥塞控制

TCP 连接管理

TCP 使用有限状态机管理连接生命周期:

/* TCP 状态枚举 */
enum {
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING,
    TCP_NEW_SYN_RECV,
};

/* TCP 控制块 */
struct tcp_sock {
    struct inet_connection_sock inet_conn;

    /* 序列号管理 */
    u32    rcv_nxt;      /* 期望接收的下一个序列号 */
    u32    snd_nxt;      /* 下一个要发送的序列号 */
    u32    snd_una;      /* 最早未确认的序列号 */
    u32    snd_wnd;      /* 发送窗口大小 */
    u32    rcv_wnd;      /* 接收窗口大小 */

    /* 拥塞控制 */
    u32    snd_cwnd;     /* 拥塞窗口 */
    u32    snd_ssthresh; /* 慢启动阈值 */
    u32    prior_cwnd;   /* 之前的拥塞窗口 */

    /* RTT 估算 */
    struct minmax rtt_min;  /* 最小 RTT */
    u32    srtt_us;      /* 平滑 RTT (微秒) */
    u32    mdev_us;      /* RTT 平均偏差 */
    u32    rttvar_us;    /* RTT 方差 */
    u32    rto;          /* 重传超时 */

    /* 拥塞控制算法 */
    const struct tcp_congestion_ops *icsk_ca_ops;

    /* 重传队列 */
    struct sk_buff_head out_of_order_queue;
    struct tcp_sack_block selective_acks[4];

    /* 时间戳 */
    u32    tsoffset;     /* 时间戳偏移 */
    u32    ts_recent;    /* 最近接收的时间戳 */
    u32    ts_recent_stamp; /* ts_recent 的时间 */
};

三次握手实现

TCP 三次握手的内核实现涉及 SYN cookie、Fast Open 等优化:

/* 服务器端:处理 SYN */
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_options_received tmp_opt;
    struct tcp_sock *tp = tcp_sk(sk);
    struct request_sock *req;

    /* SYN flood 防护:SYN cookie */
    if (sk_acceptq_is_full(sk)) {
        want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
        if (!want_cookie)
            goto drop;
    }

    /* 分配请求套接字 */
    req = inet_reqsk_alloc(&tcp_request_sock_ops, sk, !want_cookie);

    /* 解析 TCP 选项 */
    tcp_parse_options(skb, &tmp_opt, 0, NULL);

    /* 初始化序列号 */
    tcp_openreq_init(req, &tmp_opt, skb, sk);

    /* 生成 SYN cookie(如果需要) */
    if (want_cookie) {
        isn = cookie_init_sequence(req, skb, &req->mss);
        req->cookie_ts = tmp_opt.tstamp_ok;
    } else {
        /* 正常的 ISN 生成 */
        isn = tcp_v4_init_seq(skb);
    }

    /* 发送 SYN+ACK */
    err = tcp_v4_send_synack(sk, dst, &fl4, req, skb);

    /* 加入半连接队列 */
    inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
}

/* 客户端:发送 SYN */
int tcp_connect(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *buff;

    /* 分配 SYN 包 */
    buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);

    /* 初始化 TCP 头部 */
    tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);

    /* TCP Fast Open */
    if (fastopen) {
        tcp_fastopen_init_key_once();
        tp->fastopen_req = kzalloc(sizeof(*tp->fastopen_req), 
                                    sk->sk_allocation);
    }

    /* 设置初始拥塞窗口 */
    tp->snd_cwnd = TCP_INIT_CWND;

    /* 启动重传定时器 */
    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
                              inet_csk(sk)->icsk_rto, TCP_RTO_MAX);

    /* 发送 SYN */
    return tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
}

拥塞控制算法

Linux 支持可插拔的拥塞控制算法,默认使用 CUBIC:

/* 拥塞控制操作接口 */
struct tcp_congestion_ops {
    struct list_head    list;
    u32 key;
    u32 flags;

    /* 必需的回调函数 */
    void (*init)(struct sock *sk);
    void (*release)(struct sock *sk);

    /* 拥塞窗口调整 */
    u32 (*ssthresh)(struct sock *sk);
    void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);

    /* 状态变化 */
    void (*set_state)(struct sock *sk, u8 new_state);
    void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);

    /* 丢包处理 */
    void (*in_ack_event)(struct sock *sk, u32 flags);
    u32 (*undo_cwnd)(struct sock *sk);
    void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);

    /* BBR 使用的回调 */
    u32 (*min_tso_segs)(struct sock *sk);
    void (*cong_control)(struct sock *sk, const struct rate_sample *rs);

    char name[TCP_CA_NAME_MAX];
};

/* CUBIC 算法核心 */
static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_csk_ca(sk);

    if (!tcp_is_cwnd_limited(sk))
        return;

    if (tcp_in_slow_start(tp)) {
        /* 慢启动:指数增长 */
        acked = tcp_slow_start(tp, acked);
        if (!acked)
            return;
    }

    /* 拥塞避免:CUBIC 函数 */
    bictcp_update(ca, tp->snd_cwnd, acked);
    tcp_cong_avoid_ai(tp, ca->cnt, acked);
}

/* CUBIC 更新函数 */
static inline void bictcp_update(struct bictcp *ca, u32 cwnd, u32 acked)
{
    u32 delta, bic_target, max_cnt;
    u64 offs, t;

    ca->ack_cnt += acked;

    if (ca->last_cwnd == cwnd &&
        (s32)(tcp_jiffies32 - ca->last_time) <= HZ / 32)
        return;

    /* CUBIC 函数:W(t) = C * (t - K)^3 + Wmax */
    t = (s32)(tcp_jiffies32 - ca->epoch_start);
    t += ca->delay_min >> 3;

    /* 计算 K = cubic_root(Wmax * beta / C) */
    K = ca->K;

    offs = ca->ack_cnt / delta;
    if (t < K)
        bic_target = ca->origin_point - delta;
    else
        bic_target = ca->origin_point + delta;

    /* 更新拥塞窗口 */
    if (bic_target > cwnd) {
        ca->cnt = cwnd / (bic_target - cwnd);
    } else {
        ca->cnt = 100 * cwnd;
    }
}

快速重传与恢复

TCP 使用 SACK(Selective Acknowledgment)实现高效的丢包恢复:

/* 快速重传判定 */
static bool tcp_time_to_recover(struct sock *sk, int flag)
{
    struct tcp_sock *tp = tcp_sk(sk);

    /* 条件1:收到3个重复 ACK */
    if (tp->fackets_out > tp->reordering)
        return true;

    /* 条件2:SACK 检测到丢包 */
    if (tcp_is_sack(tp) && tp->lost_out > 0)
        return true;

    /* 条件3:FACK 检测 */
    if (tcp_is_fack(tp) && tp->fackets_out > tp->reordering)
        return true;

    return false;
}

/* 进入快速恢复 */
void tcp_enter_recovery(struct sock *sk, bool ece_ack)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int mib_idx;

    /* 保存当前状态 */
    tp->prior_ssthresh = 0;
    tcp_init_undo(tp);

    /* 调整拥塞窗口 */
    tp->snd_ssthresh = tcp_current_ssthresh(sk);
    tp->snd_cwnd = tp->snd_ssthresh;
    tp->snd_cwnd_cnt = 0;
    tp->prior_cwnd = tp->snd_cwnd;
    tp->prr_delivered = 0;
    tp->prr_out = 0;

    /* 设置恢复点 */
    tp->recovery = tp->snd_nxt;

    /* 进入恢复状态 */
    tcp_set_ca_state(sk, TCP_CA_Recovery);
}

9.3.3 套接字层:socket API 实现

socket 结构体系

Linux 套接字层提供统一的编程接口,屏蔽底层协议差异:

/* 通用套接字结构 */
struct socket {
    socket_state        state;      /* 套接字状态 */
    short               type;       /* SOCK_STREAM, SOCK_DGRAM 等 */
    unsigned long       flags;      /* 套接字标志 */

    struct file        *file;       /* 关联的文件对象 */
    struct sock        *sk;         /* 网络层套接字 */
    const struct proto_ops *ops;    /* 协议操作函数表 */

    struct socket_wq    wq;         /* 等待队列 */
};

/* 网络层套接字 */
struct sock {
    struct sock_common  __sk_common;

    /* 接收队列 */
    struct sk_buff_head sk_receive_queue;
    struct sk_buff_head sk_write_queue;

    /* 内存管理 */
    atomic_t            sk_rmem_alloc;  /* 接收缓冲区大小 */
    atomic_t            sk_wmem_alloc;  /* 发送缓冲区大小 */
    int                 sk_sndbuf;      /* 发送缓冲区限制 */
    int                 sk_rcvbuf;      /* 接收缓冲区限制 */

    /* 回调函数 */
    void (*sk_state_change)(struct sock *sk);
    void (*sk_data_ready)(struct sock *sk);
    void (*sk_write_space)(struct sock *sk);
    void (*sk_error_report)(struct sock *sk);

    /* 协议相关 */
    struct proto       *sk_prot;
    const struct proto_ops *sk_prot_creator;
};

/* 协议操作函数表 */
struct proto_ops {
    int (*bind)(struct socket *sock, struct sockaddr *addr, int len);
    int (*connect)(struct socket *sock, struct sockaddr *addr, int len, int flags);
    int (*accept)(struct socket *sock, struct socket *newsock, int flags);
    int (*listen)(struct socket *sock, int len);
    int (*sendmsg)(struct socket *sock, struct msghdr *m, size_t len);
    int (*recvmsg)(struct socket *sock, struct msghdr *m, size_t len, int flags);
    /* ... */
};

系统调用实现

套接字系统调用的内核实现:

/* socket() 系统调用 */
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    struct socket *sock;
    int retval;

    /* 创建套接字 */
    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        return retval;

    /* 分配文件描述符 */
    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    if (retval < 0)
        goto out_release;

    return retval;
}

/* bind() 实现 */
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err;

    /* 获取套接字 */
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        return err;

    /* 复制地址 */
    err = move_addr_to_kernel(umyaddr, addrlen, &address);
    if (err >= 0)
        err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);

    fput_light(sock->file, fput_needed);
    return err;
}

/* send() 数据发送路径 */
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
                unsigned int, flags, struct sockaddr __user *, addr,
                int, addr_len)
{
    struct socket *sock;
    struct msghdr msg;
    struct iovec iov;

    /* 构造消息结构 */
    iov.iov_base = buff;
    iov.iov_len = len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_flags = flags;

    /* 设置目标地址 */
    if (addr) {
        err = move_addr_to_kernel(addr, addr_len, &address);
        msg.msg_name = (struct sockaddr *)&address;
        msg.msg_namelen = addr_len;
    }

    /* 调用协议层发送 */
    err = sock->ops->sendmsg(sock, &msg, len);

    return err;
}

异步 I/O 支持

Linux 5.1 引入的 io_uring 为网络编程带来革命性改进:

/* io_uring 网络操作 */
static int io_send(struct io_kiocb *req, unsigned int issue_flags)
{
    struct io_sr_msg *sr = &req->sr_msg;
    struct socket *sock;
    struct msghdr msg;
    int ret;

    sock = sock_from_file(req->file);
    if (unlikely(!sock))
        return -ENOTSOCK;

    /* 构造消息 */
    ret = io_setup_async_msg(req, &msg, issue_flags);
    if (ret)
        return ret;

    /* 非阻塞发送 */
    msg.msg_flags = sr->msg_flags | MSG_NOSIGNAL;
    if (issue_flags & IO_URING_F_NONBLOCK)
        msg.msg_flags |= MSG_DONTWAIT;

    ret = sock_sendmsg(sock, &msg);

    /* 处理 EAGAIN */
    if (ret == -EAGAIN && (issue_flags & IO_URING_F_NONBLOCK))
        return -EAGAIN;

    /* 完成请求 */
    io_req_complete(req, ret);
    return 0;
}

9.3.4 连接管理与 TIME_WAIT 优化

TIME_WAIT 状态管理

TIME_WAIT 状态确保延迟数据包不会干扰新连接,但大量 TIME_WAIT 会消耗资源:

/* TIME_WAIT 套接字结构(精简版) */
struct inet_timewait_sock {
    struct sock_common  __tw_common;

    int                 tw_timeout;     /* TIME_WAIT 超时时间 */
    volatile unsigned char tw_substate; /* 子状态 */
    unsigned char       tw_rcv_wscale;  /* 接收窗口缩放 */

    /* 这些字段从 struct tcp_sock 复制 */
    __be16              tw_sport;       /* 源端口 */
    __be16              tw_dport;       /* 目的端口 */
    __u32               tw_rcv_nxt;     /* 期望接收序列号 */
    __u32               tw_snd_nxt;     /* 下一个发送序列号 */
    __u32               tw_rcv_wnd;     /* 接收窗口 */
    __u32               tw_ts_recent;   /* 时间戳 */

    struct timer_list   tw_timer;       /* TIME_WAIT 定时器 */
    struct inet_bind_bucket *tw_tb;     /* 绑定哈希桶 */
};

/* 进入 TIME_WAIT 状态 */
void tcp_time_wait(struct sock *sk, int state, int timeo)
{
    struct inet_timewait_sock *tw;
    struct tcp_sock *tp = tcp_sk(sk);

    /* 分配 timewait 套接字 */
    tw = inet_twsk_alloc(sk, state);
    if (tw) {
        /* 复制必要字段 */
        tw->tw_rcv_wscale = tp->rx_opt.rcv_wscale;
        tw->tw_rcv_nxt = tp->rcv_nxt;
        tw->tw_snd_nxt = tp->snd_nxt;
        tw->tw_rcv_wnd = tcp_receive_window(tp);
        tw->tw_ts_recent = tp->rx_opt.ts_recent;
        tw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp;

        /* 设置 TIME_WAIT 定时器 */
        if (timeo < 0)
            timeo = TCP_TIMEWAIT_LEN;  /* 默认 60 秒 */

        inet_twsk_schedule(tw, timeo);

        /* 将原始套接字转换为 timewait */
        inet_twsk_hashdance(tw, sk, &tcp_hashinfo);
    }

    /* 销毁原始套接字 */
    tcp_done(sk);
}

TIME_WAIT 优化策略

Linux 提供多种机制优化 TIME_WAIT:

/* 1. TIME_WAIT 重用(tw_reuse) */
static int tcp_tw_reuse_check(const struct inet_timewait_sock *tw,
                               const struct tcp_options_received *rx_opt)
{
    /* 检查时间戳,确保不会接收旧数据包 */
    if (rx_opt->ts_recent &&
        get_seconds() - tw->tw_ts_recent_stamp > 1)
        return 1;

    return 0;
}

/* 2. TIME_WAIT 回收(tw_recycle)- 已废弃 */
/* 由于 NAT 环境问题,4.12 内核已移除 */

/* 3. TIME_WAIT 暗杀(tw_kill) */
static void inet_twsk_kill(struct inet_timewait_sock *tw)
{
    struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
    spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);

    spin_lock(lock);
    if (hlist_nulls_unhashed(&tw->tw_node)) {
        spin_unlock(lock);
        return;
    }

    /* 从哈希表移除 */
    hlist_nulls_del_rcu(&tw->tw_node);
    sk_nulls_node_init(&tw->tw_node);
    spin_unlock(lock);

    /* 释放端口 */
    inet_twsk_bind_unhash(tw, hashinfo);

    /* 释放内存 */
    inet_twsk_put(tw);
}

/* 4. SO_LINGER 选项 */
struct linger {
    int l_onoff;    /* 0 = off, 非0 = on */
    int l_linger;   /* 延迟时间(秒) */
};

/* 设置 SO_LINGER 跳过 TIME_WAIT */
if (sk->sk_linger && !sk->sk_lingertime) {
    /* l_onoff = 1, l_linger = 0: 立即关闭,发送 RST */
    tcp_send_active_reset(sk, gfp_any());
    tcp_done(sk);
} else {
    /* 正常关闭流程 */
    tcp_close_state(sk);
}

连接表管理

Linux 使用哈希表管理大量并发连接:

/* TCP 连接哈希表 */
struct inet_hashinfo tcp_hashinfo = {
    .ehash = NULL,          /* established 哈希表 */
    .ehash_locks = NULL,    /* 哈希锁数组 */
    .ehash_mask = 0,        /* 哈希掩码 */
    .ehash_locks_mask = 0,

    .bhash = NULL,          /* bind 哈希表 */
    .bhash_size = 0,

    .lhash2 = NULL,         /* listen 哈希表 */
    .lhash2_mask = 0,
};

/* 连接查找 */
struct sock *__inet_lookup_established(struct net *net,
                                        struct inet_hashinfo *hashinfo,
                                        const __be32 saddr, const __be16 sport,
                                        const __be32 daddr, const u16 hnum,
                                        const int dif)
{
    INET_ADDR_COOKIE(acookie, saddr, daddr);
    const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
    struct sock *sk;
    const struct hlist_nulls_node *node;

    /* 计算哈希值 */
    unsigned int hash = inet_ehashfn(net, daddr, hnum, saddr, sport);
    unsigned int slot = hash & hashinfo->ehash_mask;
    struct inet_ehash_bucket *head = &hashinfo->ehash[slot];

    /* RCU 读锁保护 */
    rcu_read_lock();
begin:
    sk_nulls_for_each_rcu(sk, node, &head->chain) {
        if (sk->sk_hash != hash)
            continue;

        /* 快速路径:cookie 匹配 */
        if (likely(INET_MATCH(sk, net, acookie, saddr, daddr, 
                              ports, dif))) {
            if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
                goto out;

            /* 再次检查(防止并发删除) */
            if (unlikely(!INET_MATCH(sk, net, acookie, saddr, daddr,
                                     ports, dif))) {
                sock_gen_put(sk);
                goto begin;
            }
            goto found;
        }
    }

    /* 检查是否需要重试(NULLS 保护) */
    if (get_nulls_value(node) != slot)
        goto begin;

out:
    sk = NULL;
found:
    rcu_read_unlock();
    return sk;
}

端口管理与复用

Linux 支持 SO_REUSEADDR 和 SO_REUSEPORT 实现端口复用:

/* SO_REUSEPORT 实现负载均衡 */
struct sock *inet_lookup_listener(struct net *net,
                                   struct inet_hashinfo *hashinfo,
                                   struct sk_buff *skb, int doff,
                                   const __be32 saddr, __be16 sport,
                                   const __be32 daddr, const unsigned short hnum,
                                   const int dif, const int sdif)
{
    struct sock *sk, *result = NULL;
    struct inet_listen_hashbucket *ilb;
    unsigned int hash = inet_lhashfn(net, hnum);

    ilb = &hashinfo->listening_hash[hash];

    /* 查找监听套接字 */
    sk_for_each_rcu(sk, &ilb->head) {
        score = compute_score(sk, net, hnum, daddr, dif, sdif);

        if (score > hiscore) {
            hiscore = score;
            result = sk;

            /* SO_REUSEPORT 负载均衡 */
            if (sk->sk_reuseport) {
                phash = inet_ehashfn(net, daddr, hnum, saddr, sport);

                /* 使用包哈希选择套接字 */
                result = reuseport_select_sock(sk, phash, skb, doff);
            }
        }
    }

    return result;
}

/* REUSEPORT 组管理 */
struct sock_reuseport {
    struct rcu_head     rcu;

    u16                 max_socks;      /* 数组大小 */
    u16                 num_socks;      /* 当前套接字数 */
    unsigned int        synq_overflow_ts; /* SYN 队列溢出时间戳 */
    unsigned int        reuseport_id;   /* ID */
    unsigned int        bind_inany:1;   /* 绑定任意地址 */
    unsigned int        has_conns:1;    /* 有已建立连接 */
    struct bpf_prog __rcu *prog;        /* eBPF 程序 */
    struct sock        *socks[0];       /* 套接字数组 */
};

9.2 sk_buff 与网络数据流

sk_buff(socket buffer)是 Linux 网络栈的核心数据结构,每个网络数据包在内核中都由一个 sk_buff 结构表示。这个精心设计的数据结构支持高效的零拷贝操作、协议头部的动态添加删除,以及数据包的分片与重组。

9.2.1 sk_buff 结构体详解

sk_buff 结构体包含了数据包的所有元信息和指向实际数据的指针:

struct sk_buff {
    /* 链表管理 */
    struct sk_buff    *next;
    struct sk_buff    *prev;

    /* 时间戳 */
    ktime_t           tstamp;

    /* 关联的网络设备 */
    struct net_device *dev;

    /* 关联的 socket */
    struct sock       *sk;

    /* 数据指针 

     * head: 缓冲区起始地址
     * data: 有效数据起始地址  
     * tail: 有效数据结束地址
     * end:  缓冲区结束地址
     */
    unsigned char     *head,
                      *data,
                      *tail,
                      *end;

    /* 传输层头部指针 */
    union {
        struct tcphdr  *th;
        struct udphdr  *uh;
        struct icmphdr *icmph;
        unsigned char  *raw;
    } h;

    /* 网络层头部指针 */
    union {
        struct iphdr   *iph;
        struct ipv6hdr *ipv6h;
        struct arphdr  *arph;
        unsigned char  *raw;
    } nh;

    /* 链路层头部指针 */
    union {
        struct ethhdr  *ethernet;
        unsigned char  *raw;
    } mac;

    /* 路由信息 */
    struct dst_entry *dst;

    /* 控制信息 */
    __u32             priority;
    __u16             protocol;
    __u8              pkt_type;

    /* 引用计数 */
    atomic_t          users;

    /* 分片信息 */
    struct skb_shared_info *shinfo;
};

关键指针关系

sk_buff 中最重要的是四个数据指针的关系:

        head                data               tail                end
                                                                  
    ┌────┬──────────────┬────────────────────┬─────────────────┬────┐
           headroom        packet data        tailroom         
    └────┴──────────────┴────────────────────┴─────────────────┴────┘
         ←───────────────── skb->len ──────────→
         ←──────────────────── skb->data_len ────────────────────→
         ←────────────────────── skb->truesize ──────────────────────→

这种设计允许在数据包前后高效地添加或删除协议头部:

  • skb_push():在数据前添加头部(data 指针前移)
  • skb_pull():移除数据前的头部(data 指针后移)
  • skb_put():在数据后添加内容(tail 指针后移)
  • skb_trim():截断数据末尾(tail 指针前移)

9.2.2 sk_buff 的生命周期管理

分配与初始化

网络数据包的 sk_buff 通常在以下场景分配:

  1. 接收路径:网卡驱动在 DMA 缓冲区准备时预分配
  2. 发送路径:应用程序发送数据时动态分配
  3. 转发路径:可能复用原 sk_buff 或克隆
/* 分配 sk_buff */
struct sk_buff *skb = alloc_skb(size, GFP_ATOMIC);

/* 为网络设备分配(包含 NET_SKB_PAD 对齐) */
struct sk_buff *skb = netdev_alloc_skb(dev, size);

/* 分配并预留头部空间 */
struct sk_buff *skb = alloc_skb(size + headroom, GFP_KERNEL);
skb_reserve(skb, headroom);

引用计数管理

sk_buff 使用引用计数管理生命周期:

/* 增加引用 */
skb_get(skb);  // atomic_inc(&skb->users)

/* 减少引用,到 0 时释放 */
kfree_skb(skb);  // 错误路径释放
consume_skb(skb); // 正常路径释放

克隆与复制

为了避免数据复制,sk_buff 支持高效的克隆:

/* 克隆:共享数据,复制元数据 */
struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);

/* 完整复制:数据和元数据都复制 */
struct sk_buff *copy = skb_copy(skb, GFP_ATOMIC);

/* COW 复制:必要时才复制数据 */
if (skb_shared(skb))
    skb = skb_unshare(skb, GFP_ATOMIC);

9.2.3 数据包在各层的处理流程

接收路径(Bottom-up)

网卡 DMA  驱动 RX 中断  NAPI poll  netif_receive_skb
    
链路层处理eth_type_trans
    
网络层处理ip_rcv / ipv6_rcv
    
Netfilter PREROUTING 钩子
    
路由决策本地 / 转发
    
本地传输层处理tcp_v4_rcv / udp_rcv
    
套接字队列sk->sk_receive_queue
    
应用程序 recv()

每层处理的典型操作:

/* 链路层:解析以太网头部 */
static int eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
    struct ethhdr *eth = eth_hdr(skb);
    skb->protocol = eth->h_proto;
    skb_pull(skb, ETH_HLEN);  // 移除以太网头部
    return 0;
}

/* 网络层:IP 头部校验 */
static int ip_rcv(struct sk_buff *skb, struct net_device *dev, 
                  struct packet_type *pt, struct net_device *orig_dev)
{
    struct iphdr *iph = ip_hdr(skb);

    /* 校验 IP 头部 */
    if (iph->ihl < 5 || iph->version != 4)
        goto drop;

    if (!pskb_may_pull(skb, iph->ihl * 4))
        goto drop;

    /* 更新 skb 指针 */
    skb->transport_header = skb->network_header + iph->ihl * 4;

    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, 
                   skb, dev, NULL, ip_rcv_finish);
}

发送路径(Top-down)

应用程序 send() → socket 层
    ↓
传输层构造(tcp_transmit_skb)
    ↓
网络层处理(ip_output)
    ↓
Netfilter POSTROUTING 钩子
    ↓
邻居子系统(ARP 解析)
    ↓
设备队列(dev_queue_xmit)
    ↓
流量控制(qdisc)
    ↓
驱动 TX → DMA → 网卡发送

9.2.4 零拷贝与 scatter-gather I/O

分散聚集 I/O

sk_buff 支持将数据分散在多个内存页中,避免大块连续内存分配:

struct skb_shared_info {
    unsigned short  nr_frags;
    struct skb_frag_struct {
        struct page *page;
        __u32       page_offset;
        __u32       size;
    } frags[MAX_SKB_FRAGS];
};

这种设计支持:

  • 零拷贝发送:直接映射用户空间页面
  • TSO/GSO:硬件分段卸载
  • GRO:接收端数据包聚合

页面翻转(Page Flipping)

高性能网卡驱动使用页面翻转技术避免数据复制:

/* 接收时直接将 DMA 页面赋给 sk_buff */
static void rx_page_flip(struct sk_buff *skb, struct page *page)
{
    skb_fill_page_desc(skb, 0, page, offset, len);
    skb->data_len = len;
    skb->len += len;
}

MSG_ZEROCOPY 发送

Linux 4.14 引入的 MSG_ZEROCOPY 允许应用程序零拷贝发送:

/* 用户空间 */
send(fd, buffer, len, MSG_ZEROCOPY);

/* 内核空间:直接引用用户页面 */
static int tcp_zerocopy_send(struct sock *sk, struct msghdr *msg)
{
    struct sk_buff *skb = tcp_write_queue_tail(sk);

    /* 将用户页面添加到 skb frags */
    err = skb_zerocopy_add_frags_iter(sk, skb, &msg->msg_iter, 
                                       len, &uarg, sk->sk_allocation);

    /* 设置回调通知 */
    skb_zcopy_set(skb, uarg);
}

内存管理优化

skb 缓存池

为了减少分配开销,内核维护了 per-CPU 的 sk_buff 缓存:

struct netdev_alloc_cache {
    struct page_frag    frag;
    unsigned int        pagecnt_bias;
};

static DEFINE_PER_CPU(struct netdev_alloc_cache, netdev_alloc_cache);

内存预分配策略

网卡驱动通常预分配接收缓冲区:

/* Intel ixgbe 驱动的接收缓冲区管理 */
static bool ixgbe_alloc_rx_buffers(struct ixgbe_ring *rx_ring, u16 cleaned)
{
    union ixgbe_adv_rx_desc *rx_desc;
    struct ixgbe_rx_buffer *bi;

    /* 批量分配,减少内存分配开销 */
    while (cleaned--) {
        bi = &rx_ring->rx_buffer_info[i];
        bi->skb = netdev_alloc_skb_ip_align(rx_ring->netdev,
                                             rx_ring->rx_buf_len);

        /* 设置 DMA 映射 */
        bi->dma = dma_map_single(rx_ring->dev, bi->skb->data,
                                  rx_ring->rx_buf_len, DMA_FROM_DEVICE);
    }
}