IPv6源地址选择

源地址的选择依据出接口、目的地址、label、参数prefs和地址类型scope等参数来确定,在函数ipv6_dev_get_saddr开始,先行将这些判断参数组织到结构ipv6_saddr_dst中。

scores记录每个可选地址的得分,最终分数高的地址选为要使用的源地址。

int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
               const struct in6_addr *daddr, unsigned int prefs, struct in6_addr *saddr)
{
    struct ipv6_saddr_score scores[2], *hiscore;
    struct ipv6_saddr_dst dst;
    bool use_oif_addr = false;

    dst_type = __ipv6_addr_type(daddr);
    dst.addr = daddr;
    dst.ifindex = dst_dev ? dst_dev->ifindex : 0;
    dst.scope = __ipv6_addr_src_scope(dst_type);
    dst.label = ipv6_addr_label(net, daddr, dst_type, dst.ifindex);
    dst.prefs = prefs;

    scores[hiscore_idx].rule = -1;
    scores[hiscore_idx].ifa = NULL;

根据RFC6724中第4章的定义,由以下几种情况:

1) 对于多播和链路本地目的地址(内核增加了NODELOCAL),可选的源地址仅为和出接口位于同一个链路的接口上的地址;
2) 对于site-local目的地址,可选的源地址仅为和出接口位于同属一个site的接口上的地址;
3) 可选的源地址推荐使用下一跳对应接口上的单播地址。

另外,内核可通过PROC文件use_oif_addrs_only指定仅使用下一跳出接口上的地址作为可选源地址。

$ cat /proc/sys/net/ipv6/conf/ens33/use_oif_addrs_only  
0

如下代码:

    /* Candidate Source Address (section 4)
     *  - multicast and link-local destination address, the set of candidate source address MUST only 
     *    include addresses assigned to interfaces belonging to the same link as the outgoing interface.
     * (- For site-local destination addresses, the set of candidate source addresses MUST only
     *    include addresses assigned to interfaces belonging to the same site as the outgoing interface.)
     *  - "It is RECOMMENDED that the candidate source addresses be the set of unicast addresses assigned to the
     *    interface that will be used to send to the destination (the 'outgoing' interface)." (RFC 6724)
     */
    if (dst_dev) {
        idev = __in6_dev_get(dst_dev);
        if ((dst_type & IPV6_ADDR_MULTICAST) ||
            dst.scope <= IPV6_ADDR_SCOPE_LINKLOCAL ||
            (idev && idev->cnf.use_oif_addrs_only)) {
            use_oif_addr = true;
        }
    }

函数__ipv6_dev_get_saddr负责遍历接口的地址链表,进行分数计算。

    if (use_oif_addr) {
        if (idev)
            hiscore_idx = __ipv6_dev_get_saddr(net, &dst, idev, scores, hiscore_idx);
    } else {

否则,对于use_oif_addr为0的情况,即不确定的出接口,目的地址非多播,非链路本地(非NODELOCAL),并且未指定use_oif_addrs_only时,进行以下的处理。首先如果出接口dst_dev是某个L3mdev的子设备,优先使用dst_dev接口上的地址,其次是L3mdev主接口上的地址,最后考虑L3mdev的其它子设备接口的地址。

        const struct net_device *master;
        int master_idx = 0;

        /* if dst_dev exists and is enslaved to an L3 device, then
         * prefer addresses from dst_dev and then the master over
         * any other enslaved devices in the L3 domain.
         */
        master = l3mdev_master_dev_rcu(dst_dev);
        if (master) {
            master_idx = master->ifindex;

            hiscore_idx = ipv6_get_saddr_master(net, dst_dev, master, &dst, scores, hiscore_idx);
            if (scores[hiscore_idx].ifa)
                goto out;
        }

如果以上未能找到可用的源地址,将遍历当前命名空间中的设备列表,查找得分最高的地址。

        for_each_netdev_rcu(net, dev) {
            /* only consider addresses on devices in the same L3 domain
             */
            if (l3mdev_master_ifindex_rcu(dev) != master_idx)
                continue;
            idev = __in6_dev_get(dev);
            if (!idev)
                continue;
            hiscore_idx = __ipv6_dev_get_saddr(net, &dst, idev, scores, hiscore_idx);
        }
    }

最后,hiscore->ifa->addr即为选取的源地址。

