summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2010-05-27 13:03:45 -0700
committerBen Pfaff <blp@nicira.com>2010-05-27 13:08:44 -0700
commit69a4a76b9413b88f5b5eec5705250b4312811478 (patch)
treed02901db0224f2b07f78ec06619d2d2d1da46264
parent87103e1fccd88e0fc8a64d774ddb620deeb90678 (diff)
downloadopenvswitch-69a4a76b9413b88f5b5eec5705250b4312811478.tar.gz
datapath: Fix VLAN insertion behavior on Linux 2.6.27 and 2.6.28.
The behavior of __vlan_put_tag() has changed over time: - In 2.6.26 and earlier, it adjusted both MAC and network header pointers. (The latter didn't make any sense.) - In 2.6.27 and 2.6.28, it did not adjust any header pointers at all. - In 2.6.29 and later, it adjusts the MAC header pointer only. The behavior in 2.6.26 and earlier, and in 2.2.29 and later, works OK for Open vSwitch. The 2.6.27 and 2.6.28 behavior *almost* works OK, with a few subtle problems. If an action that sets a VLAN tag is followed by an action that strips a VLAN tag, the "strip" action silently fails. This is because vlan_pull_tag() in datapath/actions.c sees the encapsulated protocol, not the 802.1Q protocol, because the MAC header was not adjusted and does not point to the 802.1Q header. If multiple set-VLAN actions occur in a single flow, the second and later actions will fail for the same reason. This commit fixes the problem by ensuring that __vlan_put_tag() always behaves as in 2.6.29 and later. Reported-by: Reid Price <reid@nicira.com> Bug #2867.
-rw-r--r--datapath/linux-2.6/Modules.mk1
-rw-r--r--datapath/linux-2.6/compat-2.6/include/linux/if_vlan.h46
2 files changed, 47 insertions, 0 deletions
diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk
index f5f13e2c8..baa6f53cd 100644
--- a/datapath/linux-2.6/Modules.mk
+++ b/datapath/linux-2.6/Modules.mk
@@ -16,6 +16,7 @@ openvswitch_headers += \
linux-2.6/compat-2.6/include/linux/if.h \
linux-2.6/compat-2.6/include/linux/if_arp.h \
linux-2.6/compat-2.6/include/linux/if_ether.h \
+ linux-2.6/compat-2.6/include/linux/if_vlan.h \
linux-2.6/compat-2.6/include/linux/in.h \
linux-2.6/compat-2.6/include/linux/inetdevice.h \
linux-2.6/compat-2.6/include/linux/ip.h \
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/if_vlan.h b/datapath/linux-2.6/compat-2.6/include/linux/if_vlan.h
new file mode 100644
index 000000000..3bede9302
--- /dev/null
+++ b/datapath/linux-2.6/compat-2.6/include/linux/if_vlan.h
@@ -0,0 +1,46 @@
+#ifndef __LINUX_IF_VLAN_WRAPPER_H
+#define __LINUX_IF_VLAN_WRAPPER_H 1
+
+#include_next <linux/if_vlan.h>
+
+/*
+ * The behavior of __vlan_put_tag() has changed over time:
+ *
+ * - In 2.6.26 and earlier, it adjusted both MAC and network header
+ * pointers. (The latter didn't make any sense.)
+ *
+ * - In 2.6.27 and 2.6.28, it did not adjust any header pointers at all.
+ *
+ * - In 2.6.29 and later, it adjusts the MAC header pointer only.
+ *
+ * This is the version from 2.6.33. We unconditionally substitute this version
+ * to avoid the need to guess whether the version in the kernel tree is
+ * acceptable.
+ */
+#define __vlan_put_tag rpl_vlan_put_tag
+static inline struct sk_buff *__vlan_put_tag(struct sk_buff *skb, u16 vlan_tci)
+{
+ struct vlan_ethhdr *veth;
+
+ if (skb_cow_head(skb, VLAN_HLEN) < 0) {
+ kfree_skb(skb);
+ return NULL;
+ }
+ veth = (struct vlan_ethhdr *)skb_push(skb, VLAN_HLEN);
+
+ /* Move the mac addresses to the beginning of the new header. */
+ memmove(skb->data, skb->data + VLAN_HLEN, 2 * VLAN_ETH_ALEN);
+ skb->mac_header -= VLAN_HLEN;
+
+ /* first, the ethernet type */
+ veth->h_vlan_proto = htons(ETH_P_8021Q);
+
+ /* now, the TCI */
+ veth->h_vlan_TCI = htons(vlan_tci);
+
+ skb->protocol = htons(ETH_P_8021Q);
+
+ return skb;
+}
+
+#endif /* linux/if_vlan.h wrapper */