esp: Add a software GRO codepath
authorSteffen Klassert <steffen.klassert@secunet.com>
Wed, 15 Feb 2017 08:40:00 +0000 (09:40 +0100)
committerSteffen Klassert <steffen.klassert@secunet.com>
Wed, 15 Feb 2017 10:04:11 +0000 (11:04 +0100)
This patch adds GRO ifrastructure and callbacks for ESP on
ipv4 and ipv6.

In case the GRO layer detects an ESP packet, the
esp{4,6}_gro_receive() function does a xfrm state lookup
and calls the xfrm input layer if it finds a matching state.
The packet will be decapsulated and reinjected it into layer 2.

Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
12 files changed:
include/net/xfrm.h
net/ipv4/Kconfig
net/ipv4/Makefile
net/ipv4/esp4_offload.c [new file with mode: 0644]
net/ipv4/xfrm4_input.c
net/ipv4/xfrm4_mode_transport.c
net/ipv6/Kconfig
net/ipv6/Makefile
net/ipv6/esp6_offload.c [new file with mode: 0644]
net/ipv6/xfrm6_input.c
net/ipv6/xfrm6_mode_transport.c
net/xfrm/xfrm_input.c

index 10086a0986f8a7071ebbd0d293959a2000ca256b..14d82bf16692a6d0ed66721b1548ba152dbc18de 100644 (file)
@@ -682,6 +682,7 @@ struct xfrm_spi_skb_cb {
 
        unsigned int daddroff;
        unsigned int family;
+       __be32 seq;
 };
 
 #define XFRM_SPI_SKB_CB(__skb) ((struct xfrm_spi_skb_cb *)&((__skb)->cb[0]))
index 6e7baaf814c608b4e222406c42287382d4eef474..e0878c3f66a856e361cfa25f5456a8b83fe8d395 100644 (file)
@@ -360,6 +360,19 @@ config INET_ESP
 
          If unsure, say Y.
 
+config INET_ESP_OFFLOAD
+       tristate "IP: ESP transformation offload"
+       depends on INET_ESP
+       select XFRM_OFFLOAD
+       default n
+       ---help---
+         Support for ESP transformation offload. This makes sense
+         only if this system really does IPsec and want to do it
+         with high throughput. A typical desktop system does not
+         need it, even if it does IPsec.
+
+         If unsure, say N.
+
 config INET_IPCOMP
        tristate "IP: IPComp transformation"
        select INET_XFRM_TUNNEL
index 48af58a5686e3e9d81120217e5949d06f5ee6ce9..c6d4238ff94a8a329bf44bf59b30fb09fb07f707 100644 (file)
@@ -29,6 +29,7 @@ obj-$(CONFIG_NET_IPVTI) += ip_vti.o
 obj-$(CONFIG_SYN_COOKIES) += syncookies.o
 obj-$(CONFIG_INET_AH) += ah4.o
 obj-$(CONFIG_INET_ESP) += esp4.o
+obj-$(CONFIG_INET_ESP_OFFLOAD) += esp4_offload.o
 obj-$(CONFIG_INET_IPCOMP) += ipcomp.o
 obj-$(CONFIG_INET_XFRM_TUNNEL) += xfrm4_tunnel.o
 obj-$(CONFIG_INET_XFRM_MODE_BEET) += xfrm4_mode_beet.o