out:
    hiscore = &scores[hiscore_idx];
    if (!hiscore->ifa)
        ret = -EADDRNOTAVAIL;
    else
        *saddr = hiscore->ifa->addr;

L3mdev源地址选择

优先选取dst_dev接口上的地址,其次选取L3mdev主设备接口上的地址。

static int ipv6_get_saddr_master(struct net *net,
                 const struct net_device *dst_dev, const struct net_device *master,
                 struct ipv6_saddr_dst *dst, struct ipv6_saddr_score *scores,
                 int hiscore_idx)
{
    struct inet6_dev *idev;

    idev = __in6_dev_get(dst_dev);
    if (idev)
        hiscore_idx = __ipv6_dev_get_saddr(net, dst, idev,
                           scores, hiscore_idx);

    idev = __in6_dev_get(master);
    if (idev)
        hiscore_idx = __ipv6_dev_get_saddr(net, dst, idev,
                           scores, hiscore_idx);

    return hiscore_idx;
}

地址选择

  • TENTATIVE地址不认为是接口地址,除非同时设置了OPTIMISTIC标志;
  • 任何情况下,Anycast、Multicast和全零地址都不能在可选地址集中。

scores为2个元素的数组,由1减去hiscore_idx即切换到另外一个元素。hiscore保存上一次得到的元素。

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;

        score->addr_type = __ipv6_addr_type(&score->ifa->addr);

        if (unlikely(score->addr_type == IPV6_ADDR_ANY ||
                 score->addr_type & IPV6_ADDR_MULTICAST)) {
            net_dbg_ratelimited("ADDRCONF: unspecified / multicast address assigned as unicast address on %s",
                        idev->dev->name);
            continue;
        }

以下遍历源地址规则,由函数ipv6_get_saddr_eval计算前一个地址(历史)的hiscore->ifa和当前地址(score->ifa),在当前规则下的得分。如果两者得分不同,在hiscore中保留得分高的地址,开始遍历下一个地址。可见源地址规则是按照优先级由高到低排序的,一旦找到得分高者,不在判断后续的规则。

如果hiscore中地址得分高于当前遍历score中地址得分,并且当前的规则为IPV6_SADDR_RULE_SCOPE,score中地址的scopedist值大于零,具体判断hiscore中地址为最终的源地址,不在进行后续的遍历,因为接口地址链表中的元素是按照scope排序的,后续地址的scope在减小。

注意初始化时,hiscore中的地址ifa为空,参照首个规则IPV6_SADDR_RULE_INIT,其得分为0; 而score中包含第一个遍历的接口地址,得分为1,即可判定minihiscore < miniscore为真,swap交互两者的内容,之后hiscore->ifa记录的为score中的地址,也即当前遍历的地址,将其赋值回score->ifa,以便list_for_each_entry_rcu由此地址开始遍历下一个地址。

        score->rule = -1;
        bitmap_zero(score->scorebits, IPV6_SADDR_RULE_MAX);

        for (i = 0; i < IPV6_SADDR_RULE_MAX; i++) {
            int minihiscore, miniscore;

            minihiscore = ipv6_get_saddr_eval(net, hiscore, dst, i);
            miniscore = ipv6_get_saddr_eval(net, score, dst, i);

            if (minihiscore > miniscore) {
                if (i == IPV6_SADDR_RULE_SCOPE && score->scopedist > 0) {
                    /* special case:
                     * each remaining entry has too small (not enough)
                     * scope, because ifa entries are sorted by their scope values.
                     */
                    goto out;
                }
                break;
            } else if (minihiscore < miniscore) {
                swap(hiscore, score);
                hiscore_idx = 1 - hiscore_idx;

                /* restore our iterator */
                score->ifa = hiscore->ifa;
                break;
            }
        }
    }
out:
    return hiscore_idx;

源地址规则如下:

/* Choose an appropriate source address (RFC3484)
 */
enum {
    IPV6_SADDR_RULE_INIT = 0,
    IPV6_SADDR_RULE_LOCAL,
    IPV6_SADDR_RULE_SCOPE,
    IPV6_SADDR_RULE_PREFERRED,
#ifdef CONFIG_IPV6_MIP6
    IPV6_SADDR_RULE_HOA,
#endif
    IPV6_SADDR_RULE_OIF,
    IPV6_SADDR_RULE_LABEL,
    IPV6_SADDR_RULE_PRIVACY,
    IPV6_SADDR_RULE_ORCHID,
    IPV6_SADDR_RULE_PREFIX,
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    IPV6_SADDR_RULE_NOT_OPTIMISTIC,
#endif
    IPV6_SADDR_RULE_MAX
};

