Skip to content

Commit

Permalink
net-sysfs: try not to restart the syscall if it will fail eventually
Browse files Browse the repository at this point in the history
Due to deadlocks in the networking subsystem spotted 12 years ago[1],
a workaround was put in place[2] to avoid taking the rtnl lock when it
was not available and restarting the syscall (back to VFS, letting
userspace spin). The following construction is found a lot in the net
sysfs and sysctl code:

  if (!rtnl_trylock())
          return restart_syscall();

This can be problematic when multiple userspace threads use such
interfaces in a short period, making them to spin a lot. This happens
for example when adding and moving virtual interfaces: userspace
programs listening on events, such as systemd-udevd and NetworkManager,
do trigger actions reading files in sysfs. It gets worse when a lot of
virtual interfaces are created concurrently, say when creating
containers at boot time.

Returning early without hitting the above pattern when the syscall will
fail eventually does make things better. While it is not a fix for the
issue, it does ease things.

[1] https://lore.kernel.org/netdev/[email protected]/
    https://lore.kernel.org/netdev/[email protected]/
    and https://lore.kernel.org/netdev/20090226084924.16cb3e08@nehalam/
[2] Rightfully, those deadlocks are *hard* to solve.

Signed-off-by: Antoine Tenart <[email protected]>
Reviewed-by: Paolo Abeni <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
atenart authored and davem330 committed Oct 8, 2021
1 parent 2b12d51 commit 146e5e7
Showing 1 changed file with 55 additions and 0 deletions.
55 changes: 55 additions & 0 deletions net/core/net-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ static int change_carrier(struct net_device *dev, unsigned long new_carrier)
static ssize_t carrier_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct net_device *netdev = to_net_dev(dev);

/* The check is also done in change_carrier; this helps returning early
* without hitting the trylock/restart in netdev_store.
*/
if (!netdev->netdev_ops->ndo_change_carrier)
return -EOPNOTSUPP;

return netdev_store(dev, attr, buf, len, change_carrier);
}

Expand All @@ -196,6 +204,12 @@ static ssize_t speed_show(struct device *dev,
struct net_device *netdev = to_net_dev(dev);
int ret = -EINVAL;

/* The check is also done in __ethtool_get_link_ksettings; this helps
* returning early without hitting the trylock/restart below.
*/
if (!netdev->ethtool_ops->get_link_ksettings)
return ret;

if (!rtnl_trylock())
return restart_syscall();

Expand All @@ -216,6 +230,12 @@ static ssize_t duplex_show(struct device *dev,
struct net_device *netdev = to_net_dev(dev);
int ret = -EINVAL;

/* The check is also done in __ethtool_get_link_ksettings; this helps
* returning early without hitting the trylock/restart below.
*/
if (!netdev->ethtool_ops->get_link_ksettings)
return ret;

if (!rtnl_trylock())
return restart_syscall();

Expand Down Expand Up @@ -468,6 +488,14 @@ static ssize_t proto_down_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct net_device *netdev = to_net_dev(dev);

/* The check is also done in change_proto_down; this helps returning
* early without hitting the trylock/restart in netdev_store.
*/
if (!netdev->netdev_ops->ndo_change_proto_down)
return -EOPNOTSUPP;

return netdev_store(dev, attr, buf, len, change_proto_down);
}
NETDEVICE_SHOW_RW(proto_down, fmt_dec);
Expand All @@ -478,6 +506,12 @@ static ssize_t phys_port_id_show(struct device *dev,
struct net_device *netdev = to_net_dev(dev);
ssize_t ret = -EINVAL;

/* The check is also done in dev_get_phys_port_id; this helps returning
* early without hitting the trylock/restart below.
*/
if (!netdev->netdev_ops->ndo_get_phys_port_id)
return -EOPNOTSUPP;

if (!rtnl_trylock())
return restart_syscall();

Expand All @@ -500,6 +534,13 @@ static ssize_t phys_port_name_show(struct device *dev,
struct net_device *netdev = to_net_dev(dev);
ssize_t ret = -EINVAL;

/* The checks are also done in dev_get_phys_port_name; this helps
* returning early without hitting the trylock/restart below.
*/
if (!netdev->netdev_ops->ndo_get_phys_port_name &&
!netdev->netdev_ops->ndo_get_devlink_port)
return -EOPNOTSUPP;

if (!rtnl_trylock())
return restart_syscall();

Expand All @@ -522,6 +563,14 @@ static ssize_t phys_switch_id_show(struct device *dev,
struct net_device *netdev = to_net_dev(dev);
ssize_t ret = -EINVAL;

/* The checks are also done in dev_get_phys_port_name; this helps
* returning early without hitting the trylock/restart below. This works
* because recurse is false when calling dev_get_port_parent_id.
*/
if (!netdev->netdev_ops->ndo_get_port_parent_id &&
!netdev->netdev_ops->ndo_get_devlink_port)
return -EOPNOTSUPP;

if (!rtnl_trylock())
return restart_syscall();

Expand Down Expand Up @@ -1226,6 +1275,12 @@ static ssize_t tx_maxrate_store(struct netdev_queue *queue,
if (!capable(CAP_NET_ADMIN))
return -EPERM;

/* The check is also done later; this helps returning early without
* hitting the trylock/restart below.
*/
if (!dev->netdev_ops->ndo_set_tx_maxrate)
return -EOPNOTSUPP;

err = kstrtou32(buf, 10, &rate);
if (err < 0)
return err;
Expand Down

0 comments on commit 146e5e7

Please sign in to comment.