转载自:
3.6版本内核移除了FIB查询前的路由缓存,取而代之的是下一跳缓存,这在路由缓存的前世今生 中已经说过了。本文要说的是在该版本中引入的另一个概念:FIB Nexthop Exception
,用于记录下一跳的例外情形。
它有什么用呢?
内核通过查询转发信息表(fib_lookup),得到下一跳(fib_nh),从而得到了关于此条路由的相关信息,其中重要的包括下一跳设备nh_dev,下一跳网关nh_gw等。这些表项基本是稳定的。然而,但内核实际发包时,关于此条路由可能存在两个变数exception,其一是这条路由相关的路径MTU(PMTU)发生改变;其二是收到了关于此条路由的ICMP重定向报文。由于此两种改变并不是永久的,内核将他们保存在下一跳fib_nh的exception
这两种情况是针对单一目的地址的,什么意思呢?已PMTU为例,在下面的网络拓扑中,我在主机A上配置了下面一条路由
ip route add 4.4.0.0/16 via 4.4.4.4
意思是向所有目的地址在4.4.0.0/16的主机发包,下一跳都走192.168.99.1。
当A发送一个长度为1500的IP报文给C时 ,中间的一台网络设备B觉得这个报文太大了,因此它向A发送ICMP FRAGNEEDED报文,说我只能转发1300以下的报文,请将报文分片。A收到该报文后怎么办呢?总不能以后命中这条路由的报文全部按1300发送吧,因为并不是所有报文的路径都会包含B。
这时FIB Nexthop Exception就派上用场了,他可以记录下这个例外情况。当发送报文命中这条路由时,如果目的地址不是C,那么按1500进行分片,如果是C,则按1300进行分片。
内核中使用fib_nh_exception
表示这种例外表项
(include/net/ip_fib.h) struct fib_nh_exception { struct fib_nh_exception __rcu *fnhe_next; /* 冲突链上的下个fib_nh_exception结构 */ __be32 fnhe_daddr; /* 例外的目标地址 */ u32 fnhe_pmtu; /* 收到的ICMP FRAGNEEDED通告的PMTU */ __be32 fnhe_gw; /* 收到的ICMP REDIRECT通告的网关 */ unsigned long fnhe_expires; /* 该例外表项的过期时间 */ struct rtable __rcu *fnhe_rth; /* 关联的路由缓存 */ unsigned long fnhe_stamp; };
每一个下一跳结构fib_nh
上有一个指针指向fnhe_hash_bucket
哈希桶的指针:
struct fib_nh { ------------------------------- /* code omitted */ struct fnhe_hash_bucket *nh_exceptions; };
哈希桶在update_or_create_fnhe
中创建,每个哈希桶包含2048条冲突链,每条冲突链可以存5个fib_nh_exception
以PMTU为例,在收到网络设备返回的ICMP FRAGNEEDED报文后,会调用下列函数将通告的pmtu值记录到fib_nh_exception
上(也会记录到绑定的路由缓存rtable
上)
static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu) { /* */ if (fib_lookup(dev_net(dst->dev), fl4, &res) == 0) { struct fib_nh *nh = &FIB_RES_NH(res); update_or_create_fnhe(nh, fl4->daddr, 0, mtu, jiffies + ip_rt_mtu_expires); } }
而在发包流程查询FIB之后,会首先看是否存在以目标地址为KEY的例外表项,如果有,就使用其绑定的路由缓存,如果没有就使用下一跳上的缓存
static struct rtable *__mkroute_output(const struct fib_result *res, const struct flowi4 *fl4, int orig_oif, struct net_device *dev_out, unsigned int flags) { /* code omitted */ if (fi) { struct rtable __rcu prth; struct fib_nh *nh = &FIB_RES_NH(*res); fnhe = find_exception(nh, fl4->daddr); // 查找 fl4->daddr 是否存在 fib_nh_exception if (fnhe) prth = &fnhe->fnhe_rth; // 如果有,直接使用其绑定的路由缓存 else { if (unlikely(fl4->flowi4_flags & FLOWI_FLAG_KNOWN_NH && !(nh->nh_gw && nh->nh_scope == RT_SCOPE_LINK))) { do_cache = false; goto add; } prth = __this_cpu_ptr(nh->nh_pcpu_rth_output); // 如果没有,使用下一跳上缓存的路由缓存 } rth = rcu_dereference(*prth); if (rt_cache_valid(rth)) { dst_hold(&rth->dst); return rth; } } }