IPv6地址有效性检测

IPv6地址检测和DAD冲突检测使用的都是addrconf_wq队列,其在addrconf_init函数中创建。

int __init addrconf_init(void)
{
    struct inet6_dev *idev;
    ...

    addrconf_wq = create_workqueue("ipv6_addrconf");
    if (!addrconf_wq) {
        err = -ENOMEM;
        goto out_nowq;
    }

    addrconf_verify();

addr_chk_work为地址验证的延迟work。

static struct workqueue_struct *addrconf_wq;
static DECLARE_DELAYED_WORK(addr_chk_work, addrconf_verify_work);

在初始化函数addrconf_init中,将使能addr_chk_work。

static void addrconf_verify(void)
{
    mod_delayed_work(addrconf_wq, &addr_chk_work, 0);
}

配置流程可使用如下地址检测函数addrconf_verify_work。

static void addrconf_verify_work(struct work_struct *w)
{
    rtnl_lock();
    addrconf_verify_rtnl();
    rtnl_unlock();
}

地址检测

地址验证work最长的执行周期为120秒(ADDR_CHECK_FREQUENCY),在开始处理之前,先行取消此work。

static void addrconf_verify_rtnl(void)
{
    unsigned long now, next, next_sec, next_sched;
    struct inet6_ifaddr *ifp;

    ASSERT_RTNL();

    rcu_read_lock_bh();
    now = jiffies;
    next = round_jiffies_up(now + ADDR_CHECK_FREQUENCY);

    cancel_delayed_work(&addr_chk_work);

这里for循环遍历所有的hash数组成员,数组长度为256(IN6_ADDR_HSIZE); hlist_for_each_entry_rcu_bh将遍历每个hash链表中的地址项。在删除链表中的地址项时,不在继续遍历,而是跳到restart标签,再次从头开始遍历,避免漏过链表上的地址项。

    for (i = 0; i < IN6_ADDR_HSIZE; i++) {
restart:
        hlist_for_each_entry_rcu_bh(ifp, &inet6_addr_lst[i], addr_lst) {
            unsigned long age;

如果地址项设置了PERMANENT标记,并且prefered时长为无限,不处理此地址项。否则,计算地址的age,这里会适当增大地址age,对于即将到期的地址,也会一并处理。偏差值为ADDRCONF_TIMER_FUZZ_MINUS,单位jiffies。

            /* When setting preferred_lft to a value not zero or infinity, 
             * while valid_lft is infinity IFA_F_PERMANENT has a non-infinity life time.
             */
            if ((ifp->flags & IFA_F_PERMANENT) && (ifp->prefered_lft == INFINITY_LIFE_TIME))
                continue;

            spin_lock(&ifp->lock);
            /* We try to batch several events at once. */
            age = (now - ifp->tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;

以下几种情况处理地址的时长相关参数:

1) 如果地址的有效期valid不是无限,并且生存期age已经大于等于有效valid时长,需要删除此地址;
2) 如果地址的prefered时长为无限,继续遍历下一项;

            if (ifp->valid_lft != INFINITY_LIFE_TIME && age >= ifp->valid_lft) {
                spin_unlock(&ifp->lock);
                in6_ifa_hold(ifp);
                ipv6_del_addr(ifp);
                goto restart;
            } else if (ifp->prefered_lft == INFINITY_LIFE_TIME) {
                spin_unlock(&ifp->lock);
                continue;

3) 以上都不成立,并且地址的prefered时长已经到期,设置DEPRECATED标志。如果地址的valid时长不是无限,并且valid到期早于之前计算的next调度时刻,将next设置为valid到期时间。

对于deprecate地址,发送通知事件。开始遍历下一项。

            } else if (age >= ifp->prefered_lft) {
                /* jiffies - ifp->tstamp > age >= ifp->prefered_lft */
                int deprecate = 0;

                if (!(ifp->flags&IFA_F_DEPRECATED)) {
                    deprecate = 1;
                    ifp->flags |= IFA_F_DEPRECATED;
                }

                if ((ifp->valid_lft != INFINITY_LIFE_TIME) &&
                    (time_before(ifp->tstamp + ifp->valid_lft * HZ, next)))
                    next = ifp->tstamp + ifp->valid_lft * HZ;

                spin_unlock(&ifp->lock);

                if (deprecate) {
                    in6_ifa_hold(ifp);

                    ipv6_ifa_notify(0, ifp);
                    in6_ifa_put(ifp);
                    goto restart;
                }

4) 对于TEMPORARY隐私地址,如果未设置TENTATIVE标志,表明已经为可用地址。由于隐私地址的生成需要一定的时间,即regen_advance时长(包括DAD和重试),为保证接口随时有可用的隐私地址,需要提前regen_advance时长去生成新的隐私地址。即当前隐私地址的prefered时长在剩余regen_advance时,进行新隐私地址的生成。

否则,如果新的隐私地址应开始生成的时刻在next调度时刻之前,使用前者更新next,避免错过隐私地址的生成。

            } else if ((ifp->flags&IFA_F_TEMPORARY) && !(ifp->flags&IFA_F_TENTATIVE)) {
                unsigned long regen_advance = ifp->idev->cnf.regen_max_retry *
                    ifp->idev->cnf.dad_transmits *
                    max(NEIGH_VAR(ifp->idev->nd_parms, RETRANS_TIME), HZ/100) / HZ;

                if (age >= ifp->prefered_lft - regen_advance) {
                    struct inet6_ifaddr *ifpub = ifp->ifpub;
                    if (time_before(ifp->tstamp + ifp->prefered_lft * HZ, next))
                        next = ifp->tstamp + ifp->prefered_lft * HZ;
                    if (!ifp->regen_count && ifpub) {
                        ifp->regen_count++;
                        ...
                        ifpub->regen_count = 0;
                        ipv6_create_tempaddr(ifpub, true);
                        ...
                        goto restart;
                    }
                } else if (time_before(ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ, next))
                    next = ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ;
                spin_unlock(&ifp->lock);
  1. 以上情况都不成立时, 如果地址的prefered到期时间小于调度值next,使用prefered时间为下次调度时间。
            } else {
                /* ifp->prefered_lft <= ifp->valid_lft */
                if (time_before(ifp->tstamp + ifp->prefered_lft * HZ, next))
                    next = ifp->tstamp + ifp->prefered_lft * HZ;
                spin_unlock(&ifp->lock);
            }
        }
    }

