diff mbox

[ovs-dev,1/1] userspace: Add IPv6 extension header parsing for tunnels

Message ID 66c68bf86385d681c0e4f9bfc7eeffe632f37d89.1499345286.git.echaudro@redhat.com
State Superseded
Headers show

Commit Message

Eelco Chaudron July 6, 2017, 12:48 p.m. UTC
While OVS userspace datapath (OVS-DPDK) supports GREv6, it does not
inter-operate with a native Linux ip6gretap tunnel. This is because
the Linux driver uses IPv6 optional headers for the Tunnel
Encapsulation Limit (rfc 2473, section 6.6).

OVS userspace simply does not parse these IPv6 extension headers
inside netdev_tnl_ip_extract_tnl_md(), as such popping the tunnel
leaves extra bytes resulting in a mangled decapsulated frame.

The change below will parse the IPv6 "next header" chain, and return
the offset to the upper layer protocol.

Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
---
 lib/netdev-native-tnl.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

Comments

Eric Garver July 24, 2017, 1:50 p.m. UTC | #1
Hi Eelco.

Sorry for the delayed review.

On Thu, Jul 06, 2017 at 02:48:47PM +0200, Eelco Chaudron wrote:
> While OVS userspace datapath (OVS-DPDK) supports GREv6, it does not
> inter-operate with a native Linux ip6gretap tunnel. This is because
> the Linux driver uses IPv6 optional headers for the Tunnel
> Encapsulation Limit (rfc 2473, section 6.6).
> 
> OVS userspace simply does not parse these IPv6 extension headers
> inside netdev_tnl_ip_extract_tnl_md(), as such popping the tunnel
> leaves extra bytes resulting in a mangled decapsulated frame.
> 
> The change below will parse the IPv6 "next header" chain, and return
> the offset to the upper layer protocol.
> 
> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
> ---
>  lib/netdev-native-tnl.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 90 insertions(+), 1 deletion(-)
> 
> diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
> index 7f3cf98..9a97b7f 100644
> --- a/lib/netdev-native-tnl.c
> +++ b/lib/netdev-native-tnl.c
> @@ -57,6 +57,80 @@ static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5);
>  uint16_t tnl_udp_port_min = 32768;
>  uint16_t tnl_udp_port_max = 61000;
>  
> +static int
> +netdev_tnl_ip6_get_upperlayer_offset(struct ovs_16aligned_ip6_hdr *ip6)
> +{
> +    uint8_t  ext = ip6->ip6_nxt;
> +    int      ext_hdr_offset = IPV6_HEADER_LEN;
> +    uint16_t bytes_left = ntohs(ip6->ip6_plen);
> +
> +    while (true) {
> +        size_t          ext_size = 0;
> +        struct ip6_ext *ext_hdr = (struct ip6_ext *) ((uint8_t *)ip6 + \
> +                                                      ext_hdr_offset);
> +
> +        if (bytes_left < 8) {
> +            /*
> +             * We need at least 8 bytes of data, which is the minimal extension
> +             * header length. The upper layer headers are also minimal 8 bytes.
> +             */
> +            break;
> +        }
> +
> +        switch (ext) {
> +        case IPPROTO_TCP:
> +        case IPPROTO_UDP:
> +        case IPPROTO_GRE:
> +            /*
> +             * If its any of the upper layer protocols we support for tunnels
> +             * return the offset.
> +             */
> +            return ext_hdr_offset;
> +
> +        case IPPROTO_HOPOPTS:
> +        case IPPROTO_ROUTING:
> +        case IPPROTO_DSTOPTS:
> +            /*
> +             * Silently ignore these extensions, and their options.
> +             */
> +            ext_size = (ext_hdr->ip6e_len + 1) * 8;
> +            if (bytes_left < ext_size) {
> +                return 0;
> +            }
> +            break;
> +
> +        case IPPROTO_FRAGMENT:
> +            /*
> +             * Currently IPv6 reassembly is not supported, so do not process
> +             * fragmented packets.
> +             */
> +            return 0;
> +
> +        case IPPROTO_AH:
> +            /*
> +             * Currently authentication is not supported, so do not process
> +             * the packet further.
> +             */
> +            return 0;
> +
> +        default:
> +            /*
> +             * Drop all packets with an unsupported transport layer,
> +             * or unknown extension header.
> +             */
> +            return 0;
> +        }
> +
> +        ext = ext_hdr->ip6e_nxt;
> +        bytes_left -= ext_size;
> +        ext_hdr_offset += ext_size;
> +    }
> +    /*
> +     * If we end up here the packet is invalid, so drop it...
> +     */
> +    return 0;
> +}
> +

