linux网口<UP> 和state UP 的区别

常见问题场景

我们在平常定位问题,经常会通过ip linkifconfig查看网络环境和配置。其中网口的标志位是其中很重要的一个检查环节。在输出结果里,会有两组标志位信息

  • 一组是尖括弧里包含的值<BROADCAST,MULTICAST,UP,LOWER_UP>,有很多标志位的组合。
  • 另一组是 state UP。还有其他状态如DOWN, LOWERLAYERDOWN等。

网口状态对比图
这些信息有时候看起来是一致的, 有时候是重复的,甚至矛盾的。难免让人产生一些疑惑。

  • 这两组标志位分别代表什么?
  • 他们是什么关系?
    下面我们就这两组标志,逐个展开分析下。
    我们先来看几个场景里,网口在不同状态下,输出的标志位有哪些差异。

Read More

Linux 内核 Nexthop 对象化: Patch 4c7e8084 深度解析

一、问题起源 (Problem Origin)

2.1 性能瓶颈 (Performance Bottleneck)

David Ahern 在 2017 年左右开始构思 nexthop 对象化 (nexthop objectification),核心动机来自大规模路由注入的性能瓶颈 (large-scale route injection performance bottleneck)。当时的状况:

场景 (Scenario) 耗时 (Duration)
注入 70 万+ IPv4 路由,单路径 (single path) 18 秒
注入 70 万+ IPv4 路由,4 路径 ECMP 28 秒
注入 70 万+ IPv4 路由,16 路径 ECMP 72+ 秒

时间随 ECMP (Equal-Cost Multi-Path,等价多路径) 路径数急剧膨胀,这对 BGP 大表 (full internet table,完整互联网路由表) 场景完全不可接受。

Read More

ECMP选路: fib_select_multipath Hash 机制深度解析

一、概述 (Overview)

Linux 内核中的 ECMP(Equal-Cost Multi-Path)选路机制允许将流量分布到多条等价路径上。

核心实现主要分为两个阶段, 分别对应两个函数。

  1. hash计算 : fib_multipath_hash()net/ipv4/route.c)—— 计算 hash 值
    如根据skb或flow的五元组、三元组等策略,计算出hash值。
  2. 根据hash选择nexthop: fib_select_multipath()net/ipv4/fib_semantics.c)—— 用 hash 选 nexthop

本文基于 Linux v6.16 源码,逐层剖析 ECMP 的 hash 计算与路径选择全流程。
使用coebuddy+claude-4.6-ops辅助生成。

二、整体架构 (Architecture)

2.1 内部流程

Read More

fib data struct

综述

fib tie是一个多叉树。其原理可以简化为:
根据前缀每一位是0还是1, 决定走左子树还是右子树。
绝大部分路由树,都没有那么多条目路由,所以再优化下,

  1. 多个联系节点只有一个子树,如
    仅有两条路由:8.8.2.0/248.8.3.0/24, 前面16+6位都完全一样。
  2. 多级子树转换成一个节点下多个孩子。
    仅有5条路由:
1
2
3
4
5
8.8.4.0/24
8.8.5.0/24
8.8.6.0/24
8.8.7.0/24
8.8.8.0/24

这就催生了Fib compress tree的产生。

fib data struct overview

Read More

linux内核多路由表与策略路由的实现

使用场景

接上篇文章,我们提到在网口eth1 上增加一个 ip 地址,内核会生成 3 个路由。

  • local 路由表:增加/32 主机路由
  • main 路由表:增加本网段前缀的网络路由

为什么需要使用local/main 两个路由表呢? ip地址引发的路由变化或者手动添加一个目的网段的路由,这些场景看起来,完全可以使用一个路由表就能满足场景需求。那内核为什么要支持多个路由表?

其中一个场景就是 策略路由
比如公司有两个网关, 其中一个网关GW1,网络质量比较高。我们希望对网络质量有较高要求的个别网段的外访流量,走GW1.其他网段都用 GW2.
这时,我们就不能仅仅依赖目前 IP 去做路由。我们需要:

  • 先根据源 IP 地址做一次筛选,选中的这部分流量,缺省路由到GW1.
  • 没有选中的走我们的正常路由查找,其中缺省路由到GW2。
  • 这两个缺省路由只能放到两个单独的路由表里。

