admin管理员组

文章数量:1642357

在接收到邻居发现协议的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

本文标签: 路由邻居协议发现