ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

IPv4添加路由表项

2021-11-19 23:58:45  阅读:419  来源: 互联网

标签:info alias fib fa 添加 IPv4 new tb 路由表


如下IP命令添加路由表项,默认情况下路由添加在main路由表中:

# ip route add 19.1.0.0/16 via 192.168.9.1
#
# ip route show table main 
19.1.0.0/16 via 192.168.9.1 dev ens34 

内核函数inet_rtm_newroute处理路由的添加。函数rtm_to_fib_config将netlink数据转换为内核结构fib_config,fib_table_insert根据fib_config内容执行路由表项添加操作。

static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
                 struct netlink_ext_ack *extack)
{
    struct net *net = sock_net(skb->sk);
    struct fib_config cfg;
    struct fib_table *tb;

    err = rtm_to_fib_config(net, skb, nlh, &cfg, extack);
    if (err < 0)
        goto errout;

    tb = fib_new_table(net, cfg.fc_table);
    if (!tb) {
        err = -ENOBUFS;
        goto errout;
    }

    err = fib_table_insert(net, tb, &cfg, extack);
    if (!err && cfg.fc_type == RTN_LOCAL)
        net->ipv4.fib_has_custom_local_routes = true;

路由表项添加

路由表项插入函数fib_table_insert,首先检查前缀和前缀长度plen的合法性,在IPv4中,前缀长度plen不能大于32,并且前缀key除去plen长度之后的部分应为全零,参见函数fib_valid_key_len的实现。

int fib_table_insert(struct net *net, struct fib_table *tb,
             struct fib_config *cfg, struct netlink_ext_ack *extack)
{
    struct trie *t = (struct trie *)tb->tb_data;
    struct fib_alias *fa, *new_fa;
    struct key_vector *l, *tp;
    u16 nlflags = NLM_F_EXCL;
    struct fib_info *fi;
    u8 plen = cfg->fc_dst_len;
    u8 slen = KEYLENGTH - plen;
    u8 tos = cfg->fc_tos;

    key = ntohl(cfg->fc_dst);

    if (!fib_valid_key_len(key, plen, extack))
        return -EINVAL;

    pr_debug("Insert table=%u %08x/%d\n", tb->tb_id, key, plen);

对于每一个路由表项,内核创建一个fib_info结构,但是,如果此路由表项引用一个nexthop项,可能已经存在可用的fib_info结构,在函数fib_create_info中进行相应判断。

# ip nexthop add id 1 via 192.168.2.1 dev ens33
# 
# ip route add 192.2.0.0/16 nhid 1  

接下来需要找到一个fib_alias结构,利用其将新的路由项fib_info添加到trie树中。函数fib_find_node根据目的网络前缀值key在trie树中查找合适的节点,参见 IPv4路由tries树节点添加与查找

如果l有值表明找到了合适的叶子节点,之后遍历叶子节点的fib_alias链表(fib_find_alias函数),查看是否存在可用的fib_alias结构。如果找到fa,表明其与要添加表项前缀/后缀/表ID等完全相同,但是tos和priority不一定相等,之后的代码判断这两项。在tos和priority也相同的情况下,如果新路由表项设置了标志NLM_F_EXCL,保留老的表项,退出处理。

    fi = fib_create_info(cfg, extack);

    l = fib_find_node(t, &tp, key);
    fa = l ? fib_find_alias(&l->leaf, slen, tos, fi->fib_priority, tb->tb_id, false) : NULL;

