土法炼钢兴趣小组的算法知识备份

【Linux 网络子系统深度拆解】路由子系统深度拆解:FIB、策略路由与路由缓存

文章导航

分类入口
linuxnetworking
标签入口
#routing#fib#lc-trie#policy-routing#ecmp#nexthop#dst-entry#rtable#fnhe#fib6#bpftrace

目录

IP 层内核实现 一文中,我们看到 ip_rcv_finish() 调用 fib_lookup() 做路由决策——找到下一跳 IP、出接口和路由类型。那篇文章给出了全景概览,本文则深入拆解路由子系统的内部实现。

核心问题:一个 IP 包从”知道目的 IP”到”知道从哪个网卡、经过哪个网关发出”,中间经历了什么?

发包路径:
ip_route_output_flow(net, flowi4, sk)
    → __ip_route_output_key(net, flowi4)
        → fib_lookup(net, flowi4, &res, flags)
            → 策略路由:遍历 fib_rules 规则链
                → 匹配规则:fib_rule->table
                    → FIB 查找:fib_table_lookup(tb, flowi4, &res, flags)
                        → LC-trie 最长前缀匹配
                            → fib_result{fi, nhc, type, scope}
            → ECMP:fib_select_path()
                → 哈希选路 → 选定 fib_nh
        → 构造 rtable(dst_entry 的 IPv4 封装)
            → 设置 output = ip_output / ip_mc_output
            → 设置 input = ip_local_deliver / ip_forward
    → xfrm_lookup_route()  IPsec 策略检查
    → 返回 &rtable->dst

收包路径:
ip_route_input_noref(skb, dst, src, tos, dev)
    → fib_lookup()  同上
    → skb_dst_set_noref(skb, &rtable->dst)

一、FIB 表:路由信息的容器

fib_table 结构

struct fib_tableinclude/net/ip_fib.h:253)是路由表的顶层容器:

struct fib_table {
    struct hlist_node tb_hlist;     /* 哈希链中的节点 */
    u32              tb_id;         /* 表 ID:254=main, 255=local */
    int              tb_num_default;/* 默认路由计数 */
    struct rcu_head  rcu;
    unsigned long    *tb_data;      /* 指向 LC-trie 根节点 */
    unsigned long    __data[];      /* 内联 trie 数据 */
};

Linux 默认维护三张路由表:

表 ID 名称 用途
255 local 本机地址、广播地址(内核自动维护)
254 main 用户配置的路由(ip route 操作的默认表)
253 default 默认路由(通常为空)

开启 CONFIG_IP_MULTIPLE_TABLES 后(几乎所有发行版都开启),可以创建 1-252 号自定义表,用于策略路由。

LC-trie:最长前缀匹配

fib_table_lookup()ip_fib.h:274)在 LC-trie(Level-Compressed trie)中执行最长前缀匹配。LC-trie 是 trie 的压缩变体——把只有一个子节点的路径压缩为单个节点,大幅减少内存和查找深度。

对于典型的互联网全表(约 100 万条路由),LC-trie 的查找时间复杂度约 O(W),其中 W = 32(IPv4 地址位数),实际跳数通常不超过 4-6 层。

tb_data 指向 trie 根节点。trie 的内部节点记录”在第几个比特分叉”,叶节点指向 fib_aliasfib_info 链。

二、fib_info 与 fib_nh:路由条目的内核表示

fib_info:一条路由的全部信息

struct fib_infoip_fib.h:134)存储一条路由的完整信息——协议来源、度量值、下一跳列表:

struct fib_info {
    struct hlist_node fib_hash;
    struct hlist_node fib_lhash;
    struct list_head  nh_list;
    struct net        *fib_net;
    refcount_t        fib_treeref;     /* trie 引用计数 */
    refcount_t        fib_clntref;     /* 客户端引用计数 */
    unsigned int      fib_flags;       /* RTNH_F_* */
    unsigned char     fib_dead;
    unsigned char     fib_protocol;    /* RTPROT_BOOT/STATIC/BIRD/... */
    unsigned char     fib_scope;       /* RT_SCOPE_UNIVERSE/LINK/HOST */
    unsigned char     fib_type;        /* RTN_UNICAST/LOCAL/BROADCAST/... */
    __be32            fib_prefsrc;     /* 首选源 IP */
    u32               fib_tb_id;       /* 所属表 ID */
    u32               fib_priority;    /* 度量值(metric) */
    struct dst_metrics *fib_metrics;   /* MTU, RTT 等度量 */
    int               fib_nhs;         /* 下一跳数量(ECMP) */
    struct nexthop    *nh;             /* 新式 nexthop 对象 */
    struct fib_nh     fib_nh[] __counted_by(fib_nhs); /* 柔性数组 */
};

