多径路由选择

以函数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

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页