1.TCP/IP源码探索--数据结构篇

4 阅读13分钟

Linux 内核里的 TCP 连接,就像一个巨大的“俄罗斯套娃”(或者层层改装的卡车):

  1. 最里层 (sock_common) :是 “身份证” 。只记录最核心的身份(我是谁,IP 是多少),为了查得快,做得极小。
  2. 中间层 (struct sock) :是 “通用卡车底盘” 。装上了油箱(内存管理)、货箱(收发队列)、方向盘(锁与回调),不管是拉 TCP 的货还是 UDP 的货,底盘都一样。
  3. 最外层 (struct tcp_sock) :是 “特种改装” 。因为 TCP 太复杂,需要加装“防丢雷达”(序列号)、“限速器”(拥塞控制)、“重传机械臂”(重传队列)。

1. sock_common

当网卡收到一个数据包,内核需要在几百万个连接中,瞬间找到这个包属于哪个 Socket。它是怎么做到的?答案就藏在 struct sock_common 里。

在 Linux 内核中,TCP、UDP、甚至 RAW Socket,虽然性格迥异,但它们都有一个共同点:都需要被寻址(IP+端口),都需要被管理(状态+引用计数)。

为了避免代码重复,内核设计者采用了类似 C++ “继承”的思想(虽然 C 语言没有继承)。

  • struct sock_common:就是那个“基类”。它只存最核心的、所有协议都共用的数据。
  • struct sock:继承自 sock_common,增加了通用的发送/接收队列。
  • struct tcp_sock:继承自 sock,增加了 TCP 特有的序列号、拥塞窗口。

sock_common 就像是每个连接身份证上的基本信息:我是谁(IP/端口),我来自哪里(Family),我现在什么状态(State)。”

/**
 * struct sock_common - 网络协议栈中 Socket 的“最小公分母”
 *
 * 【核心概念】:
 * 它是 struct sock (TCP/UDP通用套接字) 和 struct inet_timewait_sock (TIME_WAIT套接字) 的公共头部。
 * 内核在进行快速查找(如收包匹配)时,只需要访问这个结构体,就能获取四元组和状态。
 */
struct sock_common {
    // --- 1. 核心身份标识:四元组 (Identity) ---
    // 为了性能,IP地址和端口被紧凑地排列在一起,方便 CPU 一次性读取比对。

    /* 
     * 地址对 (Address Pair)
     * 在 64 位系统上,这两个 32 位 IP 会被打包成一个 64 位整数进行比对。
     * 注意:这里使用的是网络字节序 (__be32),即大端序。
     */
    union {
        __addrpair        skc_addrpair;      // 用于快速比对的 64 位整体
        struct {
            __be32        skc_daddr;         // 【目的IP】(Foreign IP) - 对方是谁
            __be32        skc_rcv_saddr;     // 【源IP】(Local IP) - 我是谁
        };
    };

    /* 
     * 哈希值 (Hash Value)
     * 这个哈希值是根据四元组计算出来的,用于在哈希表中快速定位。
     */
    union {
        unsigned int      skc_hash;          // 哈希桶的索引
        __u16             skc_u16hashes[2];  // UDP 专用的两个 16 位哈希
    };

    /* 
     * 端口对 (Port Pair)
     * 同样是为了紧凑排列,方便一次性读取。
     */
    union {
        __portpair        skc_portpair;      // 用于快速比对的 32 位整体
        struct {
            __be16        skc_dport;         // 【目的端口】(Foreign Port) - 对方端口
            __u16         skc_num;           // 【源端口】(Local Port) - 本地端口 (主机序)
        };
    };

    // --- 2. 基础属性 (Attributes) ---

    unsigned short        skc_family;        // 协议族 (AF_INET 或 AF_INET6)
    
    /* 
     * 状态 (State)
     * volatile 保证多核可见性。
     * 这里的值如 TCP_ESTABLISHED, TCP_TIME_WAIT 等。
     */
    volatile unsigned char skc_state;        

