From: Herbert Xu <herbert@gondor.apana.org.au> Date: Sun, 6 Jan 2008 16:09:35 +1100 Subject: [ipsec] add async resume support on output Message-id: E1JBNlP-0001C6-00@gondolin.me.apana.org.au O-Subject: [PATCH 20/32] [IPSEC]: Add async resume support on output Bugzilla: 253051 [IPSEC]: Add async resume support on output This patch adds support for async resumptions on output. To do so, the transform would return -EINPROGRESS and subsequently invoke the function xfrm_output_resume to resume processing. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Acked-by: "David S. Miller" <davem@redhat.com> diff --git a/include/net/xfrm.h b/include/net/xfrm.h index e98006e..b5541af 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -927,6 +927,7 @@ extern int xfrm_state_mtu(struct xfrm_state *x, int mtu); extern int xfrm_init_state(struct xfrm_state *x); extern int xfrm4_rcv(struct sk_buff *skb); extern int xfrm4_output(struct sk_buff *skb); +extern int xfrm4_output_resume(struct sk_buff *skb, int err); extern int xfrm4_tunnel_register(struct xfrm_tunnel *handler); extern int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler); extern int xfrm6_rcv_spi(struct sk_buff *skb, u32 spi); @@ -937,6 +938,7 @@ extern u32 xfrm6_tunnel_alloc_spi(xfrm_address_t *saddr); extern void xfrm6_tunnel_free_spi(xfrm_address_t *saddr); extern u32 xfrm6_tunnel_spi_lookup(xfrm_address_t *saddr); extern int xfrm6_output(struct sk_buff *skb); +extern int xfrm6_output_resume(struct sk_buff *skb, int err); #ifdef CONFIG_XFRM extern int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type); diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index f66049e..c3d9bfa 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -11,6 +11,9 @@ obj-y := route.o inetpeer.o protocol.o \ datagram.o raw.o udp.o arp.o icmp.o devinet.o af_inet.o igmp.o \ sysctl_net_ipv4.o fib_frontend.o fib_semantics.o +xfrm4_esp-objs += xfrm4_noutput.o +obj-$(CONFIG_INET_ESP) += xfrm4_esp.o + obj-$(CONFIG_IP_FIB_HASH) += fib_hash.o obj-$(CONFIG_IP_FIB_TRIE) += fib_trie.o obj-$(CONFIG_PROC_FS) += proc.o diff --git a/net/ipv4/xfrm4_noutput.c b/net/ipv4/xfrm4_noutput.c new file mode 100644 index 0000000..5b2c3dc --- /dev/null +++ b/net/ipv4/xfrm4_noutput.c @@ -0,0 +1,141 @@ +/* + * xfrm4_noutput.c - Common IPsec encapsulation code for IPv4. + * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netfilter_ipv4.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <net/icmp.h> +#include <net/ip.h> +#include <net/xfrm.h> + +static int xfrm4_output_finish2(struct sk_buff *skb); + +static int xfrm4_tunnel_check_size(struct sk_buff *skb) +{ + int mtu, ret = 0; + struct dst_entry *dst; + struct iphdr *iph = skb->nh.iph; + + if (IPCB(skb)->flags & IPSKB_XFRM_TUNNEL_SIZE) + goto out; + + IPCB(skb)->flags |= IPSKB_XFRM_TUNNEL_SIZE; + + if (!(iph->frag_off & htons(IP_DF)) || skb->local_df) + goto out; + + dst = skb->dst; + mtu = dst_mtu(dst); + if (skb->len > mtu) { + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu)); + ret = -EMSGSIZE; + } +out: + return ret; +} + +static int xfrm4_output_one(struct sk_buff *skb, int err) +{ + struct dst_entry *dst = skb->dst; + struct xfrm_state *x = dst->xfrm; + + if (skb->ip_summed == CHECKSUM_HW) { + err = skb_checksum_help(skb, 0); + if (err) + goto error_nolock; + } + + if (x->props.mode) { + err = xfrm4_tunnel_check_size(skb); + if (err) + goto error_nolock; + } + + do { + spin_lock_bh(&x->lock); + err = xfrm_state_check(x, skb); + if (err) + goto error; + + err = x->mode->output(skb); + if (err) + goto error; + + err = x->type->output(x, skb); + if (err) + goto error; + + x->curlft.bytes += skb->len; + x->curlft.packets++; + + spin_unlock_bh(&x->lock); + + if (!(skb->dst = dst_pop(dst))) { + err = -EHOSTUNREACH; + goto error_nolock; + } + dst = skb->dst; + x = dst->xfrm; + } while (x && !x->props.mode); + + IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED; + err = 0; + +out_exit: + return err; +error: + spin_unlock_bh(&x->lock); + if (err == -EINPROGRESS) + goto out_exit; +error_nolock: + kfree_skb(skb); + goto out_exit; +} + +int xfrm4_output_resume(struct sk_buff *skb, int err) +{ + while (likely((err = xfrm4_output_one(skb, err)) == 0)) { + nf_reset(skb); + + err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, &skb, NULL, + skb->dst->dev, dst_output); + if (unlikely(err != 1)) + goto out; + + if (!skb->dst->xfrm) + return dst_output(skb); + + err = nf_hook(PF_INET, NF_IP_POST_ROUTING, &skb, NULL, + skb->dst->dev, xfrm4_output_finish2); + if (unlikely(err != 1)) + goto out; + } + + if (err == -EINPROGRESS) + err = 0; + +out: + return err; +} +EXPORT_SYMBOL_GPL(xfrm4_output_resume); + +static int xfrm4_output_finish2(struct sk_buff *skb) +{ + return xfrm4_output_resume(skb, 1); +} + +static void xfrm4_noutput_exit(void) +{ +} + +module_exit(xfrm4_noutput_exit); +MODULE_LICENSE("GPL"); diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 9eebf60..31a9c61 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -16,6 +16,9 @@ ipv6-$(CONFIG_NETFILTER) += netfilter.o ipv6-$(CONFIG_IPV6_MULTIPLE_TABLES) += fib6_rules.o ipv6-objs += $(ipv6-y) +xfrm6_esp-objs += xfrm6_noutput.o +obj-$(CONFIG_INET6_ESP) += xfrm6_esp.o + obj-$(CONFIG_INET6_AH) += ah6.o obj-$(CONFIG_INET6_ESP) += esp6.o obj-$(CONFIG_INET6_IPCOMP) += ipcomp6.o diff --git a/net/ipv6/xfrm6_noutput.c b/net/ipv6/xfrm6_noutput.c new file mode 100644 index 0000000..00c9fb1 --- /dev/null +++ b/net/ipv6/xfrm6_noutput.c @@ -0,0 +1,137 @@ +/* + * xfrm6_noutput.c - Common IPsec encapsulation code for IPv6. + * Copyright (C) 2002 USAGI/WIDE Project + * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/icmpv6.h> +#include <linux/module.h> +#include <linux/netfilter_ipv6.h> +#include <net/ipv6.h> +#include <net/xfrm.h> + +static int xfrm6_output_finish2(struct sk_buff *skb); + +static int xfrm6_tunnel_check_size(struct sk_buff *skb) +{ + int mtu, ret = 0; + struct dst_entry *dst = skb->dst; + + mtu = dst_mtu(dst); + if (mtu < IPV6_MIN_MTU) + mtu = IPV6_MIN_MTU; + + if (skb->len > mtu) { + skb->dev = dst->dev; + icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, skb->dev); + ret = -EMSGSIZE; + } + + return ret; +} + +static int xfrm6_output_one(struct sk_buff *skb, int err) +{ + struct dst_entry *dst = skb->dst; + struct xfrm_state *x = dst->xfrm; + + if (skb->ip_summed == CHECKSUM_HW) { + err = skb_checksum_help(skb, 0); + if (err) + goto error_nolock; + } + + if (x->props.mode) { + err = xfrm6_tunnel_check_size(skb); + if (err) + goto error_nolock; + } + + do { + spin_lock_bh(&x->lock); + err = xfrm_state_check(x, skb); + if (err) + goto error; + + err = x->mode->output(skb); + if (err) + goto error; + + err = x->type->output(x, skb); + if (err) + goto error; + + x->curlft.bytes += skb->len; + x->curlft.packets++; + + spin_unlock_bh(&x->lock); + + skb->nh.raw = skb->data; + + if (!(skb->dst = dst_pop(dst))) { + err = -EHOSTUNREACH; + goto error_nolock; + } + dst = skb->dst; + x = dst->xfrm; + } while (x && !x->props.mode); + + IP6CB(skb)->flags |= IP6SKB_XFRM_TRANSFORMED; + err = 0; + +out_exit: + return err; +error: + spin_unlock_bh(&x->lock); + if (err == -EINPROGRESS) + goto out_exit; +error_nolock: + kfree_skb(skb); + goto out_exit; +} + +int xfrm6_output_resume(struct sk_buff *skb, int err) +{ + while (likely((err = xfrm6_output_one(skb, err)) == 0)) { + nf_reset(skb); + + err = nf_hook(PF_INET6, NF_IP6_LOCAL_OUT, &skb, NULL, + skb->dst->dev, dst_output); + if (unlikely(err != 1)) + goto out; + + if (!skb->dst->xfrm) + return dst_output(skb); + + err = nf_hook(PF_INET6, NF_IP6_POST_ROUTING, &skb, NULL, + skb->dst->dev, xfrm6_output_finish2); + if (unlikely(err != 1)) + goto out; + } + + if (err == -EINPROGRESS) + err = 0; + +out: + return err; +} +EXPORT_SYMBOL_GPL(xfrm6_output_resume); + +static int xfrm6_output_finish2(struct sk_buff *skb) +{ + return xfrm6_output_resume(skb, 1); +} + +static void xfrm6_noutput_exit(void) +{ +} + +module_exit(xfrm6_noutput_exit); +MODULE_LICENSE("GPL");