关键设计fib_nh[] 是变长数组——单路径路由只有 1 个元素,ECMP 路由有多个。fib_nhs 记录数量。

fib_nh:下一跳

struct fib_nhip_fib.h:105)继承 fib_nh_commonip_fib.h:81),存储单个下一跳的信息:

struct fib_nh_common {
    struct net_device *nhc_dev;       /* 出接口 */
    int              nhc_oif;         /* 出接口 index */
    unsigned char    nhc_scope;       /* 路由范围 */
    u8               nhc_family;      /* AF_INET / AF_INET6 */
    u8               nhc_gw_family;   /* 网关地址族 */
    unsigned char    nhc_flags;       /* RTNH_F_DEAD/ONLINK/... */
    struct lwtunnel_state *nhc_lwtstate; /* 轻量级隧道状态 */
    union {
        __be32       ipv4;            /* IPv4 网关 */
        struct in6_addr ipv6;         /* IPv6 网关 */
    } nhc_gw;
    int              nhc_weight;      /* ECMP 权重 */
    atomic_t         nhc_upper_bound; /* 加权 ECMP 上界 */

    /* 路由缓存 */
    struct rtable __rcu * __percpu *nhc_pcpu_rth_output; /* per-CPU 输出缓存 */
    struct rtable __rcu           *nhc_rth_input;        /* 输入路由缓存 */
    struct fnhe_hash_bucket __rcu *nhc_exceptions;       /* FNHE 异常表 */
};

per-CPU 路由缓存nhc_pcpu_rth_output)是路由快路径的关键——每个 CPU 缓存自己最近使用的 rtable,避免每次都重新构造。

三、fib_result:路由查找的输出

fib_table_lookup() 的结果写入 struct fib_resultip_fib.h:169):

struct fib_result {
    __be32           prefix;          /* 匹配的网络前缀 */
    unsigned char    prefixlen;       /* 前缀长度 */
    unsigned char    nh_sel;          /* 选中的下一跳索引(ECMP) */
    unsigned char    type;            /* RTN_UNICAST/LOCAL/... */
    unsigned char    scope;           /* RT_SCOPE_* */
    u32              tclassid;        /* TC 分类 ID */
    dscp_t           dscp;
    struct fib_nh_common *nhc;        /* 选中的下一跳 */
    struct fib_info  *fi;             /* 路由信息 */
    struct fib_table *table;          /* 来源表 */
};

type 字段决定包的命运:

RTN 类型 含义 对应 dst->input
RTN_LOCAL 1 本机地址 ip_local_deliver
RTN_UNICAST 2 单播转发 ip_forward
RTN_BROADCAST 3 广播 ip_local_deliver
RTN_MULTICAST 5 组播 组播路由
RTN_BLACKHOLE 6 静默丢弃 dst_discard
RTN_UNREACHABLE 7 不可达 ip_error(发 ICMP)
RTN_PROHIBIT 8 管理禁止 ip_error(发 ICMP)

四、策略路由:ip rule

fib_rule 结构

当开启 CONFIG_IP_MULTIPLE_TABLES 时,路由查找不是直接查单张表,而是遍历规则链。每条规则是一个 struct fib_ruleinclude/net/fib_rules.h:20):

struct fib_rule {
    struct list_head list;
    int              iifindex;       /* 入接口匹配 */
    int              oifindex;       /* 出接口匹配 */
    u32              mark;           /* fwmark 匹配 */
    u32              mark_mask;      /* fwmark 掩码 */
    u32              table;          /* 目标表 ID */
    u8               action;         /* FR_ACT_TO_TBL/BLACKHOLE/... */
    u8               ip_proto;       /* 协议匹配 */
    u32              pref;           /* 优先级(数字越小越先匹配) */
    char             iifname[IFNAMSIZ];
    char             oifname[IFNAMSIZ];
    struct fib_kuid_range       uid_range;    /* UID 范围匹配 */
    struct fib_rule_port_range  sport_range;  /* 源端口范围 */
    struct fib_rule_port_range  dport_range;  /* 目的端口范围 */
};

默认规则链