    /* Now fa, if non-NULL, points to the first fib alias
     * with the same keys [prefix,tos,priority], if such key already
     * exists or to the node before which we will insert new one.
     * If fa is NULL, we will need to allocate a new one and
     * insert to the tail of the section matching the suffix length of the new alias.
     */
    if (fa && fa->fa_tos == tos && fa->fa_info->fib_priority == fi->fib_priority) {
        struct fib_alias *fa_first, *fa_match;

        err = -EEXIST;
        if (cfg->fc_nlflags & NLM_F_EXCL)
		    goto out;

在没有设置NLM_F_EXCL标志的情况下,由叶子节点的fib_alias链表的位置fa开始,继续遍历,如果当前遍历fa的后缀长度、表ID、tos值、优先级priority的其中一项与配置项不同结束遍历(这些项在链表中都是按照顺序排列的)。如果以上项都相等,并且路由类型也相等,而且此fib_alias指向的fib_info与新插入路由的fib_info是同一个,即找到匹配的fib_alias。

通过函数fib_create_info可知,一般情况下都会创建新的fib_info结构,仅在路由配置时使用了nhid时有可能使用已有的fib_info结构。所以以下fa->fa_info等于fi只在后一种情况下生效。下一跳未使用nhid的通用路由,fa_match总是空。

        nlflags &= ~NLM_F_EXCL;
        /* We have 2 goals:
         * 1. Find exact match for type, scope, fib_info to avoid duplicate routes
         * 2. Find next 'fa' (or head), NLM_F_APPEND inserts before it */
        fa_first = fa;
        hlist_for_each_entry_from(fa, fa_list) {
            if ((fa->fa_slen != slen) || (fa->tb_id != tb->tb_id) || (fa->fa_tos != tos))
                break;
            if (fa->fa_info->fib_priority != fi->fib_priority)
                break;
            if (fa->fa_type == cfg->fc_type && fa->fa_info == fi) {
                fa_match = fa;
                break;
            }
        }

以下处理替换已有路由(NLM_F_REPLACE)的情况,如果以上hlist_for_each_entry_from的遍历(包括fa自身)找到了匹配的fa_match,说明表项已经存在,返回EEXIST,但是如果fa_match与函数fib_find_alias返回的fa值相等,函数返回0(不清楚这里的逻辑:匹配项是第一个时,替换成功,否则,返回表项已存在。可能与在fib_table_lookup函数中进行路由查询时,首先匹配第一个fa有关系)。否则,在fa_match为空的情况下(路由类型或者fib_info不相同)分配一个新的fib_alias,进行初始化,并替换掉链表中旧的fib_alias节点。新节点的fa_info指向当前的fib_info结构。

        if (cfg->fc_nlflags & NLM_F_REPLACE) {
            struct fib_info *fi_drop;
            u8 state;

            nlflags |= NLM_F_REPLACE;
            fa = fa_first;
            if (fa_match) {
                if (fa == fa_match)
                    err = 0;
                goto out;
            }
            err = -ENOBUFS;
            new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
            if (!new_fa) goto out;

            fi_drop = fa->fa_info;
            new_fa->fa_tos = fa->fa_tos;
            new_fa->fa_info = fi;
            new_fa->fa_type = cfg->fc_type;
            state = fa->fa_state;
            new_fa->fa_state = state & ~FA_S_ACCESSED;
            new_fa->fa_slen = fa->fa_slen;
            new_fa->tb_id = tb->tb_id;
            new_fa->fa_default = -1;
            new_fa->offload = 0;
            new_fa->trap = 0;

            hlist_replace_rcu(&fa->fa_list, &new_fa->fa_list);

函数fib_find_alias遍历叶子结点的fib_alias链表,找到第一个后缀长度为fa_slen的项(这里的查找忽略了tos和priority的值),如果其等于新添加的new_fa,发送FIB_EVENT_ENTRY_REPLACE通知链事件。

            if (fib_find_alias(&l->leaf, fa->fa_slen, 0, 0, tb->tb_id, true) == new_fa) {
                enum fib_event_type fib_event;

                fib_event = FIB_EVENT_ENTRY_REPLACE;
                err = call_fib_entry_notifiers(net, fib_event, key, plen, new_fa, extack);
                if (err) {
                    hlist_replace_rcu(&new_fa->fa_list, &fa->fa_list);
                    goto out_free_new_fa;
                }
            }

之后,向应用层发送新路由添加的rtnetlink消息。释放原有的fib_alias结构fa以及其指向的fib_info,结束处理。

            rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, tb->tb_id, &cfg->fc_nlinfo, nlflags);

            alias_free_mem_rcu(fa);

            fib_release_info(fi_drop);
            if (state & FA_S_ACCESSED)
                rt_cache_flush(cfg->fc_nlinfo.nl_net);

            goto succeeded;
        }

如果没有设置替换标志NLM_F_REPLACE,在scope、type、nexthop都相同的情况下,返回EEXIST。之后处理新建表项的情况,如果没有设置NLM_F_CREATE,返回错误。

