以函数ip_route_input_slow为例,在fib查询之后,由函数ip_mkroute_input创建路由缓存项。
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev, struct fib_result *res)
{
err = fib_lookup(net, &fl4, res, 0);
if (err != 0) {
if (!IN_DEV_FORWARD(in_dev))
err = -EHOSTUNREACH;
goto no_route;
}
make_route:
err = ip_mkroute_input(skb, res, in_dev, daddr, saddr, tos, flkeys);
out: return err;
如果fib查询结果包含多个路径,由函数fib_multipath_hash计算hash值,之后,函数fib_select_multipath通过hash值选择其中的某个下一跳。
static int ip_mkroute_input(struct sk_buff *skb,
struct fib_result *res, struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos, struct flow_keys *hkeys)
{
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (res->fi && fib_info_num_path(res->fi) > 1) {
int h = fib_multipath_hash(res->fi->fib_net, NULL, skb, hkeys);
fib_select_multipath(res, h);
}
#endif
/* create a routing cache entry */
return __mkroute_input(skb, res, in_dev, daddr, saddr, tos);
路径数量判断
首先,如果fib_info的成员nh(下一跳对象)有值,根据其获得路径的数量。其次,nh为空时,使用fib_info结构成员fib_nhs的数量值。
static inline unsigned int fib_info_num_path(const struct fib_info *fi)
{
if (unlikely(fi->nh))
return nexthop_num_path(fi->nh);
return fi->fib_nhs;
如果下一跳对象为组,并且组内有多个路径,返回组内路径数量。否则,返回1。
static inline unsigned int nexthop_num_path(const struct nexthop *nh)
{
unsigned int rc = 1;
if (nh->is_group) {
struct nh_group *nh_grp;
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
if (nh_grp->mpath)
rc = nh_grp->num_nh;
}
return rc;
路径选择Hash计算
PROC文件fib_multipath_hash_policy用于指定路径选择时使用的哈希策略。默认值为0,表示基于三层头部数据的hash策略,另外值1,表示四层hash;值2表示三层或者内部三层的hash。
$ cat /proc/sys/net/ipv4/fib_multipath_hash_policy
0
如下函数fib_multipath_hash,如果哈希策略fib_multipath_hash_policy值为0,使用流结构fl4中保存的源和目的IP地址,但是,如果skb有值,将使用函数ip_multipath_l3_keys获取源和目的IP地址,对于ICMP报文,此函数将得到内部IP头部中的IP地址信息。
/* if skb is set it will be used and fl4 can be NULL */
int fib_multipath_hash(const struct net *net, const struct flowi4 *fl4,
const struct sk_buff *skb, struct flow_keys *flkeys)
{
u32 multipath_hash = fl4 ? fl4->flowi4_multipath_hash : 0;
struct flow_keys hash_keys;
u32 mhash;
switch (net->ipv4.sysctl_fib_multipath_hash_policy) {
case 0:
memset(&hash_keys, 0, sizeof(hash_keys));
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
if (skb) {
ip_multipath_l3_keys(skb, &hash_keys);
} else {
hash_keys.addrs.v4addrs.src = fl4->saddr;
hash_keys.addrs.v4addrs.dst = fl4->daddr;
}
break;
当哈希策略值为1,根据skb是否为空,由以下两种处理。如果skb有值,并且已经计算了四层的哈希值,这里直接使用此值。否则,根据流的key值得到四层数据,包括:源和目的地址,源和目的端口号以及协议号。另外,如果skb为空,由flowi4结构的参数fl4中获取四层信息。
case 1:
/* skb is currently provided only when forwarding */
if (skb) {
unsigned int flag = FLOW_DISSECTOR_F_STOP_AT_ENCAP;
struct flow_keys keys;
/* short-circuit if we already have L4 hash present */
if (skb->l4_hash)
return skb_get_hash_raw(skb) >> 1;
memset(&hash_keys, 0, sizeof(hash_keys));
if (!flkeys) {
skb_flow_dissect_flow_keys(skb, &keys, flag);
flkeys = &keys;
}
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
hash_keys.addrs.v4addrs.src = flkeys->addrs.v4addrs.src;
hash_keys.addrs.v4addrs.dst = flkeys->addrs.v4addrs.dst;
hash_keys.ports.src = flkeys->ports.src;
hash_keys.ports.dst = flkeys->ports.dst;
hash_keys.basic.ip_proto = flkeys->basic.ip_proto;
} else {
memset(&hash_keys, 0, sizeof(hash_keys));
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
hash_keys.addrs.v4addrs.src = fl4->saddr;
hash_keys.addrs.v4addrs.dst = fl4->daddr;
hash_keys.ports.src = fl4->fl4_sport;
hash_keys.ports.dst = fl4->fl4_dport;
hash_keys.basic.ip_proto = fl4->flowi4_proto;
}
break;
当哈希策略值为2时,也是根据skb的值由两种情况。其一,skb有值,根据解析skb得到的流key值的地址类型,获取到源和目的IP地址,对于IPv6,还需要流标签和协议字段。当地址类型不等于IPv4和IPv6时,由函数ip_multipath_l3_keys获取三层信息,这与哈希策略值为0时,是一致的。
其二,当skb为空时,也与哈希策略为0时的处理相同,由流结构fl4中获取源和目的IP地址。
case 2:
memset(&hash_keys, 0, sizeof(hash_keys));
/* skb is currently provided only when forwarding */
if (skb) {
struct flow_keys keys;
skb_flow_dissect_flow_keys(skb, &keys, 0);
/* Inner can be v4 or v6 */
if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
hash_keys.addrs.v4addrs.src = keys.addrs.v4addrs.src;
hash_keys.addrs.v4addrs.dst = keys.addrs.v4addrs.dst;
} else if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
hash_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
hash_keys.addrs.v6addrs.dst = keys.addrs.v6addrs.dst;
hash_keys.tags.flow_label = keys.tags.flow_label;
hash_keys.basic.ip_proto = keys.basic.ip_proto;
} else {
/* Same as case 0 */
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
ip_multipath_l3_keys(skb, &hash_keys);
}
} else {
/* Same as case 0 */
hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
hash_keys.addrs.v4addrs.src = fl4->saddr;
hash_keys.addrs.v4addrs.dst = fl4->daddr;
}
break;
}
函数最后,依据以上获得的数据,计算hash值。另外,只有在IP隧道的情况下,multipath_hash才会有值,其表示由内层报文计算而来的hash值。
mhash = flow_hash_from_keys(&hash_keys);
if (multipath_hash)
mhash = jhash_2words(mhash, multipath_hash, 0);
return mhash >> 1;
路径选择
fib_multipath_use_neigh的值用于表示是否根据邻居表的状态选择路径,内核默认值0,表示不使用邻居表状态信息。
$ cat /proc/sys/net/ipv4/fib_multipath_use_neigh
0
函数fib_select_multipath根据以上得到的hash值,选择下一跳。如果fib_info结构中的nh下一跳成员已经存在,由函数nexthop_path_fib_result来获取下一跳的通用信息。
void fib_select_multipath(struct fib_result *res, int hash)
{
struct fib_info *fi = res->fi;
struct net *net = fi->fib_net;
bool first = false;
if (unlikely(res->fi->nh)) {
nexthop_path_fib_result(res, hash);
return;
}
遍历所有的下一跳,如果没有开启fib_multipath_use_neigh,判断hash值是否小于等于当前下一跳的fib_nh_upper_bound值,为真则在结果中记录下当前下一跳的索引值和相关信息。在开启fib_multipath_use_neigh的情况下,将通过函数fib_good_nh来判断是否为可用的下一跳,
fib_nh_upper_bound的值不仅与自身下一跳地址的权重相关,而且与当前路由的其它下一跳地址的权重也相关(参见内核函数:fib_rebalance)。在下一跳地址数组中,fib_nh_upper_bound的值有小到大,被设置RTNH_F_DEAD标记的下一跳的fib_nh_upper_bound值为-1,不会被选择。
change_nexthops(fi) {
if (net->ipv4.sysctl_fib_multipath_use_neigh) {
if (!fib_good_nh(nexthop_nh))
continue;
if (!first) {
res->nh_sel = nhsel;
res->nhc = &nexthop_nh->nh_common;
first = true;
}
}
if (hash > atomic_read(&nexthop_nh->fib_nh_upper_bound))
continue;
res->nh_sel = nhsel;
res->nhc = &nexthop_nh->nh_common;
return;
} endfor_nexthops(fi);
下一跳对象
函数nexthop_path_fib_result用于在下一跳对象中进行选择,核心函数为nexthop_select_path。
static inline void nexthop_path_fib_result(struct fib_result *res, int hash)
{
struct nh_info *nhi;
struct nexthop *nh;
nh = nexthop_select_path(res->fi->nh, hash);
nhi = rcu_dereference(nh->nh_info);
res->nhc = &nhi->fib_nhc;
首先看一下nexthop对象及对象组的设置,如下前两天命令创建的为nexthop对象,第三条nexthop命令,创建了一个id为3的nexthop组,其是由之前的下一跳id 1与id 2组成的一个组,其中前者的权重为5,后者的权重为11。
$ ip nexthop add id 1 via 192.168.9.1 dev eth0
$ ip nexthop add id 2 via 10.1.9.1 dev eth1
$ ip nexthop add id 3 group 1,5/2,11
$ ip route add 20.1.1.0/30 nhid 3
以下创建fdb类型的下一跳对象以及下一跳对象组。fdb类型主要用于VXLAN协议的远端VTEP地址。
$ ip nexthop add id 4 via 192.168.10.1 fdb
$ ip nexthop add id 5 via 10.1.10.1 fdb
$ ip nexthop add id 6 group 4/5 fdb
如果nh结构不是下一跳对象组,直接返回其自身。
struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
{
struct nexthop *rc = NULL;
struct nh_group *nhg;
if (!nh->is_group) return nh;
否则,遍历组中的所有下一跳成员(nh_grp_entry结构),找到hash值小于等于其upper_bound值的下一个组成员,如果此下一跳为fdb类型,返回其下一跳结构,对于fdb类型,不需要进行之后的可达性检查。
nhg = rcu_dereference(nh->nh_grp);
for (i = 0; i < nhg->num_nh; ++i) {
struct nh_grp_entry *nhge = &nhg->nh_entries[i];
struct nh_info *nhi;
if (hash > atomic_read(&nhge->upper_bound))
continue;
nhi = rcu_dereference(nhge->nh->nh_info);
if (nhi->fdb_nh)
return nhge->nh;
对于非FDB类型的下一跳,这里不考虑sysctl_fib_multipath_use_neigh的设置值,都进行可达性检查,对于IPv4,检查函数为ipv4_good_nh。对于IPv6,检查函数为ipv6_good_nh。如果当前下一跳对象检查通过,返回其下一跳结构。
如果最终没有找到可以通过可达性检查的下一跳,返回的为第一个upper_bound负荷要求的下一跳对象。
/* nexthops always check if it is good and does
* not rely on a sysctl for this behavior
*/
switch (nhi->family) {
case AF_INET:
if (ipv4_good_nh(&nhi->fib_nh))
return nhge->nh;
break;
case AF_INET6:
if (ipv6_good_nh(&nhi->fib6_nh))
return nhge->nh;
break;
}
if (!rc) rc = nhge->nh;
}
return rc;
对于IPv6,依据下一跳设备和IPv6网关地址,查找邻居表项,检查其状态是否符合NUD_VALID。这里将state初始化为NUD_REACHABLE,这样在找不到相应的邻居表项时,ipv6_good_nh返回真,因为NUD_REACHABLE为一种有效状态。所以,只有在存在邻居表项,且其状态为NUD_INCOMPLETE/NUD_FAILED时,此函数才返回0。
static bool ipv6_good_nh(const struct fib6_nh *nh)
{
int state = NUD_REACHABLE;
struct neighbour *n;
rcu_read_lock_bh();
n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
if (n)
state = n->nud_state;
rcu_read_unlock_bh();
return !!(state & NUD_VALID);
对于IPv4,依据下一跳设备和IPv4网关地址,查找邻居表项,检查其状态是否符合NUD_VALID。
static bool ipv4_good_nh(const struct fib_nh *nh)
{
int state = NUD_REACHABLE;
struct neighbour *n;
rcu_read_lock_bh();
n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
(__force u32)nh->fib_nh_gw4);
if (n)
state = n->nud_state;
rcu_read_unlock_bh();
return !!(state & NUD_VALID);
SYSCTL邻居检查
如果路由没有匹配上一节的下一跳对象,并且sysctl_fib_multipath_use_neigh设置为真,将由函数fib_good_nh检查下一跳结构的有效性。前提条件是下一跳对象的scope范围为RT_SCOPE_LINK,即本地链路。
static bool fib_good_nh(const struct fib_nh *nh)
{
int state = NUD_REACHABLE;
if (nh->fib_nh_scope == RT_SCOPE_LINK) {
struct neighbour *n;
rcu_read_lock_bh();
if (likely(nh->fib_nh_gw_family == AF_INET))
n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
(__force u32)nh->fib_nh_gw4);
else if (nh->fib_nh_gw_family == AF_INET6)
n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev,
&nh->fib_nh_gw6);
else
n = NULL;
if (n)
state = n->nud_state;
rcu_read_unlock_bh();
}
return !!(state & NUD_VALID);
内核版本 5.10