diff --git a/net/ipv4/esp4_offload.c b/net/ipv4/esp4_offload.c
new file mode 100644 (file)
index 0000000..1de4426
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * IPV4 GSO/GRO offload support
+ * Linux INET implementation
+ *
+ * Copyright (C) 2016 secunet Security Networks AG
+ * Author: Steffen Klassert <steffen.klassert@secunet.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * ESP GRO support
+ */
+
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <net/protocol.h>
+#include <crypto/aead.h>
+#include <crypto/authenc.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <net/ip.h>
+#include <net/xfrm.h>
+#include <net/esp.h>
+#include <linux/scatterlist.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <net/udp.h>
+
+static struct sk_buff **esp4_gro_receive(struct sk_buff **head,
+                                        struct sk_buff *skb)
+{
+       int offset = skb_gro_offset(skb);
+       struct xfrm_offload *xo;
+       struct xfrm_state *x;
+       __be32 seq;
+       __be32 spi;
+       int err;
+
+       skb_pull(skb, offset);
+
+       if ((err = xfrm_parse_spi(skb, IPPROTO_ESP, &spi, &seq)) != 0)
+               goto out;
+
+       err = secpath_set(skb);
+       if (err)
+               goto out;
+
+       if (skb->sp->len == XFRM_MAX_DEPTH)
+               goto out;
+
+       x = xfrm_state_lookup(dev_net(skb->dev), skb->mark,
+                             (xfrm_address_t *)&ip_hdr(skb)->daddr,
+                             spi, IPPROTO_ESP, AF_INET);
+       if (!x)
+               goto out;
+
+       skb->sp->xvec[skb->sp->len++] = x;
+       skb->sp->olen++;
+
+       xo = xfrm_offload(skb);
+       if (!xo) {
+               xfrm_state_put(x);
+               goto out;
+       }
+       xo->flags |= XFRM_GRO;
+
+       XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
+       XFRM_SPI_SKB_CB(skb)->family = AF_INET;
+       XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct iphdr, daddr);
+       XFRM_SPI_SKB_CB(skb)->seq = seq;
+
+       /* We don't need to handle errors from xfrm_input, it does all
+        * the error handling and frees the resources on error. */
+       xfrm_input(skb, IPPROTO_ESP, spi, -2);
+
+       return ERR_PTR(-EINPROGRESS);
+out:
+       skb_push(skb, offset);
+       NAPI_GRO_CB(skb)->same_flow = 0;
+       NAPI_GRO_CB(skb)->flush = 1;
+
+       return NULL;
+}
+
+static const struct net_offload esp4_offload = {
+       .callbacks = {
+               .gro_receive = esp4_gro_receive,
+       },
+};
+
+static int __init esp4_offload_init(void)
+{
+       return inet_add_offload(&esp4_offload, IPPROTO_ESP);
+}
+
+static void __exit esp4_offload_exit(void)
+{
+       inet_del_offload(&esp4_offload, IPPROTO_ESP);
+}
+
+module_init(esp4_offload_init);
+module_exit(esp4_offload_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Steffen Klassert <steffen.klassert@secunet.com>");
index 62e1e72db4612d0aa5c912ac7ec7aeea24123a35..1fc684111ce6afe6798bb21f5dcbcbcab53b9f6b 100644 (file)
@@ -40,6 +40,7 @@ drop:
 
 int xfrm4_transport_finish(struct sk_buff *skb, int async)
 {
+       struct xfrm_offload *xo = xfrm_offload(skb);
        struct iphdr *iph = ip_hdr(skb);
 
        iph->protocol = XFRM_MODE_SKB_CB(skb)->protocol;
@@ -53,6 +54,11 @@ int xfrm4_transport_finish(struct sk_buff *skb, int async)
        iph->tot_len = htons(skb->len);
        ip_send_check(iph);
 
+       if (xo && (xo->flags & XFRM_GRO)) {
+               skb_mac_header_rebuild(skb);
+               return 0;
+       }
+
        NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
                dev_net(skb->dev), NULL, skb, skb->dev, NULL,
                xfrm4_rcv_encap_finish);
index fd840c7d75ea9bc0214bb14dd238e3cee820521a..4acc0508c5ebc65dc392de50a207901b2ea8d305 100644 (file)
@@ -43,6 +43,7 @@ static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
 static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb)
 {
        int ihl = skb->data - skb_transport_header(skb);
+       struct xfrm_offload *xo = xfrm_offload(skb);
 
        if (skb->transport_header != skb->network_header) {
                memmove(skb_transport_header(skb),
@@ -50,7 +51,8 @@ static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb)
                skb->network_header = skb->transport_header;
        }
        ip_hdr(skb)->tot_len = htons(skb->len + ihl);
-       skb_reset_transport_header(skb);
+       if (!xo || !(xo->flags & XFRM_GRO))
+               skb_reset_transport_header(skb);
        return 0;
 }
 
index ec1267e2bd1f8c9168687513010cec7a7271ba71..b2a85cca91f7c0c5624a92a0a3800854da57dee1 100644 (file)
@@ -75,6 +75,19 @@ config INET6_ESP
 
          If unsure, say Y.
 