I found parse_ipv6_ext_hdrs() in lib/flow.c. It seems like we should be
able to make use of that. It'd be nice if this was common between
tunnels and normal IPv6.

>  void *
>  netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>                    unsigned int *hlen)
> @@ -115,6 +189,7 @@ netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>  
>      } else if (IP_VER(ip->ip_ihl_ver) == 6) {
>          ovs_be32 tc_flow = get_16aligned_be32(&ip6->ip6_flow);
> +        unsigned int upper_layer_offset = 0;
>  
>          memcpy(tnl->ipv6_src.s6_addr, ip6->ip6_src.be16, sizeof ip6->ip6_src);
>          memcpy(tnl->ipv6_dst.s6_addr, ip6->ip6_dst.be16, sizeof ip6->ip6_dst);
> @@ -122,7 +197,21 @@ netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>          tnl->ip_tos = ntohl(tc_flow) >> 20;
>          tnl->ip_ttl = ip6->ip6_hlim;
>  
> -        *hlen += IPV6_HEADER_LEN;
> +        if ((l3_size - IPV6_HEADER_LEN) >= ntohs(ip6->ip6_plen)) {
> +            /*
> +             * Make sure the remaining buffer is big enough to contain
> +             * the entire payload.
> +             */
> +            upper_layer_offset = netdev_tnl_ip6_get_upperlayer_offset(ip6);
> +        }
> +
> +        if (upper_layer_offset <= 0) {
> +            VLOG_WARN_RL(&err_rl,
> +                         "ipv6 packet has unsupported extension headers");
> +            return NULL;
> +        }
> +
> +        *hlen += upper_layer_offset;
>  
>      } else {
>          VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)",
> -- 
> 2.7.5
> 
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Eelco Chaudron July 24, 2017, 2:47 p.m. UTC | #2
On 24/07/17 15:50, Eric Garver wrote:
> Hi Eelco.
>
> Sorry for the delayed review.
No problem...
> On Thu, Jul 06, 2017 at 02:48:47PM +0200, Eelco Chaudron wrote:
>> While OVS userspace datapath (OVS-DPDK) supports GREv6, it does not
>> inter-operate with a native Linux ip6gretap tunnel. This is because
>> the Linux driver uses IPv6 optional headers for the Tunnel
>> Encapsulation Limit (rfc 2473, section 6.6).
>>
>> OVS userspace simply does not parse these IPv6 extension headers
>> inside netdev_tnl_ip_extract_tnl_md(), as such popping the tunnel
>> leaves extra bytes resulting in a mangled decapsulated frame.
>>
>> The change below will parse the IPv6 "next header" chain, and return
>> the offset to the upper layer protocol.
>>
>> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
>> ---
>>   lib/netdev-native-tnl.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-
>>   1 file changed, 90 insertions(+), 1 deletion(-)
>>
>> diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
>> index 7f3cf98..9a97b7f 100644
>> --- a/lib/netdev-native-tnl.c
>> +++ b/lib/netdev-native-tnl.c
>> @@ -57,6 +57,80 @@ static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5);
>>   uint16_t tnl_udp_port_min = 32768;
>>   uint16_t tnl_udp_port_max = 61000;
>>   
>> +static int
>> +netdev_tnl_ip6_get_upperlayer_offset(struct ovs_16aligned_ip6_hdr *ip6)
>> +{
>> +    uint8_t  ext = ip6->ip6_nxt;
>> +    int      ext_hdr_offset = IPV6_HEADER_LEN;
>> +    uint16_t bytes_left = ntohs(ip6->ip6_plen);
>> +
>> +    while (true) {
>> +        size_t          ext_size = 0;
>> +        struct ip6_ext *ext_hdr = (struct ip6_ext *) ((uint8_t *)ip6 + \
>> +                                                      ext_hdr_offset);
>> +
>> +        if (bytes_left < 8) {
>> +            /*
>> +             * We need at least 8 bytes of data, which is the minimal extension
>> +             * header length. The upper layer headers are also minimal 8 bytes.
>> +             */
>> +            break;
>> +        }
>> +
>> +        switch (ext) {
>> +        case IPPROTO_TCP:
>> +        case IPPROTO_UDP:
>> +        case IPPROTO_GRE:
>> +            /*
>> +             * If its any of the upper layer protocols we support for tunnels
>> +             * return the offset.
>> +             */
>> +            return ext_hdr_offset;
>> +
>> +        case IPPROTO_HOPOPTS:
>> +        case IPPROTO_ROUTING:
>> +        case IPPROTO_DSTOPTS:
>> +            /*
>> +             * Silently ignore these extensions, and their options.
>> +             */
>> +            ext_size = (ext_hdr->ip6e_len + 1) * 8;
>> +            if (bytes_left < ext_size) {
>> +                return 0;
>> +            }
>> +            break;
>> +
>> +        case IPPROTO_FRAGMENT:
>> +            /*
>> +             * Currently IPv6 reassembly is not supported, so do not process
>> +             * fragmented packets.
>> +             */
>> +            return 0;
>> +
>> +        case IPPROTO_AH:
>> +            /*
>> +             * Currently authentication is not supported, so do not process
>> +             * the packet further.
>> +             */
>> +            return 0;
>> +
>> +        default:
>> +            /*
>> +             * Drop all packets with an unsupported transport layer,
>> +             * or unknown extension header.
>> +             */
>> +            return 0;
>> +        }
>> +
>> +        ext = ext_hdr->ip6e_nxt;
>> +        bytes_left -= ext_size;
>> +        ext_hdr_offset += ext_size;
>> +    }
>> +    /*
>> +     * If we end up here the packet is invalid, so drop it...
>> +     */
>> +    return 0;
>> +}
>> +
> I found parse_ipv6_ext_hdrs() in lib/flow.c. It seems like we should be
> able to make use of that. It'd be nice if this was common between
> tunnels and normal IPv6.
I saw that function to, but thought it would be good to have a separate 
one for
tunnels so we can take specific actions in the future when processing 
tunnel
specific options. But I can re-work this patch to use 
parse_ipv6_ext_hdrs() for
now.
>
>>   void *
>>   netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>>                     unsigned int *hlen)
>> @@ -115,6 +189,7 @@ netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>>   
>>       } else if (IP_VER(ip->ip_ihl_ver) == 6) {
>>           ovs_be32 tc_flow = get_16aligned_be32(&ip6->ip6_flow);
>> +        unsigned int upper_layer_offset = 0;
>>   
>>           memcpy(tnl->ipv6_src.s6_addr, ip6->ip6_src.be16, sizeof ip6->ip6_src);
>>           memcpy(tnl->ipv6_dst.s6_addr, ip6->ip6_dst.be16, sizeof ip6->ip6_dst);
>> @@ -122,7 +197,21 @@ netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
>>           tnl->ip_tos = ntohl(tc_flow) >> 20;
>>           tnl->ip_ttl = ip6->ip6_hlim;
>>   
>> -        *hlen += IPV6_HEADER_LEN;
>> +        if ((l3_size - IPV6_HEADER_LEN) >= ntohs(ip6->ip6_plen)) {
>> +            /*
>> +             * Make sure the remaining buffer is big enough to contain
>> +             * the entire payload.
>> +             */
>> +            upper_layer_offset = netdev_tnl_ip6_get_upperlayer_offset(ip6);
>> +        }
>> +
>> +        if (upper_layer_offset <= 0) {
>> +            VLOG_WARN_RL(&err_rl,
>> +                         "ipv6 packet has unsupported extension headers");
>> +            return NULL;
>> +        }
>> +
>> +        *hlen += upper_layer_offset;
>>   
>>       } else {
>>           VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)",
>> -- 
>> 2.7.5
>>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
diff mbox