$ ip rule list
0:      from all lookup local       # 优先级 0:先查 local 表
32766:  from all lookup main        # 优先级 32766:再查 main 表
32767:  from all lookup default     # 优先级 32767:最后查 default 表

查找流程

fib_lookup()ip_fib.h:311)的策略路由路径:

/* 快路径:没有自定义规则 */
if (!net->ipv4.fib_has_custom_rules) {
    /* 直接查 local → main,跳过规则遍历 */
    tb = rcu_dereference(net->ipv4.fib_local);
    err = fib_table_lookup(tb, flp, res, flags);
    if (!err) goto out;
    tb = rcu_dereference(net->ipv4.fib_main);
    err = fib_table_lookup(tb, flp, res, flags);
    goto out;
}

/* 慢路径:遍历规则链 */
return __fib_lookup(net, flp, res, flags);
    → fib_rules_lookup(ops, flowi, flags, arg)
        for_each_rule(rule) {
            if (!fib_rule_match(rule, flowi))
                continue;
            switch (rule->action) {
            case FR_ACT_TO_TBL:
                err = fib_table_lookup(table, flp, res, flags);
                break;
            case FR_ACT_BLACKHOLE:
                return -EINVAL;
            ...
            }
        }

fib_has_custom_rules 是关键优化——大多数服务器只用默认三条规则,直接走快路径。只有执行 ip rule add 添加自定义规则后才切换到慢路径。

策略路由示例

# 基于源 IP 选路:来自 10.0.1.0/24 的包走表 100
ip rule add from 10.0.1.0/24 table 100
ip route add default via 10.0.1.1 table 100

# 基于 fwmark 选路:标记为 0x1 的包走表 200
ip rule add fwmark 0x1 table 200
iptables -t mangle -A OUTPUT -p tcp --dport 443 -j MARK --set-mark 0x1

# 基于入接口选路
ip rule add iif eth1 table 300

五、ECMP:多路径负载均衡

哈希选路

fib_info->fib_nhs > 1 时,fib_select_path()ip_fib.h:527)执行 ECMP 选路:

fib_select_path(net, res, fl4, skb)
    ├── fib_multipath_hash(net, fl4, skb, &flkeys)
    │   └── 计算流哈希(基于五元组)
    └── fib_select_multipath(res, hash)
        └── 用哈希值在 fib_nh[] 中选路

哈希字段

fib_multipath_hash()ip_fib.h:521)的哈希输入由 sysctl 控制:

# 哈希策略:0=L3, 1=L3+L4, 2=L3+L4+inner(隧道), 3=自定义
sysctl net.ipv4.fib_multipath_hash_policy

# 自定义哈希字段(policy=3 时生效)
sysctl net.ipv4.fib_multipath_hash_fields

默认哈希字段:

#define FIB_MULTIPATH_HASH_FIELD_SRC_IP    BIT(0)
#define FIB_MULTIPATH_HASH_FIELD_DST_IP    BIT(1)
#define FIB_MULTIPATH_HASH_FIELD_IP_PROTO  BIT(2)
#define FIB_MULTIPATH_HASH_FIELD_SRC_PORT  BIT(4)
#define FIB_MULTIPATH_HASH_FIELD_DST_PORT  BIT(5)

同一条流(五元组相同)的所有包始终走同一个下一跳——避免乱序。

加权 ECMP

每个 fib_nh_commonnhc_weight 字段(1-256)。nhc_upper_bound 是归一化后的上界,选路时用哈希值与上界比较:

nh[0].weight=2, nh[1].weight=1, nh[2].weight=1
→ 总权重 4
→ nh[0].upper_bound = 2/4 = 0.5
→ nh[1].upper_bound = 3/4 = 0.75
→ nh[2].upper_bound = 4/4 = 1.0
→ hash < 0.5 选 nh[0],0.5 ≤ hash < 0.75 选 nh[1],hash ≥ 0.75 选 nh[2]

六、Nexthop 对象:现代路由 API

传统模型的问题

传统路由中,下一跳信息嵌入在 fib_info->fib_nh[] 里——修改一个下一跳需要替换整个 fib_info 对象,影响所有引用它的路由。

Linux 5.3 引入了 nexthop 对象include/net/nexthop.h:132),将下一跳解耦为独立管理的资源:

struct nexthop {
    struct rb_node  rb_node;       /* 红黑树节点(按 ID 查找) */
    struct list_head fi_list;      /* 引用此 NH 的 IPv4 路由列表 */
    struct list_head f6i_list;     /* 引用此 NH 的 IPv6 路由列表 */
    struct net      *net;
    u32              id;           /* 唯一 ID(用户指定) */
    u8               protocol;     /* 管理此 NH 的应用 */
    u8               nh_flags;
    bool             is_group;     /* 是否为组 */
    union {
        struct nh_info __rcu  *nh_info;  /* 单个下一跳 */
        struct nh_group __rcu *nh_grp;   /* 下一跳组 */
    };
};

下一跳组

struct nh_groupnexthop.h:119)管理 ECMP 组:

struct nh_group {
    struct nh_group *spare;        /* 原子替换用 */
    u16              num_nh;       /* 成员数量 */
    bool             is_multipath;
    bool             hash_threshold;  /* 哈希阈值模式 */
    bool             resilient;       /* 弹性哈希组 */
    struct nh_res_table __rcu *res_table;  /* 弹性哈希表 */
    struct nh_grp_entry nh_entries[];      /* 成员列表 */
};

弹性哈希(Resilient Hash)

传统 ECMP 的问题:增删下一跳时,哈希空间重新分配,大量已有流被迁移到不同路径——导致 TCP 连接跨路径、乱序甚至中断。

弹性哈希nh_res_tablenexthop.h:80)用固定数量的桶(bucket)映射到下一跳。增删下一跳时,只重新分配受影响桶的映射,最小化流迁移:

# 创建弹性哈希组
ip nexthop add id 1 via 10.0.0.1 dev eth0
ip nexthop add id 2 via 10.0.0.2 dev eth0
ip nexthop add id 10 group 1/2 type resilient buckets 32

# 添加新成员——只影响 ~1/3 的桶
ip nexthop add id 3 via 10.0.0.3 dev eth0
ip nexthop replace id 10 group 1/2/3 type resilient buckets 32

iproute2 操作

# 创建 nexthop 对象
ip nexthop add id 100 via 10.0.0.1 dev eth0
ip nexthop add id 101 via 10.0.0.2 dev eth0

# 创建 nexthop 组(ECMP)
ip nexthop add id 200 group 100/101

# 路由引用 nexthop 对象
ip route add 192.168.0.0/24 nhid 200

# 修改下一跳——所有引用它的路由自动更新
ip nexthop replace id 100 via 10.0.0.3 dev eth0

七、FNHE:路由缓存的替代品

路由缓存的废弃

Linux 3.5 及之前版本有一个全局路由缓存(rt_cache)——每个目的 IP 对应一条缓存条目,存储完整的路由查找结果。问题:

  1. 内存爆炸:互联网服务器可能缓存数百万条目
  2. 无效化代价大:拓扑变化时需要遍历整个缓存
  3. DDoS 放大:攻击者发送大量不同目的 IP 的包,撑爆缓存

Linux 3.6 移除了全局路由缓存,替换为两种机制。

per-CPU rtable 缓存

fib_nh_common->nhc_pcpu_rth_output 是 per-CPU 的 rtable 指针。对于同一个下一跳,每个 CPU 缓存自己最近构造的 rtable——复用它避免重复分配:

/* 简化的查找逻辑 */
rth = rcu_dereference(*this_cpu_ptr(nhc->nhc_pcpu_rth_output));
if (rt_cache_valid(rth))
    return rth;  /* 命中 per-CPU 缓存 */

/* 未命中:构造新 rtable */
rth = rt_dst_alloc(dev, flags, type, ...);
rcu_assign_pointer(*this_cpu_ptr(nhc->nhc_pcpu_rth_output), rth);

FNHE(Forwarding Nexthop Exception)

struct fib_nh_exceptionip_fib.h:59)缓存例外信息——PMTU 发现结果和 ICMP Redirect:

struct fib_nh_exception {
    struct fib_nh_exception __rcu *fnhe_next;   /* 哈希链 */
    __be32           fnhe_daddr;     /* 目的 IP */
    u32              fnhe_pmtu;      /* 缓存的 PMTU */
    bool             fnhe_mtu_locked;/* MTU 锁定标志 */
    __be32           fnhe_gw;        /* 重定向网关 */
    unsigned long    fnhe_expires;   /* 过期时间 */
    struct rtable __rcu *fnhe_rth_input;   /* 输入 rtable */
    struct rtable __rcu *fnhe_rth_output;  /* 输出 rtable */
};

