IPv6地址自动配置

默认情况下,配置项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

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页