        /* Error if we find a perfect match which
         * uses the same scope, type, and nexthop information.
         */
        if (fa_match) goto out;

        if (cfg->fc_nlflags & NLM_F_APPEND)
            nlflags |= NLM_F_APPEND;
        else
            fa = fa_first;
    }
    err = -ENOENT;
    if (!(cfg->fc_nlflags & NLM_F_CREATE))
        goto out;

对于没有匹配fib_alias的情况,这里分配新的fib_alias,将其添加到trie树中。

    nlflags |= NLM_F_CREATE;
    err = -ENOBUFS;
    new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
    if (!new_fa) goto out;

    new_fa->fa_info = fi;
    new_fa->fa_tos = tos;
    new_fa->fa_type = cfg->fc_type;
    new_fa->fa_state = 0;
    new_fa->fa_slen = slen;
    new_fa->tb_id = tb->tb_id;
    new_fa->fa_default = -1;
    new_fa->offload = 0;
    new_fa->trap = 0;

    /* Insert new entry to the list. */
    err = fib_insert_alias(t, tp, l, new_fa, fa, key);
    if (err)
        goto out_free_new_fa;

由于以上添加了叶子节点,这里的查询叶子节点必定存在。遍历叶子结点的fib_alias链表,找到第一个后缀长度等于新fib_alias后缀长度fa_slen的项,如果其等于新创建的fib_alias,发送FIB_EVENT_ENTRY_REPLACE通知链事件。

    /* The alias was already inserted, so the node must exist. */
    l = l ? l : fib_find_node(t, &tp, key);
    if (WARN_ON_ONCE(!l))
        goto out_free_new_fa;

    if (fib_find_alias(&l->leaf, new_fa->fa_slen, 0, 0, tb->tb_id, true) ==
        new_fa) {
        enum fib_event_type fib_event;

        fib_event = FIB_EVENT_ENTRY_REPLACE;
        err = call_fib_entry_notifiers(net, fib_event, key, plen, new_fa, extack);
        if (err)
            goto out_remove_new_fa;
    }

最后,通知用户层新路由表项的创建。

    if (!plen)
        tb->tb_num_default++;

    rt_cache_flush(cfg->fc_nlinfo.nl_net);
    rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id, &cfg->fc_nlinfo, nlflags);
succeeded:
    return 0;

前缀长度检查

如下IP命令提示错误,对于16位前缀长度,前缀应当为192.2.0.0。

# ip route add 192.2.1.0/16 via 192.168.1.1
Error: Invalid prefix for given prefix length.
#
# ip route add 192.2.1.0/33 via 192.168.1.1  
Error: any valid prefix is expected rather than "192.2.1.0/33".

函数fib_valid_key_len返回此错误信息。对于前缀长度超过KEYLENGTH(32)的情况,IP命令本身就会提示错误信息,如上所示,所以看不到内核中的错误信息(Invalid prefix length)。

static bool fib_valid_key_len(u32 key, u8 plen, struct netlink_ext_ack *extack)
{
    if (plen > KEYLENGTH) {
        NL_SET_ERR_MSG(extack, "Invalid prefix length");
        return false;
    }

    if ((plen < KEYLENGTH) && (key << plen)) {
        NL_SET_ERR_MSG(extack, "Invalid prefix for given prefix length");
        return false;
    }

    return true;

内核版本 5.10

标签:info,alias,fib,fa,添加,IPv4,new,tb,路由表
来源: https://blog.csdn.net/sinat_20184565/article/details/121217377

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有