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_finishint 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.cint 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_lookupip_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_norefstatic 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-inputstatic 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 UnreachableNetwork 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被消费本地交付或转发发送后释放。