net: add skb_checksum_setup
authorPaul Durrant <Paul.Durrant@citrix.com>
Thu, 9 Jan 2014 10:02:46 +0000 (10:02 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 14 Jan 2014 22:24:19 +0000 (14:24 -0800)
This patch adds a function to set up the partial checksum offset for IP
packets (and optionally re-calculate the pseudo-header checksum) into the
core network code.
The implementation was previously private and duplicated between xen-netback
and xen-netfront, however it is not xen-specific and is potentially useful
to any network driver.

Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
Cc: David Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Veaceslav Falico <vfalico@redhat.com>
Cc: Alexander Duyck <alexander.h.duyck@intel.com>
Cc: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/skbuff.h
net/core/skbuff.c

index d97f2d07d02b71a967488bd445d2e91dd0b51c58..48b760505cb61eb423857e17b1f23874da9dde33 100644 (file)
@@ -2893,6 +2893,8 @@ static inline void skb_checksum_none_assert(const struct sk_buff *skb)
 
 bool skb_partial_csum_set(struct sk_buff *skb, u16 start, u16 off);
 
+int skb_checksum_setup(struct sk_buff *skb, bool recalculate);
+
 u32 __skb_get_poff(const struct sk_buff *skb);
 
 /**
index 1d641e781f85c51d650e94aa800e546f34024aaa..15057d29b0105c730cb9af40591678ac522c0833 100644 (file)
@@ -65,6 +65,7 @@
 #include <net/dst.h>
 #include <net/sock.h>
 #include <net/checksum.h>
+#include <net/ip6_checksum.h>
 #include <net/xfrm.h>
 
 #include <asm/uaccess.h>
@@ -3549,6 +3550,278 @@ bool skb_partial_csum_set(struct sk_buff *skb, u16 start, u16 off)
 }
 EXPORT_SYMBOL_GPL(skb_partial_csum_set);
 
+static int skb_maybe_pull_tail(struct sk_buff *skb, unsigned int len,
+                              unsigned int max)
+{
+       if (skb_headlen(skb) >= len)
+               return 0;
+
+       /* If we need to pullup then pullup to the max, so we
+        * won't need to do it again.
+        */
+       if (max > skb->len)
+               max = skb->len;
+
+       if (__pskb_pull_tail(skb, max - skb_headlen(skb)) == NULL)
+               return -ENOMEM;
+
+       if (skb_headlen(skb) < len)
+               return -EPROTO;
+
+       return 0;
+}
+
+/* This value should be large enough to cover a tagged ethernet header plus
+ * maximally sized IP and TCP or UDP headers.
+ */
+#define MAX_IP_HDR_LEN 128
+
+static int skb_checksum_setup_ip(struct sk_buff *skb, bool recalculate)
+{
+       unsigned int off;
+       bool fragment;
+       int err;
+
+       fragment = false;
+
+       err = skb_maybe_pull_tail(skb,
+                                 sizeof(struct iphdr),
+                                 MAX_IP_HDR_LEN);
+       if (err < 0)
+               goto out;
+
+       if (ip_hdr(skb)->frag_off & htons(IP_OFFSET | IP_MF))
+               fragment = true;
+
+       off = ip_hdrlen(skb);
+
+       err = -EPROTO;
+
+       if (fragment)
+               goto out;
+
+       switch (ip_hdr(skb)->protocol) {
+       case IPPROTO_TCP:
+               err = skb_maybe_pull_tail(skb,
+                                         off + sizeof(struct tcphdr),
+                                         MAX_IP_HDR_LEN);
+               if (err < 0)
+                       goto out;
+
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct tcphdr, check))) {
+                       err = -EPROTO;
+                       goto out;
+               }
+
+               if (recalculate)
+                       tcp_hdr(skb)->check =
+                               ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
+                                                  ip_hdr(skb)->daddr,
+                                                  skb->len - off,
+                                                  IPPROTO_TCP, 0);
+               break;
+       case IPPROTO_UDP:
+               err = skb_maybe_pull_tail(skb,
+                                         off + sizeof(struct udphdr),
+                                         MAX_IP_HDR_LEN);
+               if (err < 0)
+                       goto out;
+
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct udphdr, check))) {
+                       err = -EPROTO;
+                       goto out;
+               }
+
+               if (recalculate)
+                       udp_hdr(skb)->check =
+                               ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
+                                                  ip_hdr(skb)->daddr,
+                                                  skb->len - off,
+                                                  IPPROTO_UDP, 0);
+               break;
+       default:
+               goto out;
+       }
+
+       err = 0;
+
+out:
+       return err;
+}
+
+/* This value should be large enough to cover a tagged ethernet header plus
+ * an IPv6 header, all options, and a maximal TCP or UDP header.
+ */
+#define MAX_IPV6_HDR_LEN 256
+
+#define OPT_HDR(type, skb, off) \
+       (type *)(skb_network_header(skb) + (off))
+
+static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate)
+{
+       int err;
+       u8 nexthdr;
+       unsigned int off;
+       unsigned int len;
+       bool fragment;
+       bool done;
+
+       fragment = false;
+       done = false;
+
+       off = sizeof(struct ipv6hdr);
+
+       err = skb_maybe_pull_tail(skb, off, MAX_IPV6_HDR_LEN);
+       if (err < 0)
+               goto out;
+
+       nexthdr = ipv6_hdr(skb)->nexthdr;
+
+       len = sizeof(struct ipv6hdr) + ntohs(ipv6_hdr(skb)->payload_len);
+       while (off <= len && !done) {
+               switch (nexthdr) {
+               case IPPROTO_DSTOPTS:
+               case IPPROTO_HOPOPTS:
+               case IPPROTO_ROUTING: {
+                       struct ipv6_opt_hdr *hp;
+
+                       err = skb_maybe_pull_tail(skb,
+                                                 off +
+                                                 sizeof(struct ipv6_opt_hdr),
+                                                 MAX_IPV6_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       hp = OPT_HDR(struct ipv6_opt_hdr, skb, off);
+                       nexthdr = hp->nexthdr;
+                       off += ipv6_optlen(hp);
+                       break;
+               }
+               case IPPROTO_AH: {
+                       struct ip_auth_hdr *hp;
+
+                       err = skb_maybe_pull_tail(skb,
+                                                 off +
+                                                 sizeof(struct ip_auth_hdr),
+                                                 MAX_IPV6_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       hp = OPT_HDR(struct ip_auth_hdr, skb, off);
+                       nexthdr = hp->nexthdr;
+                       off += ipv6_authlen(hp);
+                       break;
+               }
+               case IPPROTO_FRAGMENT: {
+                       struct frag_hdr *hp;
+
+                       err = skb_maybe_pull_tail(skb,
+                                                 off +
+                                                 sizeof(struct frag_hdr),
+                                                 MAX_IPV6_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       hp = OPT_HDR(struct frag_hdr, skb, off);
+
+                       if (hp->frag_off & htons(IP6_OFFSET | IP6_MF))
+                               fragment = true;
+
+                       nexthdr = hp->nexthdr;
+                       off += sizeof(struct frag_hdr);
+                       break;
+               }
+               default:
+                       done = true;
+                       break;
+               }
+       }
+
+       err = -EPROTO;
+
+       if (!done || fragment)
+               goto out;
+
+       switch (nexthdr) {
+       case IPPROTO_TCP:
+               err = skb_maybe_pull_tail(skb,
+                                         off + sizeof(struct tcphdr),
+                                         MAX_IPV6_HDR_LEN);
+               if (err < 0)
+                       goto out;
+
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct tcphdr, check))) {
+                       err = -EPROTO;
+                       goto out;
+               }
+
+               if (recalculate)
+                       tcp_hdr(skb)->check =
+                               ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+                                                &ipv6_hdr(skb)->daddr,
+                                                skb->len - off,
+                                                IPPROTO_TCP, 0);
+               break;
+       case IPPROTO_UDP:
+               err = skb_maybe_pull_tail(skb,
+                                         off + sizeof(struct udphdr),
+                                         MAX_IPV6_HDR_LEN);
+               if (err < 0)
+                       goto out;
+
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct udphdr, check))) {
+                       err = -EPROTO;
+                       goto out;
+               }
+
+               if (recalculate)
+                       udp_hdr(skb)->check =
+                               ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+                                                &ipv6_hdr(skb)->daddr,
+                                                skb->len - off,
+                                                IPPROTO_UDP, 0);
+               break;
+       default:
+               goto out;
+       }
+
+       err = 0;
+
+out:
+       return err;
+}
+
+/**
+ * skb_checksum_setup - set up partial checksum offset
+ * @skb: the skb to set up
+ * @recalculate: if true the pseudo-header checksum will be recalculated
+ */
+int skb_checksum_setup(struct sk_buff *skb, bool recalculate)
+{
+       int err;
+
+       switch (skb->protocol) {
+       case htons(ETH_P_IP):
+               err = skb_checksum_setup_ip(skb, recalculate);
+               break;
+
+       case htons(ETH_P_IPV6):
+               err = skb_checksum_setup_ipv6(skb, recalculate);
+               break;
+
+       default:
+               err = -EPROTO;
+               break;
+       }
+
+       return err;
+}
+EXPORT_SYMBOL(skb_checksum_setup);
+
 void __skb_warn_lro_forwarding(const struct sk_buff *skb)
 {
        net_warn_ratelimited("%s: received packets cannot be forwarded while LRO is enabled\n",