首先,对于历史hiscore,其已经有过对比规则,记录在rule成员变量中,当规则i值小于等于rule时,不需要再重复计算其得分,但是要返回适当的值,对于除去RULE_SCOPE和RULE_PREFIX的其它情况,仅返回计分板scorebits中规则位的值(0或者1)。而对于前两种情况,需要返回scopedist和matchlen的值。

static int ipv6_get_saddr_eval(struct net *net,
         struct ipv6_saddr_score *score, struct ipv6_saddr_dst *dst,
         int i)
{
    int ret;

    if (i <= score->rule) {
        switch (i) {
        case IPV6_SADDR_RULE_SCOPE:
            ret = score->scopedist;
            break;
        case IPV6_SADDR_RULE_PREFIX:
            ret = score->matchlen;
            break;
        default:
            ret = !!test_bit(i, score->scorebits);
        }
        goto out;
    }

规则0 - 初始情况下,hiscore的地址ifa为空,得分为零; 而第一个遍历地址不为空,得分为1。

    switch (i) {
    case IPV6_SADDR_RULE_INIT:
        /* Rule 0: remember if hiscore is not ready yet */
        ret = !!score->ifa;
        break;

规则1 - 优先选择和目的地址相同的源地址。如果score中地址与目的地址相等,在scorebits中将RULE_LOCAL位设置为1。

    case IPV6_SADDR_RULE_LOCAL:
        /* Rule 1: Prefer same address */
        ret = ipv6_addr_equal(&score->ifa->addr, dst->addr);
        break;

规则2 - 当两个地址的scope都大于目的地址的scope时,scope值较小的地址优先级高;而当两个地址的scope都小于目的地址的scpe时,scope值较大的地址优先级高。对于第一种情况,由于逻辑相反(scope小,优先级高),这里使用一个变量B,将其减去scope值(B - scope),来将逻辑翻转过来,例如取B为0,则scope取值为负数,如 scope为2(LINKLOCAL)的地址,优先级高于scope为5(SITELOCAL)的地址值,数值取负之后,-2大于-5。假定d为目的地址的scope值,此种情况下,得到的ret返回值的范围在B-d到B-15之间。

对于第二种源地址scope值小于目的地址scope值的情况,是scope值越大优先级越高,但是此种情况下的地址的优先级低于第一种情况下的地址。这里不能直接做对比,也不能像以上直接将scope取负值,因为这样做之后,会导致源地址的负scope值大于第一种情况下的源地址的负scope值,所以需要将此种情况下的scope值偏移到第一种情况下最小值(-15)之后,变为更小的值。

由下图可见,在d点,以上描述的两种情况有重合(实际上第二种情况在d点没有值,空心),假定C来表示要偏移的量,最终返回值为:(scope - C),在重合点可得B-d > d-C-1,据此可得 C > 2d - 1 -B,即C > 29-B,其中B等于0,d取最大值15,C的取值需大于29。

注意,由于d的取值范围为[-1, 15],所以重合点的值为d-C再减去1。以上可见C取值30即可,内核使用的为128。

    case IPV6_SADDR_RULE_SCOPE:
        /* Rule 2: Prefer appropriate scope
         *
         *      ret
         *       ^
         *    -1 |  d 15
         *    ---+--+-+---> scope
         *       |
         *       |             d is scope of the destination.
         *  B-d  |  \
         *       |   \      <- smaller scope is better if
         *  B-15 |    \        if scope is enough for destination.
         *       |             ret = B - scope (-1 <= scope >= d <= 15).
         * d-C-1 | /
         *       |/         <- greater is better
         *   -C  /             if scope is not enough for destination.
         *      /|             ret = scope - C (-1 <= d < scope <= 15).
         *
         * d - C - 1 < B -15 (for all -1 <= d <= 15).
         * C > d + 14 - B >= 15 + 14 - B = 29 - B.
         * Assume B = 0 and we get C > 29.
         */
        ret = __ipv6_addr_src_scope(score->addr_type);
        if (ret >= dst->scope)
            ret = -ret;
        else
            ret -= 128; /* 30 is enough */
        score->scopedist = ret;
        break;

规则3 - 优先使用MAPPED、COMPATv4和LOOPBACK类型的地址,不使用DEPRECATED状态的地址,其次,在允许的情况下,可使用OPTIMISTIC状态地址。如果score中地址符合以上条件,设置scorebits中的RULE_PREFERRED位。

    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;
        }

规则4 - 如果没有指定使用COA地址(Care-Of-Address),优先使用HOA地址(Home Address),如果score中的地址为HOA地址,优先使用(在scorebits中标记1)。反之,如果指定优先使用COA地址,并且score中地址为COA地址,优先使用此地址。

#ifdef CONFIG_IPV6_MIP6
    case IPV6_SADDR_RULE_HOA:
        {
        /* Rule 4: Prefer home address */
        int prefhome = !(dst->prefs & IPV6_PREFER_SRC_COA);
        ret = !(score->ifa->flags & IFA_F_HOMEADDRESS) ^ prefhome;
        break;
        }
#endif

规则5 - 优先使用出接口上的地址。如果score中地址为出接口上的地址,设置scorebits中的RULE_OIF位。

    case IPV6_SADDR_RULE_OIF:
        /* Rule 5: Prefer outgoing interface */
        ret = (!dst->ifindex ||
               dst->ifindex == score->ifa->idev->dev->ifindex);
        break;

规则6 - 如果score中地址的label与目的地址的label相等,优先使用此地址,设置scorebits中的RULE_LABEL位。

    case IPV6_SADDR_RULE_LABEL:
        /* Rule 6: Prefer matching label */
        ret = ipv6_addr_label(net,
                      &score->ifa->addr, score->addr_type,
                      score->ifa->idev->dev->ifindex) == dst->label;
        break;

规则7 - 如果指定使用隐私地址,或者没有指定使用公开地址,并且use_temaddr大于等于2,在score中地址为隐私地址时,使用隐私地址。否则优先使用公开地址。处理逻辑与规则4相同。

    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;
        }

