Files
linux/net/core/failover.c
Minhong He d13bb65dd2 net: failover: check register_netdevice_notifier() error in failover_init()
failover_init() ignores register_netdevice_notifier() errors and always
returns success, which can leave the failover module loaded without its
netdev notifier registered.

Return the notifier registration result directly so module initialization
fails when registration fails.

This is a future looking check, register_netdevice_notifier()
only fails on double registration or if the registered notifier
itself returns an error.

Signed-off-by: Minhong He <heminhong@kylinos.cn>
Link: https://patch.msgid.link/20260731030338.82508-1-heminhong@kylinos.cn
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-08-04 18:46:34 -07:00

318 lines
7.5 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/netdev_lock.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->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 | IFF_NO_ADDRCONF);
if (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 | IFF_NO_ADDRCONF);
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->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 | IFF_NO_ADDRCONF);
if (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->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->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)) {
netdev_lock_ops(dev);
failover_slave_register(dev);
netdev_unlock_ops(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 || !ops)
return ERR_PTR(-EINVAL);
failover = kzalloc_obj(*failover);
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)
{
return register_netdevice_notifier(&failover_notifier);
}
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");