    /* 
     * 标志位 (Flags) - 位域节省空间
     * skc_reuse: 对应 SO_REUSEADDR (允许端口复用)
     * skc_reuseport: 对应 SO_REUSEPORT (允许多个进程绑定同一端口)
     */
    unsigned char         skc_reuse:4;
    unsigned char         skc_reuseport:1;
    unsigned char         skc_ipv6only:1;    // IPv6 套接字是否仅监听 IPv6
    unsigned char         skc_net_refcnt:1;  // 网络命名空间引用计数标志

    int                   skc_bound_dev_if;  // 绑定的网卡接口索引 (如 eth0)

    // --- 3. 链表节点 (List Linkage) ---
    // 用于将这个 Socket 挂载到内核的全局哈希表或监听列表中

    union {
        struct hlist_node       skc_bind_node;   // 绑定哈希表节点
        struct hlist_nulls_node skc_portaddr_node; // 端口地址哈希表节点
    };

    struct proto            *skc_prot;         // 协议操作集 (如 tcp_prot, udp_prot) - 类似 C++ 的虚函数表指针
    possible_net_t          skc_net;           // 指向所在的网络命名空间 (Network Namespace)

    // --- 4. IPv6 扩展 (Padding & IPv6) ---
#if IS_ENABLED(CONFIG_IPV6)
    // 如果是 IPv6,这里存放 128 位地址
    struct in6_addr         skc_v6_daddr;
    struct in6_addr         skc_v6_rcv_saddr;
#endif

    atomic64_t              skc_cookie;        // 用于防御 SYN Flood 攻击的 Cookie

    // --- 5. 巧妙复用 (Polymorphism & Padding) ---
    // 为了内存对齐,同时也为了在不同类型的 Socket 中复用这块内存

    union {
        unsigned long       skc_flags;         // 普通 Socket 的标志位
        struct sock*        skc_listener;      // [请求套接字] 指向创建它的监听套接字
        struct inet_timewait_death_row *skc_tw_dr; // [TIME_WAIT套接字] 指向死亡行
    };

    // --- 6. “不复制”区域标记 ---
    // 这是一个 C 语言的黑魔法,标记哪些字段在 clone 时不需要复制
    /* private: */
    int                     skc_dontcopy_begin[0];
    /* public: */

    union {
        struct hlist_node       skc_node;      // 主哈希表节点 (Established Hash Table)
        struct hlist_nulls_node skc_nulls_node;
    };
    int                     skc_tx_queue_mapping; // 发送队列映射

    union {
        int                 skc_incoming_cpu;  // 记录处理入站包的 CPU 编号 (用于 RPS)
        u32                 skc_rcv_wnd;       // [普通套接字] 接收窗口大小
        u32                 skc_tw_rcv_nxt;    // [TIME_WAIT套接字] 下一个期望的序列号
    };

    atomic_t                skc_refcnt;        // 引用计数 (生命周期的关键!)

    /* private: */
    int                     skc_dontcopy_end[0];
    union {
        u32                 skc_rxhash;        // 接收哈希
        u32                 skc_window_clamp;  // 窗口钳制值
        u32                 skc_tw_snd_nxt;    // [TIME_WAIT套接字] 下一个发送序列号
    };
    /* public: */
};

2. sock

一个数据包从网卡进来,最终被应用层 read 读取,中间经历了什么?它在 struct sock 的接收队列里排队;
应用层 write 数据发往互联网,数据在哪里暂存?它在 struct sock 的发送队列里等待。
struct sock 就是连接用户态与网络世界的“蓄水池”

/**
 * struct sock - network layer representation of sockets
 *
 * 【核心概念】:
 * 这是 Linux 网络协议栈中最庞大的结构体之一。
 * 它继承了 sock_common(身份),并扩展了数据传输所需的所有通用资源。
 */
struct sock {
	/*
	 * 第一部分:继承 (Inheritance)
	 * ----------------------------
	 * 必须放在最开头!
	 * 这样内核才能把 struct sock* 强转为 struct sock_common* 进行快速查找。
	 */
	struct sock_common	__sk_common;

