一、初始化
inet_init
ip_init
ip_rt_init
//计算随机数,该随机数在路由缓存生成hash关键字时做为一个参数使用,目
//的是为了防止DDOS攻击,该随机值后期在每次缓存刷新时也会重新生成。
rt_hash_rnd = (int) ((num_physpages ^ (num_physpages>>8)) ^
(jiffies ^ (jiffies >> 7)));
//为基于路由的分类器分配统计信息的内存页,每个CPU占用256项。
for (order = 0;(PAGE_SIZE << order) < 256 * sizeof(struct ip_rt_acct) *
NR_CPUS; order++)
ip_rt_acct = (struct ip_rt_acct *)__get_free_pages(GFP_KERNEL, order);
memset(ip_rt_acct, 0, PAGE_SIZE << order);
//为路由缓存构造内存池
ipv4_dst_ops.kmem_cachep =
kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
//为路由缓存构造hash表
rt_hash_table = (struct rt_hash_bucket *)
alloc_large_system_hash("IP route cache",sizeof(struct rt_hash_bucket),
rhash_entries,(num_physpages >= 128 * 1024) ?15 : 17,0,
&rt_hash_log,&rt_hash_mask,0);
memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket));
//hash锁初始化
rt_hash_lock_init();
//进行强制路由缓存垃圾回收的阀值,为路由缓存hash桶的个数。
ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1);
//允许最大的路由缓存条目数,为路由缓存hash桶的16倍。
ip_rt_max_size = (rt_hash_mask + 1) * 16;
devinet_init();
//为SIOCGIFCONF ioctl项设置回调,当用户层使用设置该ioctl项时
//,该回调会给用户层返回请求的设备接口的IP地址信息。
register_gifconf(PF_INET, inet_gifconf);
//设置设备通知链,当设备模块有变动时会使用该回调来通知设备接口
//的变动信息,包括设备UP、DOWN、注册、解注册、MTU参数变化、
//设备名称变化等。
register_netdevice_notifier(&ip_netdev_notifier);
//设置netlink套接口处理的回调,当前新的ip2工具添加删除路由表项
//时都使用netlinke机制,就会触发这里的回调进行路由项操作。
rtnetlink_links[PF_INET] = inet_rtnetlink_table;
//向/proc/sys创建配置文件
devinet_sysctl.sysctl_header =
register_sysctl_table(devinet_sysctl.devinet_root_dir, 0);
devinet_sysctl_register(NULL, &ipv4_devconf_dflt);
//路由表初始化
ip_fib_init();
for (i = 0; i < FIB_TABLE_HASHSZ; i++)
INIT_HLIST_HEAD(&fib_table_hash[i]);
//策略路由初始化
fib4_rules_init();
//添加3条默认的策略规则,其中local_rule为查找本地路由表(即
//本地接口地址、广播地址的路由表项),main_rule为查找主路由表
//(即平时使用route命令可以操作的路由表项)。default_rule为查
//找默认路由表项,目前基本不怎么使用。这3个默认表项中其中
//local的优先级最高,其次是main、default,所以搜索顺序也是
//先从local表开始,另外local_rule设置了FIB_RULE_PERMANENT
//标记,含有该标记的规则在使用用户接口进行策略规则删除时是
//被禁止的。
list_add_tail(&local_rule.common.list, &fib4_rules);
list_add_tail(&main_rule.common.list, &fib4_rules);
list_add_tail(&default_rule.common.list, &fib4_rules);
//向rules_ops链表中添加ipv4的策略规则操作对象。后续针对ipv4
//策略路由处理时,都使用该操作对象的函数。
fib_rules_register(&fib4_rules_ops);
//注册设备通知回调,当设备相关事件变更后,会使用该回调进行路由模
//块相关操作,后面在“外部事件”小节中进行分析。
register_netdevice_notifier(&fib_netdev_notifier);
//注册设备地址变更通知回调,后面在“外部事件”小节中进行分析。
register_inetaddr_notifier(&fib_inetaddr_notifier);
//创建NETLINK_FIB_LOOKUP类型的netlink套接口,接收处理函数为
//nl_fib_input,该回调可以根据传入的参数进行路由表查找,并返回查询
//结果。
nl_fib_lookup_init();
netlink_kernel_create(NETLINK_FIB_LOOKUP, 0, nl_fib_input,
THIS_MODULE);
//初始化路由缓存刷新定时器,该定时器的回调函数完成路由缓存的整个
//刷新。
init_timer(&rt_flush_timer);
rt_flush_timer.function = rt_run_flush;
//路由缓存的异步垃圾回收定时器
init_timer(&rt_periodic_timer);
rt_periodic_timer.function = rt_check_expire;
//周期性路由缓存刷新定时器
init_timer(&rt_secret_timer);
rt_secret_timer.function = rt_secret_rebuild;
//启动异步垃圾回收定时器,初始时间为1分钟多。
rt_periodic_timer.expires = jiffies + net_random() % ip_rt_gc_interval +
ip_rt_gc_interval;
add_timer(&rt_periodic_timer);
//启动周期性路由缓存刷新定时器,初始时间为10分钟左右。
rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval +
ip_rt_secret_interval;
add_timer(&rt_secret_timer);
//为路由缓存查看创建proc文件
struct proc_dir_entry *rtstat_pde = NULL;
proc_net_fops_create("rt_cache", S_IRUGO, &rt_cache_seq_fops)
rtstat_pde = create_proc_entry("rt_cache", S_IRUGO,proc_net_stat)
rtstat_pde->proc_fops = &rt_cpu_seq_fops;
//为基于路由的分类信息创建proc文件
create_proc_read_entry("rt_acct", 0, proc_net, ip_rt_acct_read, NULL);
//安全模块,暂不分析。
xfrm_init();
xfrm4_init();
二、添加路由条目
对于路由模块的分析,不管是路由条目的添加、删除,还是路由表的查找,都会涉及一些
数据对象的关联,内核模块的开发者为了防止添加大量路由表项占用过多内存,对路由
表项数据的关联进行了灵活的划分,清楚这些数据对象的关联,对于整解路由模块会
有很大帮助。一条路由条目大体数据对象关联如下:
zone-->node-->alias->info->nexttop
a、zone对象对应添加路由条目时的子网掩码,所有相同子网掩码的路由都在一个zone对象下。
b、node对象对应添加路由条目时的目的地址,所有相同子网掩码,相同目的地址都在一个node对象下。
c、alias对象对应添加路由条目时,除目的地址、子网掩码之外的其它匹配项,比如tos值、
scope范围值,这些参数组合在一起才是一条路由条目的完整匹配条件,只是平常我们在
进行路由表添加时多数都只是关注目的地址、子网掩码,而忽略了这些匹配条件。有时候
在添加路由表项的目的地址、子网掩码都相同,如果tos值不同,则该条目就会在原有
zone对象下node对象的alias链表中多添加一个alias对象,来区分仅仅是tos不同的另
外一个路由条目。
d、info对象表示当前路由条目匹配成功后的目的信息,比如优先源地址、优先级、
各种度量值(PMTU、TTL等)。
e、nexttop对象表示当前路由条目匹配成功后的下一跳信息,包括出口设备、下一跳
范围参数、出口设备索引、是否有网关等。
1、用户侧处理(仅分析ip route命令)
添加一个网段的路由,发往202.100网段的报文,从eth0设备输出,下一跳地址为
192.168.1.1。
ip route add 202.100.0.0/16 dev eth0 via 192.168.1.1
--------------------------------------------------------------------------------------------------------------------
main
//创建NETLINK_ROUTE协议类型的netlink套接口,用于和内核通讯
rtnl_open(&rth, 0)
//遍历当前支持的所有子命令,当前命令为route,对应的子命令处理函数为do_iproute
do_cmd(argv[1], argc-1, argv+1);
for (c = cmds; c->cmd; ++c)
if (matches(argv0, c->cmd) == 0)
return -(c->func(argc-1, argv+1));
//关闭创建的netlink套接口
rtnl_close(&rth);
---------------------------------------------------------------------------------------------------------------------
do_iproute
if (matches(*argv, "add") == 0)
iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_EXCL,
argc-1, argv+1);
//初始化netlink消息头
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
// NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL
req.n.nlmsg_flags = NLM_F_REQUEST|flags;
req.n.nlmsg_type = cmd; //RTM_NEWROUTE
req.r.rtm_family = preferred_family; //AF_UNSPEC
req.r.rtm_table = RT_TABLE_MAIN;
req.r.rtm_scope = RT_SCOPE_NOWHERE;
if (cmd != RTM_DELROUTE)
req.r.rtm_protocol = RTPROT_BOOT;
req.r.rtm_scope = RT_SCOPE_UNIVERSE;
req.r.rtm_type = RTN_UNICAST;
//初始化METRICS属性
mxrta->rta_type = RTA_METRICS;
mxrta->rta_len = RTA_LENGTH(0);
while (argc > 0)
if (strcmp(*argv, "via") == 0)
gw_ok = 1;
NEXT_ARG();
get_addr(&addr, *argv, req.r.rtm_family);
get_addr_1(dst, arg, family)
//没有指定地址族时,默认为IPV4
addr->family = AF_INET;
//获取命令行传入的地址,存储到addr->data中。
get_addr_ipv4((__u8 *)addr->data, name)
addr->bytelen = 4;
addr->bitlen = -1;
//在netlink消息尾部增加RTA_GATEWAY属性值。
addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data,
addr.bytelen);
else if (strcmp(*argv, "dev") == 0 ||strcmp(*argv, "oif") == 0)
NEXT_ARG();
d = *argv; //指针d指向设备名称字符串
else
//获取当前网络地址参数
//dst.data = IP地址
//dst.bytelen = 4
//dst.bitlen = -1
//dst->flags |= PREFIXLEN_SPECIFIED
//dst->bitlen = 掩码比特位;
get_prefix(&dst, *argv, req.r.rtm_family);
req.r.rtm_dst_len = dst.bitlen; //掩码比特位;
dst_ok = 1;
//在netlink消息尾部增加RTA_DST属性值。
addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen);
//在netlink消息尾部增加输出接口的索引
if (d || nhs_ok)
idx = ll_name_to_index(d)
addattr32(&req.n, sizeof(req), RTA_OIF, idx);
//发送netlink消息。
rtnl_talk(&rth, &req.n, 0, 0, NULL)
2、内核侧处理
inet_rtm_newroute
//将用户层传来的参数转换为配置块
rtm_to_fib_config(skb, nlh, &cfg);
cfg->fc_dst_len = rtm->rtm_dst_len; //掩码比特位
cfg->fc_tos = rtm->rtm_tos; //0
cfg->fc_table = rtm->rtm_table; //RT_TABLE_MAIN
cfg->fc_protocol = rtm->rtm_protocol; //RTPROT_BOOT
cfg->fc_scope = rtm->rtm_scope; //RT_SCOPE_UNIVERSE
cfg->fc_type = rtm->rtm_type; //RTN_UNICAST
cfg->fc_flags = rtm->rtm_flags; //无
//NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL
cfg->fc_nlflags = nlh->nlmsg_flags;
cfg->fc_nlinfo.pid = NETLINK_CB(skb).pid;
cfg->fc_nlinfo.nlh = nlh;
//分别将用户侧传入的目的地址、网关、输出设备索引存储到cfg中
nlmsg_for_each_attr(attr, nlh, sizeof(struct rtmsg), remaining)
switch (attr->nla_type)
case RTA_DST:
cfg->fc_dst = nla_get_be32(attr);
case RTA_GATEWAY:
cfg->fc_gw = nla_get_be32(attr);
case RTA_OIF:
cfg->fc_oif = nla_get_u32(attr);
//根据用户侧路由表ID,查找路由表对象,当前为MAIN表。
tb = fib_new_table(cfg.fc_table);
h = id & (FIB_TABLE_HASHSZ - 1);
hlist_for_each_entry_rcu(tb, node, &fib_table_hash[h], tb_hlist)
if (tb->tb_id == id)
return tb;
//调用路由表对象的回调插入路由条目,其中main表对象是在ip_fib_init中创建
//的,可以参考上面初始化分析小节。假设当前使用的是hash类型路由表,则回
//调函数为fn_hash_insert。
tb->tb_insert(tb, &cfg);
fn_hash_insert
//根据目的网络地址掩码找到当前掩码区节点对象。Ipv4掩码长度为32位,
//则对应有32个掩码区节点对象。
fz = table->fn_zones[cfg->fc_dst_len];
//如果当前掩码区节点对象不存在则创建一个新的。
//1、掩码区节点对象的双向链表按掩码长度从短到长排列。
//2、table->fn_zone_list指向最长掩码的节点对象,这样在进行路由查找时
//,通过遍历fn_zone_list就可以实现最长优级匹配的处理原则。
//3、除了掩码为0(就是默认网关,即0.0.0.0)的节点对象只创建1个hash
//桶外,其它节点对象都创建16个hash桶。
if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len)))
return -ENOBUFS;
key = 0;
if (cfg->fc_dst)
//将目标地址与子网掩码与操作,得到的网络地址做为key
key = fz_key(cfg->fc_dst, fz);
fi = fib_create_info(cfg);
//校正范围是否出错
if (fib_props[cfg->fc_type].scope > cfg->fc_scope)
goto err_inval;
err = -ENOBUFS;
//INFO对象的个数如果已经达到当前上线,则进行扩冲
if (fib_info_cnt >= fib_hash_size)
//每次扩冲大小为原来的2倍
unsigned int new_size = fib_hash_size << 1;
//初始fib_hash_size为0,所以第1次扩冲为1个条目
if (!new_size)
new_size = 1;
bytes = new_size * sizeof(struct hlist_head *);
//INFO对象会存储到两个hash表中,这里一次分配两个
new_info_hash = fib_hash_alloc(bytes);
new_laddrhash = fib_hash_alloc(bytes);
memset(new_info_hash, 0, bytes);
memset(new_laddrhash, 0, bytes);
//将原来的hash列表对象成员移到新的hash表中。
fib_hash_move(new_info_hash, new_laddrhash, new_size);
old_info_hash = fib_info_hash;
old_laddrhash = fib_info_laddrhash;
fib_hash_size = new_size;
//将原来fib_info_hash项删除,并全部移到新的hash表中。
for (i = 0; i < old_size; i++)
struct hlist_head *head = &fib_info_hash[i];
struct hlist_node *node, *n;
struct fib_info *fi;
hlist_for_each_entry_safe(fi, node, n, head, fib_hash)
hlist_del(&fi->fib_hash);
new_hash = fib_info_hashfn(fi);
dest = &new_info_hash[new_hash];
hlist_add_head(&fi->fib_hash, dest);
fib_info_hash = new_info_hash;
//将原来fib_info_laddrhash项删除,并全部移到新的hash表中。
for (i = 0; i < old_size; i++)
struct hlist_head *lhead = &fib_info_laddrhash[i];
struct hlist_node *node, *n;
struct fib_info *fi;
hlist_for_each_entry_safe(fi, node, n, lhead, fib_lhash)
struct hlist_head *ldest;
unsigned int new_hash;
hlist_del(&fi->fib_lhash);
new_hash = fib_laddr_hashfn(fi->fib_prefsrc);
ldest = &new_laddrhash[new_hash];
hlist_add_head(&fi->fib_lhash, ldest);
fib_info_laddrhash = new_laddrhash;
//释放老hash表占用的内存
bytes = old_size * sizeof(struct hlist_head *);
fib_hash_free(old_info_hash, bytes);
fib_hash_free(old_laddrhash, bytes);
fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
fib_info_cnt++;
//RTPROT_BOOT
fi->fib_protocol = cfg->fc_protocol;
fi->fib_flags = cfg->fc_flags; //无
fi->fib_priority = cfg->fc_priority; //0
fi->fib_prefsrc = cfg->fc_prefsrc; //0
//当前不对多路径功能进行分析,则默认下一跳个数为1
fi->fib_nhs = nhs;
//遍历fi对象中所有下一跳对象,将其关联到fi对象。
change_nexthops(fi)
nh->nh_parent = fi;
endfor_nexthops(fi)
//如果配置了度量相关参数,则存储到fi对象中。
if (cfg->fc_mx)
nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining)
int type = nla->nla_type;
fi->fib_metrics[type - 1] = nla_get_u32(nla);
//将出口设备索引、下一跳网关、标记参数、基于路由分类ID参数存储
//到下一跳对象中。
struct fib_nh *nh = fi->fib_nh;
nh->nh_oif = cfg->fc_oif;
nh->nh_gw = cfg->fc_gw;
nh->nh_flags = cfg->fc_flags;
nh->nh_tclassid = cfg->fc_flow;
//有些路由条目类型不能配置下一跳网关、出口设备等,这里进行校验
if (fib_props[cfg->fc_type].error)
if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
goto err_inval;
goto link_it;
//无效范围校验,HOST范围已经最小的了。
if (cfg->fc_scope > RT_SCOPE_HOST)
goto err_inval;
//HOST范围的路由条目类型,设置及下一跳对象的范围为不到达任何
//地方,即不离开本机。
if (cfg->fc_scope == RT_SCOPE_HOST)
struct fib_nh *nh = fi->fib_nh;
if (nhs != 1 || nh->nh_gw)
goto err_inval;
nh->nh_scope = RT_SCOPE_NOWHERE;
nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
//其它范围的路由条目类型
else
change_nexthops(fi)
//更改下一跳对象参数
fib_check_nh(cfg, fi, nh)
//下一跳含有出口网关情况
if (nh->nh_gw)
//如果当前标记下一跳为同一链路,则不再进行下一跳
//路由查找处理。
if (nh->nh_flags&RTNH_F_ONLINK)
//校验范围值、出口网关类型、输口设备是否存在
//并且为UP状态。
if (cfg->fc_scope >= RT_SCOPE_LINK)
return -EINVAL;
if (inet_addr_type(nh->nh_gw) != RTN_UNICAST)
return -EINVAL;
if ((dev = __dev_get_by_index(nh->nh_oif)) ==
NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
//设置下一跳对象参数
nh->nh_dev = dev;
dev_hold(dev);
nh->nh_scope = RT_SCOPE_LINK;
return 0;
struct flowi fl = {
.nl_u = {
.ip4_u = {
.daddr = nh->nh_gw,
.scope = cfg->fc_scope + 1,
.oif = nh->nh_oif,
if (fl.fl4_scope < RT_SCOPE_LINK)
fl.fl4_scope = RT_SCOPE_LINK;
//使用目的地为当前要配置的出口网关进行路由表查
//找,如果当前路由表没有可到达这个出口网关的路由
//,则当前路由条目插入失败。
if ((err = fib_lookup(&fl, &res)) != 0)
return err;
//合法类型校验
if (res.type != RTN_UNICAST && res.type !=
RTN_LOCAL)
goto out;
//下一跳的范围从路由查找结果中获取
nh->nh_scope = res.scope;
//下一跳的输出接口
nh->nh_oif = FIB_RES_OIF(res);
nh->nh_dev = FIB_RES_DEV(res)
dev_hold(nh->nh_dev);
//校验接口是否UP
err = -ENETDOWN;
if (!(nh->nh_dev->flags & IFF_UP))
goto out;
err = 0;
//在fib_lookup时增加了引用,这里再递减
fib_res_put(&res);
return err;
//没有出口网关的情况
else
//这两个标记仅用于网关情况
if (nh->nh_flags&(RTNH_F_PERVASIVE|
RTNH_F_ONLINK))
return -EINVAL;
//获取输出设备的IPV4设备对象
in_dev = inetdev_by_index(nh->nh_oif);
if (in_dev == NULL)
return -ENODEV;
//设备必须为UP状态
if (!(in_dev->dev->flags&IFF_UP))
in_dev_put(in_dev);
return -ENETDOWN;
nh->nh_dev = in_dev->dev;
dev_hold(nh->nh_dev);
//下一跳的范围为本地主机
nh->nh_scope = RT_SCOPE_HOST;
in_dev_put(in_dev);
endfor_nexthops(fi)
//如果配置了首选源地址,在以下三种情况下需要校验这个地址是否是
//当前设备的地址,如果不是则为非法。
//1、当前路由条目类型不是本地
//2、或者当前路由的目的地址为0.0.0.0
//3、或者当前首选源地址不等于目的地址
if (fi->fib_prefsrc)
if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||
fi->fib_prefsrc != cfg->fc_dst)
if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
goto err_inval;
link_it:
//检测当前info对象是否已经在fib_info_hash表中存在,如果存在,则
//释放之前创建的info对象资源,同时增加已存在对象的引用计数。
if ((ofi = fib_find_info(fi)) != NULL)
fi->fib_dead = 1;
free_fib_info(fi);
ofi->fib_treeref++;
return ofi;
//增加info对象的引用计数。
//fib_treeref是对象引用计数,fib_clntref是查找结果后的引用计数
fi->fib_treeref++;
atomic_inc(&fi->fib_clntref);
//将新建的info对象插入到fib_info_hash表中。
hlist_add_head(&fi->fib_hash,&fib_info_hash[fib_info_hashfn(fi)]);
//如果配置了首选源,则将该info对象插入到fib_info_laddrhash表中
if (fi->fib_prefsrc)
head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];
hlist_add_head(&fi->fib_lhash, head);
change_nexthops(fi)
if (!nh->nh_dev)
continue;
//将所有下一跳对象插入到fib_info_devhash表中
hash = fib_devindex_hashfn(nh->nh_dev->ifindex);
head = &fib_info_devhash[hash];
hlist_add_head(&nh->nh_hash, head);
endfor_nexthops(fi)
return fi;
err_inval:
err = -EINVAL;
failure:
if (fi)
fi->fib_dead = 1;
free_fib_info(fi);
return ERR_PTR(err);
//如果当前区域对象个数已经超出区域hash桶个数的2倍,并且当前目的
//地址掩码为32,或者2^掩码大于区域hash桶个数。则区域hash表需要
//重新调整。
if (fz->fz_nent > (fz->fz_divisor<<1) &&fz->fz_divisor < FZ_MAX_DIVISOR
&&(cfg->fc_dst_len == 32 || (1 << cfg->fc_dst_len) > fz->fz_divisor))
fn_rehash_zone(fz);
old_divisor = fz->fz_divisor;
//根据原来的桶数进行扩冲计算,掩码为全0(即默认网关)的桶
//初始值为1,其它掩码的桶初始值为16,这里可以看到如果是其
//它掩码的桶则前两次扩冲倍数很大。
switch (old_divisor)
case 16:
new_divisor = 256;
case 256:
new_divisor = 1024;
default:
new_divisor = (old_divisor << 1);
new_hashmask = (new_divisor - 1);
//创建新的hash表
ht = fz_hash_alloc(new_divisor);
//将老的hash表条目移动到新的hash表中
memset(ht, 0, new_divisor * sizeof(struct hlist_head));
old_ht = fz->fz_hash;
fz->fz_hash = ht;
fz->fz_hashmask = new_hashmask;
fz->fz_divisor = new_divisor;
fn_rebuild_zone(fz, old_ht, old_divisor);
fib_hash_genid++;
//删除旧的hash表
fz_hash_free(old_ht, old_divisor);
//使用目的网络地址做为key,进行路由node对象查找。
f = fib_find_node(fz, key);
if (!f)
fa = NULL;
else
//如果找到node对象,则根据tos值及优先级查找node对象下的
//alias对象。查找条件如下,后面会将新建的alias对象插入到此
//alias对象之后。根据查找条件可以看出alias的排列条件为,首先看tos
//值,tos越小越优先,当tos值相等时,优先级越大越优先。
//1、tos值相等,优先级不能小于
//2、或者tos值小于
fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority);
//如果有相同tos值,并且优先级也相同,则表明已经存在完全相同的路由条
//目。
if (fa && fa->fa_tos == tos &&fa->fa_info->fib_priority == fi->fib_priority)
err = -EEXIST;
//当前使用ip route命令,则存在此标记位,如果路由条目已经存在,
//则返回错误。
if (cfg->fc_nlflags & NLM_F_EXCL)
goto out;
//如果用户层传入标记为替换,则将该规则项的下一跳相关信息进行替
//换。
if (cfg->fc_nlflags & NLM_F_REPLACE)
//更新下一跳等相关信息。
fi_drop = fa->fa_info;
fa->fa_info = fi;
fa->fa_type = cfg->fc_type;
fa->fa_scope = cfg->fc_scope;
//获取当前规则项的使用情况
state = fa->fa_state;
fa->fa_state &= ~FA_S_ACCESSED;
fib_hash_genid++;
//释放之前规则项引用的老的info对象
fib_release_info(fi_drop);
//如果之前该路由规则被路由过,则启动刷新定时器,后面在异步
//垃圾回收中再进行分析。
if (state & FA_S_ACCESSED)
rt_cache_flush(-1);
return 0;
fa_orig = fa;
//从当前alias对象位置起,开始遍历node对象下的alias对象链表,查
//找一个与当前tos不同、优先级不同的alias对象,如果出现不但tos、
//优先级相同,连fa_type、fa_scope、fa_info也都相同,则表明是一个
//一模一样的条目出现,直接返回。
fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
list_for_each_entry_continue(fa, &f->fn_alias, fa_list)
if (fa->fa_tos != tos)
break;
if (fa->fa_info->fib_priority != fi->fib_priority)
break;
if (fa->fa_type == cfg->fc_type &&fa->fa_scope == cfg->fc_scope &&
fa->fa_info == fi)
goto out;
//如果用户传入标记为NLM_F_APPEND,则新的alias对象插入到
//上面找到的对象尾部,否则新的alias对象插入到刚才找到的相同tos、
//相同优先级alias对象的前面。
if (!(cfg->fc_nlflags & NLM_F_APPEND))
fa = fa_orig;
//正常情况下,走到这里,表明没有相同的条目,如果用户没有需要创建的
//标记,则返回错误。
err = -ENOENT;
if (!(cfg->fc_nlflags & NLM_F_CREATE))
goto out;
//分配新的alias对象资源
err = -ENOBUFS;
new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
if (new_fa == NULL)
goto out;
//如果之前查找,则返和目的地址相同的node对象,则分配新的node对象。
new_f = NULL;
if (!f)
new_f = kmem_cache_alloc(fn_hash_kmem, GFP_KERNEL);
INIT_HLIST_NODE(&new_f->fn_hash);
INIT_LIST_HEAD(&new_f->fn_alias);
new_f->fn_key = key;
f = new_f;
//将新的alias对象与info对象关联,同时设置新的alias对象参数
new_fa->fa_info = fi;
new_fa->fa_tos = tos;
new_fa->fa_type = cfg->fc_type;
new_fa->fa_scope = cfg->fc_scope;
new_fa->fa_state = 0;
//如果node对象是新建的,则将node对象插入到区哉对象的hash表中
if (new_f)
fib_insert_node(fz, new_f);
//将alias对象关联到node对象中
list_add_tail(&new_fa->fa_list,
(fa ? &fa->fa_list : &f->fn_alias));
fib_hash_genid++;
if (new_f)
fz->fz_nent++;
//新插到一个路由条目,则将路由缓存延时刷新。
rt_cache_flush(-1);
//发送netlink通知
rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id,
&cfg->fc_nlinfo);
return 0;
out_free_new_fa:
kmem_cache_free(fn_alias_kmem, new_fa);
out:
fib_release_info(fi);
三、外部事件
1、设备相关事件变更触发
fib_netdev_notifier
in_dev = __in_dev_get_rtnl(dev);
//设备去注册,比如USB网卡被拔插
if (event == NETDEV_UNREGISTER)
fib_disable_ip(dev, 2);
//将和该设备相关的下一跳对象标记为DEAD、如果当前info对象下的所有
//下一跳对象都已经为DEAD,则将info对象也标记为DEAD,该函数
//下面单独分析。该函数返回值为info对象被标记为DEAD的个数。
if (fib_sync_down(0, dev, force))
fib_flush();
//遍历所有路由表模块的刷新回调,当前仅分析hash实现的路由表
//模块,则对应的tb_flush回调函数为fn_hash_flush
for (h = 0; h < FIB_TABLE_HASHSZ; h++)
hlist_for_each_entry(tb, node, &fib_table_hash[h], tb_hlist)
flushed += tb->tb_flush(tb);
fn_hash_flush
table = (struct fn_hash *) tb->tb_data;
//遍历当路由表的所有区域对象
for (fz = table->fn_zone_list; fz; fz = fz->fz_next)
//遍历当前区域对象的所有hash桶
for (i = fz->fz_divisor - 1; i >= 0; i--)
//遍历当前hash桶下所有zone对象中的
//node对象,如果node对象下的alias对
//象关联的info对象已经标记为删除,则
//将alias对象也释放。如果这个node对
//象中alias链表全部删除,则这个node
//对象也将被删除。如果有alias对象删除
//,则返回被删除的alias对象个数。
found += fn_flush_list(fz, i);
//上面处理中有alias对象被删除,则延时进行路由缓存刷新。
if (flushed)
rt_cache_flush(-1);
//路由缓存立即刷新。
rt_cache_flush(0);
arp_ifdown(dev);
neigh_ifdown(&arp_tbl, dev);
//遍历所有邻居条目,找到与当前设备相关的邻居项。将该邻居
//项标记为DEAD,如果邻居项还有其它模块进行引用,则将
//邻居项的output回调设置为黑洞回调函数,以后通过该邻居
//项的输出报文都直接丢弃,同时设置为无效的安全状态。如
//果邻居项没有被引用,则进行删除。
neigh_flush_dev(tbl, dev);
//删除与这个设备相关的arp代理条目
pneigh_ifdown(tbl, dev);
//停止arp代理定时器,并删除arp代理所有缓存的待转发报文
del_timer_sync(&tbl->proxy_timer);
pneigh_queue_purge(&tbl->proxy_queue);
return NOTIFY_DONE;
//设备没有配置地址相关信息,则返回。
if (!in_dev)
return NOTIFY_DONE;
switch (event)
//设备UP
case NETDEV_UP:
//遍历当前设备所有IP地址
for_ifa(in_dev)
fib_add_ifaddr(ifa);
//获取地址所属的接口设备
in_dev = ifa->ifa_dev;
dev = in_dev->dev;
//获取IP地址、子网掩码、网络前缀
mask = ifa->ifa_mask;
addr = ifa->ifa_local;
prefix = ifa->ifa_address&mask;
//将本地地址加入到LOCAL路由表中
fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
cfg = {
.fc_protocol = RTPROT_KERNEL,
.fc_type = type, //RTN_LOCAL
.fc_dst = dst, //addr
.fc_dst_len = dst_len, //32
.fc_prefsrc = ifa->ifa_local, //addr
.fc_oif = ifa->ifa_dev->dev->ifindex,
.fc_nlflags = NLM_F_CREATE | NLM_F_APPEND,
//根据加入的路由类型,确认加入到哪个路由表中。
if (type == RTN_UNICAST)
tb = fib_new_table(RT_TABLE_MAIN);
else
tb = fib_new_table(RT_TABLE_LOCAL);
cfg.fc_table = tb->tb_id;
//根据加入的路由类型,确认路由范围类型
if (type != RTN_LOCAL)
cfg.fc_scope = RT_SCOPE_LINK;
else
cfg.fc_scope = RT_SCOPE_HOST;
//这里调用对应路由表对象的回调,当前路由表对象假设是使用
//hash路由表,则插入的回调函数为fn_hash_insert,这个函数
//上面已经分析过。
if (cmd == RTM_NEWROUTE)
tb->tb_insert(tb, &cfg);
else
tb->tb_delete(tb, &cfg);
//如果当前设备又没有UP,则只将本地地址加入到LOCAL路由表中。
//后面的广播地址、同网段的网络地址不再添加到路由表中。
if (!(dev->flags&IFF_UP))
return;
//如果当前配置了广播地址,并不是受限型广播地址(255.255.255.255)
//则将该广播地址加入到LOCAL表中。
if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
fib_magic(RTM_NEWROUTE, RTN_BROADCAST,
ifa->ifa_broadcast, 32, prim);
//如果当前地址前缀不是无效类型、并且当前地址不是次地址,
//则检查如果满足下面两个条件任意一个,则进行同网段的路由规则
//添加。
//1、当前地址与前缀不同
//2、或者子网掩码小于32位。
if (!ZERONET(prefix) && !(ifa->ifa_flags&IFA_F_SECONDARY) &&
(prefix != addr || ifa->ifa_prefixlen < 32))
//添加同网段的网络地址。
fib_magic(RTM_NEWROUTE, dev->flags&IFF_LOOPBACK ?
RTN_LOCAL :RTN_UNICAST, prefix, ifa->ifa_prefixlen, prim);
//如果子网掩码小于31位,则添加本网段的广播地址,大于或等
//于31位则没有本网段的广播地址可言。
if (ifa->ifa_prefixlen < 31)
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32,
prim);
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix|~mask,
32, prim);
endfor_ifa(in_dev);
//启动延迟定时器,超时后进行路由缓存刷新
rt_cache_flush(-1);
//接口设备DOWN
case NETDEV_DOWN:
//该函数在上面已经分析过,主要清除内核创建的设备相关的路由表项,进行
//相关对象删除,并且立即进行路由缓存的刷新,以及触发ARP针对该设备
//相关的ARP缓存清除。这里force的参数为0,表明类型为本地的路由条目
//不能删除,其它类型都可以。
fib_disable_ip(dev, 0);
//当进行设备其它参数修改后,立即刷新路由缓存。
case NETDEV_CHANGEMTU:
case NETDEV_CHANGE:
rt_cache_flush(0);
----------------------------------------------------------------------------------------------------------------------
fib_sync_down
int scope = RT_SCOPE_NOWHERE;
//如果传入参数为强制,则将范围值修改为无效值,保证下面匹配处理时一定不会
//匹配成功。
if (force)
scope = -1;
//如果传入参数表明这是一个接口设备的本地地址,则需要从fib_laddr_hashfn
//hash表中查找所有info对象的首选源地址是否匹配这个地址,如果匹配,则
//标记这个info对象已经被删除。fib_laddr_hashfn条目的插入是在进行路由添加时
//,如果判断用户侧配置了首先源地址,则会进行插入,可以参考上面添加路由条目
//小节的分析。
if (local && fib_info_laddrhash)
hash = fib_laddr_hashfn(local);
head = &fib_info_laddrhash[hash];
hlist_for_each_entry(fi, node, head, fib_lhash)
if (fi->fib_prefsrc == local)
fi->fib_flags |= RTNH_F_DEAD;
ret++;
//从设备索引hash表中查找所有下一跳对象
hash = fib_devindex_hashfn(dev->ifindex);
head = &fib_info_devhash[hash];
hlist_for_each_entry(nh, node, head, nh_hash)
//获取下一跳对象的父亲,即它属于哪个info对象
struct fib_info *fi = nh->nh_parent;
//跳过不是使用此接口输出的下一跳
if (nh->nh_dev != dev || fi == prev_fi)
continue;
prev_fi = fi;
dead = 0;
change_nexthops(fi)
//如果当前下一跳对象已经标记为删除,则再递增引用计数
if (nh->nh_flags&RTNH_F_DEAD)
dead++;
//满足以下条件则该下一跳对象需要标记为删除。
//1、当前下一跳的输出接口与当前相同
//2、如果传入参数为force,则这时scope为-1无效值,即这个下一跳一
//定会标记为删除;否则scope默认为RT_SCOPE_NOWHERE,在创建
//路由条目时了解到,路由条目为RT_SCOPE_HOST类型,则此时路由
//条目中的下一跳对象的范围即为RT_SCOPE_NOWHERE,这里也就是
//说需要删除的条目不能是本地路由类型。
else if (nh->nh_dev == dev &&nh->nh_scope != scope)
nh->nh_flags |= RTNH_F_DEAD;
dead++;
endfor_nexthops(fi)
//如果当前info对象下的所有下一跳对象都被删除,则info对象也需要标记为删除。
if (dead == fi->fib_nhs)
fi->fib_flags |= RTNH_F_DEAD;
ret++;
//返回info对象被删除的个数
return ret;
2、设备地址事件变更触发
fib_inetaddr_notifier
switch (event)
//添加了一个地址
case NETDEV_UP:
//该函数在上面已经分析过,主要将本地地址加入到LOCAL路由表中,以及根据
//条件,确定是否添加广播地址到路由表中、是否添加同网段的网络地址到路由表。
fib_add_ifaddr(ifa);
//启动延迟路由缓存刷新定时器,超时后进行路由缓存刷新。
rt_cache_flush(-1);
//删除了一个地址
case NETDEV_DOWN:
fib_del_ifaddr(ifa);
//获取地址所属设备
in_dev = ifa->ifa_dev;
dev = in_dev->dev;
//获取主地址及两个同网段的广播地址
prim = ifa;
brd = ifa->ifa_address|~ifa->ifa_mask;
any = ifa->ifa_address&ifa->ifa_mask;
//如果当前地址是主地址,则删除其中一个同网段的广播地址
if (!(ifa->ifa_flags&IFA_F_SECONDARY))
fib_magic(RTM_DELROUTE, dev->flags&IFF_LOOPBACK ?
RTN_LOCAL :RTN_UNICAST, any, ifa->ifa_prefixlen, prim);
//否则如果当前地址是次地址,则校验是否主地址已经不存在了。
else
prim = inet_ifa_byprefix(in_dev, any, ifa->ifa_mask);
if (prim == NULL)
return;
//分配检测当前设备是否还存在相关地址的使用
for (ifa1 = in_dev->ifa_list; ifa1; ifa1 = ifa1->ifa_next)
if (ifa->ifa_local == ifa1->ifa_local)
ok |= LOCAL_OK;
if (ifa->ifa_broadcast == ifa1->ifa_broadcast)
ok |= BRD_OK;
if (brd == ifa1->ifa_broadcast)
ok |= BRD1_OK;
if (any == ifa1->ifa_broadcast)
ok |= BRD0_OK;
//如果不存在此广播地址的使用,则进行路由表的删除。
if (!(ok&BRD_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32,
prim);
//如果不存在同网段的广播地址的使用,则进行路由表的删除。
if (!(ok&BRD1_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim);
if (!(ok&BRD0_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim);
//如果不存在本地地址的使用,则进行路由表的删除。
if (!(ok&LOCAL_OK))
fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim);
//进行本地路由表查找,如果当前地址在上面fib_magic确实被标记为
//DEAD,则这里查找本地路由表应该失败,就应该返回非RTN_LOCAL
//类型的地址,否则如果在本地路由表查找成功,则表明没有被删除。
if (inet_addr_type(ifa->ifa_local) != RTN_LOCAL)
//如果确实被删除了,则在fib_info_laddrhash哈希表中对本地地址
//进行索引,将找到的INFO对象标记为DEAD。fib_info_laddrhash
//哈希表的插入是当本地地址做为首选源地址进行路由表插入时触
//发的。同时当确实有INFO对象被标记为DEAD之后,调用fib_flush
//对相关资源进行释放。
if (fib_sync_down(ifa->ifa_local, NULL, 0))
fib_flush();
//如果当前被删除的IP地址是设备最后一个IP地址,则需要将和这个设备相关
//的路由及ARP条目信息都进行删除,同时立即进行路由缓冲刷新。
if (ifa->ifa_dev->ifa_list == NULL)
fib_disable_ip(ifa->ifa_dev->dev, 1);
//否则,当前设备还有其它IP地址,则仅进行延迟的路由缓冲刷新。
else
rt_cache_flush(-1);
四、输入方向路由查找
ip_route_input
iif = dev->ifindex;
//TOS值仅使用了前6位。
tos &= IPTOS_RT_MASK;
//根据源地址、目的地址、输入设备三个关键值构成hash key的生成
hash = rt_hash(daddr, saddr, iif);
//进行路由缓存的查找,进行匹配的所有关键字包括如下,注意:如果使用策略
//路由机制,当在这里的路由缓存命中时,将不会进行策略路由的处理,仅仅根
//据缓存中记录的上次策略路由查找结果进行目的地路由,所以如果策略路由
//规则有变更,则都需要将缓存清除。
//1、目的地址
//2、源地址
//3、输入接口
//4、防火墙标记
//5、TOS值。
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
rth = rcu_dereference(rth->u.rt_next))
if (rth->fl.fl4_dst == daddr &&rth->fl.fl4_src == saddr &&
rth->fl.iif == iif &&rth->fl.oif == 0 &&rth->fl.mark == skb->mark &&
rth->fl.fl4_tos == tos)
//标记当前已经使用
rth->u.dst.__use++;
//dst_entry结构是嵌入在rtable结构的最开头位置,所以可以直接转换为dst对
//象,这里查找成功后,将dst关联到当前数据报文skb中。
skb->dst = (struct dst_entry*)rth;
return 0;
//当路由缓存查找失败,则进行下面后继处理。
//如果目的地址是多播地址,则对于进来的多播报文需要单独进行处理
if (MULTICAST(daddr))
in_dev = __in_dev_get_rcu(dev)
//查本地是否加入了当前多播组
our = ip_check_mc(in_dev, daddr, saddr,skb->nh.iph->protocol);
//如下两种情况任意满足,则进行多播输入的处理。
//1、本地接口已经加入到此多播组
//2、如果本地接口没有加入些多播组,则该多播地址不能是本地多播地址
//(即地址不能是224.0.0.X),并且当前接口允许多播转发,通常本地用户侧
//的多播路由进程启动后,都会将允许多播转发开启。
if (our|| (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
//进行多播路由输入处理,下面单独分析。
return ip_route_input_mc(skb, daddr, saddr,tos, dev, our);
//如果当前接收的是多播地址,但本地没有加入此多播组,并且此多播地址是
//本地多播地址,或者本地没有启动多播路由进程,则接收的报文被丢弃。
return -EINVAL;
//路由缓存没有找到,此目的地址不是多播报文,则进行慢速路由查找处理。
ip_route_input_slow(skb, daddr, saddr, tos, dev);
in_dev = in_dev_get(dev);
//填充路由查找条件
fl = { .nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = saddr,
.tos = tos,
.scope = RT_SCOPE_UNIVERSE,
} },
.mark = skb->mark,
.iif = dev->ifindex };
//设备没有有效的地址信息
if (!in_dev)
goto out;
//校验有效源地址,不能是如下类型:
//1、多播地址
//2、E类保留地址
//3、回环地址
if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr))
goto martian_source;
//如果目的地址为受限广播地址、或源/目的都为0,则走广播地址处理流程。
if (daddr == htonl(0xFFFFFFFF) || (saddr == 0 && daddr == 0))
goto brd_input;
//0.X.X.X源地址,为非法地址。
if (ZERONET(saddr))
goto martian_source;
//如下目的地址为非法地址
//1、E类地址
//2、0.X.X.X地址
//3、回环地址
if (BADCLASS(daddr) || ZERONET(daddr) || LOOPBACK(daddr))
goto martian_destination;
//进行路由查找
err = fib_lookup(&fl, &res)
struct fib_lookup_arg arg = {
.result = res,
fib_rules_lookup(&fib4_rules_ops, flp, 0, &arg);
//遍历ipv4所有策表
list_for_each_entry_rcu(rule, ops->rules_list, list)
//进行策略规则的匹配
r = fib_rule_match(rule, ops, fl, flags)
int ret = 0;
//如果规则中需要进行输入设备索引匹配,则进入输入设备
//索引匹配
if (rule->ifindex && (rule->ifindex != fl->iif))
goto out;
//规则中需要进行make匹配。
if ((rule->mark ^ fl->mark) & rule->mark_mask)
goto out;
//其它匹配项,则使用不同协议的规则对象匹配回调进行处理
//当前fib4_rules的回调函数为fib4_rule_match
ret = ops->match(rule, fl, flags);
fib4_rule_match
r = (struct fib4_rule *) rule;
daddr = fl->fl4_dst;
saddr = fl->fl4_src;
//进行源IP地址匹配,目的IP地址匹配
if (((saddr ^ r->src) & r->srcmask) ||
if (((saddr ^ r->src) & r->srcmask) ||
return 0;
//进行TOS值匹配
if (r->tos && (r->tos != fl->fl4_tos))
return 0;
//以上条件如果规则项设置了需要匹配的话,到这里全
//部匹配成功。
return 1;
out:
//如果规则设置为反向结果处理,则将匹配结果反转。
//即如果匹配成功,则返回失败;如果匹配失败则返回成功。
return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
//没有匹配成功,则继续。
if ( !r )
continue;
//匹配成功后,执行对应匹配规则的动作处理,
//fib4_rules的回调函数为fib4_rule_match
err = ops->action(rule, fl, flags, arg);
fib4_rule_action
switch (rule->action)
//进行路由表查表处理
case FR_ACT_TO_TBL:
break;
//返回网络不可达错误
case FR_ACT_UNREACHABLE:
err = -ENETUNREACH;
goto errout;
//返回受限错误
case FR_ACT_PROHIBIT:
err = -EACCES;
goto errout;
//返回处理错误
case FR_ACT_BLACKHOLE:
default:
err = -EINVAL;
goto errout;
//根据规则中配置的路由表ID,找到指向路由表
tbl = fib_get_table(rule->table)
//没有指定ID,则默认使用MAIN表的ID
if (id == 0)
id = RT_TABLE_MAIN;
//根据路由表ID生成hash key,来遍历
/fib_table_hash 哈希表,找到已经加入到表中匹配
//正确的路由表对象。
h = id & (FIB_TABLE_HASHSZ - 1);
hlist_for_each_entry_rcu(tb, node, &fib_table_hash[h],
tb_hlist)
if (tb->tb_id == id)
return tb;
return NULL;
//如果没有找到路由表,则返回EAGAIN错误,使得
//调用此函数的代码继续进行下一条规则匹配处理。
if ( tbl == NULL )
goto errout;
//进行路由查找,这里假设路由表为hash类型的,则
//对应的回调函数为fn_hash_lookup
err = tbl->tb_lookup(tbl, flp, (struct fib_result *) arg->result);
fn_hash_lookup
struct fn_hash *t = (struct fn_hash*)tb->tb_data;
//遍历所有zone对象,注意这里是从fn_zone_list
//开始,在插入路由表时可以看到fn_zone_list
//是指向前缀长度最长的zone对象,即路由表
//匹配是看掩码最长匹配。
for (fz = t->fn_zone_list; fz; fz = fz->fz_next)
//根据目的地址与上当前路由条目掩码
//得到key
k = fz_key(flp->fl4_dst, fz);
//遍历当前zone对象下的hash链表中存储
//的node对象。
head = &fz->fz_hash[fn_hash(k, fz)];
hlist_for_each_entry(f, node, head, fn_hash)
//当目的地址和node对象存储的不
//同时,则遍历下一个路由条目。
if (f->fn_key != k)
continue;
//当目的地址匹配成功,则继续匹配
//更多条件。fib_semantic_match进行
//语义匹配处理,下面单独分析。
err = fib_semantic_match(&f->fn_alias,
flp, res,f->fn_key, fz->fz_mask,
fz->fz_order);
//小于0表示匹配处理有错误。
//等于0表示匹配成功。这两种情况
//下直接返回处理结果。否则继续进行
//下一个node对象匹配处理。
if (err <= 0)
goto out;
//当所有zone对象遍历完,没有找到完全匹配的
//路由条目,则返回1,表明没有找到路由条目。
err = 1;
out:
return err;
//如果查找失败,则返回EAGAIN,使得调用此函数的代
//码继续进行下一条规则匹配处理。
if (err > 0)
err = -EAGAIN;
errout:
return err;
//如果动作处理返回结果为EAGAIN,则需要继续匹配策略规则
//表中下一个表项。否则处理完成。
if (err != -EAGAIN)
fib_rule_get(rule);
arg->rule = rule;
goto out;
//如果所有策略规则表项全部遍历完没有匹配项,则返回网络不可达
//错误。
err = -ENETUNREACH;
out:
return err;
res->r = arg.rule;
//如果路由查找失败,同时接入设备没有开启允许路由转发,则走主机不可
//达处理流程,否则走没有找到路由的流程。
if ( err != 0 )
if (!IN_DEV_FORWARD(in_dev))
goto e_hostunreach;
goto no_route;
free_res = 1;
RT_CACHE_STAT_INC(in_slow_tot);
//如果查找到的路由条目为指定广播类型、或者指定网络的方播类型,则走
//广播地址处理流程。
if (res.type == RTN_BROADCAST)
goto brd_input;
//如果查找到的路由条目为路由到本地主机。
if (res.type == RTN_LOCAL)
//进行源地址的反向路径校验。
result = fib_validate_source(saddr, daddr, tos,loopback_dev.ifindex,
dev, &spec_dst, &itag);
fl = { .nl_u = { .ip4_u =
{ .daddr = src,
.saddr = dst,
.tos = tos } },
.iif = oif };
in_dev = __in_dev_get_rcu(dev);
if (in_dev)
//获取当前接口设备是否含有有效地址,以及是否开启了反向路径
//过滤。
no_addr = in_dev->ifa_list == NULL;
rpf = IN_DEV_RPFILTER(in_dev);
//如果IPv4形设备对象不存在,则返回错误
if (in_dev == NULL)
goto e_inval;
//将源地址做为目的地址,进行路由查找,如果查找失败,则返回错误
if (fib_lookup(&fl, &res))
goto last_resort;
//当前数据包是从远端收入的,如果使用源地址做为目的地进行查找,
//查找结果应该是RTN_UNICAST类型,否则错误。
if (res.type != RTN_UNICAST)
goto e_inval_res;
//使用路由条目的首选源地址做为特定目的地址
*spec_dst = FIB_RES_PREFSRC(res);
//获取基于路由分类器的tag
fib_combine_itag(itag, &res);
//如果查找到的路由条目的输出接口与当前输入接口相同,则反向路径
//校验成功。根据下一跳范围值则确定此路由的范围,如果大于等于HOST
//,则表明此路由的下一跳是同网段的,返回1。否则如果小于HOST,
//则表明此路由是其它网段的路由,返回0。
if (FIB_RES_DEV(res) == dev)
ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
fib_res_put(&res);
return ret;
fib_res_put(&res);
//当找到的路由输出接口与当前报文输入接口不同时,如果当前设备没有
//地址信息,则返回错误;如果当前设备开启了反向路径检测,则也返回
//错误。
if (no_addr)
goto last_resort;
if (rpf)
goto e_inval;
//增加路由查找条件,限制输出接口为当前输入接口,再次进行路由
//查找。
//1、如果没有查找成功,则返回0,因为上面已经确定用户配置为
//不需要进行反向路径校验,否则不返回错误结果。
//2、如果路由查找成功,但不是UNICAST类型,则也不是一个有效
//项,因为当前报文是从远端发来的,查找结果应该是UNICAST,这种
//情况下因为用户配置为不需要反向路径校验,则也返回0。
//3、这时候查找成功了,并且路由条目类型也是正确的UNICAST,则
//根据当前下一跳范围类型确定返回值。如果范围类型大于等于HOST,
//则表明下一跳是同网段的,返回1,否则下一跳不是同网段的,返回0
fl.oif = dev->ifindex;
ret = 0;
if (fib_lookup(&fl, &res) == 0)
if (res.type == RTN_UNICAST)
*spec_dst = FIB_RES_PREFSRC(res);
ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
fib_res_put(&res);
return ret;
last_resort:
if (rpf)
goto e_inval;
*spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
*itag = 0;
return 0;
e_inval_res:
fib_res_put(&res);
e_inval:
return -EINVAL;
//反向路径校验失败。
if (result < 0)
goto martian_source;
//进行反向路径校验处理,如果返回值为1,则表明源地址与当前设备是
//同一网段的,标识中增加同一网段源的标记
if (result)
flags |= RTCF_DIRECTSRC;
spec_dst = daddr;
goto local_input; //走本地路由处理流程
//接收了广播报文、组播报文、发往本地的单播报文都已经处理了,剩下就
//就是非本地的单播报文,此时如果本地设备不允许进行转发,则返回错误。
if (!IN_DEV_FORWARD(in_dev))
goto e_hostunreach;
//走到此流程,如果路由查找结果还不是UNICAST,则是异常报文。
if (res.type != RTN_UNICAST)
goto martian_destination;
//构造单播转发的路由缓存,并插入到rt_hash_table中。
ip_mkroute_input(skb, &res, &fl, in_dev, daddr, saddr, tos);
ip_mkroute_input_def(skb, res, fl, in_dev, daddr, saddr, tos);
__mkroute_input(skb, res, in_dev, daddr, saddr, tos, &rth);
//从下一跳对象中获取输出设备
out_dev = in_dev_get(FIB_RES_DEV(*res));
//进行反向路径校验
err = fib_validate_source(saddr, daddr, tos, FIB_RES_OIF(*res),
in_dev->dev, &spec_dst, &itag);
//如果校验失败,则返回错误
if (err < 0)
ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,saddr);
err = -EINVAL;
goto cleanup;
//如果反向路径校验返回值为1,则表明源地址和当前设备是同一
//网段,则标记为源可以直接到达。
if (err)
flags |= RTCF_DIRECTSRC;
//如果以下条件满足,则标记可以进行IGMP重定向。
//1、输入设备与输出设备相同。
//2、当前路由器和源设备是同一网段。
//3、当前路由标记没有设备为需要进行NAT处理或伪装处理。
//4、当前输出设备配置为共享媒介,或者源地址与下一跳网关是同
//一网段。
if (out_dev == in_dev && err && !(flags & (RTCF_NAT |
RTCF_MASQ)) &&(IN_DEV_SHARED_MEDIA(out_dev) ||
inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))
flags |= RTCF_DOREDIRECT;
//如果转发的报文不是IPV4类型,则如果输入和输出接口相同,并
//且路由条目不是进行DNAT处理,则返回错误。注释里提到比如
//ARP的情况,即如果ARP请求的目的地址是同一网段的,则应该
//由同一网段的设备进行回应,不应该由路由器在做无用的同网段
//转发处理。
if (skb->protocol != htons(ETH_P_IP))
if (out_dev == in_dev && !(flags & RTCF_DNAT))
err = -EINVAL;
goto cleanup;
//分配路由缓存条目
rth = dst_alloc(&ipv4_dst_ops);
//初始化路由缓存条目
atomic_set(&rth->u.dst.__refcnt, 1);
rth->u.dst.flags= DST_HOST;
if (in_dev->cnf.no_policy)
rth->u.dst.flags |= DST_NOPOLICY;
if (out_dev->cnf.no_xfrm)
rth->u.dst.flags |= DST_NOXFRM;
rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
rth->rt_gateway = daddr;
rth->rt_iif =
rth->fl.iif = in_dev->dev->ifindex;
rth->u.dst.dev = (out_dev)->dev;
dev_hold(rth->u.dst.dev);
rth->idev = in_dev_get(rth->u.dst.dev);
rth->fl.oif = 0;
rth->rt_spec_dst= spec_dst;
//转发单播类型报文的input、output回调
rth->u.dst.input = ip_forward;
rth->u.dst.output = ip_output;
rt_set_nexthop(rth, res, itag);
fi = res->fi;
//查找到的路由条目含有info对象
if (fi)
//如果路由条目的下一跳含有出口网关的配置、并且下一
//跳的范围类型为LINK(即路由条目的范围类型为
//UNIVERSE),则将下一跳中网关值设置到路由缓存对象中
if (FIB_RES_GW(*res) &&FIB_RES_NH(*res).nh_scope ==
RT_SCOPE_LINK)
rt->rt_gateway = FIB_RES_GW(*res);
//把info对象中的度量值复制到路由缓存对象中
memcpy(rt->u.dst.metrics, fi->fib_metrics,
sizeof(rt->u.dst.metrics));
//如果info对象中没有配置mtu值,则DST对象的
//MTU从出口设备中提取。
if (fi->fib_mtu == 0)
rt->u.dst.metrics[RTAX_MTU-1] = rt->u.dst.dev->mtu;
//如果DST中MTU的度量值已经被锁定,同时当前
//出口网关与目的地址不同时,如果当前设备MTU
//大于576,则将DST中MTU校正为最大576。
if (rt->u.dst.metrics[RTAX_LOCK-1] &
(1 << RTAX_MTU) &&rt->rt_gateway != rt->rt_dst &&
rt->u.dst.dev->mtu > 576)
rt->u.dst.metrics[RTAX_MTU-1] = 576;
//如果查找到的路由条目没有info对象,则直接将设备MTU
//设置到DST对象中。
else
rt->u.dst.metrics[RTAX_MTU-1]= rt->u.dst.dev->mtu;
//如果TTL没有设置,则使用默认值,默认是64
if (rt->u.dst.metrics[RTAX_HOPLIMIT-1] == 0)
rt->u.dst.metrics[RTAX_HOPLIMIT-1] = sysctl_ip_default_ttl;
//进行最大MTU校正
if (rt->u.dst.metrics[RTAX_MTU-1] > IP_MAX_MTU)
rt->u.dst.metrics[RTAX_MTU-1] = IP_MAX_MTU;
//如果没有配置MSS,则初始MSS取MTU参照值与256之
//间最大的一个值,这里mtu-40是因为MTU是指二层数据
//最大负载(除去以太网头和最后的4字节校验和),而MSS
//是指TCP的最大负载,所以这里用MTU减去IP头20字节、
//TCP头20字节。
if (rt->u.dst.metrics[RTAX_ADVMSS-1] == 0)
rt->u.dst.metrics[RTAX_ADVMSS-1] = max_t(unsigned int,
rt->u.dst.dev->mtu - 40,ip_rt_min_advmss);
//MSS最大值校正。
if (rt->u.dst.metrics[RTAX_ADVMSS-1] > 65535 - 40)
rt->u.dst.metrics[RTAX_ADVMSS-1] = 65535 - 40;
//设置当前路由条目类型
rt->rt_type = res->type;
rth->rt_flags = flags;
*result = rth;
err = 0;
cleanup:
in_dev_put(out_dev);
return err;
//根据目的地址、源地址、输入接口索引计算出hash key。
hash = rt_hash(daddr, saddr, fl->iif);
//将新建的路由缓存对象插入到rt_hash_table中
rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);
//判断当前没在软中断上下文
attempts = !in_softirq();
restart:
chain_length = 0;
min_score = ~(u32)0;
cand = NULL;
candp = NULL;
now = jiffies;
rthp = &rt_hash_table[hash].chain;
//遍历当前hash桶的链表
while ((rth = *rthp) != NULL)
//根据目的地址、源地址、MARK、TOS、输入接口索引、
//输出接口索引这些关键值进行比较,判断当前新建的路由
//缓存对象的路由查找条件是否与当前已存在的相同。
if (compare_keys(&rth->fl, &rt->fl))
*rthp = rth->u.rt_next;
//如果已经存在,则将已存在的路由缓存条目放
//到当前桶的最前端。
rcu_assign_pointer(rth->u.rt_next,
rt_hash_table[hash].chain);
rcu_assign_pointer(rt_hash_table[hash].chain, rth);
//增加当前DST对象的引用计数
rth->u.dst.__use++;
dst_hold(&rth->u.dst);
//更新当前DST对象最后使用时间
rth->u.dst.lastuse = now;
//释放之前创建的路由缓存对象资源
rt_drop(rt);
*rp = rth;
return 0;
//如果当前路由缓存条目引用计数为0,则根据特定
//条件尝试进行删除。
if (!atomic_read(&rth->u.dst.__refcnt))
//计算当前路由条目的价值,价值越低的路由条目
//越优先做为被删除的对象。这里价值score变量比
//特位被分为三部分。
//0~29位是一个价位等级值。
//30位是一个价位等级位。
//31位是一个非常有价值的价位等级。
score = rt_score(rth);
//根据当前路由条目最后使用时间,来计算0~29
//位的价值。在出现30、31位相同的情况下,
//未被使用时间越长的路由缓存条目越先被删除。
u32 score = jiffies - rt->u.dst.lastuse;
score = ~score & ~(3<<30);
//根据特定条件,标记出最有价值的31位,当前
//有3种情况:
//1、该路由条目是被ICMP重定向创建的。
//2、用户层创建该条目时,设置了NOTIFY标记。
//3、含有超时期限的路由条目。
if (rt_valuable(rt))
score |= (1<<31);
//根据特定条件,标记出次有价值的30位,当前
//有2种情况:
//1、该路由条目是为本地输出的路由
//2、该路由条目是为非广播、多播、本地报文。
//因为这几种报文重新创建的时间都比较短。
if (!rt->fl.iif ||
!(rt->rt_flags &
(RTCF_BROADCAST|RTCF_MULTICAST|
RTCF_LOCAL)))
score |= (1<<30);
return score;
//找出最低价值的条目进行标记。
if (score <= min_score)
cand = rth;
candp = rthp;
min_score = score;
//计算当前链的条目数。
chain_length++;
rthp = &rth->u.rt_next;
//找到了引用计数为0,可以被删除的路由缓存条目,判断
//如果当前hash链的成员数超过ip_rt_gc_elasticity阀值,则进行
//路由缓冲的删除。
if (cand)
if (chain_length > ip_rt_gc_elasticity)
*candp = cand->u.rt_next;
rt_free(cand);
//对于单播转发、或者本地输出的报文,进行路由DST条目,与
//ARP邻居对象的绑定。
if (rt->rt_type == RTN_UNICAST || rt->fl.iif == 0)
int err = arp_bind_neighbour(&rt->u.dst);
//绑定失败
if (err)
//如果不是因为内存不足而失败,则释放创建的路由
//缓存条目,并返回错误。
if (err != -ENOBUFS)
rt_drop(rt);
return err;
//否则,因为内存不足而导致绑定失败,则检测
//当前是否在非软中断模式,如果当前在非软中
//断模式,则进行强制垃圾回收处理。
if (attempts-- > 0)
//保存老的垃圾回收阀值及最小周期,之后将
//这两个值设置为最严格值,进行强制垃圾回
//收。
saved_elasticity = ip_rt_gc_elasticity;
saved_int = ip_rt_gc_min_interval;
ip_rt_gc_elasticity = 1;
ip_rt_gc_min_interval = 0;
//进行垃圾回收。下面单独分析。
rt_garbage_collect();
//强制垃圾回收完成后,再将垃圾回收阀值及最
//小周期还原。
ip_rt_gc_min_interval = saved_int;
ip_rt_gc_elasticity = saved_elasticity;
goto restart;
//如果因内存不足导致绑定失败,如果当前是在软中断
//模式或者当前已经尝试了强制垃圾回收但仍失败,则
//释放创建的路由缓存条目,返回错误。
rt_drop(rt);
return -ENOBUFS;
//将新建的路由缓存条目插入到rt_hash_table当前桶的最前端。
rt->u.rt_next = rt_hash_table[hash].chain;
rt_hash_table[hash].chain = rt;
*rp = rt;
return 0;
done:
in_dev_put(in_dev);
if (free_res)
fib_res_put(&res);
out:
return err;
//广播流程处理
brd_input:
//报文不是IPv4,则为错误
if (skb->protocol != htons(ETH_P_IP))
goto e_inval;
//如果源地址为0.X.X.X类型,则特定目的地址从接入设备中获取一个
//当前接口的地址。
if (ZERONET(saddr))
spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK);
//否则需要进行反向路径校验,如果校验失败,则返回错误。否则,如果
//确认源地址与当前设备为同一网段,则加上DIRECTSRC标记。
else
err = fib_validate_source(saddr, 0, tos, 0, dev, &spec_dst,&itag);
if (err < 0)
goto martian_source;
if (err)
flags |= RTCF_DIRECTSRC;
//设置广播类型的标记
flags |= RTCF_BROADCAST;
res.type = RTN_BROADCAST;
RT_CACHE_STAT_INC(in_brd);
//本地流程及广播流程下半部处理:
local_input:
//分配一个路由缓存对象
rth = dst_alloc(&ipv4_dst_ops);
//路由缓存对象初始化
rth->u.dst.output= ip_rt_bug;
atomic_set(&rth->u.dst.__refcnt, 1);
rth->u.dst.flags= DST_HOST;
if (in_dev->cnf.no_policy)
rth->u.dst.flags |= DST_NOPOLICY;
rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
rth->u.dst.tclassid = itag;
rth->rt_iif =
rth->fl.iif = dev->ifindex;
rth->u.dst.dev = &loopback_dev;
dev_hold(rth->u.dst.dev);
rth->idev = in_dev_get(rth->u.dst.dev);
rth->rt_gateway = daddr;
rth->rt_spec_dst= spec_dst;
rth->u.dst.input= ip_local_deliver; //input回调当前调为往本地递送
rth->rt_flags = flags|RTCF_LOCAL; //增加LOCAL标记
//路由条目没有找到,则input回调设置为错误的处理回调,当匹配到该路由
//条目时,给远端回送ICMP错误,同时去处LOCAL标记。注意即使路由
//查找错误,也会将错误的查找结果插入到路由缓存中,这样下次再有相同
//地址的包过来,就可以直接走错误流程。
if (res.type == RTN_UNREACHABLE)
rth->u.dst.input= ip_error;
rth->u.dst.error= -err;
rth->rt_flags &= ~RTCF_LOCAL;
//将新构建的路由缓存插入到rt_hash_table中。
rth->rt_type = res.type;
hash = rt_hash(daddr, saddr, fl.iif);
rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);
goto done;
//没有找到路由,则将类型设置为不可达,同时走路由不可达处理流程。
no_route:
RT_CACHE_STAT_INC(in_no_route);
RT_CACHE_STAT_INC(in_no_route);
res.type = RTN_UNREACHABLE;
goto local_input;
martian_destination:
RT_CACHE_STAT_INC(in_martian_dst);
e_hostunreach:
err = -EHOSTUNREACH;
goto done;
e_inval:
err = -EINVAL;
goto done;
e_nobufs:
err = -ENOBUFS;
goto done;
martian_source:
ip_handle_martian_source(dev, in_dev, skb, daddr, saddr);
goto e_inval;
-------------------------------------------------------------------------------------------------------------------
ip_route_input_mc
in_dev = in_dev_get(dev);
//校验设备是否已经没有可用的地址
if (in_dev == NULL)
return -EINVAL;
//校验源地址及协议是否合法
if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr) ||
skb->protocol != htons(ETH_P_IP))
goto e_inval;
//如果源地址是0.X.X.X,则检查如果目的地址不是224.0.0.X类型的本地多播,则
//报文无效。否则选择当前接收设备的本地地址做为特定目的值参数。
if (ZERONET(saddr))
if (!LOCAL_MCAST(daddr))
goto e_inval;
spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK)
//如果有有效的源地址,则需要进行反向路由校验
else if (fib_validate_source(saddr, 0, tos, 0,dev, &spec_dst, &itag) < 0)
goto e_inval;
//分配一个路由缓存条目
rth = dst_alloc(&ipv4_dst_ops);
//初始化路由缓存条目
rth->u.dst.output= ip_rt_bug;
atomic_set(&rth->u.dst.__refcnt, 1);
rth->u.dst.flags= DST_HOST;
if (in_dev->cnf.no_policy)
rth->u.dst.flags |= DST_NOPOLICY;
rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
rth->u.dst.tclassid = itag;
rth->rt_iif =rth->fl.iif = dev->ifindex;
rth->u.dst.dev = &loopback_dev; //接收的报文,则路由DST对象的dev指向回环接口
rth->idev = in_dev_get(rth->u.dst.dev);
rth->fl.oif = 0; //标识这个路由缓存条目是输入方向。
rth->rt_gateway = daddr;
rth->rt_spec_dst= spec_dst;
rth->rt_type = RTN_MULTICAST; //多播地址类型
rth->rt_flags = RTCF_MULTICAST;
//如果传入参数表示本地已经加入此多播组,则将input回调设置为ip_local_deliver
//三层接收处理函数,在进行路由处理之后,就会调用input回调进行处理,这里则
//把数据包向本地进行递送。
if (our)
rth->u.dst.input= ip_local_deliver;
rth->rt_flags |= RTCF_LOCAL;
//如果目的地址不是本地多播类型,同时使用了应用层的多播路由进程,允许了多
//播转发。则将input回调再替换为ip_mr_input,该回调函数除了进行多播转发处
//理之外,如果本地已经加入了此多播组,则同时也向本地进行递送。细节可以
//参见之前分析的《IP组播》。
if (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
rth->u.dst.input = ip_mr_input;
RT_CACHE_STAT_INC(in_slow_mc);
//函数最开始增加过引用,这里配对进行一次递减
in_dev_put(in_dev);
//将新建的路由缓存条目插入到rt_hash_table表中,后续此路由的查找则直接通过开始
//的路由缓存即可找到。
hash = rt_hash(daddr, saddr, dev->ifindex);
return rt_intern_hash(hash, rth, (struct rtable**) &skb->dst);
---------------------------------------------------------------------------------------------------------------------
//同步垃圾回收,目前有两个地方会调用,一个是在分配路由DST对象时,一个是在将
//邻居对象绑定到DST对象绑定失败时。
rt_garbage_collect();
expire = RT_GC_TIMEOUT; //300秒
now = jiffies;
RT_CACHE_STAT_INC(gc_total);
//ip_rt_gc_min_interval 默认时间为500ms,当因为路由DST绑定arp邻居对象失败
//后,会暂时调为0。
//检测如果不能同时满足如下条件,则直接返回。
//1、最后一次同步垃圾回收时间还未超出最小周期时间
//2、路由缓存条目数还未超出最大限制,目前最大限制值在路由模块初始化时设置为
//hash桶数的16倍。
if (now - last_gc < ip_rt_gc_min_interval &&atomic_read(&ipv4_dst_ops.entries)
< ip_rt_max_size)
RT_CACHE_STAT_INC(gc_ignored);
goto out;
//goal = entries - elasticity * (2 ^ rt_hash_log) = entries - elasticity * (rt_hash_mask - 1)
//ip_rt_gc_elasticity默认值为8
//rt_hash_mask为路由缓存hash桶的桶个数
//这里的goal也就是当前路由缓存总数超出8倍hash桶个数的量
goal = atomic_read(&ipv4_dst_ops.entries) -(ip_rt_gc_elasticity << rt_hash_log);
//未超过8倍hash桶个数
if (goal <= 0)
//gc_thresh值在路由模块初始化时为rt_hash_mask + 1
//校正equilibrium最小值为hash桶个数。
if (equilibrium < ipv4_dst_ops.gc_thresh)
equilibrium = ipv4_dst_ops.gc_thresh;
//计算当前总的缓存条目是否超出平衡值。
goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
//如果总的缓存条目超出平衡值,则准备进行垃圾回收,否则不进行垃圾回
//收。
if (goal > 0)
//调整平衡值,递增超出条目数的1半。
equilibrium += min_t(unsigned int, goal / 2, rt_hash_mask + 1);
//计算出希望垃圾回收条目个数为当前总数减去平衡值
goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
//超过8倍hash桶个数
else
//计算出垃圾回收条目个数为超出8位hash桶个数的一半。
goal = max_t(unsigned int, goal / 2, rt_hash_mask + 1);
//平衡值为总数减去准备垃圾回收条目数。
equilibrium = atomic_read(&ipv4_dst_ops.entries) - goal;
//更新最后同步垃圾回收的时间
if (now - last_gc >= ip_rt_gc_min_interval)
last_gc = now;
//如果当前缓存条目数还未超过gc_thresh阀值,则退出
if (goal <= 0)
equilibrium += goal;
goto work_done;
do
//rover为上一次同步垃圾回收时,记载的hash桶索引,每次进行同步垃圾回
//收都基于上次hash桶进行处理。
for (i = rt_hash_mask, k = rover; i >= 0; i--)
tmo = expire; //300秒
//k更新为下一个hash桶索引
k = (k + 1) & rt_hash_mask;
//遍历当前hash桶条目
rthp = &rt_hash_table[k].chain;
while ((rth = *rthp) != NULL)
//判断当前路由缓存条目是否可以清除,条件如下:
//1、引用计数必须等于0,否则不能清除。
//2、如果缓存条目设有超时时间(通常是0),则判断如果超时时间已经
//超时,则可以进行清除。
//3、如果第2个条件不满足,则继续进行下面条件判断处理。
//4、如果当前不是输入方向的多播、组播报文(即是输出方向的任何类
//型报文或输入方向的单播报文),当前缓存最后使用时间未超过tmo时,
//不能清除。
//5、否则缓存最后使用时间未超过expire时,并且路由是ICMP重定
//向创建的,或者路由条目手工创建时设备了NOTIFY标记,或者
//路由条目含有超时时间,则不能清除。
//6、其它情况时,该路由缓存条目都可以清除。
if (!rt_may_expire(rth, tmo, expire))
//如果当前缓存项不可清除,则将tmo时间减半,来增加检测
//强度。
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
//清除待删除的缓存条目,同时递减期望回收的个数。
*rthp = rth->u.rt_next;
rt_free(rth);
goal--;
//如果当前hash桶链表条目全部遍历完成后,满足了期望回收的个数,
//则退出。否则继续处理下一个hash桶链表。
if (goal <= 0)
break;
//记载最后处理的hash桶的索引
rover = k;
//如果满足了期望回收的个数,则返回完成
if (goal <= 0)
goto work_done;
RT_CACHE_STAT_INC(gc_goal_miss);
//超时判断条件每次减半,如果已经为0,则退出循环处理。
if (expire == 0)
break;
//超时判断条件减半。
expire >>= 1;
//经过一次hash桶遍历后,如果缓存总数已经小于16倍的hash桶桶数。
//则直接退出。
if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
goto out;
//如果没在软中断上下文中,并且当前流逝时间未超过1秒,则继续进行垃圾回
//收处理。
while (!in_softirq() && time_before_eq(jiffies, now));
//如果缓存总数已经小于16倍的hash桶桶数,返回。
if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
goto out;
RT_CACHE_STAT_INC(gc_dst_overflow);
return 1;
work_done:
//超时判断阀值递增最小垃圾回收间隔值
expire += ip_rt_gc_min_interval;
//超时判断阀值不能大于默认的垃圾回收周期,或者当缓存条目总数小于垃圾
//回收的hash桶个数时,也设置为默认的垃圾回周期。
if (expire > ip_rt_gc_timeout ||atomic_read(&ipv4_dst_ops.entries) <
ipv4_dst_ops.gc_thresh)
expire = ip_rt_gc_timeout;
return 0;
----------------------------------------------------------------------------------------------------------------------
fib_semantic_match
//遍历所有alias对象
list_for_each_entry_rcu(fa, head, fa_list)
//如果匹配条件需要进行tos值匹配,则进行tos匹配,匹配失败后继续查找下
//一个alias对象
if (fa->fa_tos &&fa->fa_tos != flp->fl4_tos)
continue;
//路由条目的范围值必须大于或等于(即距离相等或更近)匹配条件的范围值。
if (fa->fa_scope < flp->fl4_scope)
continue;
//标记当前路由条目被访问过,当路由条目被删除等处理时,会根据该标记
//确定是否进行路由缓存刷新。
fa->fa_state |= FA_S_ACCESSED;
//根据不同类型值来确定错误类型,比如如果类型为RTN_MULTICAST,则
//error为0,表示返回路由项为正常,如果类型为RTN_BLACKHOLE,则
//error为-EINVAL,表示对于黑洞类型处理方式为丢弃。要区分这里和策略
//路由的动作类型是不同的,但都可以达到根据路由模块将报文是否丢弃的
//目的。
err = fib_props[fa->fa_type].error;
//当前路由条目类型为正常情况。
if (err == 0)
struct fib_info *fi = fa->fa_info;
//当前info对象已经由于一些情况(比如接口设备DOWN、IP改变等情况)
//标记为被删除,则也表明不匹配,继续处理下一个alias对象。
if (fi->fib_flags & RTNH_F_DEAD)
continue;
//对于远端单播、本地、广播、多播常用的路由类型,需要进行下一跳对象
//的匹配处理。
switch (fa->fa_type)
case RTN_UNICAST:
case RTN_LOCAL:
case RTN_BROADCAST:
case RTN_ANYCAST:
case RTN_MULTICAST:
//遍历当前info对象中所有下一跳对象
for_nexthops(fi)
//如果当前下一跳对象已经标记为删除,则继续处理下一个
//下一跳对象。
if (nh->nh_flags&RTNH_F_DEAD)
continue;
//如果当前查找条件没有要求进行输出接口处理,则处理完成。
//或者查找条件需要进行输出接口处理,并且当前下一跳输出接口
//与查找条件匹配成功,则处理完成。
if (!flp->oif || flp->oif == nh->nh_oif)
break;
//处理完成,走最后填弃结果信息流程。
if (nhsel < 1)
goto out_fill_res;
endfor_nexthops(fi);
//其它类型则返回错误,将报文丢弃。
default:
return -EINVAL;
//错误情况流程会走到这里,返回错误。
return err;
//没有查找到匹配的条目,返回1
return 1;
//查找成功,进行结果信息的填充。
out_fill_res:
res->prefixlen = prefixlen;
res->nh_sel = nh_sel;
res->type = fa->fa_type;
res->scope = fa->fa_scope;
res->fi = fa->fa_info;
atomic_inc(&res->fi->fib_clntref);
return 0;
五、输出方向路由查找
ip_route_output_flow
//进行输出方向路由查找
err = __ip_route_output_key(rp, flp)
//根据目的地址、源地址、输出接口索引三个关键参数生成hash key
hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif);
//遍历路由缓存hash表
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
rth = rcu_dereference(rth->u.rt_next))
rth = rcu_dereference(rth->u.rt_next))
//判断目的地址、源地址、输出接口索引、MARK标签、TOS是否匹配
if (rth->fl.fl4_dst == flp->fl4_dst &&rth->fl.fl4_src == flp->fl4_src &&
rth->fl.iif == 0 &&rth->fl.oif == flp->oif &&
rth->fl.mark == flp->mark &&!((rth->fl.fl4_tos ^ flp->fl4_tos) &
(IPTOS_RT_MASK | RTO_ONLINK)))
//多路径缓存机制,暂不分析,假设没有开启,则返回0
if (multipath_select_route(flp, rth, rp))
dst_hold(&(*rp)->u.dst);
RT_CACHE_STAT_INC(out_hit);
return 0;
//路由缓存查找成功,更新DST对象最后使用时间,增加DST引用
//计数。
rth->u.dst.lastuse = jiffies;
dst_hold(&rth->u.dst);
rth->u.dst.__use++;
RT_CACHE_STAT_INC(out_hit);
*rp = rth;
return 0;
//路由缓存没有找到,进行慢速的路由策略查找。
ip_route_output_slow(rp, flp);
tos = RT_FL_TOS(oldflp);
//构造路由查找条件对象
fl = { .nl_u = { .ip4_u ={ .daddr = oldflp->fl4_dst,
.saddr = oldflp->fl4_src,.tos = tos & IPTOS_RT_MASK,
.scope = ((tos & RTO_ONLINK) ?RT_SCOPE_LINK :RT_SCOPE_UNIVERSE),
} },
.mark = oldflp->mark,.iif = loopback_dev.ifindex,.oif = oldflp->oif };
//如果输入条件含有源地址信息,则对没有指定输出接口的多播、受限广播
//报文进行单独流程处理。
if (oldflp->fl4_src)
err = -EINVAL;
//对于合法源进行校验,以下条件为非法源地址:
//1、多播地址
//2、E类预留地址
//3、0.x.x.x地址
if (MULTICAST(oldflp->fl4_src) ||BADCLASS(oldflp->fl4_src) ||
ZERONET(oldflp->fl4_src))
goto out;
//将源地址做为目的地址,在local本地路由表中进行查找,如果
//查找成功,则表明该源地址在本机设备上存在。
dev_out = ip_dev_find(oldflp->fl4_src);
//当查找条件的源地址不是有效的本地接口地址,则返回错误
if (dev_out == NULL)
goto out;
//当目的地址是组播或受限广播报文时,如果查找条件没有指定
//输出接口,则直接走生成路由缓存表,得出查找结果的流程,
//同时指定输出接口为给定的源地址的接口设备。
if (oldflp->oif == 0
&& (MULTICAST(oldflp->fl4_dst) || oldflp->fl4_dst ==
htonl(0xFFFFFFFF)))
fl.oif = dev_out->ifindex;
goto make_route;
//上面ip_dev_find已经增加设备引用,这里递减
if (dev_out)
dev_put(dev_out);
dev_out = NULL;
//如果查找条件指定了输出接口
if (oldflp->oif)
//根据输出设备索引查找设备接口是否存在
dev_out = dev_get_by_index(oldflp->oif);
//如果查找条件指定的输出接口不合法,则返回错误
err = -ENODEV;
if (dev_out == NULL)
goto out;
//获取IPV4的设备对象,如果不存在则返回错误
if (__in_dev_get_rtnl(dev_out) == NULL)
dev_put(dev_out);
goto out;
//当前条件设置了输出接口,当目的地址是本地多播地址、
//或者是受限广播地址,则直接走生成路由缓存及结果的流程。
//同时如果没有设置源地址条件,则选择输出设备的地址做为源
//地址。
if (LOCAL_MCAST(oldflp->fl4_dst) || oldflp->fl4_dst ==
htonl(0xFFFFFFFF))
if (!fl.fl4_src)
fl.fl4_src = inet_select_addr(dev_out, 0,RT_SCOPE_LINK);
goto make_route;
//如果没有指定源地址,则选择输出设备的地址做为源地址。
if (!fl.fl4_src)
if (MULTICAST(oldflp->fl4_dst))
fl.fl4_src = inet_select_addr(dev_out, 0,fl.fl4_scope);
else if (!oldflp->fl4_dst)
fl.fl4_src = inet_select_addr(dev_out, 0,RT_SCOPE_HOST);
//如果目的地址为0,则当成本地报文进行处理。
if (!fl.fl4_dst)
//默认将目的地址设置为和源地址相同。
fl.fl4_dst = fl.fl4_src;
//如果目的地址和源地址都为0,则修正为127.0.0.1
if (!fl.fl4_dst)
fl.fl4_dst = fl.fl4_src = htonl(INADDR_LOOPBACK);
if (dev_out)
dev_put(dev_out);
//重新引用输出设备为回环设备
dev_out = &loopback_dev;
dev_hold(dev_out);
fl.oif = loopback_dev.ifindex;
res.type = RTN_LOCAL;
flags |= RTCF_LOCAL;
//走生成路由缓存及结果的流程
goto make_route;
//进行路由策略处理,该函数在上面已经分析过了,这里不再进行分析。
//返回0表示查找成功,返回1表示没有找到,返回负数表示出错。
if (fib_lookup(&fl, &res))
res.fi = NULL;
//虽然路由查找失败,但当前指定了输出接口索引,则直接从
//当前接口将报文发出。
if (oldflp->oif)
//没有指定源地址,取当前输出设备的地址做为源地址。
if (fl.fl4_src == 0)
fl.fl4_src = inet_select_addr(dev_out, 0,RT_SCOPE_LINK);
res.type = RTN_UNICAST;
//走生成路由缓存及结果的流程
goto make_route;
//路由查找失败,返回错误
if (dev_out)
dev_put(dev_out);
err = -ENETUNREACH;
goto out;
free_res = 1;
//路由查找结果为本地,则进行本地流程处理
if (res.type == RTN_LOCAL)
//如果没有指定源地址,则选择目的地址做为源地址
if (!fl.fl4_src)
fl.fl4_src = fl.fl4_dst;
//本地报文将输出设备引用改为回环设备
if (dev_out)
dev_put(dev_out);
dev_out = &loopback_dev;
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
//本地路由已经不再需要info对象的引用,进行释放
if (res.fi)
fib_info_put(res.fi);
res.fi = NULL;
//走生成路由缓存及结果的流程
flags |= RTCF_LOCAL;
goto make_route;
//如果路由的子网掩码为0.0.0.0,同时路由条目类型为单播、没有设置输出接
//口,则进行默认网关选择。
if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif)
fib_select_default(&fl, &res);
//如果当前路由查找结果为动作处理为查表,同时结果含有网关,同
//时下一跳对象的范围值为LINK,则进行默认网关选择。
if (res->r && res->r->action == FR_ACT_TO_TBL &&
FIB_RES_GW(*res) && FIB_RES_NH(*res).nh_scope ==
RT_SCOPE_LINK)
//根据路由表索引获取路由表对象
tb = fib_get_table(res->r->table)
//调用路由表对象的回调,当前假设使用hash路由表,则
//回调函数为fn_hash_select_default
tb->tb_select_default(tb, flp, res);
fn_hash_select_default
//选择子网掩码为0的zone对象
struct fn_hash *t = (struct fn_hash*)tb->tb_data;
struct fn_zone *fz = t->fn_zones[0];
//如果没有配置含有0.0.0.0的路由,则直接返回
if (fz == NULL)
return;
last_idx = -1;
last_resort = NULL;
order = -1;
//遍当子网掩码为0的zone对象中的node对象
hlist_for_each_entry(f, node, &fz->fz_hash[0], fn_hash)
//遍历node对象中的所有alias对象
list_for_each_entry(fa, &f->fn_alias, fa_list)
//获取alias对象中的info对象
struct fib_info *next_fi = fa->fa_info;
//查找与当前结果范围匹配,并且类型
//为UNICAST的alias对象。
if (fa->fa_scope != res->scope ||
fa->fa_type != RTN_UNICAST)
continue;
//查找优先级小于或等于当前查找结果
if (next_fi->fib_priority > res->fi->fib_priority)
break;
//下一跳对象必须含有网关,并且一下跳
//对象的范围值必须为LINK。
if (!next_fi->fib_nh[0].nh_gw ||
next_fi->fib_nh[0].nh_scope !=
RT_SCOPE_LINK)
continue;
//当前alias对象标记当前条目已经使用过。
fa->fa_state |= FA_S_ACCESSED;
//初始时fi等于NULL,所以第一个下一跳
//不会进行出口网关是否活动的判断。
if (fi == NULL)
//如果当前条目的info对象与查找的不同
//则选择下一个node对象。
if (next_fi != res->fi)
break;
//如果有多个0.0.0.0的路由条目,则需要选择
//一个路由条目的网关做为默认路由。选择哪
//个路由条目的处理方法如下:
//1、将当前下一跳的网关进行ARP查找,
//找到的邻居项的状态为NUD_REACHABLE
//可到达状态,fib_detect_death就返回0,则
//选择该网关做为默认网关。
//2、将当前下一跳的网关进行ARP查找,
//找到的邻居项的状态为NUD_VALID任意
//有效态,同时当前已经选择的默认网关
//索引与当前条目的索引不同,则
//fib_detect_death返回0,选择该网关做为
//默认网关。
//3、其它情况则fib_detect_death返回1,
//继续查找下一个路由条目。
//
//fib_detect_death函数返回1时,表示还没有
//找到能立即做为默认网关的条目,但同时也
//会更新一个临时性的值做为
//fn_hash_last_dflt,后续整个0.0.0.0的路由
//条目都遍历完成后,就使用最后一个设置好
//的fn_hash_last_dflt做为选择的默认网关。
//在如下两种情况之一会临时更新
//fn_hash_last_dflt。
//1、根据出口网关进行ARP查找,找到的邻
//居状态为NUD_VALID任意有效态。
//2、当前还没有选择默认网关,则先选择第
//一个执行此流程的路由条目的网关。
else if (!fib_detect_death(fi, order, &last_resort,
&last_idx, &fn_hash_last_dflt))
//找到了优先可用的默认网关。则更新
//结果的info信息后退出。
if (res->fi)
if (res->fi)
res->fi = fi;
atomic_inc(&fi->fib_clntref);
fn_hash_last_dflt = order;
goto out;
//继续查找下一个alias对象。
fi = next_fi;
order++;
//没有找到出口网关,或者仅找到一个不活动的出口
//网关,则直接返回,保持原来路由查找的结果(
//比如原因已经找到一个路由条目的出口网关。)
if (order <= 0 || fi == NULL)
fn_hash_last_dflt = -1;
goto out;
//上面条件待出现仅有一条路由时,不会进行出口网关
//是否活动的检测,这里再执行一次检测。
if (!fib_detect_death(fi, order, &last_resort,
&last_idx, &fn_hash_last_dflt))
//检测到当前出口网关是活动时,则将选择查找
//结果的info对象替换为选择的。
if (res->fi)
fib_info_put(res->fi);
res->fi = fi;
atomic_inc(&fi->fib_clntref);
fn_hash_last_dflt = order;
goto out;
//走到这里,没有找到活动的出口网关,但找到了多个
//不活动的出口网关,则根据之前fib_detect_death的
//处理选择其中一个。
if (last_idx >= 0)
if (res->fi)
fib_info_put(res->fi);
res->fi = last_resort;
if (last_resort)
atomic_inc(&last_resort->fib_clntref);
fn_hash_last_dflt = last_idx;
out:
read_unlock(&fib_hash_lock);
//如果查找条件没有设置源地址,则选择一个源地址。
//1、如果当前查找结果的info对象中在添加路由表时设置优先源地址则
//使用此地址。
//2、否则根据输出设备,及当前路由条目的范围值选择一个源地址。
if (!fl.fl4_src)
fl.fl4_src = FIB_RES_PREFSRC(res);
//将输出接口信息替换为查找到的路由条目中的输出接口。
if (dev_out)
dev_put(dev_out);
dev_out = FIB_RES_DEV(res);
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
make_route:
//进行路由缓存创建添加
ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);
ip_mkroute_output_def(rp, res, fl, oldflp, dev_out, flags);
//生成路由缓存
__mkroute_output(&rth, res, fl, oldflp, dev_out, flags);
//当源地址为127.0.0.1时,同时当前设备不是环回设备时
//返回错误。
if (LOOPBACK(fl->fl4_src)
&& !(dev_out->flags&IFF_LOOPBACK))
return -EINVAL;
//当前目的地址为受限广播,则标记为广播类型
if (fl->fl4_dst == htonl(0xFFFFFFFF))
res->type = RTN_BROADCAST;
//目的地址为多播类型
else if (MULTICAST(fl->fl4_dst))
res->type = RTN_MULTICAST;
//目的地址为E类预留地址或者为0.X.X.X地址,则返回
//错误。
else if (BADCLASS(fl->fl4_dst) || ZERONET(fl->fl4_dst))
return -EINVAL;
//如果输出设备为回环设备,则修正类型为本地输入
if (dev_out->flags & IFF_LOOPBACK)
flags |= RTCF_LOCAL;
in_dev = in_dev_get(dev_out);
//对于广播类型的路由,则标上广播和本地的标记
if (res->type == RTN_BROADCAST)
flags |= RTCF_BROADCAST | RTCF_LOCAL;
//广播类型的报文是不需要查找结果中info对
//象的目的信息的,将引用的info对象释放。
if (res->fi)
fib_info_put(res->fi);
res->fi = NULL;
//多播类型的报文
else if (res->type == RTN_MULTICAST)
flags |= RTCF_MULTICAST|RTCF_LOCAL;
//进行多播路由表查找,如果本地接口没有加入此多
//播组,则去除需要进行本地处理的标记。
if (!ip_check_mc(in_dev, oldflp->fl4_dst, oldflp->fl4_src,
oldflp->proto))
flags &= ~RTCF_LOCAL;
//多播地址是D类地址,其中前4位固定为1110,
//子网掩码怎么可能会出现小于4的情况?
if (res->fi && res->prefixlen < 4)
fib_info_put(res->fi);
res->fi = NULL;
//分配DST对象,同时做一些基本的初始化,
rth = dst_alloc(&ipv4_dst_ops);
//DST对象初始化
atomic_set(&rth->u.dst.__refcnt, 1);
rth->u.dst.flags= DST_HOST;
if (in_dev->cnf.no_xfrm)
rth->u.dst.flags |= DST_NOXFRM;
if (in_dev->cnf.no_policy)
rth->u.dst.flags |= DST_NOPOLICY;
rth->fl.fl4_dst = oldflp->fl4_dst;
rth->fl.fl4_dst = oldflp->fl4_dst;
rth->fl.fl4_src = oldflp->fl4_src;
rth->fl.oif = oldflp->oif;
rth->fl.mark = oldflp->mark;
rth->rt_dst = fl->fl4_dst;
rth->rt_src = fl->fl4_src;
rth->rt_iif = oldflp->oif ? : dev_out->ifindex;
rth->u.dst.dev = dev_out;
dev_hold(dev_out);
rth->idev = in_dev_get(dev_out);
rth->rt_gateway = fl->fl4_dst;
rth->rt_spec_dst= fl->fl4_src;
//设置output输出回调为ip_output
rth->u.dst.output=ip_output;
RT_CACHE_STAT_INC(out_slow_tot);
//如果标识报文有向本地路由方向,则修改input
//的回调函数为ip_local_deliver
if (flags & RTCF_LOCAL)
rth->u.dst.input = ip_local_deliver;
rth->rt_spec_dst = fl->fl4_dst;
//对于广播或组播的报文回调函数有所不同
if (flags & (RTCF_BROADCAST | RTCF_MULTICAST))
rth->rt_spec_dst = fl->fl4_src;
//如果标识为有路由到本地的报文,同时输出设备
//不是回环设备,则对于广播或组播的output回调
//替换为ip_mc_output
if (flags & RTCF_LOCAL &&
!(dev_out->flags & IFF_LOOPBACK))
rth->u.dst.output = ip_mc_output;
RT_CACHE_STAT_INC(out_slow_mc);
//对于多播类型的报文,如果当前目的地址不是本
//地多播类型,同时当前设备支持多播转发,则修
//正input、output回调为多播使用的回调。
if (res->type == RTN_MULTICAST)
if (IN_DEV_MFORWARD(in_dev) &&
!LOCAL_MCAST(oldflp->fl4_dst))
rth->u.dst.input = ip_mr_input;
rth->u.dst.output = ip_mc_output;
//更新路由缓存的下一跳相关信息。
rt_set_nexthop(rth, res, 0);
rth->rt_flags = flags;
*result = rth;
cleanup:
in_dev_put(in_dev);
//将新建的路由缓存对象加入到rt_hash_table表中。
rt_hash(oldflp->fl4_dst, oldflp->fl4_src, oldflp->oif);
rt_intern_hash(hash, rth, rp);
//释放不使用的资源。
if (free_res)
fib_res_put(&res);
if (dev_out)
dev_put(dev_out);
//查找失败,返回错误
if ( err != 0 )
return err;
//安全模块相关,暂不分析。
if (flp->proto)
if (!flp->fl4_src)
flp->fl4_src = (*rp)->rt_src;
if (!flp->fl4_dst)
flp->fl4_dst = (*rp)->rt_dst;
return xfrm_lookup((struct dst_entry **)rp, flp, sk, flags);
return 0;
六、异步垃圾回收
//该定时器大约为1分钟左右进行一份异步垃圾回收
rt_check_expire
//rover记载了上次进行异步垃圾回收时处理的hash桶索引
i = rover
//mult = ip_rt_gc_interval * 2 ^ rt_hash_log
//rt_hash_mask = (2 ^ rt_hash_log) - 1
//mult = ip_rt_gc_interval * (rt_hash_mask + 1)
//mult值为垃圾时间倍数的hash桶各数
mult = ((u64)ip_rt_gc_interval) << rt_hash_log;
//ip_rt_gc_timeout默认时间为300秒
//如果有垃圾回收超时时间,则以超时时间为单位计算需要进行垃圾回次的桶
//个数。
//mult = mult / ip_rt_gc_timeout
if (ip_rt_gc_timeout > 1)
do_div(mult, ip_rt_gc_timeout);
goal = (unsigned int)mult;
//goal值不能大于hash桶个数
if (goal > rt_hash_mask) goal = rt_hash_mask + 1;
//依次遍历goal个hash桶链表个数。
for (; goal > 0; goal--)
tmo = ip_rt_gc_timeout;
//i更新为下一个待处理的hash桶的索引
i = (i + 1) & rt_hash_mask;
//当前hash桶下的链表
rthp = &rt_hash_table[i].chain;
//当前hash桶下没有条目则继续处理下一个hash桶
if (*rthp == 0)
continue;
//遍历当前hash桶下所有链表条目
while ((rth = *rthp) != NULL)
//1、如果当前路由缓存条目有超时时间,在未超时情况下不进行释放,
//同时缩短tmo的超时条件,继续查找当前hash桶链表的下一个条目。
//2、如果当前路由缓存条目有超时时间,并且已经超时,则走一面释放该
//路由缓存的处理流程。
if (rth->u.dst.expires)
if (time_before_eq(now, rth->u.dst.expires))
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
//否则如果当前路由条目未满足可以释放的条件,则缩短tmo的超时条件,
//继续查找当前hash桶链表的下一个条目。满足可以释放的条件,则走
//下面进行释放的流程。满足的条件为:
//1、如果引用计数大于1则不能删除。
//2、如果最后使用时间小于tmo,则对于输出方向的报文或者输入方向的单
//播报文不能删除。
//3、如果最后使用时间小于ip_rt_gc_timeout,则对于通过ICMP重定向创
//建的路由缓存或者用户创建路由时指定通知标记的路由缓存不能删除。
else if (!rt_may_expire(rth, tmo, ip_rt_gc_timeout))
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
//对当前满足条件的路由缓存条目进行释放。
*rthp = rth->u.rt_next;
rt_free(rth);
//如果已经进行了1秒钟的处理,则退出for循环,异步垃圾回收每次最多只处
//理1秒钟的任务,如果在1秒钟内没有遍历完所有hash桶的处理,则需要
//立即退出,待下一次异步垃圾回收定时器超时时再继续处理。
if (time_after(jiffies, now))
break;
//记载下一次需要从哪个hash桶开始处理。
rover = i;
//重新启动异步垃圾回收定时器。
mod_timer(&rt_periodic_timer, jiffies + ip_rt_gc_interval);
七、路由缓冲刷新
路由缓存刷新在以下情况下都可能被触发:
1、设备状态变化或参数变化引起的外部事件
2、设备的地址信息改变引起的外部事件
3、允许设备转发单播的配置项改变时
4、添加路由表项时对已经存在的路由表项的替换
5、添加、删除路由表项
6、多播报文相关的虚拟接口添加、删除,多播组加入、退出等。
7、通过/proc进行手动路由缓存刷新
8、异步定时路由缓冲刷新
这里仅分析异步定时器进行路由缓存刷新,在路由模块初始化时设置了进行异步定时刷新的
定时器,该定时器的回调函数为rt_secret_rebuild,定时器的周期时间为10分钟。
rt_secret_rebuild
now = jiffies;
//立即进行路由缓冲刷新
rt_cache_flush(0);
now = jiffies;
user_mode = !in_softirq();
//delay值有如下三种情况:
//1、大于0,则根据delay值进行延迟delay时间后再进行缓存刷新
//2、等于0,则表示要立即进行缓存刷新
//3、小于0,则表示使用默认的ip_rt_min_delay值进行延时刷新。
/ip_rt_min_delay默认是2秒钟。
if (delay < 0)
delay = ip_rt_min_delay;
//多路径缓存刷新,暂不分析和多路径缓存相关,暂时假设没有开启该功能宏
multipath_flush();
//尝试停止之前已经运行的延迟刷新定时器,如果之前确实有定时器运行,并且
//当前要求进行延迟刷新,同时之前已经设置了定时器期限值。
if (del_timer(&rt_flush_timer) && delay > 0 && rt_deadline)
//获取还差多少时间定时器期限值就超时。
long tmo = (long)(rt_deadline - now);
//如果当前在非软中断上下文,同时之前定时器的时间已经在min_delay
//与max_delay之间。则调整为立即超时。
if (user_mode && tmo < ip_rt_max_delay-ip_rt_min_delay)
tmo = 0;
//如果上次快超时的时间比当前要求的短,则继续上次快超时的时间。
if (delay > tmo)
delay = tmo;
//延迟时间小于等于0,则立即进行刷新。
if (delay <= 0)
rt_run_flush(0);
//刷新触发后,将期限值复位,表明已经没有等待的定时器了。
rt_deadline = 0;
//重新生成一个随机数,该随机数作为路由缓存生成的hash key使用,
//周期变化,防止DDOS攻击。
get_random_bytes(&rt_hash_rnd, 4);
//遍历所有hash桶
for (i = rt_hash_mask; i >= 0; i--)
//获取当前hash桶的链表
rth = rt_hash_table[i].chain;
//将当前hash桶的链表引用清除
if (rth)
rt_hash_table[i].chain = NULL;
//遍历老的链表所有条目
for (; rth; rth = next)
next = rth->u.rt_next;
//对每个路由缓存对象进行释放。
rt_free(rth);
return;
//初始时,或者每次缓存刷新执行,则期限值就变为0,这里为0时,重
//新设置期限值为最大延迟时间。
if (rt_deadline == 0)
rt_deadline = now + ip_rt_max_delay;
//如果当前刷新延迟,则启动延迟定时器,该定时器的回调函数为rt_run_flush
mod_timer(&rt_flush_timer, now+delay);
//重新启动定时器。
mod_timer(&rt_secret_timer, now + ip_rt_secret_interval);