以下计算addr_chk_work的下一次调度时刻,如果将jiffies表示的next向上调整到整秒的倍数之后,增加的jiffies值不大于0.25秒所对应的jiffies值(ADDRCONF_TIMER_FUZZ),内核将使用调整之后的值next_sec作为下一次调度值。否则,内核使用原本的next值。

另外,addr_chk_work的最快调度间隔为1秒(ADDRCONF_TIMER_FUZZ_MAX)。

最后,如果使用next_sec作为调度时刻,一些地址有可能在addr_chk_work执行之前已经失效。这个时间差在(0.25 - 1)秒之间。

    next_sec = round_jiffies_up(next);
    next_sched = next;

    /* If rounded timeout is accurate enough, accept it. */
    if (time_before(next_sec, next + ADDRCONF_TIMER_FUZZ))
        next_sched = next_sec;

    /* And minimum interval is ADDRCONF_TIMER_FUZZ_MAX. */
    if (time_before(next_sched, jiffies + ADDRCONF_TIMER_FUZZ_MAX))
        next_sched = jiffies + ADDRCONF_TIMER_FUZZ_MAX;

    pr_debug("now = %lu, schedule = %lu, rounded schedule = %lu => %lu\n",
         now, next, next_sec, next_sched);
    mod_delayed_work(addrconf_wq, &addr_chk_work, next_sched - now);

地址检测时机

对于自动创建的地址,函数manage_tempaddrs可能创建了隐私地址,addrconf_verify的一项功能就是确保在可以隐私地址到期前,创建新的隐私地址。

