IPv6隐私地址

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值