Patch

diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index 7f3cf98..9a97b7f 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -57,6 +57,80 @@  static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5);
 uint16_t tnl_udp_port_min = 32768;
 uint16_t tnl_udp_port_max = 61000;
 
+static int
+netdev_tnl_ip6_get_upperlayer_offset(struct ovs_16aligned_ip6_hdr *ip6)
+{
+    uint8_t  ext = ip6->ip6_nxt;
+    int      ext_hdr_offset = IPV6_HEADER_LEN;
+    uint16_t bytes_left = ntohs(ip6->ip6_plen);
+
+    while (true) {
+        size_t          ext_size = 0;
+        struct ip6_ext *ext_hdr = (struct ip6_ext *) ((uint8_t *)ip6 + \
+                                                      ext_hdr_offset);
+
+        if (bytes_left < 8) {
+            /*
+             * We need at least 8 bytes of data, which is the minimal extension
+             * header length. The upper layer headers are also minimal 8 bytes.
+             */
+            break;
+        }
+
+        switch (ext) {
+        case IPPROTO_TCP:
+        case IPPROTO_UDP:
+        case IPPROTO_GRE:
+            /*
+             * If its any of the upper layer protocols we support for tunnels
+             * return the offset.
+             */
+            return ext_hdr_offset;
+
+        case IPPROTO_HOPOPTS:
+        case IPPROTO_ROUTING:
+        case IPPROTO_DSTOPTS:
+            /*
+             * Silently ignore these extensions, and their options.
+             */
+            ext_size = (ext_hdr->ip6e_len + 1) * 8;
+            if (bytes_left < ext_size) {
+                return 0;
+            }
+            break;
+
+        case IPPROTO_FRAGMENT:
+            /*
+             * Currently IPv6 reassembly is not supported, so do not process
+             * fragmented packets.
+             */
+            return 0;
+
+        case IPPROTO_AH:
+            /*
+             * Currently authentication is not supported, so do not process
+             * the packet further.
+             */
+            return 0;
+
+        default:
+            /*
+             * Drop all packets with an unsupported transport layer,
+             * or unknown extension header.
+             */
+            return 0;
+        }
+
+        ext = ext_hdr->ip6e_nxt;
+        bytes_left -= ext_size;
+        ext_hdr_offset += ext_size;
+    }
+    /*
+     * If we end up here the packet is invalid, so drop it...
+     */
+    return 0;
+}
+
 void *
 netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
                   unsigned int *hlen)