那下面详细展开介绍下策略路由及内核实现。

策略路由

策略路由是一个按优先级排列的规则链表。路由查找时,按优先级顺序遍历链。

每个策略路由规则,由match+action两部分组成。 match 也被称为SELECTOR

  1. 每个规则的match条件,可以支持多种字段,如源IP/协议/协议源端口/目的端口等,也可以这多个字段的组合。如果满足规则匹配条件就执行规则指定的动作。
  2. 每个策略路由的action有下面几种:
    • table X:查找对应的路由表 table X。X 是 table ID 或者 table 名字。
    • goto xx:跳转到更低优先级到规则。
    • nat …:ip地址替换
  3. 所有的策略路由通过 IP rule 命令添加。
  4. 策略路由按照优先级插入到一个链表里。
  5. 按照优先级从高(数值最小)到低(数值大)按顺序排列。
  6. 通过 ip rule show命令我们可以看到每条rule对应的优先级。添加rule时候,默认的是当前除0以外最高优先级的值-1, 即默认新建的rule优先级高。

这部分用法详见ip rule命令帮助手册,
https://man7.org/linux/man-pages/man8/ip-rule.8.html

Read More

内核如何管理 local(主机)路由表

今天遇到一个有意思的问题:
Q:Linux系统在内核初始化完成后,默认创建了多少个路由表?
这个问题在技术社区中存在不少争议,无论是询问AI、Google搜索还是查阅博客,都能得到各种不同的答案。
问题的核心争议点在于:
内核如何管理主机路由(local路由)对应的本地路由表。
下面让我们逐步分析不同的观点:

step 1:咨询deepseek,系统创建了 4 个路由表

DeepSeek不仅给出了结论,还详细说明了分析过程,并提供了验证依据。

同时提供了验证方法。

1
cat /etc/iproute2/rt_tables

step 2. 手动测试发现,系统只创建了 local/main两个路由表

基于Deepseek的答案进行进一步验证,通过IP命令检查指定路由表可发现:

  • 实际仅存在local(255)和main(254)两个路由表
  • unspec(253)表实际不存在,该ID仅为保留编号
  • 表0是IPv4和IPv6所有路由的汇总视图,并非独立路由表

最终结论:系统仅创建了local和main两个路由表

step 3. 分析内核源代码