规则8 - 优先使用ORCHID(Overlay Routable Cryptographic Hash Identifiers)与目的地址相同的源地址。

    case IPV6_SADDR_RULE_ORCHID:
        /* Rule 8-: Prefer ORCHID vs ORCHID or
         *      non-ORCHID vs non-ORCHID
         */
        ret = !(ipv6_addr_orchid(&score->ifa->addr) ^
            ipv6_addr_orchid(dst->addr));
        break;

规则9 - 优先使用LMP地址,在score中记录score中地址与目的地址相同的长度,但是此长度不应超过score中地址的前缀长度。

    case IPV6_SADDR_RULE_PREFIX:
        /* Rule 8: Use longest matching prefix */
        ret = ipv6_addr_diff(&score->ifa->addr, dst->addr);
        if (ret > score->ifa->prefix_len)
            ret = score->ifa->prefix_len;
        score->matchlen = ret;
        break;

规则10 - OPTIMISTIC地址具有低优先级。如果score中地址未设置OPTIMISTIC标志,优先使用,将scorebits中的OPTIMISTIC_DAD位设置为1。其它情况下,ret为零,不操作计分板scorebits。

#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;
    }

最后,根据ret值,设置计分板scorebits中的规则位,并记录下当前的规则rule编号。

    if (ret)
        __set_bit(i, score->scorebits);
    score->rule = i;
out:
    return ret;

应用层地址优先级设置

应用层可通过setsockopt选项IPV6_ADDR_PREFERENCES指定地址优先级。

static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
           sockptr_t optval, unsigned int optlen)
{
    struct ipv6_pinfo *np = inet6_sk(sk);
    struct net *net = sock_net(sk);

    ...
    switch (optname) {

    case IPV6_ADDR_PREFERENCES:
        if (optlen < sizeof(int))
            goto e_inval;
        retv = __ip6_sock_set_addr_preferences(sk, val);
        break;

首先检查PUBLIC/TMP/PUBTMP_DEFAULT的设置冲突问题,例如不能同时设置PLUBLIC和TMP都优先。分以下情况:

1) 如果设置PUBLIC地址优先,清除prefmask中的PUBLIC和TMP,随后prefmask将与原有的srcprefs进行与操作,保证最终值不相互冲突;
2) 如果设置TMP地址优先,清除prefmask中的PUBLIC和TMP;
3) 如果设置PUBTMP_DEFAULT,清除TMP和PUBLIC的优先级设置。

