news 2026/6/13 9:39:55

Linux ip_rcv_finish路由缓存查找与dst_entry绑定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux ip_rcv_finish路由缓存查找与dst_entry绑定

Linux ip_rcv_finish路由缓存查找与dst_entry绑定

ip_rcv_finish 是IPv4接收路径上NF_INET_PRE_ROUTING钩子之后、路由决策之前的核心函数。它的主要职责是对输入数据报执行路由查找,将结果缓存的 dst_entry 绑定到 skb 上,供后续处理(转发或本地交付)使用。

一、 ip_rcv_finish的调用链

数据报从网卡经过 GRO 处理后进入 netif_receive_skb,逐级到达 ip_rcv,经过 NF_INET_PRE_ROUTING 钩子后调用 ip_rcv_finish:

int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
int err;
int rt_type;
struct rtable *rt;

/* 步骤1:尝试使用early_demux或input route cache加速查找 */
if (skb_dst(skb) == NULL) {
int dscp = iph->tos & IPTOS_TOS_MASK;

/* 路由查找 */
rt = ip_route_input_noref(skb, iph->daddr, iph->saddr,
dscp, skb->dev);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
goto drop;
}
}

/* 步骤2:根据路由类型分流处理 */
rt = skb_rtable(skb);
rt_type = rt->rt_type;

if (rt_type == RTN_MULTICAST) {
/* 多播路由处理 */
__IP_INC_STATS(net, IPSTATS_MIB_INMCASTPKTS);
} else if (rt_type == RTN_BROADCAST) {
/* 广播路由处理 */
__IP_INC_STATS(net, IPSTATS_MIB_INBCASTPKTS);
} else if (rt_type == RTN_LOCAL) {
/* 本地交付 */
} else if (rt_type == RTN_UNICAST) {
/* 单播转发 */
}

/* 步骤3:调用NF_INET_FORWARD或NF_INET_LOCAL_IN钩子 */
return dst_input(skb);
}

核心操作是调用 ip_route_input_noref 执行路由查找。该函数返回 struct rtable 指针并绑定到 skb->_skb_refdst 字段。

二、 ip_route_input_noref的查找逻辑

ip_route_input_noref 封装了路由查找的三个层级,定义于 net/ipv4/route.c:

int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev)
{
struct rtable *rt;
struct net *net = dev_net(dev);
int err;

/* 层级1:首先尝试路由缓存查找 */
rt = ip_route_input_slow(skb, daddr, saddr, tos, dev);
if (IS_ERR(rt))
return PTR_ERR(rt);

/* 绑定路由表项到skb */
skb_dst_set_noref(skb, &rt->dst);

return 0;
}

旧内核(4.16之前)使用路由缓存(rt_hash_table),查找路径为:缓存 -> FIB。新内核完全基于FIB查找,去掉了中央路由缓存以减少RCU锁竞争。

三、 FIB查找入口:fib_lookup

ip_route_input_slow 最终调用 fib_lookup 执行路由表查询:

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 rtable *rt;
struct net *net = dev_net(dev);
int err;
int flags = 0;
struct in_device *in_dev = __in_dev_get_rcu(dev);
struct flowi4 fl4 = {
.daddr = daddr,
.saddr = saddr,
.flowi4_tos = tos & IPTOS_RT_MASK,
.flowi4_oif = 0,
.flowi4_iif = dev->ifindex,
};

/* 执行路由查找 */
err = fib_lookup(net, &fl4, &res, 0);
if (err) {
/* 未命中路由,丢弃并发送ICMP Dest Unreach */
res.fi = NULL;
goto no_route;
}

/* 根据查表结果构造struct rtable */
rt = __mkroute_input(skb, &res, &fl4, in_dev, daddr, saddr, tos);
if (IS_ERR(rt))
return PTR_ERR(rt);

/* 设置skb的路由缓存 */
skb_dst_set(skb, &rt->dst);
return 0;
}

fib_lookup 返回 struct fib_result,包含命中的路由表项 fib_info 和路由类型(RTN_LOCAL/RTN_UNICAST等)。__mkroute_input 根据这些信息分配并初始化 rtable。

