optimistic-dad

配置项optimistic_dad用于控制是否执行优化的DAD检查;配置项use_optimistic控制在源地址选择时,可使用optimistic地址,但是其优先级低于Preferred地址。如下函数ipv6_allow_optimistic_dad,命名空间和设备的optimistic_dad配置项有一个为真,就启用此功能。

static bool ipv6_allow_optimistic_dad(struct net *net, struct inet6_dev *idev)                  
{ 
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD  
    if (!idev)
        return false;
    if (!net->ipv6.devconf_all->optimistic_dad && !idev->cnf.optimistic_dad)
        return false;

    return true;
#else
    return false;
#endif
}

配置项use_optimistic仅在optimistic_dad开启后有效,也是只有命名空间或者设备其中一个的use_optimistic为真,即开启此功能。

static bool ipv6_use_optimistic_addr(struct net *net, struct inet6_dev *idev)                  
{ 
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD  
    if (!idev)
        return false;              
    if (!net->ipv6.devconf_all->optimistic_dad && !idev->cnf.optimistic_dad)
        return false;              
    if (!net->ipv6.devconf_all->use_optimistic && !idev->cnf.use_optimistic)
        return false;
  
    return true;
#else
    return false;                  
#endif
} 

SLAAC自动配置地址

根据RA报文中的前缀信息配置接口地址,如果命名空间中conf/{all,interface}/optimistic_dad的值有一个不为零,则认为开启了优化DAD功能,此时如果转发功能未启用,并且RA报文中带有sllao(Source Link-Layer Address Options)的情况下,设置IFA_F_OPTIMISTIC标记,将地址标识为优化地址。

如果报文RA中没有携带sllao,将得不到路由器的链路地址。由于不能使用Optimistic地址发送NS报文,需要将数据报文(子网内)发送给路由器中转,或者路由器回复重定向报文,告知报文目的IP的链路地址,所以,如果没有sllao,Optimistic功能将不可用。

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

        /* 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);
    }

链路本地地址

与上节相同,内核打开了IPV6_OPTIMISTIC_DAD编译选项之后,在添加链路本地地址时,如果开启了优化dad配置,并且命名空间的forwarding为0,即工作在主机模式,设置IFA_F_OPTIMISTIC标志。对于路由器,不能设置optimistic地址,参考上一节说明的原因。

void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr *addr, u32 flags)
{
    struct ifa6_config cfg = {
        .pfx = addr,
        .plen = 64,
        .ifa_flags = flags | IFA_F_PERMANENT,
        .valid_lft = INFINITY_LIFE_TIME,
        .preferred_lft = INFINITY_LIFE_TIME,
        .scope = IFA_LINK
    };
    struct inet6_ifaddr *ifp;

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    if ((dev_net(idev->dev)->ipv6.devconf_all->optimistic_dad ||
         idev->cnf.optimistic_dad) &&
        !dev_net(idev->dev)->ipv6.devconf_all->forwarding)
        cfg.ifa_flags |= IFA_F_OPTIMISTIC;
#endif

    ifp = ipv6_add_addr(idev, &cfg, true, NULL);
    if (!IS_ERR(ifp)) {
        addrconf_prefix_route(&ifp->addr, ifp->prefix_len, 0, idev->dev,
                      0, 0, GFP_ATOMIC);
        addrconf_dad_start(ifp);
        in6_ifa_put(ifp);
    }
}

应用层配置地址

用户层通过IP命令设置IPv6地址时,IFA_F_NODAD 和 IFA_F_OPTIMISTIC是互斥的,不能同时设置。

static int inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
{

    if (tb[IFA_FLAGS])
        cfg.ifa_flags = nla_get_u32(tb[IFA_FLAGS]);
    else
        cfg.ifa_flags = ifm->ifa_flags;

    /* We ignore other flags so far. */
    cfg.ifa_flags &= IFA_F_NODAD | IFA_F_HOMEADDRESS |
             IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE |
             IFA_F_MCAUTOJOIN | IFA_F_OPTIMISTIC;

    idev = ipv6_find_idev(dev);
    if (IS_ERR(idev))
        return PTR_ERR(idev);

    if (!ipv6_allow_optimistic_dad(net, idev))
        cfg.ifa_flags &= ~IFA_F_OPTIMISTIC;

    if (cfg.ifa_flags & IFA_F_NODAD &&
        cfg.ifa_flags & IFA_F_OPTIMISTIC) {
        NL_SET_ERR_MSG(extack, "IFA_F_NODAD and IFA_F_OPTIMISTIC are mutually exclusive");
        return -EINVAL;
    }

    ifa = ipv6_get_ifaddr(net, cfg.pfx, dev, 1);
    if (!ifa) {
        /*
         * It would be best to check for !NLM_F_CREATE here but
         * userspace already relies on not having to provide this.
         */
        return inet6_addr_add(net, ifm->ifa_index, &cfg, extack);
    }