	/*
	 * 宏定义魔法 (Macro Magic)
	 * ------------------------
	 * 为了让代码写起来像访问普通成员变量一样(如 sk->sk_state),
	 * 而不需要写 sk->__sk_common.skc_state。
	 */
#define sk_node			__sk_common.skc_node
#define sk_nulls_node		__sk_common.skc_nulls_node
#define sk_refcnt		__sk_common.skc_refcnt
#define sk_tx_queue_mapping	__sk_common.skc_tx_queue_mapping

#define sk_dontcopy_begin	__sk_common.skc_dontcopy_begin
#define sk_dontcopy_end		__sk_common.skc_dontcopy_end
#define sk_hash			__sk_common.skc_hash
#define sk_portpair		__sk_common.skc_portpair
#define sk_num			__sk_common.skc_num
#define sk_dport		__sk_common.skc_dport
#define sk_addrpair		__sk_common.skc_addrpair
#define sk_daddr		__sk_common.skc_daddr
#define sk_rcv_saddr		__sk_common.skc_rcv_saddr
#define sk_family		__sk_common.skc_family
#define sk_state		__sk_common.skc_state
#define sk_reuse		__sk_common.skc_reuse
#define sk_reuseport		__sk_common.skc_reuseport
#define sk_ipv6only		__sk_common.skc_ipv6only
#define sk_net_refcnt		__sk_common.skc_net_refcnt
#define sk_bound_dev_if		__sk_common.skc_bound_dev_if
#define sk_bind_node		__sk_common.skc_bind_node
#define sk_prot			__sk_common.skc_prot
#define sk_net			__sk_common.skc_net
#define sk_v6_daddr		__sk_common.skc_v6_daddr
#define sk_v6_rcv_saddr	__sk_common.skc_v6_rcv_saddr
#define sk_cookie		__sk_common.skc_cookie
#define sk_incoming_cpu		__sk_common.skc_incoming_cpu
#define sk_flags		__sk_common.skc_flags
#define sk_rxhash		__sk_common.skc_rxhash

	/*
	 * 第二部分:锁与同步 (Locking)
	 * ---------------------------
	 * 网络栈是多核并发最激烈的地方,这把锁保护 Socket 的状态和队列。
	 */
	socket_lock_t		sk_lock;

	/*
	 * 第三部分:接收系统 (Receiving System)
	 * -------------------------------------
	 * 数据流向:网卡 -> 协议栈 -> sk_receive_queue -> 用户 read()
	 */
	 
	// 【核心】接收队列:存放收到的数据包 (sk_buff),等待用户读取
	struct sk_buff_head	sk_receive_queue;
	
	/*
	 * 【核心】积压队列 (Backlog)
	 * 这是一个特殊的队列。当软中断处理速度过快,或者为了优化性能,
	 * 包会先暂存在这里,等待进程上下文来处理。
	 * 注意:rmem_alloc 放在这里是为了在 64 位系统上填补内存空洞(Padding)。
	 */
	struct {
		atomic_t	rmem_alloc; // 接收缓冲区当前占用的内存
		int		len;        // 队列长度
		struct sk_buff	*head;    // 队头
		struct sk_buff	*tail;    // 队尾
	} sk_backlog;
#define sk_rmem_alloc sk_backlog.rmem_alloc

	// 前向分配空间:一种内存分配优化,减少锁竞争
	int			sk_forward_alloc;

	// 发送哈希:用于多队列网卡负载均衡
	__u32			sk_txhash;

#ifdef CONFIG_NET_RX_BUSY_POLL
	// 忙轮询 (Busy Poll) 相关配置,用于低延迟场景
	unsigned int		sk_napi_id;
	unsigned int		sk_ll_usec;
#endif
	// 丢包计数器
	atomic_t		sk_drops;
	
	// 接收缓冲区大小限制
	int			sk_rcvbuf;