这两种观点都很有道理,论据充分。真相最终还是要看代码实现,让我们深入内核源码一探究竟。
以路由查找函数fib_lookup为例,内核实际上维护了两套不同的函数实现。
具体采用哪套方案,取决于编译时是否启用了CONFIG_IP_MULTIPLE_TABLES选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
292 #ifndef CONFIG_IP_MULTIPLE_TABLES
...
316 static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
317 struct fib_result *res, unsigned int flags)
318 {
...
364 #else /* CONFIG_IP_MULTIPLE_TABLES */
...
371 int __fib_lookup(struct net *net, struct flowi4 *flp,
372 struct fib_result *res, unsigned int flags);
373
374 static inline int fib_lookup(struct net *net, struct flowi4 *flp,
375 struct fib_result *res, unsigned int flags)
376 {

不支持多路由表

#ifndef CONFIG_IP_MULTIPLE_TABLES

代码分析显示,内核路由查找函数fib_llookup的实现取决于编译选项CONFIG_IP_MULTIPLE_TABLES。当该选项未启用时,内核仅查询RT_TABLE_MAIN路由表。
由此可以确定:在此配置下,系统仅维护一个主路由main表。

初始化

1
2
3
4
5
==>  static int __net_init fib_net_init(struct net *net)
==> ==> ip_fib_net_init(net);
==> ==> ==> fib4_rules_init(net);
==> ==> ==> ==> main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
==> ==> ==> ==> local_table = fib_trie_table(RT_TABLE_LOCAL, main_table);

这里重点关注函数fib4_rules_init
从代码调用看,确实创建了两个路由表:local和main。
但在创建main表时候,我们使用的一个alias的参数,并把main表当做参数传递过去的。

1
2
3
4
5
6
7
8
9
10
50 #ifndef CONFIG_IP_MULTIPLE_TABLES
51
52 static int __net_init fib4_rules_init(struct net *net)
53 {
54 struct fib_table *local_table, *main_table;
55
56 main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
57 if (!main_table)
58 return -ENOMEM;
59

路由表初始化细节

fib4_rules_init函数中,我们可以看到路由表的创建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int __net_init fib4_rules_init(struct net *net)
{
struct fib_table *local_table, *main_table;

main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
if (!main_table)
return -ENOMEM;

local_table = fib_trie_table(RT_TABLE_LOCAL, main_table);
if (!local_table) {
fib_trie_table_free(main_table);
return -ENOMEM;
}

rcu_assign_pointer(net->ipv4.fib_main, main_table);
rcu_assign_pointer(net->ipv4.fib_local, local_table);
return 0;
}

关键发现

  1. 两个独立的路由表:系统确实创建了local(255)和main(254)两个路由表
  2. 表关系:local表基于main表创建,形成层级关系
  3. 表0:不是独立路由表,而是所有路由的汇总视图
  4. 表253:unspec表只是保留编号,实际不存在

路由表功能解析

local路由表 (RT_TABLE_LOCAL = 255)

  • 管理主机本地路由(loopback、设备地址等)
  • 优先级最高,用于本地通信
  • 包含127.0.0.1/8、设备IP地址等

main路由表 (RT_TABLE_MAIN = 254)

  • 管理常规路由规则
  • 包含默认路由、静态路由等
  • 优先级次于local表

验证方法

查看路由表配置

1
cat /etc/iproute2/rt_tables

检查特定路由表

1
2
3
4
5
6
7
8
# 检查local路由表
ip route show table local

# 检查main路由表
ip route show table main

# 检查表0(汇总视图)
ip route show table 0

验证表253不存在

1
2
3
# 尝试查看表253会报错
ip route show table 253
# 输出:Error: argument "253" is wrong: table id value is invalid

总结

通过深入分析内核源代码,我们得出了明确的结论:

Linux内核在初始化完成后,默认只创建了两个路由表:

  1. local表 (255):管理主机本地路由
  2. main表 (254):管理常规路由规则

其他表编号(如0、253)并非独立的路由表:

  • 表0是所有路由的汇总视图
  • 表253是保留编号,实际不存在

这个问题的争议源于对路由表编号和功能的误解。正确的理解是:内核通过local和main两个表的协作,实现了完整的路由功能。

技术要点

  1. 编译选项影响CONFIG_IP_MULTIPLE_TABLES选项决定了是否支持多路由表
  2. 表层级关系:local表基于main表创建,形成路由查找的优先级关系
  3. 实际验证:通过iproute2工具可以验证路由表的实际存在状态

这个问题的分析过程展示了内核源码分析的重要性,也提醒我们在技术讨论中要基于事实和代码验证,而不是依赖表面的数字统计。

ping本机网口的IP地址,tcpdump在lo口才能抓到对应报文

问题背景

Q1:ping 本机eth1口的ip地址时,对应的ICMP报文会被发送到eth1口上吗?

实验: ping本机网口地址

ping本机网口地址实验

通过tcpdump抓包命令,我们发现报文被发送到lo上,而不是对应的物理口 eth1。
虽然ip 地址被配在本机的物理口上,但是报文并没有被发送到物理口。为了理解这个问题,我们需要先看下下面这个问题。

Q2: 当一个IP地址被添加到一个网口后,会引起哪些路由变化呢?
为一个网口增加一个 IP 地址是个很常见的网络操作。
具体添加方式有多种,包括命令行手动添加、通过配置文件在系统启动时候添加,还是通过dhcp自动获取并添加等。
无论哪种场景,添加完IP地址后,系统路由有什么变化。
场景:在本机物理口 eth1上配置192.168.8.8/24,网口状态正常(UP).

Read More