每个 fib_nh_common 有独立的 FNHE 哈希表(nhc_exceptions),大小 2048 桶。

FNHE 生命周期

1. 收到 ICMP "Fragmentation Needed"
   → fnhe_pmtu = 对端建议的 MTU
   → fnhe_expires = jiffies + 10min

2. 后续发包检查 FNHE
   → 如果 fnhe_pmtu < 路由 MTU → 使用 fnhe_pmtu
   → 如果已过期 → 恢复原始 MTU,重新探测

3. 收到 ICMP Redirect
   → fnhe_gw = 重定向的新网关
   → 后续包走新网关

八、dst_entry 与 rtable:路由缓存的载体

dst_entry

struct dst_entryinclude/net/dst.h:26)是协议无关的路由缓存条目,所有协议栈的路由信息都继承它:

struct dst_entry {
    struct net_device *dev;         /* 出接口 */
    struct dst_ops    *ops;         /* 操作表 */
    unsigned long     _metrics;     /* 度量值 */
    unsigned long     expires;      /* 过期时间 */

    int (*input)(struct sk_buff *);                          /* 收包处理 */
    int (*output)(struct net *, struct sock *, struct sk_buff *); /* 发包处理 */

    unsigned short    flags;
    short             obsolete;
    unsigned short    header_len;   /* 封装头长度 */
    unsigned short    trailer_len;

    rcuref_t          __rcuref;     /* RCU 引用计数 */
    int               __use;        /* 使用计数 */
    unsigned long     lastuse;      /* 最后使用时间 */

    short             error;        /* 错误码 */
};

关键函数指针

场景 input output
本机收包 ip_local_deliver
转发 ip_forward ip_output
本机发包 ip_output
黑洞 dst_discard dst_discard_out

skb_dst(skb) 返回 skb 关联的 dst_entry——整个协议栈通过它判断”这个包往哪走”。

rtable

struct rtableinclude/net/route.h:60)是 IPv4 对 dst_entry 的扩展:

struct rtable {
    struct dst_entry dst;           /* 基类 */
    int              rt_genid;      /* 代际 ID(路由表变更时递增) */
    unsigned int     rt_flags;      /* RTCF_* 标志 */
    __u16            rt_type;       /* RTN_* */
    __u8             rt_is_input;   /* 1=输入路由, 0=输出路由 */
    __u8             rt_uses_gateway; /* 是否经过网关 */
    int              rt_iif;        /* 入接口 index */
    u8               rt_gw_family;
    union {
        __be32       rt_gw4;        /* IPv4 网关 */
        struct in6_addr rt_gw6;     /* IPv6 网关(IPv4-mapped) */
    };
    u32              rt_mtu_locked:1;
    u32              rt_pmtu:31;    /* 缓存的 PMTU */
};

rt_genid 用于失效检测——每次路由表变更,全局 rt_genid 递增。查找时如果 rtable->rt_genid != net->ipv4.rt_genid,说明缓存过期,需要重新查找。

九、IPv6 路由差异

fib6_table 与 fib6_node

IPv6 使用 radix tree(不是 LC-trie)实现路由查找。struct fib6_tableinclude/net/ip6_fib.h:385):

struct fib6_table {
    struct hlist_node tb6_hlist;
    u32              tb6_id;
    spinlock_t       tb6_lock;     /* IPv6 路由表需要自旋锁 */
    struct fib6_node tb6_root;     /* 内嵌根节点 */
    unsigned int     fib_seq;      /* 序列号 */
};

struct fib6_nodeip6_fib.h:73)是 radix tree 节点:

struct fib6_node {
    struct fib6_node __rcu *parent, *left, *right;
    struct fib6_node __rcu *subtree;   /* 源地址子树(源路由) */
    struct fib6_info __rcu *leaf;      /* 叶子路由 */
    __u16            fn_bit;           /* 分叉比特位 */
    __u16            fn_flags;
    int              fn_sernum;
    struct fib6_info __rcu *rr_ptr;    /* 轮询指针 */
};

IPv4 与 IPv6 路由的关键区别

特性 IPv4 (FIB) IPv6 (FIB6)
数据结构 LC-trie Radix tree
锁保护 RCU 无锁读 spinlock + RCU
源路由 无原生支持 subtree 支持源地址路由
路由条目 fib_info + fib_nh[] fib6_info + fib6_nh[]
路由缓存 per-CPU rtable + FNHE per-CPU rt6_info + rt6_exception
ECMP fib_select_multipath() fib6_select_path() 含轮询
地址长度 32 位 128 位(trie 更深)

