預設情況下,配置項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