[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [RFC PATCH net-next 1/3] net: add skb checksum setup functions for TCP and UDP



This patch adds a function to set up partial checksum offsets for both v4
and v6 TCP and UDP packets into the core network code to make it widely
available to any network driver.

The code is taken from xen-netback and both that driver and xen-netfront
will be modified to use the new function.

Signed-off-by: Paul Durrant <paul.durrant@xxxxxxxxxx>
---
 include/linux/netdevice.h |    1 +
 net/core/dev.c            |  256 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 257 insertions(+)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9d55e51..e26ee7b 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2919,6 +2919,7 @@ void *netdev_lower_dev_get_private_rcu(struct net_device 
*dev,
 void *netdev_lower_dev_get_private(struct net_device *dev,
                                   struct net_device *lower_dev);
 int skb_checksum_help(struct sk_buff *skb);
+int skb_checksum_setup(struct sk_buff *skb, bool recalculate_partial);
 struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
                                  netdev_features_t features, bool tx_path);
 struct sk_buff *skb_mac_gso_segment(struct sk_buff *skb,
diff --git a/net/core/dev.c b/net/core/dev.c
index 355df36..e565656 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2282,6 +2282,262 @@ out:
 }
 EXPORT_SYMBOL(skb_checksum_help);
 
+static inline int 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 checksum_setup_ip(struct sk_buff *skb, bool recalculate)
+{
+       unsigned int off;
+       bool fragment;
+       int err;
+
+       fragment = false;
+
+       err = 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;
+
+       switch (ip_hdr(skb)->protocol) {
+       case IPPROTO_TCP:
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct tcphdr, check)))
+                       goto out;
+
+               if (recalculate) {
+                       err = maybe_pull_tail(skb,
+                                             off + sizeof(struct tcphdr),
+                                             MAX_IP_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       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:
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct udphdr, check)))
+                       goto out;
+
+               if (recalculate) {
+                       err = maybe_pull_tail(skb,
+                                             off + sizeof(struct udphdr),
+                                             MAX_IP_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       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 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 = 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 = 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 = 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 = 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:
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct tcphdr, check)))
+                       goto out;
+
+               if (recalculate) {
+                       err = maybe_pull_tail(skb,
+                                             off + sizeof(struct tcphdr),
+                                             MAX_IPV6_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       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:
+               if (!skb_partial_csum_set(skb, off,
+                                         offsetof(struct udphdr, check)))
+                       goto out;
+
+               if (recalculate) {
+                       err = maybe_pull_tail(skb,
+                                             off + sizeof(struct udphdr),
+                                             MAX_IPV6_HDR_LEN);
+                       if (err < 0)
+                               goto out;
+
+                       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;
+}
+
+
+/* Determine correct offset for TCP or UDP checksum, as appropriate, and
+ * optionally re-calculate the pseudo header checksum.
+ */
+int skb_checksum_setup(struct sk_buff *skb, bool recalculate)
+{
+       if (skb->ip_summed != CHECKSUM_PARTIAL)
+               return -EINVAL;
+
+       if (skb->protocol == htons(ETH_P_IP))
+               return checksum_setup_ip(skb, recalculate);
+       else if (skb->protocol == htons(ETH_P_IPV6))
+               return checksum_setup_ipv6(skb, recalculate);
+       else
+               return -EPROTO;
+}
+EXPORT_SYMBOL(skb_checksum_setup);
+
 __be16 skb_network_protocol(struct sk_buff *skb)
 {
        __be16 type = skb->protocol;
-- 
1.7.10.4


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.