IPv6地址DAD检测

IPv6 专栏收录该内容
22 篇文章 0 订阅

DAD冲突检测和IPv6地址失效检测使用的都是addrconf_wq队列,其在addrconf_init函数中创建。

int __init addrconf_init(void)
{
    struct inet6_dev *idev;
    ...

    addrconf_wq = create_workqueue("ipv6_addrconf");
    if (!addrconf_wq) {
        err = -ENOMEM;
        goto out_nowq;
    }

如下与DAD相关的三个配置项,默认情况下dad_transmits为1,即发送一次NS报文,没有收到响应则认为不存在冲突地址。配置项enhanced_dad默认为1,将在NS报文中包含NONCE字段,只有在接收到的NS报文中带有不同的NONCE值,才认为存在地址冲突,用来防止环路。

static struct ipv6_devconf ipv6_devconf __read_mostly = {
    .dad_transmits      = 1,
    .accept_dad     = 0,
    .enhanced_dad           = 1,

static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
    .dad_transmits      = 1,
    .accept_dad     = 1,
    .enhanced_dad           = 1,

查看PROC目录下文件的配置值。

$ cat /proc/sys/net/ipv6/conf/all/accept_dad
0
$ cat /proc/sys/net/ipv6/conf/default/accept_dad
1

$ cat /proc/sys/net/ipv6/conf/all/enhanced_dad 
1
$ cat /proc/sys/net/ipv6/conf/default/enhanced_dad    
1

$ cat /proc/sys/net/ipv6/conf/all/dad_transmits 
1
$ cat /proc/sys/net/ipv6/conf/default/dad_transmits    
1

以下两个函数分别用于取消和启动DAD处理work。

static void addrconf_del_dad_work(struct inet6_ifaddr *ifp)
{ 
    if (cancel_delayed_work(&ifp->dad_work)) 
        __in6_ifa_put(ifp);        
} 
static void addrconf_mod_dad_work(struct inet6_ifaddr *ifp, unsigned long delay)
{ 
    in6_ifa_hold(ifp);
    if (mod_delayed_work(addrconf_wq, &ifp->dad_work, delay))
        in6_ifa_put(ifp);          
} 

DAD初始化

函数ipv6_add_addr新增一个地址后,初始化dad_work,其处理函数指定为addrconf_dad_work。

static struct inet6_ifaddr *
ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg,
          bool can_block, struct netlink_ext_ack *extack)
{
    ...
    ifa = kzalloc(sizeof(*ifa), gfp_flags);
    if (!ifa) {
        err = -ENOBUFS;
        goto out;
    }
    spin_lock_init(&ifa->lock);
    INIT_DELAYED_WORK(&ifa->dad_work, addrconf_dad_work);
    INIT_HLIST_NODE(&ifa->addr_lst);

例如以下创建隐私地址函数ipv6_create_tempaddr,在增加新地址之后,由函数addrconf_dad_start启动DAD,对新地址进行检测。类似的还有addrconf_prefix_rcv_add_addr配置SLAAC地址,应用层地址配置处理函数inet6_addr_add,以及链路本地地址配置函数addrconf_add_linklocal,都需要在地址配置之后,开启DAD。

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

    ift = ipv6_add_addr(idev, &cfg, block, NULL);
    ...
    spin_lock_bh(&ift->lock);
    ift->ifpub = ifp;
    ift->cstamp = now;
    ift->tstamp = tmp_tstamp;
    spin_unlock_bh(&ift->lock);

    addrconf_dad_start(ift);

如下函数addrconf_dad_start,只有在地址的DAD状态不等于DEAD时,才启动DAD,并将状态设置为PREDAD,标识DAD的开始。DAD检测work为一个有限地址状态机处理函数,PREDAD为首个进入的状态。

参数delay为0,表明立即调用DAD检测work状态机。

static void addrconf_dad_start(struct inet6_ifaddr *ifp)
{
    bool begin_dad = false;

    spin_lock_bh(&ifp->lock);
    if (ifp->state != INET6_IFADDR_STATE_DEAD) {
        ifp->state = INET6_IFADDR_STATE_PREDAD;
        begin_dad = true;
    }
    spin_unlock_bh(&ifp->lock);

    if (begin_dad)
        addrconf_mod_dad_work(ifp, 0);

DAD检测work状态机

在刚开始时,地址状态为PREDAD,将其修改为STATE_DAD,状态机变化为(PREDAD -> STATE_DAD),动作DAD_BEGIN,由函数addrconf_dad_begin进行处理。

static void addrconf_dad_work(struct work_struct *w)
{
    struct inet6_ifaddr *ifp = container_of(to_delayed_work(w), struct inet6_ifaddr, dad_work);
    struct inet6_dev *idev = ifp->idev;
    bool bump_id, disable_ipv6 = false;

    enum {
        DAD_PROCESS,
        DAD_BEGIN,
        DAD_ABORT,
    } action = DAD_PROCESS;

    if (ifp->state == INET6_IFADDR_STATE_PREDAD) {
        action = DAD_BEGIN;
        ifp->state = INET6_IFADDR_STATE_DAD;

如果地址状态为ERRDAD,表明发生错误,以下判断是不是需要禁用接口的IPv6功能。如果发生DAD冲突的地址为基于MAC的链路本地地址,关闭接口的IPv6功能。

    } else if (ifp->state == INET6_IFADDR_STATE_ERRDAD) {
        action = DAD_ABORT;
        ifp->state = INET6_IFADDR_STATE_POSTDAD;

        if ((dev_net(idev->dev)->ipv6.devconf_all->accept_dad > 1 ||
             idev->cnf.accept_dad > 1) && !idev->cnf.disable_ipv6 &&
            !(ifp->flags & IFA_F_STABLE_PRIVACY)) {
            struct in6_addr addr;

            addr.s6_addr32[0] = htonl(0xfe800000);
            addr.s6_addr32[1] = 0;

            if (!ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) &&
                ipv6_addr_equal(&ifp->addr, &addr)) {
                /* DAD failed for link-local based on MAC */
                idev->cnf.disable_ipv6 = 1;

                pr_info("%s: IPv6 being disabled!\n", ifp->idev->dev->name);
                disable_ipv6 = true;
            }
        }
    }

由函数addrconf_dad_begin开启DAD,退出函数。对于地址冲突的情况,由addrconf_dad_stop函数停止DAD。

    if (action == DAD_BEGIN) {
        addrconf_dad_begin(ifp);
        goto out;
    } else if (action == DAD_ABORT) {
        in6_ifa_hold(ifp);
        addrconf_dad_stop(ifp, 1);
        if (disable_ipv6)
            addrconf_ifdown(idev->dev, false);
        goto out;
    }

对于action等于DAD_PROCESS的情况,如何DAD探测次数已经用完,并且当前状态为STATE_DAD,函数addrconf_dad_end将状态修改为POSTDAD;否则,当前状态不等于STATE_DAD,退出处理。

    if (!ifp->dad_probes && addrconf_dad_end(ifp))
        goto out;

    write_lock_bh(&idev->lock);
    if (idev->dead || !(idev->if_flags & IF_READY)) {
        write_unlock_bh(&idev->lock);
        goto out;
    }

    spin_lock(&ifp->lock);
    if (ifp->state == INET6_IFADDR_STATE_DEAD) {
        spin_unlock(&ifp->lock);
        write_unlock_bh(&idev->lock);
        goto out;
    }

如果DAD探测次数用尽,没有检测到冲突地址(状态为STATA_DAD),由函数addrconf_dad_completed处理。

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

以上都不成立,递减dad_probes计数,再次发送NS请求报文。

    ifp->dad_probes--;
    addrconf_mod_dad_work(ifp, max(NEIGH_VAR(ifp->idev->nd_parms, RETRANS_TIME),
                  HZ/100));
    spin_unlock(&ifp->lock);
    write_unlock_bh(&idev->lock);

    /* send a neighbour solicitation for our addr */
    addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
    ndisc_send_ns(ifp->idev->dev, &ifp->addr, &mcaddr, &in6addr_any,
              ifp->dad_nonce);
out:
    in6_ifa_put(ifp);
    rtnl_unlock();

DAD动作BEGIN

如下如果地址状态为DEAD,表明地址也不可用,如用户正在删除此地址,或者地址所属接口变为down,不启动DAD。

static void addrconf_dad_begin(struct inet6_ifaddr *ifp)
{
    struct inet6_dev *idev = ifp->idev;
    struct net_device *dev = idev->dev;
    bool bump_id, notify = false;

    addrconf_join_solict(dev, &ifp->addr);

    prandom_seed((__force u32) ifp->addr.s6_addr32[3]);

    read_lock_bh(&idev->lock);
    spin_lock(&ifp->lock);
    if (ifp->state == INET6_IFADDR_STATE_DEAD)
        goto out;

对于环回接口,或者设置了IFF_NOARP标志的接口,或者网络命名空间中配置的accept_dad小于1,或者设备自身配置的accept_dad小于1,即不接收DAD,无法进行DAD检测。由或者当前待检测地址不是TENTATIVE地址,或者当前地址关闭了DAD,参见IFA_F_NODAD标志,可在设置IPv6地址时通过ip address命令参数nodad指定。

如果以上任一条件成立的话,即认为DAD检测完成。对于非OPTIMISTIC的TENTATIVE地址,发送NA报文。由于DAD完成,清除地址中不在使用的标志位。函数addrconf_dad_completed处理DAD的完成。

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

如果地址所属接口还未IF_READY,由函数addrconf_dad_stop停止DAD。

    if (!(idev->if_flags & IF_READY)) {
        spin_unlock(&ifp->lock);
        read_unlock_bh(&idev->lock);
        /*
         * If the device is not ready:
         * - keep it tentative if it is a permanent address.
         * - otherwise, kill it.
         */
        in6_ifa_hold(ifp);
        addrconf_dad_stop(ifp, 0);
        return;
    }

对于OPTIMISTIC地址,在DAD完成之前,已经可以使用。最后,由函数addrconf_dad_kick启动状态机。

    /* 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);
out:
    spin_unlock(&ifp->lock);
    read_unlock_bh(&idev->lock);
    if (notify)
        ipv6_ifa_notify(RTM_NEWADDR, ifp);
}

如下函数addrconf_dad_kick,对于接口配置了enhanced_dad,或者接口所在命名空间配置了enhanced_dad的情况,随机生成nonce值,用作环路判断。启动DAD的work。

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

DAD结束处理

如下addrconf_dad_completed函数,首先删除DAD状态机WORK,并通过netlink信息RTM_NEWADDR通知应用层新地址。如有必要,使用验证完成的链路本地地址重新发送MLD的report报文。

static void addrconf_dad_completed(struct inet6_ifaddr *ifp, bool bump_id, bool send_na)
{
    struct net_device *dev = ifp->idev->dev;
    struct in6_addr lladdr;
    bool send_rs, send_mld;

    addrconf_del_dad_work(ifp);
    /* Configure the address for reception. Now it is valid.
     */
    ipv6_ifa_notify(RTM_NEWADDR, ifp);

    /* If added prefix is link local and we are prepared to process
       router advertisements, start sending router solicitations.
     */
    send_mld = ifp->scope == IFA_LINK && ipv6_lonely_lladdr(ifp);
    send_rs = send_mld && ipv6_accept_ra(ifp->idev) &&
          ifp->idev->cnf.rtr_solicits != 0 &&
          (dev->flags&IFF_LOOPBACK) == 0;

    /* While dad is in progress mld report's source address is in6_addrany.
     * Resend with proper ll now.
     */
    if (send_mld) ipv6_mc_dad_complete(ifp->idev);

根据判断结果,发送非请求的NA报文,

    /* send unsolicited NA if enabled */
    if (send_na && (ifp->idev->cnf.ndisc_notify ||
         dev_net(dev)->ipv6.devconf_all->ndisc_notify)) {
        ndisc_send_na(dev, &in6addr_linklocal_allnodes, &ifp->addr,
                  /*router=*/ !!ifp->idev->cnf.forwarding,
                  /*solicited=*/ false, /*override=*/ true,
                  /*inc_opt=*/ true);
    }

发送RS报文,请求RA信息。最后,对于TEMPORARY隐私地址,为其下一次生成做准备。

    if (send_rs) {
        /*
         *  If a host as already performed a random delay
         *  [...] as part of DAD [...] there is no need
         *  to delay again before sending the first RS
         */
        if (ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE))
            return;
        ndisc_send_rs(dev, &lladdr, &in6addr_linklocal_allrouters);

        write_lock_bh(&ifp->idev->lock);
        spin_lock(&ifp->lock);
        ifp->idev->rs_interval = rfc3315_s14_backoff_init(ifp->idev->cnf.rtr_solicit_interval);
        ifp->idev->rs_probes = 1;
        ifp->idev->if_flags |= IF_RS_SENT;
        addrconf_mod_rs_timer(ifp->idev, ifp->idev->rs_interval);
        spin_unlock(&ifp->lock);
        write_unlock_bh(&ifp->idev->lock);
    }

    if (bump_id) rt_genid_bump_ipv6(dev_net(dev));

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

DAD停止STOP

DAD未完成(失败)处理,对于隐私地址,这里尝试生成新的隐私地址,对新地址再次进行DAD检查,删除旧地址。

另外,由于地址删除等原因引起的DAD停止,参数dad_failed为0,不设置IFA_F_DADFAILED标志,

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) {
        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);
            in6_ifa_put(ifpub);
        } else {
            spin_unlock_bh(&ifp->lock);
        }
        ipv6_del_addr(ifp);

对于PERMANENT地址,或者DAD并未失败的情况,删除DAD检测work,将地址标记为TENTATIVE,如果DAD失败,清除OPTIMISTIC标志,并向应用层发送RTM_NEWADDR消息。

以上情况都不成立的话,删除当前地址。

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

如下删除地址的操作,将地址的DAD状态设置为INET6_IFADDR_STATE_DEAD,并且停止DAD检测work。

static void ipv6_del_addr(struct inet6_ifaddr *ifp)
{
    state = ifp->state;
    ifp->state = INET6_IFADDR_STATE_DEAD;
    spin_unlock_bh(&ifp->lock);

    if (state == INET6_IFADDR_STATE_DEAD)
        goto out;
    ...

    addrconf_del_dad_work(ifp);

DAD检测失败

如果接收到针对TENTATIVE地址的NA回复报文,调用addrconf_dad_failure。

static void ndisc_recv_na(struct sk_buff *skb)
{

    ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
    if (ifp) {
        if (skb->pkt_type != PACKET_LOOPBACK
            && (ifp->flags & IFA_F_TENTATIVE)) {
                addrconf_dad_failure(skb, ifp);
                return;
        }

或者,接收到NS报文,即另外的设备也在对此地址进行DAD检查。对于增强型DAD,此处判断NONCE值,以检查是否为本地发送的NS报文,通过环路又被接收到。

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;

首先看一下函数addrconf_dad_end,如果当前状态等于STATE_DAD,将地址状态设置为STATE_POSTDAD,表示已经结束,返回零。否则,表示DAD并不在进行中。

static int addrconf_dad_end(struct inet6_ifaddr *ifp)
{
    int err = -ENOENT;

    spin_lock_bh(&ifp->lock);
    if (ifp->state == INET6_IFADDR_STATE_DAD) {
        ifp->state = INET6_IFADDR_STATE_POSTDAD;
        err = 0;
    }
    spin_unlock_bh(&ifp->lock);

    return err;
}

DAD失败处理函数addrconf_dad_failure,首先,如果DAD还没有结束,不进行处理。

void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp)
{
    struct inet6_dev *idev = ifp->idev;
    struct net *net = dev_net(ifp->idev->dev);

    if (addrconf_dad_end(ifp)) {
        in6_ifa_put(ifp);
        return;
    }
    net_info_ratelimited("%s: IPv6 duplicate address %pI6c used by %pM detected!\n",
                 ifp->idev->dev->name, &ifp->addr, eth_hdr(skb)->h_source);
    spin_lock_bh(&ifp->lock);

对于STABLE隐私地址(非temporary隐私地址),如果其生成次数还没有超过命名空间中配置的限定值idgen_retries,并且接口的最大地址数量也没有超限,将生成新的地址,初始状态为PREDAD,启动DAD检测。

    if (ifp->flags & IFA_F_STABLE_PRIVACY) {
        struct in6_addr new_addr;
        struct inet6_ifaddr *ifp2;
        int retries = ifp->stable_privacy_retry + 1;
        struct ifa6_config cfg = {
            .pfx = &new_addr,
            .plen = ifp->prefix_len,
            .ifa_flags = ifp->flags,
            .valid_lft = ifp->valid_lft,
            .preferred_lft = ifp->prefered_lft,
            .scope = ifp->scope,
        };
        if (retries > net->ipv6.sysctl.idgen_retries) {
            goto errdad;
        }
        new_addr = ifp->addr;
        if (ipv6_generate_stable_address(&new_addr, retries, idev))
            goto errdad;
        if (idev->cnf.max_addresses && ipv6_count_addresses(idev) >= idev->cnf.max_addresses)
            goto lock_errdad;

        ifp2 = ipv6_add_addr(idev, &cfg, false, NULL);
        if (IS_ERR(ifp2)) goto lock_errdad;

        ifp2->stable_privacy_retry = retries;
        ifp2->state = INET6_IFADDR_STATE_PREDAD;

        addrconf_mod_dad_work(ifp2, net->ipv6.sysctl.idgen_delay);

对于以上没有新生成STABLE地址的情况,以及其它类型的地址,设置状态为ERRDAD,由DAD检测work进行错误处理,这里delay参数设置为0,将马上执行DAD检测work。

errdad:
    /* transition from _POSTDAD to _ERRDAD */
    ifp->state = INET6_IFADDR_STATE_ERRDAD;
    spin_unlock_bh(&ifp->lock);

    addrconf_mod_dad_work(ifp, 0);

DAD状态机

DAD-WORK状态机,以及处理函数。

                  PREDAD  (addrconf_dad_start)
                    |
                    |
                 STATE_DAD --> addrconf_dad_begin 
                    |                 |
                    |                 | - NODAD / accept_dad < 1 /...
                    |                 |      addrconf_dad_completed
         |--------->|                 | - !IF_READY
         |          |                 |      addrconf_dad_stop
         |          |                 |
         |          |              addrconf_dad_kick
         |          |                 |  dad_probes = idev->cnf.dad_transmits
         |          |<----------------|
         |          |                 
         |    |----------------|         |---------------|
         |----| dad_probes > 0 | ------> | ndisc_send_ns |
              |----------------|         |---------------|
                    |
                    |  dad_probes == 0
                    |  / or:
                    |  ndisc_recv_na / ndisc_recv_ns
                    |         addrconf_dad_failure
                    |
addrconf_dad_end    |
   |-----------> POSTDAD -------> addrconf_dad_completed
   |                |
   |                |
   |             ERRDAD --------> addrconf_dad_stop
   |                |
   |----------------|

ipv6_del_addr     DEAD

内核版本 5.10

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值