ipv6设备的删除标记dead

在创建inet6_dev设备结构函数中,在分配了inet6_dev结构内存之后,如果出现邻居参数结构neigh_parms分配失败,或者snmp相关结构分配失败的情况,需要释放inet6_dev结构,直接返回错误。

static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
{
    struct inet6_dev *ndev;

    ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
    if (!ndev) return ERR_PTR(err);

    ndev->dev = dev;

    ndev->cnf.mtu6 = dev->mtu;
    ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
    if (!ndev->nd_parms) {
        kfree(ndev);
        return ERR_PTR(err);
    }
    if (snmp6_alloc_dev(ndev) < 0) {
        neigh_parms_release(&nd_tbl, ndev->nd_parms);
        dev_put(dev);
        kfree(ndev);
        return ERR_PTR(err);
    }

随后,如果初始化过程再出现错误,需要先设置dead标志,再由in6_dev_finish_destroy去释放inet6_dev结构。

    if (snmp6_register_dev(ndev) < 0) {
        netdev_dbg(dev, "%s: cannot create /proc/net/dev_snmp6/%s\n", __func__, dev->name);
        goto err_release;
    }
    ...

    ndev->token = in6addr_any;

    if (netif_running(dev) && addrconf_link_ready(dev))
        ndev->if_flags |= IF_READY;

    ipv6_mc_init_dev(ndev);
    ndev->tstamp = jiffies;
    err = addrconf_sysctl_register(ndev);
    if (err) {
        ipv6_mc_destroy_dev(ndev);
        snmp6_unregister_dev(ndev);
        goto err_release;
    }
    ...

    return ndev;

err_release:
    neigh_parms_release(&nd_tbl, ndev->nd_parms);
    ndev->dead = 1;
    in6_dev_finish_destroy(ndev);
    return ERR_PTR(err);

在注销设备时,需要首先设置dead标志,防止其它操作再次使用此设备。函数in6_dev_put将递减inet6_dev结构的使用计数,如果为零,也是调用in6_dev_finish_destroy去释放。

static int addrconf_ifdown(struct net_device *dev, bool unregister)
{
    unsigned long event = unregister ? NETDEV_UNREGISTER : NETDEV_DOWN;
    struct inet6_dev *idev;

    idev = __in6_dev_get(dev);
    if (!idev) return -ENODEV;

    /* Step 1: remove reference to ipv6 device from parent device.Do not dev_put!
     */
    if (unregister) {
        idev->dead = 1;

        RCU_INIT_POINTER(dev->ip6_ptr, NULL);
        /* Step 1.5: remove snmp6 entry */
        snmp6_unregister_dev(idev);
    }
	...
    /* Last: Shot the device (if unregistered) */
    if (unregister) {
        addrconf_sysctl_unregister(idev);
        neigh_parms_release(&nd_tbl, idev->nd_parms);
        neigh_ifdown(&nd_tbl, dev); 
        in6_dev_put(idev);
    }

如下in6_dev_finish_destroy函数,dead标志为零的情况下,不执行释放。call_rcu调用实际的释放函数,这里可能会有系统的调度,如果此期间使用inet6_dev结构,需要先行判断dead标志。

void in6_dev_finish_destroy(struct inet6_dev *idev)
{
    struct net_device *dev = idev->dev;

    WARN_ON(!list_empty(&idev->addr_list));
    WARN_ON(idev->mc_list);
    WARN_ON(timer_pending(&idev->rs_timer));

#ifdef NET_REFCNT_DEBUG
    pr_debug("%s: %s\n", __func__, dev ? dev->name : "NIL");
#endif
    dev_put(dev);
    if (!idev->dead) {
        pr_warn("Freeing alive inet6 device %p\n", idev);
        return;
    }
    call_rcu(&idev->rcu, in6_dev_finish_destroy_rcu);

DEAD标志判断

对于用户接口函数,如在为设备增加地址时,先行判断设备是否处于即将要销毁的状态。

static struct inet6_ifaddr * ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg,
          bool can_block, struct netlink_ext_ack *extack)
{
    ...
    if (idev->dead) {
        err = -ENODEV;          /*XXX*/
        goto out;
    }

用户层设置接口token的操作,如果dead标志为1,不发送RS报文,因为即使收到了RA回复报文,设备已经删除,也不能进行处理了。

static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
{
    struct net_device *dev = idev->dev;

    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)) {
        ndisc_send_rs(dev, &ll_addr, &in6addr_linklocal_allrouters);
        update_rs = true;
    }
update_lft:
    write_lock_bh(&idev->lock);

发送地址事件时,如RTM_NEWADDR或者RTM_DELADDR事件,只有在dead为零时发送,才有意义。

static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
{
    rcu_read_lock_bh();
    if (likely(ifp->idev->dead == 0))
        __ipv6_ifa_notify(event, ifp);
    rcu_read_unlock_bh();
}

另外,还有类似的定时器相关函数,delayed-work等异步处理函数,如addrconf_rs_timer函数,addrconf_dad_work,在获得inet6_dev的写操作锁之后,都需要检查一下dead标志,。

static void addrconf_rs_timer(struct timer_list *t)
{
    struct inet6_dev *idev = from_timer(idev, t, rs_timer);

    write_lock(&idev->lock);
    if (idev->dead || !(idev->if_flags & IF_READY))
        goto out;

static void addrconf_dad_work(struct work_struct *w)
{
    write_lock_bh(&idev->lock);
    if (idev->dead || !(idev->if_flags & IF_READY)) {
        write_unlock_bh(&idev->lock);
        goto out;
    }

组播相关

函数ip6_mc_find_dev_rcu在调用前,已经使用了RCU锁,所以设备可能设置了dead标志,但是相关的RCU释放操作还没有执行,这里需要对dead进行判断。另外,函数__in6_dev_get没有增加设备计数,不需要执行put操作。

/* called with rcu_read_lock() */
static struct inet6_dev *ip6_mc_find_dev_rcu(struct net *net, const struct in6_addr *group, int ifindex)
{   
    struct net_device *dev = NULL;
    struct inet6_dev *idev = NULL;
    ...
    idev = __in6_dev_get(dev);
    if (!idev) 
        return NULL;
    read_lock_bh(&idev->lock);
    if (idev->dead) {
        read_unlock_bh(&idev->lock);
        return NULL;
    }
    return idev;

在函数igmp6_group_dropped的调用中,并没有锁住inet6_dev结构的写锁,这里需要判断dead标志。

static void igmp6_group_dropped(struct ifmcaddr6 *mc)
{
    struct net_device *dev = mc->idev->dev;
    char buf[MAX_ADDR_LEN];

    if (IPV6_ADDR_MC_SCOPE(&mc->mca_addr) <
        IPV6_ADDR_SCOPE_LINKLOCAL)
        return;

    spin_lock_bh(&mc->mca_lock);
    if (mc->mca_flags&MAF_LOADED) {
        mc->mca_flags &= ~MAF_LOADED;
        if (ndisc_mc_map(&mc->mca_addr, buf, dev, 0) == 0)
            dev_mc_del(dev, buf);
    }

    spin_unlock_bh(&mc->mca_lock);
    if (mc->mca_flags & MAF_NOREPORT)
        return;

    if (!mc->idev->dead)
        igmp6_leave_group(mc);

在__ipv6_dev_mc_inc函数中,in6_dev_get获取设备时增加了计数,在判断dead标志为真时,需要递减计数,为零的话,in6_dev_put函数将释放设备。

/* device multicast group inc (add if not found)
 */
static int __ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr, unsigned int mode)
{
    struct ifmcaddr6 *mc;
    struct inet6_dev *idev;

    ASSERT_RTNL();

    /* we need to take a reference on idev */
    idev = in6_dev_get(dev);
    if (!idev) return -EINVAL;

    write_lock_bh(&idev->lock);
    if (idev->dead) {
        write_unlock_bh(&idev->lock);
        in6_dev_put(idev);
        return -ENODEV;
    }

如下__ipv6_dev_ac_inc函数,在锁住inet6_dev之后,检查其dead标志,其为1,表明设备正在删除中。

/* device anycast group inc (add if not found)
 */
int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
{
    struct ifacaddr6 *aca;
    struct fib6_info *f6i;

    write_lock_bh(&idev->lock);
    if (idev->dead) {
        err = -ENODEV;
        goto out;
    }

内核版本 5.10

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