在接收到邻居发现协议的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文件中的forwarding值时,清除默认路由器。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