int addrconf_prefix_rcv_add_addr(struct net *net, ...)
{

    if (ifp) {
        ...
        manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft, create, now);

        in6_ifa_put(ifp);
        addrconf_verify();

以下是对于用户手动添加的地址,调用地址检测函数。

static int inet6_addr_add(struct net *net, int ifindex,
              struct ifa6_config *cfg, struct netlink_ext_ack *extack)
{

    ifp = ipv6_add_addr(idev, cfg, true, extack);
    if (!IS_ERR(ifp)) {
        ...
        if (cfg->ifa_flags & IFA_F_MANAGETEMPADDR)
            manage_tempaddrs(idev, ifp, cfg->valid_lft,
                     cfg->preferred_lft, true, jiffies);
        in6_ifa_put(ifp);
        addrconf_verify_rtnl();
        return 0;

对于用户删除的地址,如果地址为隐私地址,并且接口已经没有隐私地址可用,manage_tempaddrs函数将创建新隐私地址,而addrconf_verify_rtnl地址检测函数,会进一步预备下一次隐私地址的生成。

static int inet6_addr_del(struct net *net, int ifindex, u32 ifa_flags,
              const struct in6_addr *pfx, unsigned int plen)
{
    struct inet6_ifaddr *ifp;
    ...
    list_for_each_entry(ifp, &idev->addr_list, if_list) {
        if (ifp->prefix_len == plen && ipv6_addr_equal(pfx, &ifp->addr)) {
            in6_ifa_hold(ifp);
            read_unlock_bh(&idev->lock);

            if (!(ifp->flags & IFA_F_TEMPORARY) && (ifa_flags & IFA_F_MANAGETEMPADDR))
                manage_tempaddrs(idev, ifp, 0, 0, false, jiffies);
            ipv6_del_addr(ifp);
            addrconf_verify_rtnl();

在DAD结束之后,隐私地址变为可用状态,由函数addrconf_verify_rtnl准备新的隐私地址。

static void addrconf_dad_completed(struct inet6_ifaddr *ifp, bool bump_id, bool send_na)
{
    ...
    /* Make sure that a new temporary address will be created
     * before this temporary address becomes deprecated.
     */ 
    if (ifp->flags & IFA_F_TEMPORARY)
        addrconf_verify_rtnl();

在地址修改函数最后,调用地址检测函数。

static int inet6_addr_modify(struct inet6_ifaddr *ifp, struct ifa6_config *cfg)
{
    ...
    if (was_managetempaddr || ifp->flags & IFA_F_MANAGETEMPADDR) {
        if (was_managetempaddr && !(ifp->flags & IFA_F_MANAGETEMPADDR)) {
            cfg->valid_lft = 0;
            cfg->preferred_lft = 0;
        }
        manage_tempaddrs(ifp->idev, ifp, cfg->valid_lft,
                 cfg->preferred_lft, !was_managetempaddr, jiffies);
    }

    addrconf_verify_rtnl();

设置接口的token值,可能修改了接口地址的valid和prefered时长,调用检测函数。

static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
{
    ...
    list_for_each_entry(ifp, &idev->addr_list, if_list) {
        spin_lock(&ifp->lock);
        if (ifp->tokenized) {
            ifp->valid_lft = 0;
            ifp->prefered_lft = 0;
        }
        spin_unlock(&ifp->lock);
    }
    
    write_unlock_bh(&idev->lock);
    inet6_ifinfo_notify(RTM_NEWLINK, idev);
    addrconf_verify_rtnl();

在IPv6模块初始化失败时,参见函数inet6_init,如果已经初始化了addrconf功能,需要调用其清理函数,其中将取消地址检查work,并删除work队列addrconf_wq。

void addrconf_cleanup(void)
{
    ...
    cancel_delayed_work(&addr_chk_work);
    rtnl_unlock();

    destroy_workqueue(addrconf_wq);

内核版本 5.10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值