case为0时,表示用户层未设置PUBLIC/TMP/PUBTMP_DEFAULT相关优先级;其它情况下,表明用户层配置错误。

static inline int __ip6_sock_set_addr_preferences(struct sock *sk, int val)
{
    unsigned int pref = 0;
    unsigned int prefmask = ~0;

    /* check PUBLIC/TMP/PUBTMP_DEFAULT conflicts */
    switch (val & (IPV6_PREFER_SRC_PUBLIC |
               IPV6_PREFER_SRC_TMP |
               IPV6_PREFER_SRC_PUBTMP_DEFAULT)) {
    case IPV6_PREFER_SRC_PUBLIC:
        pref |= IPV6_PREFER_SRC_PUBLIC;
        prefmask &= ~(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_TMP);
        break;
    case IPV6_PREFER_SRC_TMP:
        pref |= IPV6_PREFER_SRC_TMP;
        prefmask &= ~(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_TMP);
        break;
    case IPV6_PREFER_SRC_PUBTMP_DEFAULT:
        prefmask &= ~(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_TMP);
        break;
    case 0:
        break;
    default:
        return -EINVAL;
    }

检查HOME/COA设置冲突,即如果优先使用HOME地址,那就将prefmask中的COA位去掉。

    /* check HOME/COA conflicts */
    switch (val & (IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_COA)) {
    case IPV6_PREFER_SRC_HOME:
        prefmask &= ~IPV6_PREFER_SRC_COA;
        break;
    case IPV6_PREFER_SRC_COA:
        pref |= IPV6_PREFER_SRC_COA;
        break;
    case 0:
        break;
    default:
        return -EINVAL;
    }

检查CGA/NONCGA配置冲突,内核目前不支持CGA地址(Cryptographically Generated Addresse)。

    /* check CGA/NONCGA conflicts */
    switch (val & (IPV6_PREFER_SRC_CGA|IPV6_PREFER_SRC_NONCGA)) {
    case IPV6_PREFER_SRC_CGA:
    case IPV6_PREFER_SRC_NONCGA:
    case 0:
        break;
    default:
        return -EINVAL;
    }
    inet6_sk(sk)->srcprefs = (inet6_sk(sk)->srcprefs & prefmask) | pref;
    return 0;

内核版本 5.10

相关推荐
程序员的必经之路! 【限时优惠】 现在下单,还享四重好礼: 1、教学课件免费下载 2、课程案例代码免费下载 3、专属VIP学员群免费答疑 4、下单还送800元编程大礼包 【超实用课程内容】  根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!   套餐中一共包含2门MySQL数据库必学的核心课程(共98课时)   课程1:《MySQL数据库从入门到实战应用》   课程2:《高性能MySQL实战课》   【哪些人适合学习这门课程?】  1)平时只接触了语言基础,并未学习任何数据库知识的人;  2)对MySQL掌握程度薄弱的人,课程可以让你更好发挥MySQL最佳性能; 3)想修炼更好的MySQL内功,工作中遇到高并发场景可以游刃有余; 4)被面试官打破沙锅问到底的问题问到怀疑人生的应聘者。 【课程主要讲哪些内容?】 课程一:《MySQL数据库从入门到实战应用》 主要从基础篇,SQL语言篇、MySQL进阶篇三个角度展开讲解,帮助大家更加高效的管理MySQL数据库。 课程二:《高性能MySQL实战课》主要从高可用篇、MySQL8.0新特性篇,性能优化篇,面试篇四个角度展开讲解,帮助大家发挥MySQL的最佳性能的优化方法,掌握如何处理海量业务数据和高并发请求 【你能收获到什么?】  1.基础再提高,针对MySQL核心知识点学透,用对; 2.能力再提高,日常工作中的代码换新貌,不怕问题; 3.面试再加分,巴不得面试官打破沙锅问到底,竞争力MAX。 【课程如何观看?】  1、登录CSDN学院 APP 在我的课程中进行学习; 2、移动端:CSDN 学院APP(注意不是CSDN APP哦)  本课程为录播课,课程永久有效观看时长 【资料开放】 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化。  下载方式:电脑登录课程观看页面,点击右侧课件,可进行课程资料的打包下载。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页