四、 dst_entry的绑定与释放

skb_dst_set 的实现分为引用计数管理模式。普通模式使用 skb_dst_set,而 ip_route_input_noref 使用 skb_dst_set_noref:

static inline void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst)
{
WARN_ON(!rcu_read_lock_held() && !rcu_read_lock_bh_held());
skb->_skb_refdst = (unsigned long)dst | SKB_DST_NOREF;
}

static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
{
skb->_skb_refdst = (unsigned long)dst;
dst_hold(dst);
}

区别在于 skb_dst_set_noref 不增加dst_entry的引用计数,依赖RCU保护保证dst_entry不会在skb处理过程中被释放。这减少了原子操作的缓存一致性开销。路由缓存在RCU读锁保护下有效,如果内核需要将skb传递给其他上下文(如加入队列),必须调用 skb_dst_force 将NOREF转换为引用计数模式。

五、 多播/广播/本地路由的特殊处理

当路由类型为 RTN_LOCAL 时,ip_route_input_slow 调用 ip_local_deliver 处理:

if (res.type == RTN_LOCAL) {
/* 本地交付路由 */
rt = rt_dst_alloc(in_dev->dev, ...);
rt->rt_type = RTN_LOCAL;
rt->dst.input = ip_local_deliver;
skb_dst_set(skb, &rt->dst);
return 0;
}

对于多播,内核有独立的输入路径:

err = ip_route_input_mc(skb, daddr, saddr, tos, dev, &res);
if (!err) {
rt = skb_rtable(skb);
rt->rt_type = RTN_MULTICAST;
rt->dst.input = ip_local_deliver; /* 多播同时本地交付 */
return 0;
}

多播路由的 dst.input 设置为 ip_local_deliver,意味着多播数据报既会被本地交付,也可能被转发(如果启用了多播路由)。

六、 dst_input调用与后续路径

ip_rcv_finish 的最后一步调用 dst_input,展开为调用 dst->input:

static inline int dst_input(struct sk_buff *skb)
{
return skb_dst(skb)->input(skb);
}

对于单播转发,dst->input 是 ip_forward;对于本地交付,dst->input 是 ip_local_deliver。这个函数指针在路由查找阶段由 __mkroute_input 或 rt_dst_alloc 设置。

七、 路由查找的性能优化

ip_route_input_noref 使用了多项优化:
- ip_route_input_slow 的结果不会缓存到中央哈希表,但per-cpu的cache hit仍然存在
- 使用 RCU 保护的 FIB Trie 查找而不是旧的路由缓存
- 通过 fib_multipath_hash 实现等价多路径的哈希分发

当路由查找失败时,ip_route_input_slow 发送 ICMP Destination Unreachable(Network Unreachable)并丢弃数据报:

no_route:
/* 发送ICMP Network Unreachable */
rt = rt_dst_alloc(dev, ...);
rt->rt_type = RTN_UNREACHABLE;
rt->dst.error = -EHOSTUNREACH;
rt->dst.output = ip_error;
...

ip_rcv_finish 通过将路由查找结果以 dst_entry 形式绑定到 skb,为后续处理提供了完整的转发/交付决策依据。该绑定贯穿skb的整个生命周期,直到skb被消费(本地交付)或转发(发送)后释放。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 9:34:51

如何通过bili2text智能转录工具将B站视频快速转换为文字稿

如何通过bili2text智能转录工具将B站视频快速转换为文字稿 【免费下载链接】bili2text Bilibili视频转文字,一步到位,输入链接即可使用 项目地址: https://gitcode.com/gh_mirrors/bi/bili2text 你是否曾为了整理B站视频内容而反复暂停、回放、手…

作者头像 李华
网站建设 2026/6/13 9:32:54

3.2.3 按索引是否在主键创建

在 InnoDB 存储引擎中,按索引是否建立在主键之上(或者说,索引是否直接承载整行数据)来划分,索引可以分为两类:聚簇索引(Clustered Index) 和 二级索引(Secondary Index,也称辅助索引)。这是 InnoDB 最核心的物理存储架构,直接决定了查询是否需要“回表”,以及主键…

作者头像 李华