	/*
	 * 第四部分:过滤与路由 (Filtering & Routing)
	 * ------------------------------------------
	 */
	// Socket 过滤器 (如 BPF),用于过滤数据包
	struct sk_filter __rcu	*sk_filter;
	
	// 等待队列:用于 select/poll/epoll 机制
	union {
		struct socket_wq __rcu	*sk_wq;
		struct socket_wq	*sk_wq_raw;
	};

#ifdef CONFIG_XFRM
	// IPsec 安全策略
	struct xfrm_policy __rcu *sk_policy[2];
#endif
	// 接收路由缓存:记录入站包的路由信息,避免重复查表
	struct dst_entry	*sk_rx_dst;
	
	// 目的路由缓存:记录出站包的路由信息
	struct dst_entry __rcu	*sk_dst_cache;

	/*
	 * 第五部分:发送系统 (Sending System)
	 * -----------------------------------
	 * 数据流向:用户 write() -> sk_write_queue -> 协议栈 -> 网卡
	 */
	 
	// 发送内存占用计数器
	atomic_t		sk_wmem_alloc;
	
	// 其他内存占用计数器 (Option/Other)
	atomic_t		sk_omem_alloc;
	
	// 发送缓冲区大小限制
	int			sk_sndbuf;
	
	// 【核心】发送队列:存放待发送的数据包
	struct sk_buff_head	sk_write_queue;
	
	// 标志位集合 (使用位域节省内存)
	kmemcheck_bitfield_begin(flags);
	unsigned int		sk_shutdown  : 2,   // 关闭状态 (读/写)
				sk_no_check_tx : 1, // 发送不校验和
				sk_no_check_rx : 1, // 接收不校验和
				sk_userlocks : 4,   // 用户锁设置
				sk_protocol  : 8,   // 协议类型 (TCP/UDP)
				sk_type      : 16;  // Socket 类型
#define SK_PROTOCOL_MAX U8_MAX
	kmemcheck_bitfield_end(flags);

	// 持久化队列大小
	int			sk_wmem_queued;
	
	// 分配模式 (GFP_KERNEL 等)
	gfp_t			sk_allocation;
	
	// 限速参数 (Pacing Rate)
	u32			sk_pacing_rate; /* bytes per second */
	u32			sk_max_pacing_rate;
	
	// 网卡特性能力集 (如 TSO, GSO)
	netdev_features_t	sk_route_caps;
	netdev_features_t	sk_route_nocaps;
	
	// GSO (通用分段卸载) 相关配置
	int			sk_gso_type;
	unsigned int		sk_gso_max_size;
	u16			sk_gso_max_segs;
	
	// 接收低水位标记:当接收数据少于这个值时,触发通知
	int			sk_rcvlowat;
	
	// SO_LINGER 选项的时间设置
	unsigned long	        sk_lingertime;
	
	// 错误队列:存放带外数据或错误信息
	struct sk_buff_head	sk_error_queue;
	
	// 原始 Socket 创建者的协议处理函数
	struct proto		*sk_prot_creator;
	
	// 回调函数的锁
	rwlock_t		sk_callback_lock;
	
	// 错误码记录
	int			sk_err,
				sk_err_soft;
	
	// 连接队列当前长度 / 最大长度
	u32			sk_ack_backlog;
	u32			sk_max_ack_backlog;
	
	// 优先级设置
	__u32			sk_priority;
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
	__u32			sk_cgrp_prioidx;
#endif
	// 对端进程信息 (用于 Unix Domain Socket)
	struct pid		*sk_peer_pid;
	const struct cred	*sk_peer_cred;
	
	// 超时设置
	long			sk_rcvtimeo;
	long			sk_sndtimeo;
	
	// 定时器:用于保活、重传等
	struct timer_list	sk_timer;
	
	// 时间戳相关
	ktime_t			sk_stamp;
	u16			sk_tsflags;
	u32			sk_tskey;
	
	// 指向 VFS 层的 socket 结构
	struct socket		*sk_socket;
	