+config INET6_ESP_OFFLOAD
+       tristate "IPv6: ESP transformation offload"
+       depends on INET6_ESP
+       select XFRM_OFFLOAD
+       default n
+       ---help---
+         Support for ESP transformation offload. This makes sense
+         only if this system really does IPsec and want to do it
+         with high throughput. A typical desktop system does not
+         need it, even if it does IPsec.
+
+         If unsure, say N.
+
 config INET6_IPCOMP
        tristate "IPv6: IPComp transformation"
        select INET6_XFRM_TUNNEL
index a9e9fec387ce828be30af69e49bd31b71604652f..217e9ff0e24b6db62307d27ddc50f1a88c2da73e 100644 (file)
@@ -30,6 +30,7 @@ ipv6-objs += $(ipv6-y)
 
 obj-$(CONFIG_INET6_AH) += ah6.o
 obj-$(CONFIG_INET6_ESP) += esp6.o
+obj-$(CONFIG_INET6_ESP_OFFLOAD) += esp6_offload.o
 obj-$(CONFIG_INET6_IPCOMP) += ipcomp6.o
 obj-$(CONFIG_INET6_XFRM_TUNNEL) += xfrm6_tunnel.o
 obj-$(CONFIG_INET6_TUNNEL) += tunnel6.o
