/*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
* Distributed under the terms of the GNU GPL version 2.
*
* Significant portions of this file may be copied from parts of the Linux
* kernel, by Linus Torvalds and others.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/if_arp.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <net/llc.h>
+#include "checksum.h"
#include "datapath.h"
+#include "vlan.h"
#include "vport-internal_dev.h"
#include "vport-netdev.h"
-#include "compat.h"
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) && \
+ !defined(HAVE_VLAN_BUG_WORKAROUND)
+#include <linux/module.h>
+
+static int vlan_tso __read_mostly = 0;
+module_param(vlan_tso, int, 0644);
+MODULE_PARM_DESC(vlan_tso, "Enable TSO for VLAN packets");
+#else
+#define vlan_tso true
+#endif
/* If the native device stats aren't 64 bit use the vport stats tracking instead. */
#define USE_VPORT_STATS (sizeof(((struct net_device_stats *)0)->rx_bytes) < sizeof(u64))
static void netdev_port_receive(struct vport *vport, struct sk_buff *skb);
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+/* Called with rcu_read_lock and bottom-halves disabled. */
+static rx_handler_result_t netdev_frame_hook(struct sk_buff **pskb)
+{
+ struct sk_buff *skb = *pskb;
+ struct vport *vport;
+
+ if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
+ return RX_HANDLER_PASS;
+
+ vport = netdev_get_vport(skb->dev);
+
+ netdev_port_receive(vport, skb);
+
+ return RX_HANDLER_CONSUMED;
+}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
/* Called with rcu_read_lock and bottom-halves disabled. */
static struct sk_buff *netdev_frame_hook(struct sk_buff *skb)
{
}
#endif
-static struct vport *netdev_create(const char *name, const void __user *config)
+static struct vport *netdev_create(const struct vport_parms *parms)
{
struct vport *vport;
struct netdev_vport *netdev_vport;
int err;
- vport = vport_alloc(sizeof(struct netdev_vport), &netdev_vport_ops);
+ vport = vport_alloc(sizeof(struct netdev_vport), &netdev_vport_ops, parms);
if (IS_ERR(vport)) {
err = PTR_ERR(vport);
goto error;
netdev_vport = netdev_vport_priv(vport);
- netdev_vport->dev = dev_get_by_name(&init_net, name);
+ netdev_vport->dev = dev_get_by_name(&init_net, parms->name);
if (!netdev_vport->dev) {
err = -ENODEV;
goto error_free_vport;
vport_set_stats(vport, &stats);
}
- return vport;
-
-error_put:
- dev_put(netdev_vport->dev);
-error_free_vport:
- vport_free(vport);
-error:
- return ERR_PTR(err);
-}
-
-static int netdev_destroy(struct vport *vport)
-{
- struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
-
- dev_put(netdev_vport->dev);
- vport_free(vport);
-
- return 0;
-}
-
-static int netdev_attach(struct vport *vport)
-{
- struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
- int err;
-
err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,
vport);
if (err)
- return err;
+ goto error_put;
dev_set_promiscuity(netdev_vport->dev, 1);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
dev_disable_lro(netdev_vport->dev);
+#endif
netdev_vport->dev->priv_flags |= IFF_OVS_DATAPATH;
- return 0;
+ return vport;
+
+error_put:
+ dev_put(netdev_vport->dev);
+error_free_vport:
+ vport_free(vport);
+error:
+ return ERR_PTR(err);
}
-static int netdev_detach(struct vport *vport)
+static void netdev_destroy(struct vport *vport)
{
struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
netdev_rx_handler_unregister(netdev_vport->dev);
dev_set_promiscuity(netdev_vport->dev, -1);
- return 0;
-}
+ synchronize_rcu();
-int netdev_set_mtu(struct vport *vport, int mtu)
-{
- struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
- return dev_set_mtu(netdev_vport->dev, mtu);
+ dev_put(netdev_vport->dev);
+ vport_free(vport);
}
int netdev_set_addr(struct vport *vport, const unsigned char *addr)
return netdev_vport->dev->ifindex;
}
-int netdev_get_iflink(const struct vport *vport)
-{
- const struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
- return netdev_vport->dev->iflink;
-}
-
int netdev_get_mtu(const struct vport *vport)
{
const struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
/* Must be called with rcu_read_lock. */
static void netdev_port_receive(struct vport *vport, struct sk_buff *skb)
{
+ if (unlikely(!vport)) {
+ kfree_skb(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
if (unlikely(!skb))
return;
- skb_warn_if_lro(skb);
-
skb_push(skb, ETH_HLEN);
- compute_ip_summed(skb, false);
+
+ if (unlikely(compute_ip_summed(skb, false))) {
+ kfree_skb(skb);
+ return;
+ }
+ vlan_copy_skb_tci(skb);
vport_receive(vport, skb);
}
+static inline unsigned packet_length(const struct sk_buff *skb)
+{
+ unsigned length = skb->len - ETH_HLEN;
+
+ if (skb->protocol == htons(ETH_P_8021Q))
+ length -= VLAN_HLEN;
+
+ return length;
+}
+
+static bool dev_supports_vlan_tx(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+ /* Software fallback means every device supports vlan_tci on TX. */
+ return true;
+#elif defined(HAVE_VLAN_BUG_WORKAROUND)
+ return dev->features & NETIF_F_HW_VLAN_TX;
+#else
+ /* Assume that the driver is buggy. */
+ return false;
+#endif
+}
+
static int netdev_send(struct vport *vport, struct sk_buff *skb)
{
struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
- int len = skb->len;
+ int mtu = netdev_vport->dev->mtu;
+ int len;
+
+ if (unlikely(packet_length(skb) > mtu && !skb_is_gso(skb))) {
+ if (net_ratelimit())
+ pr_warn("%s: dropped over-mtu packet: %d > %d\n",
+ dp_name(vport->dp), packet_length(skb), mtu);
+ goto error;
+ }
+
+ if (unlikely(skb_warn_if_lro(skb)))
+ goto error;
skb->dev = netdev_vport->dev;
- forward_ip_summed(skb);
+ forward_ip_summed(skb, true);
+
+ if (vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev)) {
+ int features;
+
+ features = netif_skb_features(skb);
+
+ if (!vlan_tso)
+ features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
+ NETIF_F_UFO | NETIF_F_FSO);
+
+ if (netif_needs_gso(skb, features)) {
+ struct sk_buff *nskb;
+
+ nskb = skb_gso_segment(skb, features);
+ if (!nskb) {
+ if (unlikely(skb_cloned(skb) &&
+ pskb_expand_head(skb, 0, 0, GFP_ATOMIC))) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ skb_shinfo(skb)->gso_type &= ~SKB_GSO_DODGY;
+ goto tag;
+ }
+
+ if (IS_ERR(nskb)) {
+ kfree_skb(skb);
+ return 0;
+ }
+ consume_skb(skb);
+ skb = nskb;
+
+ len = 0;
+ do {
+ nskb = skb->next;
+ skb->next = NULL;
+
+ skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
+ if (likely(skb)) {
+ len += skb->len;
+ vlan_set_tci(skb, 0);
+ dev_queue_xmit(skb);
+ }
+
+ skb = nskb;
+ } while (skb);
+
+ return len;
+ }
+
+tag:
+ skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));
+ if (unlikely(!skb))
+ return 0;
+ vlan_set_tci(skb, 0);
+ }
+
+ len = skb->len;
dev_queue_xmit(skb);
return len;
+
+error:
+ kfree_skb(skb);
+ vport_record_error(vport, VPORT_E_TX_DROPPED);
+ return 0;
}
/* Returns null if this device is not attached to a datapath. */
struct vport *netdev_get_vport(struct net_device *dev)
{
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
- /* XXX: The bridge code may have registered the data.
- * So check that the handler pointer is the datapath's.
- * Once the merge is done and IFF_OVS_DATAPATH stops
- * being the same value as IFF_BRIDGE_PORT the check can
- * simply be netdev_vport->dev->priv_flags & IFF_OVS_DATAPATH. */
- if (rcu_dereference(dev->rx_handler) != netdev_frame_hook)
+#ifdef IFF_BRIDGE_PORT
+#if IFF_BRIDGE_PORT != IFF_OVS_DATAPATH
+ if (likely(dev->priv_flags & IFF_OVS_DATAPATH))
+#else
+ if (likely(rcu_access_pointer(dev->rx_handler) == netdev_frame_hook))
+#endif
+ return (struct vport *)rcu_dereference_rtnl(dev->rx_handler_data);
+ else
return NULL;
- return (struct vport *)rcu_dereference(dev->rx_handler_data);
#else
- return (struct vport *)rcu_dereference(dev->br_port);
+ return (struct vport *)rcu_dereference_rtnl(dev->br_port);
#endif
}
const struct vport_ops netdev_vport_ops = {
- .type = "netdev",
+ .type = OVS_VPORT_TYPE_NETDEV,
.flags = (VPORT_F_REQUIRED |
(USE_VPORT_STATS ? VPORT_F_GEN_STATS : 0)),
.init = netdev_init,
.exit = netdev_exit,
.create = netdev_create,
.destroy = netdev_destroy,
- .attach = netdev_attach,
- .detach = netdev_detach,
- .set_mtu = netdev_set_mtu,
.set_addr = netdev_set_addr,
.get_name = netdev_get_name,
.get_addr = netdev_get_addr,
.is_running = netdev_is_running,
.get_operstate = netdev_get_operstate,
.get_ifindex = netdev_get_ifindex,
- .get_iflink = netdev_get_iflink,
.get_mtu = netdev_get_mtu,
.send = netdev_send,
};