	// 用户私有数据
	void			*sk_user_data;
	
	// 页面碎片缓存:用于零拷贝优化
	struct page_frag	sk_frag;
	
	// 发送头指针:指向发送队列中下一个要发的包
	struct sk_buff		*sk_send_head;
	
	// 当前 peek 偏移量
	__s32			sk_peek_off;
	
	// 写等待标志
	int			sk_write_pending;
	
#ifdef CONFIG_SECURITY
	// 安全模块指针 (SELinux 等)
	void			*sk_security;
#endif
	// 通用包标记
	__u32			sk_mark;
#ifdef CONFIG_CGROUP_NET_CLASSID
	u32			sk_classid;
#endif
	// CGroup 协议数据
	struct cg_proto		*sk_cgrp;

	/*
	 * 第六部分:回调函数 (Callbacks - The Soul)
	 * ----------------------------------------
	 * 这是 Socket 的“灵魂”,负责异步通知用户进程。
	 */
	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);
	int			(*sk_backlog_rcv)(struct sock *sk,
						  struct sk_buff *skb);
	// 析构函数:引用计数归零时调用
	void                    (*sk_destruct)(struct sock *sk);
};

3. tcp_sock

“到了这一层,画风突变。这里不再是管内存、管锁的通用代码,而是纯粹的 TCP 逻辑
如果你读过 RFC 793,你会在这里看到老朋友:

  • snd_nxt:RFC 里的 SND.NXT,下一个要发的序列号。
  • snd_una:RFC 里的 SND.UNA,最早没被确认的包。
  • snd_cwnd拥塞窗口。这是 TCP 的‘油门’。

内核就是通过不断修改这几个数字,来控制发包的速度。
比如,发现丢包了?把 snd_cwnd 砍半!这就是拥塞控制的代码实现。”


/**
 * struct tcp_sock - TCP 协议的私有数据 (TCB - Transmission Control Block)
 *
 * 【核心概念】:
 * 它继承自 inet_connection_sock (进而继承自 struct sock)。
 * 这里存放了所有 TCP 特有的状态机变量、拥塞控制参数、RTT 测量数据。
 * 如果你在读 RFC 793,你会发现很多变量名和 RFC 里是一模一样的!
 */
struct tcp_sock {
    /* 
     * ==========================================
     * 第一部分:继承 (Inheritance)
     * ==========================================
     * 必须放在最前面。
     * 层级关系:tcp_sock -> inet_connection_sock -> inet_sock -> sock -> sock_common
     */
    struct inet_connection_sock inet_conn;

    // TCP 头长度(包含选项),用于快速构建包头
    u16 tcp_header_len;
    // GSO (通用分段卸载) 分段数
    u16 gso_segs;

    /* 
     * ==========================================
     * 第二部分:头部预测 (Header Prediction)
     * ==========================================
     * 这是一个极致的性能优化。
     * 对于大多数数据包,内核不需要走完整个复杂的协议栈逻辑。
     * 如果包头符合 pred_flags 的预测(比如是常规的 ACK 包),直接快速处理。
     */
    __be32 pred_flags;

    /* 
     * ==========================================
     * 第三部分:RFC793 核心变量 (The RFC Variables)
     * ==========================================
     * 这是 TCP 可靠传输的基石。
     * 这里的变量名直接对应 RFC 文档中的大写变量(如 SND.NXT -> snd_nxt)。
     */

    // --- 接收侧 ---
    u32 rcv_nxt;      // 【核心】下一个期望接收的序列号 (Receive Next)
    u32 copied_seq;   // 用户进程已经读取到的序列号(用于 recv())
    u32 rcv_wup;      // 上一次窗口更新时的 rcv_nxt

    // --- 发送侧 ---
    u32 snd_nxt;      // 【核心】下一个要发送的序列号 (Send Next)
    u32 snd_una;      // 【核心】最早未确认的序列号 (Send UnAcknowledged)
                      // 如果 snd_una 往前走,说明数据被对方确认了。
    u32 snd_sml;      // 最近发送的小包序列号(用于 Nagle 算法判断)

