From 2de320799d5b7981b17d1e46e5a07b86158e5ada Mon Sep 17 00:00:00 2001 From: Jesse Gross Date: Fri, 19 Feb 2010 16:54:19 -0500 Subject: [PATCH] datapath: Disable large receive offload. LRO can play fast and loose with the packets that it merges, which isn't very polite when you are bridging packets for other operating systems. This disables LRO on any underlying devices that are added to the datapath, which is the same as what the bridge does. Note that this does not disable GRO, which has a more strict set of rules about what is merged and is therefore safe for bridging. Both are typically done in software anyways. --- datapath/datapath.c | 6 ++++ datapath/linux-2.6/Modules.mk | 4 ++- .../linux-2.6/compat-2.6/dev-openvswitch.c | 35 +++++++++++++++++++ .../compat-2.6/include/linux/netdevice.h | 4 +++ .../compat-2.6/include/linux/skbuff.h | 24 +++++++++++++ .../linux-2.6/compat-2.6/skbuff-openvswitch.c | 13 +++++++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 datapath/linux-2.6/compat-2.6/dev-openvswitch.c create mode 100644 datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c diff --git a/datapath/datapath.c b/datapath/datapath.c index 76569f504..f8a2aa3c7 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -358,6 +358,7 @@ static int new_nbp(struct datapath *dp, struct net_device *dev, int port_no) * in dp_frame_hook(). In turn dp_frame_hook() can reject them * back to network stack, but that's a waste of time. */ } + dev_disable_lro(dev); rcu_assign_pointer(dp->ports[port_no], p); list_add_rcu(&p->node, &dp->port_list); dp->n_ports++; @@ -505,6 +506,11 @@ out: static void do_port_input(struct net_bridge_port *p, struct sk_buff *skb) { + /* LRO isn't suitable for bridging. We turn it off but make sure + * that it wasn't reactivated. */ + if (skb_warn_if_lro(skb)) + return; + /* Make our own copy of the packet. Otherwise we will mangle the * packet for anyone who came before us (e.g. tcpdump via AF_PACKET). * (No one comes after us, since we tell handle_bridge() that we took diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk index c15b735f5..820d09b65 100644 --- a/datapath/linux-2.6/Modules.mk +++ b/datapath/linux-2.6/Modules.mk @@ -1,6 +1,8 @@ openvswitch_sources += \ + linux-2.6/compat-2.6/dev-openvswitch.c \ linux-2.6/compat-2.6/genetlink-openvswitch.c \ - linux-2.6/compat-2.6/random32.c + linux-2.6/compat-2.6/random32.c \ + linux-2.6/compat-2.6/skbuff-openvswitch.c openvswitch_headers += \ linux-2.6/compat-2.6/compat26.h \ linux-2.6/compat-2.6/include/asm-generic/bug.h \ diff --git a/datapath/linux-2.6/compat-2.6/dev-openvswitch.c b/datapath/linux-2.6/compat-2.6/dev-openvswitch.c new file mode 100644 index 000000000..7be33f6e7 --- /dev/null +++ b/datapath/linux-2.6/compat-2.6/dev-openvswitch.c @@ -0,0 +1,35 @@ +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + +#include + +#ifndef NETIF_F_LRO +void dev_disable_lro(struct net_device *dev) { } +#else + +#include + +/** + * 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); +} + +#endif /* NETIF_F_LRO */ + +#endif /* kernel < 2.6.27 */ diff --git a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h index 0cd91b9f4..924dc0d3e 100644 --- a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h +++ b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h @@ -73,4 +73,8 @@ extern void unregister_netdevice_queue(struct net_device *dev, extern void unregister_netdevice_many(struct list_head *head); #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +extern void dev_disable_lro(struct net_device *dev); +#endif + #endif diff --git a/datapath/linux-2.6/compat-2.6/include/linux/skbuff.h b/datapath/linux-2.6/compat-2.6/include/linux/skbuff.h index d9f043ac6..349e504ce 100644 --- a/datapath/linux-2.6/compat-2.6/include/linux/skbuff.h +++ b/datapath/linux-2.6/compat-2.6/include/linux/skbuff.h @@ -205,4 +205,28 @@ static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb, } #endif /* before 2.6.18 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + +extern void __skb_warn_lro_forwarding(const struct sk_buff *skb); + +#ifndef NETIF_F_LRO +static inline bool skb_warn_if_lro(const struct sk_buff *skb) +{ + return false; +} +#else +static inline bool skb_warn_if_lro(const struct sk_buff *skb) +{ + /* LRO sets gso_size but not gso_type, whereas if GSO is really + * wanted then gso_type will be set. */ + struct skb_shared_info *shinfo = skb_shinfo(skb); + if (shinfo->gso_size != 0 && unlikely(shinfo->gso_type == 0)) { + __skb_warn_lro_forwarding(skb); + return true; + } + return false; +} +#endif /* NETIF_F_LRO */ +#endif /* kernel < 2.6.27 */ + #endif diff --git a/datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c b/datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c new file mode 100644 index 000000000..a9743adc5 --- /dev/null +++ b/datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c @@ -0,0 +1,13 @@ +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + +#include + +void __skb_warn_lro_forwarding(const struct sk_buff *skb) +{ + if (net_ratelimit()) + printk(KERN_WARNING "%s: received packets cannot be forwarded" + " while LRO is enabled\n", skb->dev->name); +} + +#endif /* kernel < 2.6.27 */ -- 2.45.2