源地址选择

在选择源地址时,不考虑暂定tentative地址,除非其设置了IFA_F_OPTIMISTIC标志。

static int __ipv6_dev_get_saddr(struct net *net,
                struct ipv6_saddr_dst *dst, struct inet6_dev *idev,
                struct ipv6_saddr_score *scores, int hiscore_idx)
{
    struct ipv6_saddr_score *score = &scores[1 - hiscore_idx], *hiscore = &scores[hiscore_idx];

    list_for_each_entry_rcu(score->ifa, &idev->addr_list, if_list) {
        int i;

        /*
         * - Tentative Address (RFC2462 section 5.4)
         *  - A tentative address is not considered
         *    "assigned to an interface" in the traditional
         *    sense, unless it is also flagged as optimistic.
         * - Candidate Source Address (section 4)
         *  - In any case, anycast addresses, multicast
         *    addresses, and the unspecified address MUST
         *    NOT be included in a candidate set.
         */
        if ((score->ifa->flags & IFA_F_TENTATIVE) &&
            (!(score->ifa->flags & IFA_F_OPTIMISTIC)))
            continue;

不能使用DEPRECATED废弃地址,如果没有打开使用use_optimistic地址配置,也不能使用IFA_F_OPTIMISTIC地址。即使使用OPTIMISTIC优化地址,其优先级也是低于preferred地址。

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_PREFERRED:
        {
        /* Rule 3: Avoid deprecated and optimistic addresses */
        u8 avoid = IFA_F_DEPRECATED;

        if (!ipv6_use_optimistic_addr(net, score->ifa->idev))
            avoid |= IFA_F_OPTIMISTIC;
        ret = ipv6_saddr_preferred(score->addr_type) ||
              !(score->ifa->flags & avoid);
        break;
        }

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    case IPV6_SADDR_RULE_NOT_OPTIMISTIC:
        /* Optimistic addresses still have lower precedence than other
         * preferred addresses.
         */
        ret = !(score->ifa->flags & IFA_F_OPTIMISTIC);
        break;
#endif
    default:
        ret = 0;
    }

优化DAD

对于不使用DAD的情况,如果仅为tentative地址,没有同时设置optimistic标志,发送NA报文通知邻居。否则,如果设置了optimistic标志,不能发送NA,以免遇到地址冲突的情况下,破坏邻居设备中的地址表项。

函数addrconf_dad_completed中发送NA时,将设置Override标志。

static void addrconf_dad_begin(struct inet6_ifaddr *ifp)
{

    net = dev_net(dev);
    if (dev->flags&(IFF_NOARP|IFF_LOOPBACK) ||
        (net->ipv6.devconf_all->accept_dad < 1 &&
         idev->cnf.accept_dad < 1) ||
        !(ifp->flags&IFA_F_TENTATIVE) ||
        ifp->flags & IFA_F_NODAD) {
        bool send_na = false;

        if (ifp->flags & IFA_F_TENTATIVE &&
            !(ifp->flags & IFA_F_OPTIMISTIC))
            send_na = true;
        bump_id = ifp->flags & IFA_F_TENTATIVE;
        ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
        spin_unlock(&ifp->lock);
        read_unlock_bh(&idev->lock);

        addrconf_dad_completed(ifp, bump_id, send_na);
        return;
    }

对于使用DAD的情况,优化地址在DAD完成之前已经可以使用,这里插入路由表项,并且,向上层发送新地址通知。

    /* Optimistic nodes can start receiving Frames right away
     */
    if (ifp->flags & IFA_F_OPTIMISTIC) {
        ip6_ins_rt(net, ifp->rt);
        if (ipv6_use_optimistic_addr(net, idev)) {
            /* Because optimistic nodes can use this address,
             * notify listeners. If DAD fails, RTM_DELADDR is sent.
             */
            notify = true;
        }
    }

    addrconf_dad_kick(ifp);

在DAD成功结束之后,对于OPTIMISTIC优化地址,不需要发送NA报文,与以上函数addrconf_dad_begin中处理相同。

static void addrconf_dad_work(struct work_struct *w)
{

    if (ifp->dad_probes == 0) {
        bool send_na = false;

        /* DAD was successful
         */
        if (ifp->flags & IFA_F_TENTATIVE &&
            !(ifp->flags & IFA_F_OPTIMISTIC))
            send_na = true;
        bump_id = ifp->flags & IFA_F_TENTATIVE;
        ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
        spin_unlock(&ifp->lock);
        write_unlock_bh(&idev->lock);

        addrconf_dad_completed(ifp, bump_id, send_na);
        goto out;
    }

另外,对于优化Optimistic地址,不需要延时,立即调用DAD处理函数,发送NS报文,源地址使用未指定地址in6addr_any,报文在不包括SLLAO。

/*  Duplicate Address Detection
 */
static void addrconf_dad_kick(struct inet6_ifaddr *ifp)
{
    unsigned long rand_num;
    struct inet6_dev *idev = ifp->idev;
    u64 nonce;

    if (ifp->flags & IFA_F_OPTIMISTIC)
        rand_num = 0;
    else
        rand_num = prandom_u32() % (idev->cnf.rtr_solicit_delay ? : 1);

    nonce = 0;
    if (idev->cnf.enhanced_dad ||
        dev_net(idev->dev)->ipv6.devconf_all->enhanced_dad) {
        do
            get_random_bytes(&nonce, 6);
        while (nonce == 0);
    }
    ifp->dad_nonce = nonce;
    ifp->dad_probes = idev->cnf.dad_transmits;
    addrconf_mod_dad_work(ifp, rand_num);
}

对于PERMANENT地址,如果dad失败,将其设置为tentative地址,清除optimistic标志位,地址不可用。

static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
{   
    if (dad_failed)
        ifp->flags |= IFA_F_DADFAILED;
    
    if (ifp->flags&IFA_F_TEMPORARY) {
        ...
    } else if (ifp->flags&IFA_F_PERMANENT || !dad_failed) {
        spin_lock_bh(&ifp->lock);
        addrconf_del_dad_work(ifp);
        ifp->flags |= IFA_F_TENTATIVE;
        if (dad_failed)
            ifp->flags &= ~IFA_F_OPTIMISTIC;
        spin_unlock_bh(&ifp->lock);
        if (dad_failed)
            ipv6_ifa_notify(0, ifp);
        in6_ifa_put(ifp);
    } else {
        ipv6_del_addr(ifp);

邻居发现

在发送RS报文时,如果使用的源地址为优化地址,报文中不应包含sllao选项。另外,在找不到相应接口地址时,也不包含sllao选项。

void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr, const struct in6_addr *daddr)
{
    struct rs_msg *msg;
    int send_sllao = dev->addr_len;

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    /*
     * According to section 2.2 of RFC 4429, we must not
     * send router solicitations with a sllao from
     * optimistic addresses, but we may send the solicitation
     * if we don't include the sllao.  So here we check
     * if our address is optimistic, and if so, we
     * suppress the inclusion of the sllao.
     */
    if (send_sllao) {
        struct inet6_ifaddr *ifp = ipv6_get_ifaddr(dev_net(dev), saddr, dev, 1);
        if (ifp) {
            if (ifp->flags & IFA_F_OPTIMISTIC)  {
                send_sllao = 0;
            }
            in6_ifa_put(ifp);
        } else {
            send_sllao = 0;
        }
    }
#endif
    if (send_sllao)
        optlen += ndisc_opt_addr_space(dev, NDISC_ROUTER_SOLICITATION);

    skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
    if (!skb) return;

在发送NA报文时,如果源地址为优化地址,报文中的覆盖标志override为false,以免破坏接收设备的邻居表项。

void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
           const struct in6_addr *solicited_addr,
           bool router, bool solicited, bool override, bool inc_opt)
{
    struct inet6_ifaddr *ifp;
    const struct in6_addr *src_addr;
    struct nd_msg *msg;

    /* for anycast or proxy, solicited_addr != src_addr */
    ifp = ipv6_get_ifaddr(dev_net(dev), solicited_addr, dev, 1);
    if (ifp) {
        src_addr = solicited_addr;
        if (ifp->flags & IFA_F_OPTIMISTIC)
            override = false;
        inc_opt |= ifp->idev->cnf.force_tllao;
        in6_ifa_put(ifp);
    } else {
        if (ipv6_dev_get_saddr(dev_net(dev), dev, daddr,
                       inet6_sk(dev_net(dev)->ipv6.ndisc_sk)->srcprefs,
                       &tmpaddr))
            return;
        src_addr = &tmpaddr;
    }

    if (!dev->addr_len)
        inc_opt = false;
    if (inc_opt)
        optlen += ndisc_opt_addr_space(dev, NDISC_NEIGHBOUR_ADVERTISEMENT);

    skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
    if (!skb)
        return;
    msg = skb_put(skb, sizeof(*msg));
    *msg = (struct nd_msg) {
        .icmph = {
            .icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT,
            .icmp6_router = router,
            .icmp6_solicited = solicited,
            .icmp6_override = override,
        },
        .target = *solicited_addr,
    };

在发送非请求的NA报文时,对于未设置IFA_F_OPTIMISTIC标志的TENTATIVE地址,不进行发送。需要等待其完成DAD。

static void ndisc_send_unsol_na(struct net_device *dev)
{
    struct inet6_dev *idev;
    struct inet6_ifaddr *ifa;

    idev = in6_dev_get(dev);
    if (!idev)
        return;

    read_lock_bh(&idev->lock);
    list_for_each_entry(ifa, &idev->addr_list, if_list) {
        /* skip tentative addresses until dad completes */
        if (ifa->flags & IFA_F_TENTATIVE &&
            !(ifa->flags & IFA_F_OPTIMISTIC))
            continue;

        ndisc_send_na(dev, &in6addr_linklocal_allnodes, &ifa->addr,
                  /*router=*/ !!idev->cnf.forwarding,
                  /*solicited=*/ false, /*override=*/ true,
                  /*inc_opt=*/ true);
    }
    read_unlock_bh(&idev->lock);

    in6_dev_put(idev);
}

在发送NS报文时,如果未指定源地址,选择除TENTATIVE和OPTIMISTIC地址类型之外的其它地址做源地址。

void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
           const struct in6_addr *daddr, const struct in6_addr *saddr, u64 nonce)
{
    struct sk_buff *skb;
    struct in6_addr addr_buf;
    int inc_opt = dev->addr_len;
    int optlen = 0;
    struct nd_msg *msg;

    if (!saddr) {
        if (ipv6_get_lladdr(dev, &addr_buf, (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)))
            return;
        saddr = &addr_buf;
    }

    if (ipv6_addr_any(saddr))
        inc_opt = false;
    if (inc_opt)
        optlen += ndisc_opt_addr_space(dev, NDISC_NEIGHBOUR_SOLICITATION);
    if (nonce != 0)
        optlen += 8;

    skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
    if (!skb)
        return;

如果没有找到非tentative/optimistic的地址,源地址saddr将为空,在以上的发送函数ndisc_send_ns中,需要查找符合的链路本地地址,找不到将不能发送。

static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
    struct in6_addr *saddr = NULL;
    struct net_device *dev = neigh->dev;
    struct in6_addr *target = (struct in6_addr *)&neigh->primary_key;

    if (skb && ipv6_chk_addr_and_flags(dev_net(dev), &ipv6_hdr(skb)->saddr, dev, false, 1,
                       IFA_F_TENTATIVE|IFA_F_OPTIMISTIC))
        saddr = &ipv6_hdr(skb)->saddr;
    probes -= NEIGH_VAR(neigh->parms, UCAST_PROBES);

    if (probes < 0) {
        if (!(neigh->nud_state & NUD_VALID)) {
            ND_PRINTK(1, dbg, "%s: trying to ucast probe in NUD_INVALID: %pI6\n", __func__, target);
        }
        ndisc_send_ns(dev, target, target, saddr, 0);
    } else if ((probes -= NEIGH_VAR(neigh->parms, APP_PROBES)) < 0) {
        neigh_app_ns(neigh);
    } else {
        addrconf_addr_solict_mult(target, &mcaddr);
        ndisc_send_ns(dev, target, &mcaddr, saddr, 0);

如果接收到的NS报文不是DAD检查报文,对于Optimistic地址回复NA报文,其中Override标志设置为0。

static void ndisc_recv_ns(struct sk_buff *skb)
{

    ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
    if (ifp) {
have_ifp:
        if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {
            if (dad) {
                if (nonce != 0 && ifp->dad_nonce == nonce) {
                    u8 *np = (u8 *)&nonce;
                    /* Matching nonce if looped back */
                    ND_PRINTK(2, notice, "%s: IPv6 DAD loopback for address %pI6c nonce %pM ignored\n",
                          ifp->idev->dev->name, &ifp->addr, np);
                    goto out;
                }
                /* We are colliding with another node who is doing DAD so fail our DAD process */
                addrconf_dad_failure(skb, ifp);
                return;
            } else {
                /*
                 * This is not a dad solicitation.
                 * If we are an optimistic node, we should respond.
                 * Otherwise, we should ignore it.
                 */
                if (!(ifp->flags & IFA_F_OPTIMISTIC))
                    goto out;
            }
        }

        idev = ifp->idev;
    } else {
        struct net *net = dev_net(dev);

        /* perhaps an address on the master device */
        if (netif_is_l3_slave(dev)) {
            struct net_device *mdev;

            mdev = netdev_master_upper_dev_get_rcu(dev);
            if (mdev) {
                ifp = ipv6_get_ifaddr(net, &msg->target, mdev, 1);
                if (ifp) goto have_ifp;
            }
        }

报文发送

如果当前报文目的地址的邻居表项不可用,而报文的源地址使用的是Optimistic地址,不能发送NS报文,需要将报文发给默认的网关。

static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk, struct dst_entry **dst, struct flowi6 *fl6)
{
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    struct neighbour *n;
    struct rt6_info *rt;
#endif

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    /*
     * Here if the dst entry we've looked up
     * has a neighbour entry that is in the INCOMPLETE
     * state and the src address from the flow is
     * marked as OPTIMISTIC, we release the found
     * dst entry and replace it instead with the
     * dst entry of the nexthop router
     */
    rt = (struct rt6_info *) *dst;
    rcu_read_lock_bh();
    n = __ipv6_neigh_lookup_noref(rt->dst.dev, rt6_nexthop(rt, &fl6->daddr));
    err = n && !(n->nud_state & NUD_VALID) ? -EINVAL : 0;
    rcu_read_unlock_bh();

    if (err) {
        struct inet6_ifaddr *ifp;
        struct flowi6 fl_gw6;

        ifp = ipv6_get_ifaddr(net, &fl6->saddr, (*dst)->dev, 1);
        redirect = (ifp && ifp->flags & IFA_F_OPTIMISTIC);
        if (ifp) in6_ifa_put(ifp);

        if (redirect) {
            /* We need to get the dst entry for the default router instead
             */
            dst_release(*dst);
            memcpy(&fl_gw6, fl6, sizeof(struct flowi6));
            memset(&fl_gw6.daddr, 0, sizeof(struct in6_addr));
            *dst = ip6_route_output(net, sk, &fl_gw6);
            err = (*dst)->error;
            if (err) goto out_err_release;
        }
    }
#endif

网关在接收到此报文之后,判断其目的地址就位于链路本地网络中,将发送ND的重定向报文,在其中携带目的地址的链路地址,如下,增加TLLAO选项。

void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
{
    if (dev->addr_len) {
        struct neighbour *neigh = dst_neigh_lookup(skb_dst(skb), target);
        if (!neigh) {
            ND_PRINTK(2, warn, "Redirect: no neigh for target address\n");
            goto release;
        }
        read_lock_bh(&neigh->lock);
        if (neigh->nud_state & NUD_VALID) {
            memcpy(ha_buf, neigh->ha, dev->addr_len);
            read_unlock_bh(&neigh->lock);
            ha = ha_buf;
            optlen += ndisc_redirect_opt_addr_space(dev, neigh, ops_data_buf, &ops_data);
        } else
            read_unlock_bh(&neigh->lock);

        neigh_release(neigh);
    }

    buff = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
    if (!buff) goto release;

    msg = skb_put(buff, sizeof(*msg));
    *msg = (struct rd_msg) {
        .icmph = {
            .icmp6_type = NDISC_REDIRECT,
        },
        .target = *target,
        .dest = ipv6_hdr(skb)->daddr,
    };

    /* include target_address option */
    if (ha)
        ndisc_fill_redirect_addr_option(buff, ha, ops_data);

地址处理

如果开启了DAD,并且Optimistic标志没有设置,发送上层通知。

static int inet6_addr_add(struct net *net, int ifindex, struct ifa6_config *cfg, struct netlink_ext_ack *extack)
{
    struct inet6_ifaddr *ifp;

    ifp = ipv6_add_addr(idev, cfg, true, extack);
    if (!IS_ERR(ifp)) {
        ...
        /* Send a netlink notification if DAD is enabled and
         * optimistic flag is not set
         */
        if (!(ifp->flags & (IFA_F_OPTIMISTIC | IFA_F_NODAD)))
            ipv6_ifa_notify(0, ifp);

在修改地址时,对于非tentative地址,或者DAD失败的地址,清除配置的Optimistic标志,不允许设置。

static int inet6_addr_modify(struct inet6_ifaddr *ifp, struct ifa6_config *cfg)
{
    u32 flags;

    if (cfg->ifa_flags & IFA_F_MANAGETEMPADDR &&
        (ifp->flags & IFA_F_TEMPORARY || ifp->prefix_len != 64))
        return -EINVAL;

    if (!(ifp->flags & IFA_F_TENTATIVE) || ifp->flags & IFA_F_DADFAILED)
        cfg->ifa_flags &= ~IFA_F_OPTIMISTIC;

获取设备的未设置TENTATIVE和OPTIMISTIC标志的链路本地地址,作为源地址发送RS请求报文。

static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
{
    struct inet6_ifaddr *ifp;
    struct net_device *dev = idev->dev;
    bool clear_token, update_rs = false;
    struct in6_addr ll_addr;


    BUILD_BUG_ON(sizeof(token->s6_addr) != 16);
    memcpy(idev->token.s6_addr + 8, token->s6_addr + 8, 8);

    write_unlock_bh(&idev->lock);

    clear_token = ipv6_addr_any(token);
    if (clear_token) goto update_lft;

    if (!idev->dead && (idev->if_flags & IF_READY) &&
        !ipv6_get_lladdr(dev, &ll_addr, IFA_F_TENTATIVE |
                 IFA_F_OPTIMISTIC)) {
        /* If we're not ready, then normal ifup will take care
         * of this. Otherwise, we need to request our rs here.
         */
        ndisc_send_rs(dev, &ll_addr, &in6addr_linklocal_allrouters);
        update_rs = true;
    }

临时隐私地址,继承其生成地址的IFA_F_OPTIMISTIC标志。

static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block)
{

    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)) {
        in6_ifa_put(ifp);
        in6_dev_put(idev);
        pr_info("%s: retry temporary address regeneration\n", __func__);
        write_lock_bh(&idev->lock);
        goto retry;
    }

在检查地址函数中,如果没有明确指定禁止optimistic地址,将认为其也是合法的。由于大多数情况下,optimistic和tentative标志都是同时设置了,这里将两者分开。

static struct net_device *__ipv6_chk_addr_and_flags(struct net *net, const struct in6_addr *addr,
              const struct net_device *dev, bool skip_dev_check, int strict, u32 banned_flags)
{
    unsigned int hash = inet6_addr_hash(net, addr);
    struct inet6_ifaddr *ifp;

    hlist_for_each_entry_rcu(ifp, &inet6_addr_lst[hash], addr_lst) {
        ndev = ifp->idev->dev;
        if (!net_eq(dev_net(ndev), net)) continue;

        if (l3mdev_master_dev_rcu(ndev) != l3mdev) continue;

        /* Decouple optimistic from tentative for evaluation here.
         * Ban optimistic addresses explicitly, when required.
         */
        ifp_flags = (ifp->flags&IFA_F_OPTIMISTIC)
                ? (ifp->flags&~IFA_F_TENTATIVE) : ifp->flags;

        if (ipv6_addr_equal(&ifp->addr, addr) &&
            !(ifp_flags&banned_flags) &&
            (!dev || ndev == dev ||
             !(ifp->scope&(IFA_LINK|IFA_HOST) || strict))) {
            rcu_read_unlock();
            return ndev;

如下函数ipv6_lonely_lladdr判断ifp是否为接口上唯一的链路本地地址,由于addr_list是按照地址的scope排序的,如果第一个地址的scope大于IFA_LINK,表明链表中没有任何链路本地地址。

一个合法的地址,应当设置了IFA_F_PERMANENT标志,并且没有其它的三个标志。

static bool ipv6_lonely_lladdr(struct inet6_ifaddr *ifp)
{
    struct inet6_ifaddr *ifpiter;
    struct inet6_dev *idev = ifp->idev;

    list_for_each_entry_reverse(ifpiter, &idev->addr_list, if_list) {
        if (ifpiter->scope > IFA_LINK)
            break;
        if (ifp != ifpiter && ifpiter->scope == IFA_LINK &&
            (ifpiter->flags & (IFA_F_PERMANENT|IFA_F_TENTATIVE|
                       IFA_F_OPTIMISTIC|IFA_F_DADFAILED)) ==
            IFA_F_PERMANENT)
            return false;
    }
    return true;
}

内核版本 5.10

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