收到邻居发现协议后,RA(Router Advertisement)报文后,由ndisc_router_discovery处理。首先,找出该报纸的源地址是否有默认路由器(rt6_get_dflt_router),并检查是否有可到达的邻居表项来释放路由信息。
如果RA通知的路由时间为零,本机中有以其为默认路由的路由信息,表明该默认路由信息应删除。
static void ndisc_router_discovery(struct sk_buff *skb) { struct fib6_info *rt = NULL; lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime); /* routes added from RAs do not use nexthop objects */ rt = rt6_get_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev); if (rt) { neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6, rt->fib6_nh->fib_nh_dev, NULL, &ipv6_hdr(skb)->saddr); if (!neigh) { ND_PRINTK(0, err, "RA: %s got default router without neighbour\n", __func__); fib6_info_release(rt); return; } } if (rt && lifetime == 0) { ip6_del_rt(net, rt, false); rt = NULL; } ND_PRINTK(3, info, "RA: rt: %p lifetime: %d, for dev: %s\n", rt, lifetime, skb->dev->name);
其次,如果上述路由信息为空,本通知的有效期不为零,则根据报告源地址添加默认路由器,并检查邻居的表格。
if (!rt && lifetime) { ND_PRINTK(3, info, "RA: adding default router\n"); rt = rt6_add_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev, pref); if (!rt) { ND_PRINTK(0, err, "RA: %s failed to add default route\n", __func__); return; } neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6, rt->fib6_nh->fib_nh_dev, NULL, &ipv6_hdr(skb)->saddr); if (!neigh) { ND_PRINTK(0, err, "RA: %s got default router without neighbour\n", __func__); fib6_info_release(rt); return; } neigh->flags |= NTF_ROUTER; } else if (rt) { rt->fib6_flags = (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref); } if (rt) fib6_set_expires(rt, jiffies (HZ * lifetime)); if (in6_dev->cnf.accept_ra_min_hop_limit < 256 && ra_msg->icmph.icmp6_hop_limit) { if (in6_dev->cnf.accept_ra_min_hop_limit <= ra_msg->icmph.icmp6_hop_limit) { in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit; fib6_metric_set(rt, RTAX_HOPLIMIT, ra_msg->icmph.icmp6_hop_limit); } else { ND_PRINTK(2, warn, "RA: Got route advertisement with lower hop_limit than minimum\n"); } }
查找默认路由器
通过路由表中的所有叶片节点,找到合适的路由信息。默认路由器不得包括在内nexthop属性,跳过此类路由信息。将当前路由信息的出口设备、标志和参数与下一个跳网关进行比较,都表明找到路由信息是一样的。
struct fib6_info *rt6_get_dflt_router(struct net *net, const struct in6_addr *addr, struct net_device *dev) { u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT; struct fib6_info *rt; struct fib6_table *table; table = fib6_get_table(net, tb_id); if (!table) return NULL; rcu_read_lock(); for_each_fib6_node_rt_rcu(&table->tb6_root) { struct fib6_nh *nh; /* RA routes do not use nexthops */ if (rt->nh) continue; nh = rt->fib6_nh; if (dev == nh->fib_nh_dev && ((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) && ipv6_addr_equal(&nh->fib_nh_gw6, addr)) break; }
添加默认路由器
调用通用函数ip6_route_add在此协议中添加默认路由器RTPROT_RA,表明是根据RA报纸生成。默认路由器成功添加后,增加当前路由表RT6_TABLE_HAS_DFLT_ROUTER在查询默认路由器表项时,将使用标志,以加速搜索。
struct fib6_info *rt6_add_dflt_router(struct net *net, const struct in6_addr *gwaddr, struct net_device *dev, unsigned int pref) { struct fib6_config cfg = { .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT, .fc_metric = IP6_RT_PRIO_USER, .fc_ifindex = dev->ifindex, .fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT | RTF_UP | RTF_EXPIRES | RTF_PREF(pref), .fc_protocol = RTPROT_RA, .fc_type = RTN_UNICAST, .fc_nlinfo.portid = 0, .fc_nlinfo.nlh = NULL, .fc_nlinfo.nl_net = net, }; cfg.fc_gateway = *gwaddr; if (!ip6_route_add(&cfg, GFP_ATOMIC, NULL)) { struct fib6_table *table; table = fib6_get_table(dev_net(dev), cfg.fc_table); if (table) table->flags |= RT6_TABLE_HAS_DFLT_ROUTER; } return rt6_get_dflt_router(net, gwaddr, dev);
清除默认路由器
当用户修改PROC文件中的fowarding值时,清除默认路由器。PROC文件:/proc/sys/net/ipv6/conf/all/forwarding,forwarding的值决定系统遵循IPv6主机还是路由器行为。
static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int newf)
{
...
if (newf)
rt6_purge_dflt_routers(net);
return 1;
}
遍历系统中的所有路由表(由256个数组以及每个数组元素为头的哈希链表组成),检查每个表中是否包含默认路由器(RT6_TABLE_HAS_DFLT_ROUTER),由函数__rt6_purge_dflt_routers处理。
void rt6_purge_dflt_routers(struct net *net)
{
struct fib6_table *table;
struct hlist_head *head;
unsigned int h;
rcu_read_lock();
for (h = 0; h < FIB6_TABLE_HASHSZ; h++) {
head = &net->ipv6.fib_table_hash[h];
hlist_for_each_entry_rcu(table, head, tb6_hlist) {
if (table->flags & RT6_TABLE_HAS_DFLT_ROUTER)
__rt6_purge_dflt_routers(net, table);
}
}
遍历路由表中的所有叶子节点,删除其中的默认路由器。默认路由器路由信息包括标志位(RTF_DEFAULT和RTF_ADDRCONF)。对于accept_ra为零时,不接收RA报文,删除默认路由器。accept_ra为1时,如果forwarding禁用,系统为IPv6主机,接收RA报文,此时路由表中可能包含默认路由器。
对于accept_ra等于2的情况,即forwarding启用,系统为IPv6路由器,但是接收其它路由器的RA报文,此时不删除默认路由器。
函数最后,清除路由表中的标志RT6_TABLE_HAS_DFLT_ROUTER,此路由表中不包含默认路由器。
static void __rt6_purge_dflt_routers(struct net *net, struct fib6_table *table)
{
struct fib6_info *rt;
restart:
rcu_read_lock();
for_each_fib6_node_rt_rcu(&table->tb6_root) {
struct net_device *dev = fib6_info_nh_dev(rt);
struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL;
if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
(!idev || idev->cnf.accept_ra != 2) && fib6_info_hold_safe(rt)) {
rcu_read_unlock();
ip6_del_rt(net, rt, false);
goto restart;
}
}
rcu_read_unlock();
table->flags &= ~RT6_TABLE_HAS_DFLT_ROUTER;
内核版本 5.10