@@ -115,6 +189,7 @@  netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
 
     } else if (IP_VER(ip->ip_ihl_ver) == 6) {
         ovs_be32 tc_flow = get_16aligned_be32(&ip6->ip6_flow);
+        unsigned int upper_layer_offset = 0;
 
         memcpy(tnl->ipv6_src.s6_addr, ip6->ip6_src.be16, sizeof ip6->ip6_src);
         memcpy(tnl->ipv6_dst.s6_addr, ip6->ip6_dst.be16, sizeof ip6->ip6_dst);
@@ -122,7 +197,21 @@  netdev_tnl_ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
         tnl->ip_tos = ntohl(tc_flow) >> 20;
         tnl->ip_ttl = ip6->ip6_hlim;
 
-        *hlen += IPV6_HEADER_LEN;
+        if ((l3_size - IPV6_HEADER_LEN) >= ntohs(ip6->ip6_plen)) {
+            /*
+             * Make sure the remaining buffer is big enough to contain
+             * the entire payload.
+             */
+            upper_layer_offset = netdev_tnl_ip6_get_upperlayer_offset(ip6);
+        }
+
+        if (upper_layer_offset <= 0) {
+            VLOG_WARN_RL(&err_rl,
+                         "ipv6 packet has unsupported extension headers");
+            return NULL;
+        }
+
+        *hlen += upper_layer_offset;
 
     } else {
         VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)",