在创建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