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