X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=datapath%2Fchecksum.c;h=5146c6531ec5ffb29d20e91aa6101eb1768e45ef;hb=e0edde6fee279cdbbf3c179f5f50adaf0c7c7f1e;hp=e35194916f3afeea499a6330ef3ace0424e05911;hpb=dd8d6b8cd4d708553037e306d41d1c501d3cecad;p=sliver-openvswitch.git diff --git a/datapath/checksum.c b/datapath/checksum.c index e35194916..5146c6531 100644 --- a/datapath/checksum.c +++ b/datapath/checksum.c @@ -1,9 +1,19 @@ /* - * Copyright (c) 2010 Nicira Networks. - * Distributed under the terms of the GNU GPL version 2. + * Copyright (c) 2007-2011 Nicira, Inc. * - * Significant portions of this file may be copied from parts of the Linux - * kernel, by Linus Torvalds and others. + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -16,7 +26,82 @@ #include "checksum.h" #include "datapath.h" - /* Types of checksums that we can receive (these all refer to L4 checksums): +#ifdef NEED_CSUM_NORMALIZE + +#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID) +/* This code is based on skb_checksum_setup() from Xen's net/dev/core.c. We + * can't call this function directly because it isn't exported in all + * versions. */ +static int vswitch_skb_checksum_setup(struct sk_buff *skb) +{ + struct iphdr *iph; + unsigned char *th; + int err = -EPROTO; + __u16 csum_start, csum_offset; + + if (!skb->proto_csum_blank) + return 0; + + if (skb->protocol != htons(ETH_P_IP)) + goto out; + + if (!pskb_may_pull(skb, skb_network_header(skb) + sizeof(struct iphdr) - skb->data)) + goto out; + + iph = ip_hdr(skb); + th = skb_network_header(skb) + 4 * iph->ihl; + + csum_start = th - skb->head; + switch (iph->protocol) { + case IPPROTO_TCP: + csum_offset = offsetof(struct tcphdr, check); + break; + case IPPROTO_UDP: + csum_offset = offsetof(struct udphdr, check); + break; + default: + if (net_ratelimit()) + pr_err("Attempting to checksum a non-TCP/UDP packet, " + "dropping a protocol %d packet", + iph->protocol); + goto out; + } + + if (!pskb_may_pull(skb, th + csum_offset + 2 - skb->data)) + goto out; + + skb->proto_csum_blank = 0; + set_ip_summed(skb, OVS_CSUM_PARTIAL); + set_skb_csum_pointers(skb, csum_start, csum_offset); + + err = 0; + +out: + return err; +} +#else +static int vswitch_skb_checksum_setup(struct sk_buff *skb) +{ + return 0; +} +#endif /* not Xen old style checksums */ + +/* + * compute_ip_summed - map external checksum state onto OVS representation + * + * @skb: Packet to manipulate. + * @xmit: Whether we were on transmit path of network stack. For example, + * this is true for the internal dev vport because it receives skbs + * that passed through dev_queue_xmit() but false for the netdev vport + * because its packets come from netif_receive_skb(). + * + * Older kernels (and various versions of Xen) were not explicit enough about + * checksum offload parameters and rely on a combination of context and + * non standard fields. This deals with all those variations so that we + * can internally manipulate checksum offloads without worrying about kernel + * version. + * + * Types of checksums that we can receive (these all refer to L4 checksums): * 1. CHECKSUM_NONE: Device that did not compute checksum, contains full * (though not verified) checksum in packet but not in skb->csum. Packets * from the bridge local port will also have this type. @@ -58,25 +143,24 @@ * CHECKSUM_PARTIAL, it will be sent with the wrong checksum. However, there * shouldn't be any devices that do this with bridging. */ -#ifdef NEED_CSUM_NORMALIZE -void compute_ip_summed(struct sk_buff *skb, bool xmit) +int compute_ip_summed(struct sk_buff *skb, bool xmit) { /* For our convenience these defines change repeatedly between kernel * versions, so we can't just copy them over... */ switch (skb->ip_summed) { case CHECKSUM_NONE: - OVS_CB(skb)->ip_summed = OVS_CSUM_NONE; + set_ip_summed(skb, OVS_CSUM_NONE); break; case CHECKSUM_UNNECESSARY: - OVS_CB(skb)->ip_summed = OVS_CSUM_UNNECESSARY; + set_ip_summed(skb, OVS_CSUM_UNNECESSARY); break; #ifdef CHECKSUM_HW /* In theory this could be either CHECKSUM_PARTIAL or CHECKSUM_COMPLETE. * However, on the receive side we should only get CHECKSUM_PARTIAL * packets from Xen, which uses some special fields to represent this - * (see below). Since we can only make one type work, pick the one - * that actually happens in practice. + * (see vswitch_skb_checksum_setup()). Since we can only make one type + * work, pick the one that actually happens in practice. * * On the transmit side (basically after skb_checksum_setup() * has been run or on internal dev transmit), packets with @@ -84,87 +168,104 @@ void compute_ip_summed(struct sk_buff *skb, bool xmit) */ case CHECKSUM_HW: if (!xmit) - OVS_CB(skb)->ip_summed = OVS_CSUM_COMPLETE; + set_ip_summed(skb, OVS_CSUM_COMPLETE); else - OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL; - + set_ip_summed(skb, OVS_CSUM_PARTIAL); break; #else case CHECKSUM_COMPLETE: - OVS_CB(skb)->ip_summed = OVS_CSUM_COMPLETE; + set_ip_summed(skb, OVS_CSUM_COMPLETE); break; case CHECKSUM_PARTIAL: - OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL; + set_ip_summed(skb, OVS_CSUM_PARTIAL); break; #endif } -#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID) - /* Xen has a special way of representing CHECKSUM_PARTIAL on older - * kernels. It should not be set on the transmit path though. - */ - if (skb->proto_csum_blank) - OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL; + OVS_CB(skb)->csum_start = skb_headroom(skb) + skb_transport_offset(skb); - WARN_ON_ONCE(skb->proto_csum_blank && xmit); -#endif + return vswitch_skb_checksum_setup(skb); } -u8 get_ip_summed(struct sk_buff *skb) +/* + * forward_ip_summed - map internal checksum state back onto native + * kernel fields. + * + * @skb: Packet to manipulate. + * @xmit: Whether we are about send on the transmit path the network stack. + * This follows the same logic as the @xmit field in compute_ip_summed(). + * Generally, a given vport will have opposite values for @xmit passed to + * these two functions. + * + * When a packet is about to egress from OVS take our internal fields (including + * any modifications we have made) and recreate the correct representation for + * this kernel. This may do things like change the transport header offset. + */ +void forward_ip_summed(struct sk_buff *skb, bool xmit) { - return OVS_CB(skb)->ip_summed; -} -#endif /* NEED_CSUM_NORMALIZE */ - + switch (get_ip_summed(skb)) { + case OVS_CSUM_NONE: + skb->ip_summed = CHECKSUM_NONE; + break; + case OVS_CSUM_UNNECESSARY: + skb->ip_summed = CHECKSUM_UNNECESSARY; #if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID) -/* This code is based on skb_checksum_setup() from Xen's net/dev/core.c. We - * can't call this function directly because it isn't exported in all - * versions. */ -int vswitch_skb_checksum_setup(struct sk_buff *skb) -{ - struct iphdr *iph; - unsigned char *th; - int err = -EPROTO; - __u16 csum_start, csum_offset; - - if (!skb->proto_csum_blank) - return 0; - - if (skb->protocol != htons(ETH_P_IP)) - goto out; - - if (!pskb_may_pull(skb, skb_network_header(skb) + sizeof(struct iphdr) - skb->data)) - goto out; - - iph = ip_hdr(skb); - th = skb_network_header(skb) + 4 * iph->ihl; - - csum_start = th - skb->head; - switch (iph->protocol) { - case IPPROTO_TCP: - csum_offset = offsetof(struct tcphdr, check); + skb->proto_data_valid = 1; +#endif break; - case IPPROTO_UDP: - csum_offset = offsetof(struct udphdr, check); +#ifdef CHECKSUM_HW + case OVS_CSUM_COMPLETE: + if (!xmit) + skb->ip_summed = CHECKSUM_HW; + else + skb->ip_summed = CHECKSUM_NONE; break; - default: - if (net_ratelimit()) - pr_err("Attempting to checksum a non-TCP/UDP packet, " - "dropping a protocol %d packet", - iph->protocol); - goto out; + case OVS_CSUM_PARTIAL: + if (!xmit) { + skb->ip_summed = CHECKSUM_UNNECESSARY; +#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID) + skb->proto_csum_blank = 1; +#endif + } else { + skb->ip_summed = CHECKSUM_HW; + } + break; +#else + case OVS_CSUM_COMPLETE: + skb->ip_summed = CHECKSUM_COMPLETE; + break; + case OVS_CSUM_PARTIAL: + skb->ip_summed = CHECKSUM_PARTIAL; + break; +#endif } - if (!pskb_may_pull(skb, th + csum_offset + 2 - skb->data)) - goto out; + if (get_ip_summed(skb) == OVS_CSUM_PARTIAL) + skb_set_transport_header(skb, OVS_CB(skb)->csum_start - + skb_headroom(skb)); +} - skb->ip_summed = CHECKSUM_PARTIAL; - skb->proto_csum_blank = 0; - set_skb_csum_pointers(skb, csum_start, csum_offset); +u8 get_ip_summed(struct sk_buff *skb) +{ + return OVS_CB(skb)->ip_summed; +} - err = 0; +void set_ip_summed(struct sk_buff *skb, u8 ip_summed) +{ + OVS_CB(skb)->ip_summed = ip_summed; +} -out: - return err; +void get_skb_csum_pointers(const struct sk_buff *skb, u16 *csum_start, + u16 *csum_offset) +{ + *csum_start = OVS_CB(skb)->csum_start; + *csum_offset = skb->csum; } -#endif /* CONFIG_XEN && HAVE_PROTO_DATA_VALID */ + +void set_skb_csum_pointers(struct sk_buff *skb, u16 csum_start, + u16 csum_offset) +{ + OVS_CB(skb)->csum_start = csum_start; + skb->csum = csum_offset; +} +#endif /* NEED_CSUM_NORMALIZE */