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);
- 以上情况都不成立时, 如果地址的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