datapath: Fix checksum update for actions on UDP packets.
authorJesse Gross <jesse@nicira.com>
Tue, 6 Mar 2012 21:09:13 +0000 (13:09 -0800)
committerJesse Gross <jesse@nicira.com>
Wed, 7 Mar 2012 22:34:25 +0000 (14:34 -0800)
When modifying IP addresses or ports on a UDP packet we don't
correctly follow the rules for unchecksummed packets.  This meant
that packets without a checksum can be given a incorrect new checksum
and packets with a checksum can become marked as being unchecksummed.
This fixes it to handle those requirements.

Bug #8937

Signed-off-by: Jesse Gross <jesse@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
datapath/actions.c
datapath/linux/Modules.mk
datapath/linux/compat/include/linux/checksum.h [new file with mode: 0644]

index 4b0760395229d21f9a68818225a7a3b3238adce7..29038016716c161ebe63390db22cbfd8cf0bb6f2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007-2011 Nicira Networks.
+ * Copyright (c) 2007-2012 Nicira Networks.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of version 2 of the GNU General Public
@@ -147,9 +147,17 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
                        inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb,
                                                 *addr, new_addr, 1);
        } else if (nh->protocol == IPPROTO_UDP) {
-               if (likely(transport_len >= sizeof(struct udphdr)))
-                       inet_proto_csum_replace4(&udp_hdr(skb)->check, skb,
-                                                *addr, new_addr, 1);
+               if (likely(transport_len >= sizeof(struct udphdr))) {
+                       struct udphdr *uh = udp_hdr(skb);
+
+                       if (uh->check ||
+                           get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
+                               inet_proto_csum_replace4(&uh->check, skb,
+                                                        *addr, new_addr, 1);
+                               if (!uh->check)
+                                       uh->check = CSUM_MANGLED_0;
+                       }
+               }
        }
 
        csum_replace4(&nh->check, *addr, new_addr);
@@ -199,8 +207,22 @@ static void set_tp_port(struct sk_buff *skb, __be16 *port,
        skb_clear_rxhash(skb);
 }
 
-static int set_udp_port(struct sk_buff *skb,
-                       const struct ovs_key_udp *udp_port_key)
+static void set_udp_port(struct sk_buff *skb, __be16 *port, __be16 new_port)
+{
+       struct udphdr *uh = udp_hdr(skb);
+
+       if (uh->check && get_ip_summed(skb) != OVS_CSUM_PARTIAL) {
+               set_tp_port(skb, port, new_port, &uh->check);
+
+               if (!uh->check)
+                       uh->check = CSUM_MANGLED_0;
+       } else {
+               *port = new_port;
+               skb_clear_rxhash(skb);
+       }
+}
+
+static int set_udp(struct sk_buff *skb, const struct ovs_key_udp *udp_port_key)
 {
        struct udphdr *uh;
        int err;
@@ -212,16 +234,15 @@ static int set_udp_port(struct sk_buff *skb,
 
        uh = udp_hdr(skb);
        if (udp_port_key->udp_src != uh->source)
-               set_tp_port(skb, &uh->source, udp_port_key->udp_src, &uh->check);
+               set_udp_port(skb, &uh->source, udp_port_key->udp_src);
 
        if (udp_port_key->udp_dst != uh->dest)
-               set_tp_port(skb, &uh->dest, udp_port_key->udp_dst, &uh->check);
+               set_udp_port(skb, &uh->dest, udp_port_key->udp_dst);
 
        return 0;
 }
 
-static int set_tcp_port(struct sk_buff *skb,
-                       const struct ovs_key_tcp *tcp_port_key)
+static int set_tcp(struct sk_buff *skb, const struct ovs_key_tcp *tcp_port_key)
 {
        struct tcphdr *th;
        int err;
@@ -334,11 +355,11 @@ static int execute_set_action(struct sk_buff *skb,
                break;
 
        case OVS_KEY_ATTR_TCP:
-               err = set_tcp_port(skb, nla_data(nested_attr));
+               err = set_tcp(skb, nla_data(nested_attr));
                break;
 
        case OVS_KEY_ATTR_UDP:
-               err = set_udp_port(skb, nla_data(nested_attr));
+               err = set_udp(skb, nla_data(nested_attr));
                break;
        }
 
index 97d977b72e19f3422e8a6749c409fc784ffafa51..86341ad07d0dff15c79e6eae4fda453b9847f853 100644 (file)
@@ -13,6 +13,7 @@ openvswitch_sources += \
        linux/compat/time.c     \
        linux/compat/workqueue.c
 openvswitch_headers += \
+       linux/compat/include/linux/checksum.h \
        linux/compat/include/linux/compiler.h \
        linux/compat/include/linux/compiler-gcc.h \
        linux/compat/include/linux/cpumask.h \
diff --git a/datapath/linux/compat/include/linux/checksum.h b/datapath/linux/compat/include/linux/checksum.h
new file mode 100644 (file)
index 0000000..1d4fefc
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __LINUX_CHECKSUM_WRAPPER_H
+#define __LINUX_CHECKSUM_WRAPPER_H 1
+
+#include_next <linux/checksum.h>
+
+#ifndef CSUM_MANGLED_0
+#define CSUM_MANGLED_0 ((__force __sum16)0xffff)
+#endif
+
+#endif