Drivers: net: hyperv: Address UDP checksum issues
authorKY Srinivasan <kys@microsoft.com>
Wed, 9 Apr 2014 22:00:47 +0000 (15:00 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 11 Apr 2014 19:15:12 +0000 (15:15 -0400)
ws2008r2 does not support UDP checksum offload. Thus, we cannnot turn on
UDP offload in the host. Also, on ws2012 and ws2012 r2, there appear to be
an issue with UDP checksum offload.
Fix this issue by computing the UDP checksum in the Hyper-V driver.

Based on Dave Miller's comments, in this version, I have COWed the skb
before modifying the UDP header (the checksum field).

Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/hyperv/hyperv_net.h
drivers/net/hyperv/netvsc_drv.c
drivers/net/hyperv/rndis_filter.c

index 13010b4dae5b7ddea9b3a48cf28dc8425c5b3e6c..d18f711d0b0cd45ae50cc30cc09a9468d4e40d22 100644 (file)
@@ -747,6 +747,7 @@ struct ndis_oject_header {
 #define NDIS_TCP_LARGE_SEND_OFFLOAD_IPV4       0
 #define NDIS_TCP_LARGE_SEND_OFFLOAD_IPV6       1
 
+#define VERSION_4_OFFLOAD_SIZE                 22
 /*
  * New offload OIDs for NDIS 6
  */
index 6f39baa67a5f15aa4eeb04b0ab7dbf6ce9cd0cb4..31e55fba7cadd03d9e19221139727272b9eeec77 100644 (file)
@@ -398,7 +398,30 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
                csum_info->transmit.tcp_checksum = 1;
                csum_info->transmit.tcp_header_offset = hdr_offset;
        } else if (net_trans_info & INFO_UDP) {
-               csum_info->transmit.udp_checksum = 1;
+               /* UDP checksum offload is not supported on ws2008r2.
+                * Furthermore, on ws2012 and ws2012r2, there are some
+                * issues with udp checksum offload from Linux guests.
+                * (these are host issues).
+                * For now compute the checksum here.
+                */
+               struct udphdr *uh;
+               u16 udp_len;
+
+               ret = skb_cow_head(skb, 0);
+               if (ret)
+                       goto drop;
+
+               uh = udp_hdr(skb);
+               udp_len = ntohs(uh->len);
+               uh->check = 0;
+               uh->check = csum_tcpudp_magic(ip_hdr(skb)->saddr,
+                                             ip_hdr(skb)->daddr,
+                                             udp_len, IPPROTO_UDP,
+                                             csum_partial(uh, udp_len, 0));
+               if (uh->check == 0)
+                       uh->check = CSUM_MANGLED_0;
+
+               csum_info->transmit.udp_checksum = 0;
        }
        goto do_send;
 
@@ -438,6 +461,7 @@ do_send:
 
        ret = netvsc_send(net_device_ctx->device_ctx, packet);
 
+drop:
        if (ret == 0) {
                net->stats.tx_bytes += skb->len;
                net->stats.tx_packets++;
index 4a37e3db9e32f06c1c820c66cb08d79920bf02c0..143a98caf618a90cd45ac002005c506f5ca14a33 100644 (file)
@@ -641,6 +641,16 @@ int rndis_filter_set_offload_params(struct hv_device *hdev,
        struct rndis_set_complete *set_complete;
        u32 extlen = sizeof(struct ndis_offload_params);
        int ret, t;
+       u32 vsp_version = nvdev->nvsp_version;
+
+       if (vsp_version <= NVSP_PROTOCOL_VERSION_4) {
+               extlen = VERSION_4_OFFLOAD_SIZE;
+               /* On NVSP_PROTOCOL_VERSION_4 and below, we do not support
+                * UDP checksum offload.
+                */
+               req_offloads->udp_ip_v4_csum = 0;
+               req_offloads->udp_ip_v6_csum = 0;
+       }
 
        request = get_rndis_request(rdev, RNDIS_MSG_SET,
                RNDIS_MESSAGE_SIZE(struct rndis_set_request) + extlen);
@@ -674,7 +684,7 @@ int rndis_filter_set_offload_params(struct hv_device *hdev,
        } else {
                set_complete = &request->response_msg.msg.set_complete;
                if (set_complete->status != RNDIS_STATUS_SUCCESS) {
-                       netdev_err(ndev, "Fail to set MAC on host side:0x%x\n",
+                       netdev_err(ndev, "Fail to set offload on host side:0x%x\n",
                                   set_complete->status);
                        ret = -EINVAL;
                }