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