From 0fd0d0834f79d6cfe8a0eccc19732bae365aa575 Mon Sep 17 00:00:00 2001
From: Jesse Gross <jesse@nicira.com>
Date: Thu, 1 Dec 2011 16:09:05 -0800
Subject: [PATCH] datapath: Remove custom version of ipv6_skip_exthdr().

We currently have a version of ipv6_skip_exthdr() which is
identical to the main one with the addition of fragment reporting.
We can propose our version for upstream and then use it directly
without duplication.

Signed-off-by: Jesse Gross <jesse@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
---
 datapath/flow.c                            | 70 +++-------------------
 datapath/linux/Modules.mk                  |  1 +
 datapath/linux/compat/exthdrs_core.c       | 48 +++++++++++++++
 datapath/linux/compat/include/linux/ipv6.h |  8 +++
 datapath/tunnel.c                          |  3 +-
 lib/flow.c                                 | 13 ++--
 6 files changed, 76 insertions(+), 67 deletions(-)
 create mode 100644 datapath/linux/compat/exthdrs_core.c

diff --git a/datapath/flow.c b/datapath/flow.c
index c6f591afe..78dea3a66 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -128,66 +128,6 @@ u64 ovs_flow_used_time(unsigned long flow_jiffies)
 	(offsetof(struct sw_flow_key, field) +	\
 	 FIELD_SIZEOF(struct sw_flow_key, field))
 
-/**
- * skip_exthdr - skip any IPv6 extension headers
- * @skb: skbuff to parse
- * @start: offset of first extension header
- * @nexthdrp: Initially, points to the type of the extension header at @start.
- * This function updates it to point to the extension header at the final
- * offset.
- * @frag: Points to the @frag member in a &struct sw_flow_key.  This
- * function sets an appropriate %OVS_FRAG_TYPE_* value.
- *
- * This is based on ipv6_skip_exthdr() but adds the updates to *@frag.
- *
- * When there is more than one fragment header, this version reports whether
- * the final fragment header that it examines is a first fragment.
- *
- * Returns the final payload offset, or -1 on error.
- */
-static int skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
-		       u8 *frag)
-{
-	u8 nexthdr = *nexthdrp;
-
-	while (ipv6_ext_hdr(nexthdr)) {
-		struct ipv6_opt_hdr _hdr, *hp;
-		int hdrlen;
-
-		if (nexthdr == NEXTHDR_NONE)
-			return -1;
-		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
-		if (hp == NULL)
-			return -1;
-		if (nexthdr == NEXTHDR_FRAGMENT) {
-			__be16 _frag_off, *fp;
-			fp = skb_header_pointer(skb,
-						start+offsetof(struct frag_hdr,
-							       frag_off),
-						sizeof(_frag_off),
-						&_frag_off);
-			if (fp == NULL)
-				return -1;
-
-			if (ntohs(*fp) & ~0x7) {
-				*frag = OVS_FRAG_TYPE_LATER;
-				break;
-			}
-			*frag = OVS_FRAG_TYPE_FIRST;
-			hdrlen = 8;
-		} else if (nexthdr == NEXTHDR_AUTH)
-			hdrlen = (hp->hdrlen+2)<<2;
-		else
-			hdrlen = ipv6_optlen(hp);
-
-		nexthdr = hp->nexthdr;
-		start += hdrlen;
-	}
-
-	*nexthdrp = nexthdr;
-	return start;
-}
-
 static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key,
 			 int *key_lenp)
 {
@@ -196,6 +136,7 @@ static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key,
 	int payload_ofs;
 	struct ipv6hdr *nh;
 	uint8_t nexthdr;
+	__be16 frag_off;
 	int err;
 
 	*key_lenp = SW_FLOW_KEY_OFFSET(ipv6.label);
@@ -215,10 +156,17 @@ static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key,
 	key->ipv6.addr.src = nh->saddr;
 	key->ipv6.addr.dst = nh->daddr;
 
-	payload_ofs = skip_exthdr(skb, payload_ofs, &nexthdr, &key->ip.frag);
+	payload_ofs = ipv6_skip_exthdr(skb, payload_ofs, &nexthdr, &frag_off);
 	if (unlikely(payload_ofs < 0))
 		return -EINVAL;
 
+	if (frag_off) {
+		if (frag_off & htons(~0x7))
+			key->ip.frag = OVS_FRAG_TYPE_LATER;
+		else
+			key->ip.frag = OVS_FRAG_TYPE_FIRST;
+	}
+
 	nh_len = payload_ofs - nh_ofs;
 	skb_set_transport_header(skb, nh_ofs + nh_len);
 	key->ip.proto = nexthdr;
diff --git a/datapath/linux/Modules.mk b/datapath/linux/Modules.mk
index 1f9973b47..bb8eff6cf 100644
--- a/datapath/linux/Modules.mk
+++ b/datapath/linux/Modules.mk
@@ -1,6 +1,7 @@
 openvswitch_sources += \
 	linux/compat/addrconf_core-openvswitch.c \
 	linux/compat/dev-openvswitch.c \
+	linux/compat/exthdrs_core.c \
 	linux/compat/flex_array.c \
 	linux/compat/genetlink-openvswitch.c \
 	linux/compat/ip_output-openvswitch.c \
diff --git a/datapath/linux/compat/exthdrs_core.c b/datapath/linux/compat/exthdrs_core.c
new file mode 100644
index 000000000..658e16ac6
--- /dev/null
+++ b/datapath/linux/compat/exthdrs_core.c
@@ -0,0 +1,48 @@
+#include <linux/ipv6.h>
+#include <net/ipv6.h>
+
+/* This function is upstream but not the version which supplies the
+ * fragment offset.  We plan to propose the extended version.
+ */
+int rpl_ipv6_skip_exthdr(const struct sk_buff *skb, int start,
+			 u8 *nexthdrp, __be16 *frag_offp)
+{
+	u8 nexthdr = *nexthdrp;
+
+	*frag_offp = 0;
+
+	while (ipv6_ext_hdr(nexthdr)) {
+		struct ipv6_opt_hdr _hdr, *hp;
+		int hdrlen;
+
+		if (nexthdr == NEXTHDR_NONE)
+			return -1;
+		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
+		if (hp == NULL)
+			return -1;
+		if (nexthdr == NEXTHDR_FRAGMENT) {
+			__be16 _frag_off, *fp;
+			fp = skb_header_pointer(skb,
+						start+offsetof(struct frag_hdr,
+							       frag_off),
+						sizeof(_frag_off),
+						&_frag_off);
+			if (fp == NULL)
+				return -1;
+
+			*frag_offp = *fp;
+			if (ntohs(*frag_offp) & ~0x7)
+				break;
+			hdrlen = 8;
+		} else if (nexthdr == NEXTHDR_AUTH)
+			hdrlen = (hp->hdrlen+2)<<2;
+		else
+			hdrlen = ipv6_optlen(hp);
+
+		nexthdr = hp->nexthdr;
+		start += hdrlen;
+	}
+
+	*nexthdrp = nexthdr;
+	return start;
+}
diff --git a/datapath/linux/compat/include/linux/ipv6.h b/datapath/linux/compat/include/linux/ipv6.h
index 25a5431af..fdbbe62a5 100644
--- a/datapath/linux/compat/include/linux/ipv6.h
+++ b/datapath/linux/compat/include/linux/ipv6.h
@@ -2,6 +2,7 @@
 #define __LINUX_IPV6_WRAPPER_H 1
 
 #include_next <linux/ipv6.h>
+#include <net/ipv6.h>
 
 #ifndef HAVE_SKBUFF_HEADER_HELPERS
 static inline struct ipv6hdr *ipv6_hdr(const struct sk_buff *skb)
@@ -10,4 +11,11 @@ static inline struct ipv6hdr *ipv6_hdr(const struct sk_buff *skb)
 }
 #endif
 
