datapath: improve ipv6_find_hdr() function for IPv6 'set' action
authorAnsis Atteka <aatteka@nicira.com>
Tue, 6 Nov 2012 15:12:38 +0000 (17:12 +0200)
committerAnsis Atteka <aatteka@nicira.com>
Tue, 6 Nov 2012 19:06:49 +0000 (21:06 +0200)
This patch prepares ipv6_find_hdr() function so that it could be
able to skip routing headers, where segements_left is 0. This is
required for us to handle multiple routing header case correctly.

Signed-off-by: Ansis Atteka <aatteka@nicira.com>
datapath/linux/compat/exthdrs_core.c
datapath/linux/compat/include/net/ipv6.h

index 1749c066df9240e8f3721f3ac55710ccb362799e..eb56e6b26bbb84ddf61bc8b8cd0cd2d8dcc2bd3d 100644 (file)
@@ -70,7 +70,8 @@ int rpl_ipv6_skip_exthdr(const struct sk_buff *skb, int start,
  * if flags is not NULL and it's a fragment, then the frag flag
  * OVS_IP6T_FH_F_FRAG will be set. If it's an AH header, the
  * OVS_IP6T_FH_F_AUTH flag is set and target < 0, then this function will
- * stop at the AH header.
+ * stop at the AH header. If OVS_IP6T_FH_F_SKIP_RH flag was passed, then this
+ * function will skip all those routing headers, where segements_left was 0.
  */
 int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
                  int target, unsigned short *fragoff, int *flags)
@@ -78,6 +79,7 @@ int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
        unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
        u8 nexthdr = ipv6_hdr(skb)->nexthdr;
        unsigned int len;
+       bool found;
 
        if (fragoff)
                *fragoff = 0;
@@ -95,9 +97,10 @@ int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
        }
        len = skb->len - start;
 
-       while (nexthdr != target) {
+       do {
                struct ipv6_opt_hdr _hdr, *hp;
                unsigned int hdrlen;
+               found = (nexthdr == target);
 
                if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
                        if (target < 0)
@@ -108,6 +111,20 @@ int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
                hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
                if (hp == NULL)
                        return -EBADMSG;
+
+               if (nexthdr == NEXTHDR_ROUTING) {
+                       struct ipv6_rt_hdr _rh, *rh;
+
+                       rh = skb_header_pointer(skb, start, sizeof(_rh),
+                                               &_rh);
+                       if (rh == NULL)
+                               return -EBADMSG;
+
+                       if (flags && (*flags & OVS_IP6T_FH_F_SKIP_RH) &&
+                           rh->segments_left == 0)
+                               found = false;
+               }
+
                if (nexthdr == NEXTHDR_FRAGMENT) {
                        unsigned short _frag_off;
                        __be16 *fp;
@@ -142,10 +159,12 @@ int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
                } else
                        hdrlen = ipv6_optlen(hp);
 
-               nexthdr = hp->nexthdr;
-               len -= hdrlen;
-               start += hdrlen;
-       }
+               if (!found) {
+                       nexthdr = hp->nexthdr;
+                       len -= hdrlen;
+                       start += hdrlen;
+               }
+       } while (!found);
 
        *offset = start;
        return nexthdr;
index 9ba85594100f30e745d681839e63b726e513bcff..09115a49eb9f84ee46ca71a2210e1c6560f1e05f 100644 (file)
@@ -6,6 +6,7 @@
 enum {
        OVS_IP6T_FH_F_FRAG      = (1 << 0),
        OVS_IP6T_FH_F_AUTH      = (1 << 1),
+       OVS_IP6T_FH_F_SKIP_RH   = (1 << 2),
 };
 
 /* This function is upstream but not the version which supplies the