bd039b5ea2
Allow a network interface to be renamed when the interface is up. As described in the netconsole documentation [1], when netconsole is used as a built-in, it will bring up the specified interface as soon as possible. As a result, user space will not be able to rename the interface since the kernel disallows renaming of interfaces that are administratively up unless the 'IFF_LIVE_RENAME_OK' private flag was set by the kernel. The original solution [2] to this problem was to add a new parameter to the netconsole configuration parameters that allows renaming of the interface used by netconsole while it is administratively up. However, during the discussion that followed, it became apparent that we have no reason to keep the current restriction and instead we should allow user space to rename interfaces regardless of their administrative state: 1. The restriction was put in place over 20 years ago when renaming was only possible via IOCTL and before rtnetlink started notifying user space about such changes like it does today. 2. The 'IFF_LIVE_RENAME_OK' flag was added over 3 years ago in version 5.2 and no regressions were reported. 3. In-kernel listeners to 'NETDEV_CHANGENAME' do not seem to care about the administrative state of interface. Therefore, allow user space to rename running interfaces by removing the restriction and the associated 'IFF_LIVE_RENAME_OK' flag. Help in possible triage by emitting a message to the kernel log that an interface was renamed while UP. [1] https://www.kernel.org/doc/Documentation/networking/netconsole.rst [2] https://lore.kernel.org/netdev/20221102002420.2613004-1-andy.ren@getcruise.com/ Signed-off-by: Andy Ren <andy.ren@getcruise.com> Reviewed-by: Ido Schimmel <idosch@nvidia.com> Reviewed-by: David Ahern <dsahern@kernel.org> Signed-off-by: David S. Miller <davem@davemloft.net>
316 lines
7.4 KiB
C
316 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2018, Intel Corporation. */
|
|
|
|
/* A common module to handle registrations and notifications for paravirtual
|
|
* drivers to enable accelerated datapath and support VF live migration.
|
|
*
|
|
* The notifier and event handling code is based on netvsc driver.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <uapi/linux/if_arp.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <net/failover.h>
|
|
|
|
static LIST_HEAD(failover_list);
|
|
static DEFINE_SPINLOCK(failover_lock);
|
|
|
|
static struct net_device *failover_get_bymac(u8 *mac, struct failover_ops **ops)
|
|
{
|
|
struct net_device *failover_dev;
|
|
struct failover *failover;
|
|
|
|
spin_lock(&failover_lock);
|
|
list_for_each_entry(failover, &failover_list, list) {
|
|
failover_dev = rtnl_dereference(failover->failover_dev);
|
|
if (ether_addr_equal(failover_dev->perm_addr, mac)) {
|
|
*ops = rtnl_dereference(failover->ops);
|
|
spin_unlock(&failover_lock);
|
|
return failover_dev;
|
|
}
|
|
}
|
|
spin_unlock(&failover_lock);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* failover_slave_register - Register a slave netdev
|
|
*
|
|
* @slave_dev: slave netdev that is being registered
|
|
*
|
|
* Registers a slave device to a failover instance. Only ethernet devices
|
|
* are supported.
|
|
*/
|
|
static int failover_slave_register(struct net_device *slave_dev)
|
|
{
|
|
struct netdev_lag_upper_info lag_upper_info;
|
|
struct net_device *failover_dev;
|
|
struct failover_ops *fops;
|
|
int err;
|
|
|
|
if (slave_dev->type != ARPHRD_ETHER)
|
|
goto done;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
|
|
if (!failover_dev)
|
|
goto done;
|
|
|
|
if (fops && fops->slave_pre_register &&
|
|
fops->slave_pre_register(slave_dev, failover_dev))
|
|
goto done;
|
|
|
|
err = netdev_rx_handler_register(slave_dev, fops->slave_handle_frame,
|
|
failover_dev);
|
|
if (err) {
|
|
netdev_err(slave_dev, "can not register failover rx handler (err = %d)\n",
|
|
err);
|
|
goto done;
|
|
}
|
|
|
|
lag_upper_info.tx_type = NETDEV_LAG_TX_TYPE_ACTIVEBACKUP;
|
|
err = netdev_master_upper_dev_link(slave_dev, failover_dev, NULL,
|
|
&lag_upper_info, NULL);
|
|
if (err) {
|
|
netdev_err(slave_dev, "can not set failover device %s (err = %d)\n",
|
|
failover_dev->name, err);
|
|
goto err_upper_link;
|
|
}
|
|
|
|
slave_dev->priv_flags |= IFF_FAILOVER_SLAVE;
|
|
|
|
if (fops && fops->slave_register &&
|
|
!fops->slave_register(slave_dev, failover_dev))
|
|
return NOTIFY_OK;
|
|
|
|
netdev_upper_dev_unlink(slave_dev, failover_dev);
|
|
slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
|
|
err_upper_link:
|
|
netdev_rx_handler_unregister(slave_dev);
|
|
done:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/**
|
|
* failover_slave_unregister - Unregister a slave netdev
|
|
*
|
|
* @slave_dev: slave netdev that is being unregistered
|
|
*
|
|
* Unregisters a slave device from a failover instance.
|
|
*/
|
|
int failover_slave_unregister(struct net_device *slave_dev)
|
|
{
|
|
struct net_device *failover_dev;
|
|
struct failover_ops *fops;
|
|
|
|
if (!netif_is_failover_slave(slave_dev))
|
|
goto done;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
|
|
if (!failover_dev)
|
|
goto done;
|
|
|
|
if (fops && fops->slave_pre_unregister &&
|
|
fops->slave_pre_unregister(slave_dev, failover_dev))
|
|
goto done;
|
|
|
|
netdev_rx_handler_unregister(slave_dev);
|
|
netdev_upper_dev_unlink(slave_dev, failover_dev);
|
|
slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
|
|
|
|
if (fops && fops->slave_unregister &&
|
|
!fops->slave_unregister(slave_dev, failover_dev))
|
|
return NOTIFY_OK;
|
|
|
|
done:
|
|
return NOTIFY_DONE;
|
|
}
|
|
EXPORT_SYMBOL_GPL(failover_slave_unregister);
|
|
|
|
static int failover_slave_link_change(struct net_device *slave_dev)
|
|
{
|
|
struct net_device *failover_dev;
|
|
struct failover_ops *fops;
|
|
|
|
if (!netif_is_failover_slave(slave_dev))
|
|
goto done;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
|
|
if (!failover_dev)
|
|
goto done;
|
|
|
|
if (!netif_running(failover_dev))
|
|
goto done;
|
|
|
|
if (fops && fops->slave_link_change &&
|
|
!fops->slave_link_change(slave_dev, failover_dev))
|
|
return NOTIFY_OK;
|
|
|
|
done:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int failover_slave_name_change(struct net_device *slave_dev)
|
|
{
|
|
struct net_device *failover_dev;
|
|
struct failover_ops *fops;
|
|
|
|
if (!netif_is_failover_slave(slave_dev))
|
|
goto done;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
|
|
if (!failover_dev)
|
|
goto done;
|
|
|
|
if (!netif_running(failover_dev))
|
|
goto done;
|
|
|
|
if (fops && fops->slave_name_change &&
|
|
!fops->slave_name_change(slave_dev, failover_dev))
|
|
return NOTIFY_OK;
|
|
|
|
done:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int
|
|
failover_event(struct notifier_block *this, unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *event_dev = netdev_notifier_info_to_dev(ptr);
|
|
|
|
/* Skip parent events */
|
|
if (netif_is_failover(event_dev))
|
|
return NOTIFY_DONE;
|
|
|
|
switch (event) {
|
|
case NETDEV_REGISTER:
|
|
return failover_slave_register(event_dev);
|
|
case NETDEV_UNREGISTER:
|
|
return failover_slave_unregister(event_dev);
|
|
case NETDEV_UP:
|
|
case NETDEV_DOWN:
|
|
case NETDEV_CHANGE:
|
|
return failover_slave_link_change(event_dev);
|
|
case NETDEV_CHANGENAME:
|
|
return failover_slave_name_change(event_dev);
|
|
default:
|
|
return NOTIFY_DONE;
|
|
}
|
|
}
|
|
|
|
static struct notifier_block failover_notifier = {
|
|
.notifier_call = failover_event,
|
|
};
|
|
|
|
static void
|
|
failover_existing_slave_register(struct net_device *failover_dev)
|
|
{
|
|
struct net *net = dev_net(failover_dev);
|
|
struct net_device *dev;
|
|
|
|
rtnl_lock();
|
|
for_each_netdev(net, dev) {
|
|
if (netif_is_failover(dev))
|
|
continue;
|
|
if (ether_addr_equal(failover_dev->perm_addr, dev->perm_addr))
|
|
failover_slave_register(dev);
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
|
|
/**
|
|
* failover_register - Register a failover instance
|
|
*
|
|
* @dev: failover netdev
|
|
* @ops: failover ops
|
|
*
|
|
* Allocate and register a failover instance for a failover netdev. ops
|
|
* provides handlers for slave device register/unregister/link change/
|
|
* name change events.
|
|
*
|
|
* Return: pointer to failover instance
|
|
*/
|
|
struct failover *failover_register(struct net_device *dev,
|
|
struct failover_ops *ops)
|
|
{
|
|
struct failover *failover;
|
|
|
|
if (dev->type != ARPHRD_ETHER)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
failover = kzalloc(sizeof(*failover), GFP_KERNEL);
|
|
if (!failover)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
rcu_assign_pointer(failover->ops, ops);
|
|
netdev_hold(dev, &failover->dev_tracker, GFP_KERNEL);
|
|
dev->priv_flags |= IFF_FAILOVER;
|
|
rcu_assign_pointer(failover->failover_dev, dev);
|
|
|
|
spin_lock(&failover_lock);
|
|
list_add_tail(&failover->list, &failover_list);
|
|
spin_unlock(&failover_lock);
|
|
|
|
netdev_info(dev, "failover master:%s registered\n", dev->name);
|
|
|
|
failover_existing_slave_register(dev);
|
|
|
|
return failover;
|
|
}
|
|
EXPORT_SYMBOL_GPL(failover_register);
|
|
|
|
/**
|
|
* failover_unregister - Unregister a failover instance
|
|
*
|
|
* @failover: pointer to failover instance
|
|
*
|
|
* Unregisters and frees a failover instance.
|
|
*/
|
|
void failover_unregister(struct failover *failover)
|
|
{
|
|
struct net_device *failover_dev;
|
|
|
|
failover_dev = rcu_dereference(failover->failover_dev);
|
|
|
|
netdev_info(failover_dev, "failover master:%s unregistered\n",
|
|
failover_dev->name);
|
|
|
|
failover_dev->priv_flags &= ~IFF_FAILOVER;
|
|
netdev_put(failover_dev, &failover->dev_tracker);
|
|
|
|
spin_lock(&failover_lock);
|
|
list_del(&failover->list);
|
|
spin_unlock(&failover_lock);
|
|
|
|
kfree(failover);
|
|
}
|
|
EXPORT_SYMBOL_GPL(failover_unregister);
|
|
|
|
static __init int
|
|
failover_init(void)
|
|
{
|
|
register_netdevice_notifier(&failover_notifier);
|
|
|
|
return 0;
|
|
}
|
|
module_init(failover_init);
|
|
|
|
static __exit
|
|
void failover_exit(void)
|
|
{
|
|
unregister_netdevice_notifier(&failover_notifier);
|
|
}
|
|
module_exit(failover_exit);
|
|
|
|
MODULE_DESCRIPTION("Generic failover infrastructure/interface");
|
|
MODULE_LICENSE("GPL v2");
|