diff --git a/net/ipv6/esp6_offload.c b/net/ipv6/esp6_offload.c
new file mode 100644 (file)
index 0000000..d914eb9
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * IPV6 GSO/GRO offload support
+ * Linux INET implementation
+ *
+ * Copyright (C) 2016 secunet Security Networks AG
+ * Author: Steffen Klassert <steffen.klassert@secunet.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * ESP GRO support
+ */
+
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <net/protocol.h>
+#include <crypto/aead.h>
+#include <crypto/authenc.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <net/ip.h>
+#include <net/xfrm.h>
+#include <net/esp.h>
+#include <linux/scatterlist.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <net/ip6_route.h>
+#include <net/ipv6.h>
+#include <linux/icmpv6.h>
+
+static struct sk_buff **esp6_gro_receive(struct sk_buff **head,
+                                        struct sk_buff *skb)
+{
+       int offset = skb_gro_offset(skb);
+       struct xfrm_offload *xo;
+       struct xfrm_state *x;
+       __be32 seq;
+       __be32 spi;
+       int err;
+
+       skb_pull(skb, offset);
+
+       if ((err = xfrm_parse_spi(skb, IPPROTO_ESP, &spi, &seq)) != 0)
+               goto out;
+
+       err = secpath_set(skb);
+       if (err)
+               goto out;
+
+       if (skb->sp->len == XFRM_MAX_DEPTH)
+               goto out;
+
+       x = xfrm_state_lookup(dev_net(skb->dev), skb->mark,
+                             (xfrm_address_t *)&ipv6_hdr(skb)->daddr,
+                             spi, IPPROTO_ESP, AF_INET6);
+       if (!x)
+               goto out;
+
+       skb->sp->xvec[skb->sp->len++] = x;
+       skb->sp->olen++;
+
+       xo = xfrm_offload(skb);
+       if (!xo) {
+               xfrm_state_put(x);
+               goto out;
+       }
+       xo->flags |= XFRM_GRO;
+
+       XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL;
+       XFRM_SPI_SKB_CB(skb)->family = AF_INET6;
+       XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct ipv6hdr, daddr);
+       XFRM_SPI_SKB_CB(skb)->seq = seq;
+
+       /* We don't need to handle errors from xfrm_input, it does all
+        * the error handling and frees the resources on error. */
+       xfrm_input(skb, IPPROTO_ESP, spi, -2);
+
+       return ERR_PTR(-EINPROGRESS);
+out:
+       skb_push(skb, offset);
+       NAPI_GRO_CB(skb)->same_flow = 0;
+       NAPI_GRO_CB(skb)->flush = 1;
+
+       return NULL;
+}
+
+static const struct net_offload esp6_offload = {
+       .callbacks = {
+               .gro_receive = esp6_gro_receive,
+       },
+};
+
+static int __init esp6_offload_init(void)
+{
+       return inet6_add_offload(&esp6_offload, IPPROTO_ESP);
+}
+
+static void __exit esp6_offload_exit(void)
+{
+       inet6_del_offload(&esp6_offload, IPPROTO_ESP);
+}
+
+module_init(esp6_offload_init);
+module_exit(esp6_offload_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Steffen Klassert <steffen.klassert@secunet.com>");
index 662fb2c3e765d9024f720ffbeaa1f4150a6f7679..08a807b29298f5a2c14c30eaedcba87f3057431d 100644 (file)
@@ -33,6 +33,8 @@ EXPORT_SYMBOL(xfrm6_rcv_spi);
 
 int xfrm6_transport_finish(struct sk_buff *skb, int async)
 {
+       struct xfrm_offload *xo = xfrm_offload(skb);
+
        skb_network_header(skb)[IP6CB(skb)->nhoff] =
                XFRM_MODE_SKB_CB(skb)->protocol;
 
@@ -44,6 +46,11 @@ int xfrm6_transport_finish(struct sk_buff *skb, int async)
        ipv6_hdr(skb)->payload_len = htons(skb->len);
        __skb_push(skb, skb->data - skb_network_header(skb));
 
+       if (xo && (xo->flags & XFRM_GRO)) {
+               skb_mac_header_rebuild(skb);
+               return -1;
+       }
+
        NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING,
                dev_net(skb->dev), NULL, skb, skb->dev, NULL,
                ip6_rcv_finish);
index 4e344105b3fddeacfc79ca7492b62f223de2c02e..4439ee44c8b05461b8a66190c7800379ca5f105c 100644 (file)
@@ -47,6 +47,7 @@ static int xfrm6_transport_output(struct xfrm_state *x, struct sk_buff *skb)
 static int xfrm6_transport_input(struct xfrm_state *x, struct sk_buff *skb)
 {
        int ihl = skb->data - skb_transport_header(skb);
+       struct xfrm_offload *xo = xfrm_offload(skb);
 
        if (skb->transport_header != skb->network_header) {
                memmove(skb_transport_header(skb),
@@ -55,7 +56,8 @@ static int xfrm6_transport_input(struct xfrm_state *x, struct sk_buff *skb)
        }
        ipv6_hdr(skb)->payload_len = htons(skb->len + ihl -
                                           sizeof(struct ipv6hdr));
-       skb_reset_transport_header(skb);
+       if (!xo || !(xo->flags & XFRM_GRO))
+               skb_reset_transport_header(skb);
        return 0;
 }
 
index d2ff71230864c5fd64705fee1d8ae7be0477f5b1..46bdb4fbed0bb34a5d6ae40991b3fda6e5dff82c 100644 (file)
@@ -207,14 +207,23 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
        unsigned int family;
        int decaps = 0;
        int async = 0;
+       struct xfrm_offload *xo;
+       bool xfrm_gro = false;
 
-       /* A negative encap_type indicates async resumption. */
        if (encap_type < 0) {
-               async = 1;
                x = xfrm_input_state(skb);
-               seq = XFRM_SKB_CB(skb)->seq.input.low;
                family = x->outer_mode->afinfo->family;
-               goto resume;
+
+               /* An encap_type of -1 indicates async resumption. */
+               if (encap_type == -1) {
+                       async = 1;
+                       seq = XFRM_SKB_CB(skb)->seq.input.low;
+                       goto resume;
+               }
+               /* encap_type < -1 indicates a GRO call. */
+               encap_type = 0;
+               seq = XFRM_SPI_SKB_CB(skb)->seq;
+               goto lock;
        }
 
        daddr = (xfrm_address_t *)(skb_network_header(skb) +
@@ -260,6 +269,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 
                skb->sp->xvec[skb->sp->len++] = x;
 
+lock:
                spin_lock(&x->lock);
 
                if (unlikely(x->km.state != XFRM_STATE_VALID)) {
@@ -381,7 +391,18 @@ resume:
                gro_cells_receive(&gro_cells, skb);
                return 0;
        } else {
-               return x->inner_mode->afinfo->transport_finish(skb, async);
+               xo = xfrm_offload(skb);
+               if (xo)
+                       xfrm_gro = xo->flags & XFRM_GRO;
+
+               err = x->inner_mode->afinfo->transport_finish(skb, async);
+               if (xfrm_gro) {
+                       skb_dst_drop(skb);
+                       gro_cells_receive(&gro_cells, skb);
+                       return err;
+               }
+
+               return err;
        }
 
 drop_unlock: