RFC4941定义了IPv6的Privacy地址扩展,默认情况下,内核没有开启隐私地址,如下PROC文件use_tempaddr值为0。当use_tempaddr值大于0时,开启隐私地址功能。use_tempaddr值为1时,优先使用公开地址。而当use_tempaddr值大于1时,优先使用隐私地址。
$ cat /proc/sys/net/ipv6/conf/all/use_tempaddr
0
$ cat /proc/sys/net/ipv6/conf/default/use_tempaddr
0
创建的tempaddr隐私地址的有效时长和prefered时长默认情况下分别为7天和1天。
$ cat /proc/sys/net/ipv6/conf/all/temp_valid_lft
604800
$ cat /proc/sys/net/ipv6/conf/default/temp_valid_lft
604800
$ cat /proc/sys/net/ipv6/conf/all/temp_prefered_lft
86400
$ cat /proc/sys/net/ipv6/conf/default/temp_prefered_lft
86400
创建tempaddr隐私地址默认将尝试3次,如果都失败的话,放弃创建隐私地址。为避免多个设备同时创建隐私地址(发送DAD报文),内核增加了一个(0,max_desync_factor)之间的随机时间,默认情况下max_desync_factor值为10分钟。
$ cat /proc/sys/net/ipv6/conf/all/regen_max_retry
3
$ cat /proc/sys/net/ipv6/conf/default/regen_max_retry
3
$ cat /proc/sys/net/ipv6/conf/all/max_desync_factor
600
$ cat /proc/sys/net/ipv6/conf/default/max_desync_factor
600
如下代码,内核默认不使用tempaddr隐私地址。
static struct ipv6_devconf ipv6_devconf __read_mostly = {
...
.use_tempaddr = 0,
.temp_valid_lft = TEMP_VALID_LIFETIME,
.temp_prefered_lft = TEMP_PREFERRED_LIFETIME,
.regen_max_retry = REGEN_MAX_RETRY,
.max_desync_factor = MAX_DESYNC_FACTOR,
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
...
.use_tempaddr = 0,
.temp_valid_lft = TEMP_VALID_LIFETIME,
.temp_prefered_lft = TEMP_PREFERRED_LIFETIME,
.regen_max_retry = REGEN_MAX_RETRY,
.max_desync_factor = MAX_DESYNC_FACTOR,
但是,像ubuntu系统,其在配置中,开启了tempaddr,如下的sysctl文件。use_tempaddr值为2,意味着优先使用tempaddr。
cat /etc/sysctl.d/10-ipv6-privacy.conf
# Acceptable values:
# 0 - don’t use privacy extensions.
# 1 - generate privacy addresses
# 2 - prefer privacy addresses and use them over the normal addresses.
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
查看ubuntu系统上的PROC文件,use_tempaddr值已经为2。
$ cat /proc/sys/net/ipv6/conf/all/use_tempaddr
2
$ cat /proc/sys/net/ipv6/conf/default/use_tempaddr
2
创建隐私地址
隐私地址依据一个公开地址而生产,如下函数ipv6_create_tempaddr,依据其参数ifp来生成隐私地址。如果接口的use_tempaddr配置值小于等于0,不创建隐私地址。
static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
{
struct inet6_dev *idev = ifp->idev;
struct inet6_ifaddr *ift;
struct ifa6_config cfg;
struct in6_addr addr;
retry:
in6_dev_hold(idev);
if (idev->cnf.use_tempaddr <= 0) {
write_unlock_bh(&idev->lock);
pr_info("%s: use_tempaddr is disabled\n", __func__);
in6_dev_put(idev);
ret = -1;
goto out;
}
如果tempaddr生成次数已经超过配置的最大值(默认为3),将设备的use_tempaddr修改为无效值-1,不再尝试生成隐私地址。
spin_lock_bh(&ifp->lock);
if (ifp->regen_count++ >= idev->cnf.regen_max_retry) {
idev->cnf.use_tempaddr = -1; /*XXX*/
spin_unlock_bh(&ifp->lock);
write_unlock_bh(&idev->lock);
pr_warn("%s: regeneration time exceeded - disabled temporary address support\n", __func__);
in6_dev_put(idev);
ret = -1;
goto out;
}
临时隐私地址使用和公开地址相同的前缀,不过,前者随机生成接口ID。
in6_ifa_hold(ifp);
memcpy(addr.s6_addr, ifp->addr.s6_addr, 8);
ipv6_gen_rnd_iid(&addr);
regen_advance为隐私地址生成所需要的最长时间,为保证地址的生成,如果接口配置的隐私地址prefered时长,减去地址生成所需时间,小于接口配置的值max_desync_factor,那么取max_desync_factor较小值,以免错过地址的生成。
如果接口当前使用的desync_factor值超出以上计算的max_desync_factor值,从新选取一个不大于max_desync_factor值的随机数,做为接口使用的desync_factor值。
age = (now - ifp->tstamp) / HZ;
regen_advance = idev->cnf.regen_max_retry * idev->cnf.dad_transmits *
max(NEIGH_VAR(idev->nd_parms, RETRANS_TIME), HZ/100) / HZ;
/* recalculate max_desync_factor each time and update idev->desync_factor if it's larger
*/
cnf_temp_preferred_lft = READ_ONCE(idev->cnf.temp_prefered_lft);
max_desync_factor = min_t(__u32,
idev->cnf.max_desync_factor, cnf_temp_preferred_lft - regen_advance);
if (unlikely(idev->desync_factor > max_desync_factor)) {
if (max_desync_factor > 0) {
get_random_bytes(&idev->desync_factor, sizeof(idev->desync_factor));
idev->desync_factor %= max_desync_factor;
} else {
idev->desync_factor = 0;
}
}
以下配置隐私地址的参数:
1) 有效时长取两者最小值,a)公开地址的有效时长;b)接口配置的隐私地址valid时长与公开地址当前生存时长age之和。可见隐私地址有效时长不大于公开地址的有效时长。
2) preferred时长等于,接口配置的隐私地址preferred时长,加上当前生存时长age,减去以上计算的desync_factor值。最终的preferred时长不应大于公开地址的preferred时长。
3) 前缀长度使用公开地址的前缀长度。
memset(&cfg, 0, sizeof(cfg));
cfg.valid_lft = min_t(__u32, ifp->valid_lft, idev->cnf.temp_valid_lft + age);
cfg.preferred_lft = cnf_temp_preferred_lft + age - idev->desync_factor;
cfg.preferred_lft = min_t(__u32, ifp->prefered_lft, cfg.preferred_lft);
cfg.plen = ifp->prefix_len;
tmp_tstamp = ifp->tstamp;
如果以上计算出来的preferred时长,到当前时刻,所剩余时间已经不足regen_advance时长,可能不足以正确的生成隐私地址,放弃生成隐私地址。
/* A temporary address is created only if this calculated Preferred
* Lifetime is greater than REGEN_ADVANCE time units. In particular,
* an implementation must not create a temporary address with a zero
* Preferred Lifetime.
* Use age calculation as in addrconf_verify to avoid unnecessary
* temporary addresses being generated.
*/
age = (now - tmp_tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
if (cfg.preferred_lft <= regen_advance + age) {
in6_ifa_put(ifp);
in6_dev_put(idev);
ret = -1;
goto out;
}
隐私地址由标志位IFA_F_TEMPORARY来区分,并且其继承公开地址的IFA_F_OPTIMISTIC标志。ipv6_add_addr函数完成地址的添加。如果添加失败,跳转到函数开始进行重试。
cfg.ifa_flags = IFA_F_TEMPORARY;
/* set in addrconf_prefix_rcv() */
if (ifp->flags & IFA_F_OPTIMISTIC)
cfg.ifa_flags |= IFA_F_OPTIMISTIC;
cfg.pfx = &addr;
cfg.scope = ipv6_addr_scope(cfg.pfx);
ift = ipv6_add_addr(idev, &cfg, block, NULL);
if (IS_ERR(ift)) {
pr_info("%s: retry temporary address regeneration\n", __func__);
goto retry;
成员ifpub指向其所依附的公开地址。隐私地址中同时记录其创建时间戳cstamp和所依附公开地址的创建时间戳tstamp。最后,对于隐私地址,开启DAD检测。
ift->ifpub = ifp;
ift->cstamp = now;
ift->tstamp = tmp_tstamp;
spin_unlock_bh(&ift->lock);
addrconf_dad_start(ift);
随机生成隐私地址IID
以下函数ipv6_gen_rnd_iid随机生成接口ID,接口ID不能为保留值。此函数与ipv6_reserved_interfaceid有重复。
static void ipv6_gen_rnd_iid(struct in6_addr *addr)
{
regen:
get_random_bytes(&addr->s6_addr[8], 8);
/* <draft-ietf-6man-rfc4941bis-08.txt>, Section 3.3.1:
* check if generated address is not inappropriate:
*
* - Reserved IPv6 Interface Identifers
* - XXX: already assigned to an address on the device
*/
/* Subnet-router anycast: 0000:0000:0000:0000 */
if (!(addr->s6_addr32[2] | addr->s6_addr32[3]))
goto regen;
/* IANA Ethernet block: 0200:5EFF:FE00:0000-0200:5EFF:FE00:5212
* Proxy Mobile IPv6: 0200:5EFF:FE00:5213
* IANA Ethernet block: 0200:5EFF:FE00:5214-0200:5EFF:FEFF:FFFF
*/
if (ntohl(addr->s6_addr32[2]) == 0x02005eff &&
(ntohl(addr->s6_addr32[3]) & 0Xff000000) == 0xfe000000)
goto regen;
/* Reserved subnet anycast addresses */
if (ntohl(addr->s6_addr32[2]) == 0xfdffffff &&
ntohl(addr->s6_addr32[3]) >= 0Xffffff80)
goto regen;
}
隐私地址DAD
对于隐私地址,如果DAD失败,调用函数ipv6_create_tempaddr尝试创建一个新的隐私地址,其中将判断尝试次数是否超过配置的regen_max_retry值,如超过,放弃创建。否则,创建新的隐私地址,再次启动DAD。
static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
{
if (ifp->flags&IFA_F_TEMPORARY) {
struct inet6_ifaddr *ifpub;
spin_lock_bh(&ifp->lock);
ifpub = ifp->ifpub;
if (ifpub) {
in6_ifa_hold(ifpub);
spin_unlock_bh(&ifp->lock);
ipv6_create_tempaddr(ifpub, true);
如果DAD成功,由函数addrconf_verify_rtnl确保在此隐私地址deprecated之前,创建一个新的隐私地址。
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();
以下检查仅对于非TENTATIVE状态的隐私地址,如果其prefered时长剩余不足regen_advance,创建一个新的隐私地址,保证随时有可用的隐私地址。
static void addrconf_verify_rtnl(void)
{
...
for (i = 0; i < IN6_ADDR_HSIZE; i++) {
restart:
hlist_for_each_entry_rcu_bh(ifp, &inet6_addr_lst[i], addr_lst) {
...
} 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);
管理隐私地址
函数manage_tempaddrs管理接口的公开地址ifp所对应的隐私地址。首先,遍历接口的隐私地址链表tempaddr_list,找到ifp对于的隐私地址。
static void manage_tempaddrs(struct inet6_dev *idev, struct inet6_ifaddr *ifp,
__u32 valid_lft, __u32 prefered_lft, bool create, unsigned long now)
{
u32 flags;
struct inet6_ifaddr *ift;
read_lock_bh(&idev->lock);
/* update all temporary addresses in the list */
list_for_each_entry(ift, &idev->tempaddr_list, tmp_list) {
int age, max_valid, max_prefered;
if (ifp != ift->ifpub)
continue;
以下根据公开地址的valid和preferred时长,更新其隐私地址的时长,但是两个时长都不能超过接口配置的最大值。
/* RFC 4941 section 3.3:
* If a received option will extend the lifetime of a public
* address, the lifetimes of temporary addresses should
* be extended, subject to the overall constraint that no
* temporary addresses should ever remain "valid" or "preferred"
* for a time longer than (TEMP_VALID_LIFETIME) or
* (TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR), respectively.
*/
age = (now - ift->cstamp) / HZ;
max_valid = idev->cnf.temp_valid_lft - age;
if (max_valid < 0) max_valid = 0;
max_prefered = idev->cnf.temp_prefered_lft - idev->desync_factor - age;
if (max_prefered < 0) max_prefered = 0;
if (valid_lft > max_valid) valid_lft = max_valid;
if (prefered_lft > max_prefered) prefered_lft = max_prefered;
flags = ift->flags;
ift->valid_lft = valid_lft;
ift->prefered_lft = prefered_lft;
ift->tstamp = now;
if (prefered_lft > 0) ift->flags &= ~IFA_F_DEPRECATED;
if (!(flags&IFA_F_TENTATIVE)) ipv6_ifa_notify(0, ift);
}
如果接口没有任何隐私地址或者create为真,并且接口使能了隐私地址功能,需要根据公开地址创建新的隐私地址tempaddr。
if ((create || list_empty(&idev->tempaddr_list)) && idev->cnf.use_tempaddr > 0) {
/* When a new public address is created as described
* in [ADDRCONF], also create a new temporary address.
* Also create a temporary address if it's enabled but
* no temporary address currently exists.
*/
read_unlock_bh(&idev->lock);
ipv6_create_tempaddr(ifp, false);
隐私地址处理时机
依据邻居发现协议的RA报文,生成地址时,通告的前缀地址生成的地址设置IFA_F_MANAGETEMPADDR,作为生成隐私地址的模板。
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, ...)
{
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(net, addr, dev, 1);
int create = 0;
if (!ifp && valid_lft) {
ifp->flags |= IFA_F_MANAGETEMPADDR;
...
}
if (ifp) {
manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft, create, now);
用户层通过以下ip命令添加地址时,指定mngtmpaddr关键字可同时创建相应的隐私地址。命令行指定的地址作为模板,要求其前缀长度必须为64位。
# ip address add 2020::22:2356:9a21:2d82/64 dev ens33 mngtmpaddr
以上mngtmpaddr关键字对应于标志位IFA_F_MANAGETEMPADDR,在函数inet6_addr_add中使用manage_tempaddrs创建隐私地址。
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)) {
...
addrconf_dad_start(ifp);
if (cfg->ifa_flags & IFA_F_MANAGETEMPADDR)
manage_tempaddrs(idev, ifp, cfg->valid_lft, cfg->preferred_lft, true, jiffies);
在删除地址时,如果此地址为隐私地址的模板地址,使用函数manage_tempaddrs删除据此生成的隐私地址,这里采用将valid和prefered时长设置为0的方式,删除隐私地址。
static int inet6_addr_del(struct net *net, int ifindex, u32 ifa_flags,
const struct in6_addr *pfx, unsigned int plen)
{
...
list_for_each_entry(ifp, &idev->addr_list, if_list) {
if (ifp->prefix_len == plen &&
ipv6_addr_equal(pfx, &ifp->addr)) {
if (!(ifp->flags & IFA_F_TEMPORARY) && (ifa_flags & IFA_F_MANAGETEMPADDR))
manage_tempaddrs(idev, ifp, 0, 0, false, jiffies);
ipv6_del_addr(ifp);
如果对一个隐私地址的模板地址进行修改,应同时修改其隐私地址。特别的,如果地址不再作为模板使用,上次管理的隐私地址。
static int inet6_addr_modify(struct inet6_ifaddr *ifp, struct ifa6_config *cfg)
{
...
was_managetempaddr = ifp->flags & IFA_F_MANAGETEMPADDR;
ifp->flags &= ~(IFA_F_DEPRECATED | IFA_F_PERMANENT | IFA_F_NODAD |
IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR |
IFA_F_NOPREFIXROUTE);
ifp->flags |= cfg->ifa_flags;
ifp->tstamp = jiffies;
ifp->valid_lft = cfg->valid_lft;
ifp->prefered_lft = cfg->preferred_lft;
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);
源地址选择
如果当前遍历的规则为IPV6_SADDR_RULE_PRIVACY,如果设置了IPV6_PREFER_SRC_TMP标志,或者接口配置use_tempaddr值大于等于2,优先使用隐私地址。
static int ipv6_get_saddr_eval(struct net *net, struct ipv6_saddr_score *score,
struct ipv6_saddr_dst *dst, int i)
{
...
switch (i) {
case IPV6_SADDR_RULE_PRIVACY:
{
/* Rule 7: Prefer public address
* Note: prefer temporary address if use_tempaddr >= 2
*/
int preftmp = dst->prefs & (IPV6_PREFER_SRC_PUBLIC|IPV6_PREFER_SRC_TMP) ?
!!(dst->prefs & IPV6_PREFER_SRC_TMP) :
score->ifa->idev->cnf.use_tempaddr >= 2;
ret = (!(score->ifa->flags & IFA_F_TEMPORARY)) ^ preftmp;
break;
}
内核版本 5.10