Commit 0187bdfb authored by Ben Hutchings's avatar Ben Hutchings Committed by David S. Miller

net: Disable LRO on devices that are forwarding

Large Receive Offload (LRO) is only appropriate for packets that are
destined for the host, and should be disabled if received packets may be
forwarded.  It can also confuse the GSO on output.

Add dev_disable_lro() function which uses the appropriate ethtool ops to
disable LRO if enabled.

Add calls to dev_disable_lro() in br_add_if() and functions that enable
IPv4 and IPv6 forwarding.
Signed-off-by: default avatarBen Hutchings <bhutchings@solarflare.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 2e3216cd
...@@ -886,6 +886,7 @@ extern struct net_device *__dev_get_by_name(struct net *net, const char *name); ...@@ -886,6 +886,7 @@ extern struct net_device *__dev_get_by_name(struct net *net, const char *name);
extern int dev_alloc_name(struct net_device *dev, const char *name); extern int dev_alloc_name(struct net_device *dev, const char *name);
extern int dev_open(struct net_device *dev); extern int dev_open(struct net_device *dev);
extern int dev_close(struct net_device *dev); extern int dev_close(struct net_device *dev);
extern void dev_disable_lro(struct net_device *dev);
extern int dev_queue_xmit(struct sk_buff *skb); extern int dev_queue_xmit(struct sk_buff *skb);
extern int register_netdevice(struct net_device *dev); extern int register_netdevice(struct net_device *dev);
extern void unregister_netdevice(struct net_device *dev); extern void unregister_netdevice(struct net_device *dev);
......
...@@ -387,6 +387,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) ...@@ -387,6 +387,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
goto err2; goto err2;
rcu_assign_pointer(dev->br_port, p); rcu_assign_pointer(dev->br_port, p);
dev_disable_lro(dev);
dev_set_promiscuity(dev, 1); dev_set_promiscuity(dev, 1);
list_add_rcu(&p->list, &br->port_list); list_add_rcu(&p->list, &br->port_list);
......
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <net/net_namespace.h> #include <net/net_namespace.h>
...@@ -1123,6 +1124,29 @@ int dev_close(struct net_device *dev) ...@@ -1123,6 +1124,29 @@ int dev_close(struct net_device *dev)
} }
/**
* dev_disable_lro - disable Large Receive Offload on a device
* @dev: device
*
* Disable Large Receive Offload (LRO) on a net device. Must be
* called under RTNL. This is needed if received packets may be
* forwarded to another interface.
*/
void dev_disable_lro(struct net_device *dev)
{
if (dev->ethtool_ops && dev->ethtool_ops->get_flags &&
dev->ethtool_ops->set_flags) {
u32 flags = dev->ethtool_ops->get_flags(dev);
if (flags & ETH_FLAG_LRO) {
flags &= ~ETH_FLAG_LRO;
dev->ethtool_ops->set_flags(dev, flags);
}
}
WARN_ON(dev->features & NETIF_F_LRO);
}
EXPORT_SYMBOL(dev_disable_lro);
static int dev_boot_phase = 1; static int dev_boot_phase = 1;
/* /*
......
...@@ -168,6 +168,8 @@ static struct in_device *inetdev_init(struct net_device *dev) ...@@ -168,6 +168,8 @@ static struct in_device *inetdev_init(struct net_device *dev)
in_dev->dev = dev; in_dev->dev = dev;
if ((in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl)) == NULL) if ((in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl)) == NULL)
goto out_kfree; goto out_kfree;
if (IPV4_DEVCONF(in_dev->cnf, FORWARDING))
dev_disable_lro(dev);
/* Reference in_dev->dev */ /* Reference in_dev->dev */
dev_hold(dev); dev_hold(dev);
/* Account for reference dev->ip_ptr (below) */ /* Account for reference dev->ip_ptr (below) */
...@@ -1241,6 +1243,8 @@ static void inet_forward_change(struct net *net) ...@@ -1241,6 +1243,8 @@ static void inet_forward_change(struct net *net)
read_lock(&dev_base_lock); read_lock(&dev_base_lock);
for_each_netdev(net, dev) { for_each_netdev(net, dev) {
struct in_device *in_dev; struct in_device *in_dev;
if (on)
dev_disable_lro(dev);
rcu_read_lock(); rcu_read_lock();
in_dev = __in_dev_get_rcu(dev); in_dev = __in_dev_get_rcu(dev);
if (in_dev) if (in_dev)
...@@ -1248,8 +1252,6 @@ static void inet_forward_change(struct net *net) ...@@ -1248,8 +1252,6 @@ static void inet_forward_change(struct net *net)
rcu_read_unlock(); rcu_read_unlock();
} }
read_unlock(&dev_base_lock); read_unlock(&dev_base_lock);
rt_cache_flush(0);
} }
static int devinet_conf_proc(ctl_table *ctl, int write, static int devinet_conf_proc(ctl_table *ctl, int write,
...@@ -1335,11 +1337,20 @@ static int devinet_sysctl_forward(ctl_table *ctl, int write, ...@@ -1335,11 +1337,20 @@ static int devinet_sysctl_forward(ctl_table *ctl, int write,
if (write && *valp != val) { if (write && *valp != val) {
struct net *net = ctl->extra2; struct net *net = ctl->extra2;
if (valp == &IPV4_DEVCONF_ALL(net, FORWARDING)) if (valp != &IPV4_DEVCONF_DFLT(net, FORWARDING)) {
rtnl_lock();
if (valp == &IPV4_DEVCONF_ALL(net, FORWARDING)) {
inet_forward_change(net); inet_forward_change(net);
else if (valp != &IPV4_DEVCONF_DFLT(net, FORWARDING)) } else if (*valp) {
struct ipv4_devconf *cnf = ctl->extra1;
struct in_device *idev =
container_of(cnf, struct in_device, cnf);
dev_disable_lro(idev->dev);
}
rtnl_unlock();
rt_cache_flush(0); rt_cache_flush(0);
} }
}
return ret; return ret;
} }
......
...@@ -348,6 +348,8 @@ static struct inet6_dev * ipv6_add_dev(struct net_device *dev) ...@@ -348,6 +348,8 @@ static struct inet6_dev * ipv6_add_dev(struct net_device *dev)
kfree(ndev); kfree(ndev);
return NULL; return NULL;
} }
if (ndev->cnf.forwarding)
dev_disable_lro(dev);
/* We refer to the device */ /* We refer to the device */
dev_hold(dev); dev_hold(dev);
...@@ -442,6 +444,8 @@ static void dev_forward_change(struct inet6_dev *idev) ...@@ -442,6 +444,8 @@ static void dev_forward_change(struct inet6_dev *idev)
if (!idev) if (!idev)
return; return;
dev = idev->dev; dev = idev->dev;
if (idev->cnf.forwarding)
dev_disable_lro(dev);
if (dev && (dev->flags & IFF_MULTICAST)) { if (dev && (dev->flags & IFF_MULTICAST)) {
if (idev->cnf.forwarding) if (idev->cnf.forwarding)
ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters); ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
...@@ -487,12 +491,14 @@ static void addrconf_fixup_forwarding(struct ctl_table *table, int *p, int old) ...@@ -487,12 +491,14 @@ static void addrconf_fixup_forwarding(struct ctl_table *table, int *p, int old)
if (p == &net->ipv6.devconf_dflt->forwarding) if (p == &net->ipv6.devconf_dflt->forwarding)
return; return;
rtnl_lock();
if (p == &net->ipv6.devconf_all->forwarding) { if (p == &net->ipv6.devconf_all->forwarding) {
__s32 newf = net->ipv6.devconf_all->forwarding; __s32 newf = net->ipv6.devconf_all->forwarding;
net->ipv6.devconf_dflt->forwarding = newf; net->ipv6.devconf_dflt->forwarding = newf;
addrconf_forward_change(net, newf); addrconf_forward_change(net, newf);
} else if ((!*p) ^ (!old)) } else if ((!*p) ^ (!old))
dev_forward_change((struct inet6_dev *)table->extra1); dev_forward_change((struct inet6_dev *)table->extra1);
rtnl_unlock();
if (*p) if (*p)
rt6_purge_dflt_routers(net); rt6_purge_dflt_routers(net);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment