默认情况下,配置项autoconf都是1,即开启地址自动配置。
$ cat /proc/sys/net/ipv6/conf/all/autoconf
1
$ cat /proc/sys/net/ipv6/conf/default/autoconf
1
$ cat /proc/sys/net/ipv6/conf/ens33/autoconf
1
$ cat /proc/sys/net/ipv6/conf/all/accept_ra_pinfo
1
$ cat /proc/sys/net/ipv6/conf/default/accept_ra_pinfo
1
$ cat /proc/sys/net/ipv6/conf/ens33/accept_ra_pinfo
1
如下代码可见,autoconf都初始化为1。
static struct ipv6_devconf ipv6_devconf __read_mostly = {
.autoconf = 1,
.accept_ra_pinfo = 1,
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.autoconf = 1,
.accept_ra_pinfo = 1,
另外,在IPv6模块加载时,可通过参数autoconf指定autoconf的默认值。
struct ipv6_params ipv6_defaults = {
.disable_ipv6 = 0,
.autoconf = 1,
};
module_param_named(autoconf, ipv6_defaults.autoconf, int, 0444);
MODULE_PARM_DESC(autoconf, "Enable IPv6 address autoconfiguration on all interfaces");
static int __net_init addrconf_init_net(struct net *net)
{
/* these will be inherited by all namespaces */
dflt->autoconf = ipv6_defaults.autoconf;
dflt->disable_ipv6 = ipv6_defaults.disable_ipv6;
前缀信息处理
接收到RA报文之后,如果其中包含前缀信息,并且当前接口配置项accept_ra_pinfo为真,遍历其中的前缀信息,由函数addrconf_prefix_rcv处理其中的每一项。
static void ndisc_router_discovery(struct sk_buff *skb)
{
if (in6_dev->cnf.accept_ra_pinfo && ndopts.nd_opts_pi) {
struct nd_opt_hdr *p;
for (p = ndopts.nd_opts_pi;
p;
p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
addrconf_prefix_rcv(skb->dev, (u8 *)p,
(p->nd_opt_len) << 3,
ndopts.nd_opts_src_lladdr != NULL);
}
}
首先,检查前缀地址类型,对于多播或者链路本地类型,不进行地址自动配置。前缀的prefered时长不应大于valid有效时长。
void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
{
struct prefix_info *pinfo;
pinfo = (struct prefix_info *) opt;
/* Validation checks ([ADDRCONF], page 19)
*/
addr_type = ipv6_addr_type(&pinfo->prefix);
if (addr_type & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL))
return;
valid_lft = ntohl(pinfo->valid);
prefered_lft = ntohl(pinfo->prefered);
if (prefered_lft > valid_lft) {
net_warn_ratelimited("addrconf: prefix option has invalid lifetime\n");
return;
}
in6_dev = in6_dev_get(dev);
if (!in6_dev) {
net_dbg_ratelimited("addrconf: device %s not configured\n", dev->name);
return;
}
对于链路本地前缀信息,增加路由表项。由于前缀信息的有效valid时长单位为秒值,而路由项的计时单位为jiffies,参见路由回收函数fib6_age中的处理,这里需要进行转换。将u32类型的valid_lft秒值,转换为unsigned long类型的rt_expires值。
如果valid_lft值为0xffffffff,表示永不超时。对于32位架构的处理器,将u32秒值转换为同为32位的long型jiffies值,很可能导致溢出,由addrconf_timeout_fixup进行处理。
if (pinfo->onlink) {
struct fib6_info *rt;
unsigned long rt_expires;
/* Avoid arithmetic overflow. Really, we could save rt_expires in seconds, likely valid_lft,
* but it would require division in fib gc, that it not good.
*/
if (HZ > USER_HZ)
rt_expires = addrconf_timeout_fixup(valid_lft, HZ);
else
rt_expires = addrconf_timeout_fixup(valid_lft, USER_HZ);
if (addrconf_finite_timeout(rt_expires))
rt_expires *= HZ;
如果前缀对应的路由项已经存在,更新其超时时间,如果valid_lft为零,删除路由表项。如果valid_lft为永不超时,停止路由表项的超时检测。另外,如果表项不存在,并且valid_lft有效时间不为零,为前缀创建新的路由表项。
rt = addrconf_get_prefix_route(&pinfo->prefix, pinfo->prefix_len, dev,
RTF_ADDRCONF | RTF_PREFIX_RT, RTF_DEFAULT, true);
if (rt) {
/* Autoconf prefix route */
if (valid_lft == 0) {
ip6_del_rt(net, rt, false);
rt = NULL;
} else if (addrconf_finite_timeout(rt_expires)) {
fib6_set_expires(rt, jiffies + rt_expires); /* not infinity */
} else {
fib6_clean_expires(rt);
}
} else if (valid_lft) {
clock_t expires = 0;
int flags = RTF_ADDRCONF | RTF_PREFIX_RT;
if (addrconf_finite_timeout(rt_expires)) {
/* not infinity */
flags |= RTF_EXPIRES;
expires = jiffies_to_clock_t(rt_expires);
}
addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len,
0, dev, expires, flags, GFP_ATOMIC);
}
fib6_info_release(rt);
}
如果前缀信息设置了autoconf标志,并且设备的autoconf配置项为真,在前缀长度为64的情况下,根据前缀信息生成接口地址。由以下几种情况:
1) 接口的token有值(ip token set命令),将token地址的后8字节最为接口ID,生成地址;
2) 接口地址生成模式为STABLE,由函数ipv6_generate_stable_address生成地址;
3) 否则,生成EUI64地址;
4) 如果以上都不成立,使用上次上次的EUI64格式的接口ID,生成地址。
/* Try to figure out our local address for this prefix */
if (pinfo->autoconf && in6_dev->cnf.autoconf) {
struct in6_addr addr;
bool tokenized = false, dev_addr_generated = false;
if (pinfo->prefix_len == 64) {
memcpy(&addr, &pinfo->prefix, 8);
if (!ipv6_addr_any(&in6_dev->token)) {
read_lock_bh(&in6_dev->lock);
memcpy(addr.s6_addr + 8, in6_dev->token.s6_addr + 8, 8);
read_unlock_bh(&in6_dev->lock);
tokenized = true;
} else if (is_addr_mode_generate_stable(in6_dev) &&
!ipv6_generate_stable_address(&addr, 0, in6_dev)) {
addr_flags |= IFA_F_STABLE_PRIVACY;
goto ok;
} else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) {
goto put;
} else {
dev_addr_generated = true;
}
goto ok;
}
net_dbg_ratelimited("IPv6 addrconf: prefix with wrong length %d\n", pinfo->prefix_len);
goto put;
以下配置生成的地址。
ok:
err = addrconf_prefix_rcv_add_addr(net, dev, pinfo, in6_dev,
&addr, addr_type, addr_flags, sllao,
tokenized, valid_lft, prefered_lft);
if (err) goto put;
/* Ignore error case here because previous prefix add addr was
* successful which will be notified.
*/
ndisc_ops_prefix_rcv_add_addr(net, dev, pinfo, in6_dev, &addr,
addr_type, addr_flags, sllao, tokenized, valid_lft,
prefered_lft, dev_addr_generated);
}
前缀地址配置
在上节生成前缀地址之后,由函数addrconf_prefix_rcv_add_addr进行配置处理。首先,检查是否已经配置了此地址,如果没有,并且要配置地址的有效时间valid_lft不为零,尝试配置此地址。
int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,
const struct prefix_info *pinfo, struct inet6_dev *in6_dev,
const struct in6_addr *addr, int addr_type,
u32 addr_flags, bool sllao, bool tokenized,
__u32 valid_lft, u32 prefered_lft)
{
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(net, addr, dev, 1);
int create = 0;
if (!ifp && valid_lft) {
int max_addresses = in6_dev->cnf.max_addresses;
struct ifa6_config cfg = {
.pfx = addr,
.plen = pinfo->prefix_len,
.ifa_flags = addr_flags,
.valid_lft = valid_lft,
.preferred_lft = prefered_lft,
.scope = addr_type & IPV6_ADDR_SCOPE_MASK,
};
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
if ((net->ipv6.devconf_all->optimistic_dad || in6_dev->cnf.optimistic_dad) &&
!net->ipv6.devconf_all->forwarding && sllao)
cfg.ifa_flags |= IFA_F_OPTIMISTIC;
#endif
只有在接口当前的地址数量小于配置的最大数量max_addresses时,才能新增地址。对于新增地址,需要启动DAD检测。
/* Do not allow to create too much of autoconfigured
* addresses; this would be too easy way to crash kernel.
*/
if (!max_addresses || ipv6_count_addresses(in6_dev) < max_addresses)
ifp = ipv6_add_addr(in6_dev, &cfg, false, NULL);
if (IS_ERR_OR_NULL(ifp)) return -1;
create = 1;
spin_lock_bh(&ifp->lock);
ifp->flags |= IFA_F_MANAGETEMPADDR;
ifp->cstamp = jiffies;
ifp->tokenized = tokenized;
spin_unlock_bh(&ifp->lock);
addrconf_dad_start(ifp);
}
以下,如果地址存在,或者以上操作新建了地址(create等于1),首先看一下是否需要更新以存在地址的valid有效时长。如果地址项自身的valid时长大于已经逝去的时长,地址还在有效期内,将新的valid和prefered时长更新到地址项中。清除DEPRECATED标记,如果地址为非TENTATIVE临时地址,发送RTM_NEWADDR事件通知。
以上的地址valid时间更新操作与RFC4862中表述的不一致,RFC4862为了防止伪造的RA包含过短的valid时长,造成地址的过早失效,定义了以下三种情况:
1) 如果前缀中的valid时长大于2个小时,或者大于接口已有地址中剩余的有效时长,将前缀中valid更新到地址中的valid_lft字段;
2) 如果地址中剩余有效时长小于等于2个小时,忽略前缀中的valid值,不更新。除非前缀所在的RA是经过认证的报文(如SEND);
3) 以上情况都不成立,将地址中的valid时长设置为2个小时。
但是,内核中为了及时响应地址重新配置,尽快去除旧地址,未采用RFC中的做法。也许这里可以设置一个配置项,来决定采用哪种方式。
if (ifp) {
/* Update lifetime (RFC4862 5.5.3 e)
* We deviate from RFC4862 by honoring all Valid Lifetimes to
* improve the reaction of SLAAC to renumbering events
* (draft-gont-6man-slaac-renum-06, Section 4.2)
*/
now = jiffies;
if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
else
stored_lft = 0;
if (!create && stored_lft) {
ifp->valid_lft = valid_lft;
ifp->prefered_lft = prefered_lft;
ifp->tstamp = now;
flags = ifp->flags;
ifp->flags &= ~IFA_F_DEPRECATED;
if (!(flags&IFA_F_TENTATIVE)) ipv6_ifa_notify(0, ifp);
}
manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft, create, now);
in6_ifa_put(ifp);
addrconf_verify();
接口token设置
如下ip命令所示。
# ip token set ::0102:0304 dev ens33
#
# ip token list
token ::1.2.3.4 dev ens33
token :: dev ens34
token :: dev ens35
内核版本 5.10