Java教程

IPv4添加路由表项

本文主要是介绍IPv4添加路由表项,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

如下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

这篇关于IPv4添加路由表项的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!