if (peer) {
nk = netkit_priv(peer);
RCU_INIT_POINTER(nk->peer, NULL);
- unregister_netdevice_queue(peer, head);
+ /* Guard against the peer already being in an unregister
+ * list (e.g. same-namespace teardown where the peer is
+ * in the caller's dev_kill_list). list_move_tail() on an
+ * already-queued device would otherwise corrupt that
+ * list's iteration. This situation can occur via netkit
+ * notifier, hence guard against this scenario.
+ */
+ if (!unregister_netdevice_queued(peer))
+ unregister_netdevice_queue(peer, head);
}
}
return 0;
}
+static void netkit_check_lease_unregister(struct net_device *dev)
+{
+ LIST_HEAD(list_kill);
+ u32 q_idx;
+
+ if (READ_ONCE(dev->reg_state) != NETREG_UNREGISTERING ||
+ !dev->dev.parent)
+ return;
+
+ netdev_lock_ops(dev);
+ for (q_idx = 0; q_idx < dev->real_num_rx_queues; q_idx++) {
+ struct net_device *tmp = dev;
+ struct netdev_rx_queue *rxq;
+ u32 tmp_q_idx = q_idx;
+
+ rxq = __netif_get_rx_queue_lease(&tmp, &tmp_q_idx,
+ NETIF_PHYS_TO_VIRT);
+ if (rxq && tmp != dev &&
+ tmp->netdev_ops == &netkit_netdev_ops) {
+ /* A single phys device can have multiple queues leased
+ * to one netkit device. We can only queue that netkit
+ * device once to the list_kill. Queues of that phys
+ * device can be leased with different individual netkit
+ * devices, hence we batch via list_kill.
+ */
+ if (unregister_netdevice_queued(tmp))
+ continue;
+ netkit_del_link(tmp, &list_kill);
+ }
+ }
+ netdev_unlock_ops(dev);
+ unregister_netdevice_many(&list_kill);
+}
+
+static int netkit_notifier(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+ if (event == NETDEV_UNREGISTER)
+ netkit_check_lease_unregister(dev);
+ return NOTIFY_DONE;
+}
+
static size_t netkit_get_size(const struct net_device *dev)
{
return nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_POLICY */
.maxtype = IFLA_NETKIT_MAX,
};
+static struct notifier_block netkit_netdev_notifier = {
+ .notifier_call = netkit_notifier,
+};
+
static __init int netkit_mod_init(void)
{
+ int ret;
+
BUILD_BUG_ON((int)NETKIT_NEXT != (int)TCX_NEXT ||
(int)NETKIT_PASS != (int)TCX_PASS ||
(int)NETKIT_DROP != (int)TCX_DROP ||
(int)NETKIT_REDIRECT != (int)TCX_REDIRECT);
- return rtnl_link_register(&netkit_link_ops);
+ ret = rtnl_link_register(&netkit_link_ops);
+ if (ret)
+ return ret;
+ ret = register_netdevice_notifier(&netkit_netdev_notifier);
+ if (ret)
+ rtnl_link_unregister(&netkit_link_ops);
+ return ret;
}
static __exit void netkit_mod_exit(void)
{
+ unregister_netdevice_notifier(&netkit_netdev_notifier);
rtnl_link_unregister(&netkit_link_ops);
}