    // --- 统计信息 (64位) ---
    u64 bytes_received; // 接收字节数
    u64 bytes_acked;    // 确认字节数
    u32 segs_in;        // 接收段数
    u32 segs_out;       // 发送段数

    /* 
     * ==========================================
     * 第四部分:窗口管理 (Window Management)
     * ==========================================
     */
    u32 snd_wnd;      // 发送窗口:对方通告给我们的窗口大小 (rwnd)
    u32 snd_wl1;      // 用于判断窗口更新的序列号
    u32 max_window;   // 曾经见过的最大窗口
    u32 rcv_wnd;      // 接收窗口:我们要通告给对方的窗口大小
    u32 window_clamp; // 窗口钳制:限制 rcv_wnd 的最大值,防止内存耗尽

    /* 
     * ==========================================
     * 第五部分:拥塞控制 (Congestion Control)
     * ==========================================
     * 这是 TCP 最复杂的部分,决定了发送速度。
     */
    u32 snd_ssthresh; // 【核心】慢启动阈值 (Slow Start Threshold)
    u32 snd_cwnd;     // 【核心】拥塞窗口 (Congestion Window)
                      // 实际发送窗口 = min(snd_wnd, snd_cwnd)
    u32 snd_cwnd_cnt; // 拥塞窗口线性增长计数器
    u32 prior_cwnd;   // 恢复阶段开始前的 cwnd(用于回退)
    
    // PRR (Proportional Rate Reduction) 算法相关
    u32 prr_delivered;
    u32 prr_out;

    /* 
     * ==========================================
     * 第六部分:重传与丢失恢复 (Retransmission & Loss)
     * ==========================================
     */
    u32 packets_out;    // 在途数据包数量 (In Flight)
    u32 retrans_out;    // 重传包数量
    u32 lost_out;       // 标记为丢失的包数量
    u32 sacked_out;     // 被 SACK (选择性确认) 的包数量
    
    // 乱序队列:存放乱序到达的包(等待空洞被填满)
    struct sk_buff_head out_of_order_queue;

    // SACK (选择性确认) 块
    struct tcp_sack_block duplicate_sack[1]; // D-SACK (重复 SACK)
    struct tcp_sack_block selective_acks[4]; // SACK 块

    // 重传提示指针:加速重传队列的扫描
    struct sk_buff* lost_skb_hint;
    struct sk_buff *retransmit_skb_hint;

    /* 
     * ==========================================
     * 第七部分:RTT 测量 (RTT Measurement)
     * ==========================================
     * 用于计算 RTO (重传超时时间)。
     */
    u32 srtt_us;      // 平滑 RTT (Smoothed RTT) << 3
    u32 mdev_us;      // RTT 平均偏差 (Mean Deviation)
    u32 mdev_max_us;  // 最大偏差
    u32 rttvar_us;    // RTT 方差

    /* 
     * ==========================================
     * 第八部分:定时器与保活 (Timers & Keepalive)
     * ==========================================
     */
    u32 rcv_tstamp;   // 最后一次收到包的时间戳(用于保活检测)
    u32 lsndtime;     // 最后一次发送数据的时间(用于 RTO 计算)
    unsigned int keepalive_time;  // 保活时间
    unsigned int keepalive_intvl; // 保活探测间隔

    /* 
     * ==========================================
     * 第九部分:标志位与杂项 (Flags & Misc)
     * ==========================================
     */
    u8 nonagle : 4;   // 是否禁用 Nagle 算法 (TCP_NODELAY)
    u8 ecn_flags;     // ECN (显式拥塞通知) 标志
    u16 urg_data;     // 紧急数据标志
    
    // TCP 选项接收缓存
    struct tcp_options_received rx_opt;

    // 快速打开 (Fast Open) 相关
    struct tcp_fastopen_request *fastopen_req;
    
    // ... (省略部分安全、MD5 签名等不常用字段) ...
};