+/* This function is upstream but not the version which supplies the
+ * fragment offset.  We plan to propose the extended version.
+ */
+#define ipv6_skip_exthdr rpl_ipv6_skip_exthdr
+extern int rpl_ipv6_skip_exthdr(const struct sk_buff *skb, int start,
+				u8 *nexthdrp, __be16 *frag_offp);
+
 #endif
diff --git a/datapath/tunnel.c b/datapath/tunnel.c
index 4ce830ff6..33d2fe934 100644
--- a/datapath/tunnel.c
+++ b/datapath/tunnel.c
@@ -538,6 +538,7 @@ static bool ipv6_should_icmp(struct sk_buff *skb)
 	int addr_type;
 	int payload_off = (u8 *)(old_ipv6h + 1) - skb->data;
 	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+	__be16 frag_off;
 
 	/* Check source address is valid. */
 	addr_type = ipv6_addr_type(&old_ipv6h->saddr);
@@ -549,7 +550,7 @@ static bool ipv6_should_icmp(struct sk_buff *skb)
 		return false;
 
 	/* Don't respond to ICMP error messages. */
-	payload_off = ipv6_skip_exthdr(skb, payload_off, &nexthdr);
+	payload_off = ipv6_skip_exthdr(skb, payload_off, &nexthdr, &frag_off);
 	if (payload_off < 0)
 		return false;
 
diff --git a/lib/flow.c b/lib/flow.c
index 3865e509d..a491afffc 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -203,11 +203,14 @@ parse_ipv6(struct ofpbuf *packet, struct flow *flow)
             }
 
             /* We only process the first fragment. */
-            flow->nw_frag = FLOW_NW_FRAG_ANY;
-            if ((frag_hdr->ip6f_offlg & IP6F_OFF_MASK) != htons(0)) {
-                flow->nw_frag |= FLOW_NW_FRAG_LATER;
-                nexthdr = IPPROTO_FRAGMENT;
-                break;
+            if (frag_hdr->ip6f_offlg != htons(0)) {
+                if ((frag_hdr->ip6f_offlg & IP6F_OFF_MASK) == htons(0)) {
+                    flow->nw_frag = FLOW_NW_FRAG_ANY;
+                } else {
+                    flow->nw_frag |= FLOW_NW_FRAG_LATER;
+                    nexthdr = IPPROTO_FRAGMENT;
+                    break;
+                }
             }
         }
     }
-- 
2.47.0