IPv6 的 fib6_select_path() 支持轮询(round-robin)模式——rr_ptr 记录上次选中的路由,下次从下一个开始。这与 IPv4 纯哈希选路不同。

十、可观测性实战

路由查找追踪

# 追踪 fib_table_lookup——每次路由查找的表和结果
bpftrace -e '
kretprobe:fib_table_lookup {
    printf("fib_table_lookup: ret=%d\n", retval);
}'

# 追踪策略路由命中——哪条规则匹配了
bpftrace -e '
kprobe:fib_rules_lookup {
    printf("fib_rules_lookup family=%d\n", arg2);
}'

ECMP 分布统计

# 统计每个下一跳被选中的次数
bpftrace -e '
kprobe:fib_select_multipath {
    @selected = lhist(arg1, 0, 8, 1);
}'

FNHE 异常监控

# 追踪 PMTU 更新——频繁更新说明路径 MTU 不稳定
bpftrace -e '
kprobe:__ip_rt_update_pmtu {
    $mtu = arg2;
    printf("pmtu_update: mtu=%u\n", $mtu);
    @pmtu_updates = count();
}'

路由缓存失效检测

# rt_genid 变化频率——频繁变化说明路由表不稳定
bpftrace -e '
kprobe:rt_genid_bump_ipv4 {
    printf("rt_genid bumped! time=%lu\n", nsecs);
    @genid_bumps = count();
}'

路由查找延迟

# 测量 fib_lookup 耗时
bpftrace -e '
kprobe:fib_lookup { @start[tid] = nsecs; }
kretprobe:fib_lookup /@start[tid]/ {
    @fib_lookup_ns = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}'

十一、关键参数速查

参数 默认值 含义 调优建议
fib_multipath_hash_policy 0 ECMP 哈希策略 L4 负载均衡设为 1
fib_multipath_hash_fields 0x7 自定义哈希字段 policy=3 时配置
fib_multipath_use_neigh 0 ECMP 避开不可达邻居 建议开启
ip_forward_use_pmtu 0 转发时使用 PMTU 特定场景开启
ip_forward_update_priority 1 转发时更新 TOS 通常保持默认
fib_sync_mem 524288 FIB 同步内存阈值 大路由表增大
conf.{iface}.rp_filter 0/1 反向路径过滤 严格模式设为 1
conf.{iface}.accept_redirects 1 接受 ICMP Redirect 路由器建议关闭
conf.{iface}.send_redirects 1 发送 ICMP Redirect 非路由器关闭
conf.{iface}.forwarding 0 IP 转发开关 路由器开启

参考文献

  1. Linux 内核源码,include/net/ip_fib.h,6.8(fib_table 于第 253 行,fib_info 于第 134 行,fib_nh_common 于第 81 行)
  2. Linux 内核源码,include/net/fib_rules.h,6.8(fib_rule 于第 20 行,fib_rules_ops 于第 60 行)
  3. Linux 内核源码,include/net/nexthop.h,6.8(nexthop 于第 132 行,nh_group 于第 119 行)
  4. Linux 内核源码,include/net/route.h,6.8(rtable 于第 60 行,ip_route_output_flow 于第 148 行)
  5. Linux 内核源码,include/net/dst.h,6.8(dst_entry 于第 26 行)
  6. Linux 内核源码,include/net/ip6_fib.h,6.8(fib6_table 于第 385 行,fib6_node 于第 73 行)
  7. David S. Miller, “Removal of the route cache”, LWN.net, 2012
  8. Jakub Kicinski, “Resilient nexthop groups”, LWN.net, 2021

上一篇邻居子系统与 ARP:L2 地址解析的内核实现

下一篇Netfilter 内核实现:钩子、conntrack 与 NAT

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】IP 层内核实现:路由查找、分片与转发

IP 层是 Linux 网络栈的中枢——收包时决定本地投递还是转发,发包时查路由、过 Netfilter、做分片。本文从 Linux 6.6 内核源码出发,拆解 ip_rcv → 路由决策 → ip_local_deliver / ip_forward 的完整路径,深入 FIB 表的 LC-trie 实现、策略路由 ip rule 选表机制、IP 分片/重组状态机、PMTU 发现与 FNHE 缓存,以及 Netfilter 五个钩子点的实际调用时机。


By .