| /* | 
 |  * xfrm_output.c - Common IPsec encapsulation code. | 
 |  * | 
 |  * Copyright (c) 2007 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/errno.h> | 
 | #include <linux/module.h> | 
 | #include <linux/netdevice.h> | 
 | #include <linux/netfilter.h> | 
 | #include <linux/skbuff.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/spinlock.h> | 
 | #include <net/dst.h> | 
 | #include <net/xfrm.h> | 
 |  | 
 | #include <net/ra_nat.h> | 
 | static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *skb); | 
 |  | 
 | static int xfrm_skb_check_space(struct sk_buff *skb) | 
 | { | 
 | 	struct dst_entry *dst = skb_dst(skb); | 
 | 	int nhead = dst->header_len + LL_RESERVED_SPACE(dst->dev) | 
 | 		- skb_headroom(skb); | 
 | 	int ntail = dst->dev->needed_tailroom - skb_tailroom(skb); | 
 |  | 
 | 	if (nhead <= 0) { | 
 | 		if (ntail <= 0) | 
 | 			return 0; | 
 | 		nhead = 0; | 
 | 	} else if (ntail < 0) | 
 | 		ntail = 0; | 
 |  | 
 | 	return pskb_expand_head(skb, nhead, ntail, GFP_ATOMIC); | 
 | } | 
 |  | 
 | /* Children define the path of the packet through the | 
 |  * Linux networking.  Thus, destinations are stackable. | 
 |  */ | 
 |  | 
 | static struct dst_entry *skb_dst_pop(struct sk_buff *skb) | 
 | { | 
 | 	struct dst_entry *child = dst_clone(xfrm_dst_child(skb_dst(skb))); | 
 |  | 
 | 	skb_dst_drop(skb); | 
 | 	return child; | 
 | } | 
 |  | 
 | static int xfrm_output_one(struct sk_buff *skb, int err) | 
 | { | 
 | 	struct dst_entry *dst = skb_dst(skb); | 
 | 	struct xfrm_state *x = dst->xfrm; | 
 | 	struct net *net = xs_net(x); | 
 |  | 
 | 	if (err <= 0) | 
 | 		goto resume; | 
 |  | 
 | 	do { | 
 | 		err = xfrm_skb_check_space(skb); | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); | 
 | 			goto error_nolock; | 
 | 		} | 
 |  | 
 | 		skb->mark = xfrm_smark_get(skb->mark, x); | 
 |  | 
 | 		err = x->outer_mode->output(x, skb); | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEMODEERROR); | 
 | 			goto error_nolock; | 
 | 		} | 
 |  | 
 | 		spin_lock_bh(&x->lock); | 
 |  | 
 | 		if (unlikely(x->km.state != XFRM_STATE_VALID)) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEINVALID); | 
 | 			err = -EINVAL; | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		err = xfrm_state_check_expire(x); | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEEXPIRED); | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		err = x->repl->overflow(x, skb); | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATESEQERROR); | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		x->curlft.bytes += skb->len; | 
 | 		x->curlft.packets++; | 
 |  | 
 | 		spin_unlock_bh(&x->lock); | 
 |  | 
 | 		hwnat_magic_tag_set_zero(skb); | 
 | 		skb_dst_force(skb); | 
 | 		if (!skb_dst(skb)) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); | 
 | 			err = -EHOSTUNREACH; | 
 | 			goto error_nolock; | 
 | 		} | 
 |  | 
 | 		if (xfrm_offload(skb)) { | 
 | 			x->type_offload->encap(x, skb); | 
 | 		} else { | 
 | 			/* Inner headers are invalid now. */ | 
 | 			skb->encapsulation = 0; | 
 |  | 
 | 			err = x->type->output(x, skb); | 
 | 			if (err == -EINPROGRESS) | 
 | 				goto out; | 
 | 		} | 
 |  | 
 | resume: | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEPROTOERROR); | 
 | 			goto error_nolock; | 
 | 		} | 
 |  | 
 | 		dst = skb_dst_pop(skb); | 
 | 		if (!dst) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); | 
 | 			err = -EHOSTUNREACH; | 
 | 			goto error_nolock; | 
 | 		} | 
 | 		skb_dst_set(skb, dst); | 
 | 		x = dst->xfrm; | 
 | 	} while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL)); | 
 |  | 
 | 	return 0; | 
 |  | 
 | error: | 
 | 	spin_unlock_bh(&x->lock); | 
 | error_nolock: | 
 | 	kfree_skb(skb); | 
 | out: | 
 | 	return err; | 
 | } | 
 |  | 
 | int xfrm_output_resume(struct sk_buff *skb, int err) | 
 | { | 
 | 	struct net *net = xs_net(skb_dst(skb)->xfrm); | 
 |  | 
 | 	while (likely((err = xfrm_output_one(skb, err)) == 0)) { | 
 | 		nf_reset(skb); | 
 |  | 
 | 		err = skb_dst(skb)->ops->local_out(net, skb->sk, skb); | 
 | 		if (unlikely(err != 1)) | 
 | 			goto out; | 
 |  | 
 | 		if (!skb_dst(skb)->xfrm) | 
 | 			return dst_output(net, skb->sk, skb); | 
 |  | 
 | 		err = nf_hook(skb_dst(skb)->ops->family, | 
 | 			      NF_INET_POST_ROUTING, net, skb->sk, skb, | 
 | 			      NULL, skb_dst(skb)->dev, xfrm_output2); | 
 | 		if (unlikely(err != 1)) | 
 | 			goto out; | 
 | 	} | 
 |  | 
 | 	if (err == -EINPROGRESS) | 
 | 		err = 0; | 
 |  | 
 | out: | 
 | 	return err; | 
 | } | 
 | EXPORT_SYMBOL_GPL(xfrm_output_resume); | 
 |  | 
 | static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *skb) | 
 | { | 
 | 	return xfrm_output_resume(skb, 1); | 
 | } | 
 |  | 
 | static int xfrm_output_gso(struct net *net, struct sock *sk, struct sk_buff *skb) | 
 | { | 
 | 	struct sk_buff *segs; | 
 |  | 
 | 	BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_SGO_CB_OFFSET); | 
 | 	BUILD_BUG_ON(sizeof(*IP6CB(skb)) > SKB_SGO_CB_OFFSET); | 
 | 	segs = skb_gso_segment(skb, 0); | 
 | 	kfree_skb(skb); | 
 | 	if (IS_ERR(segs)) | 
 | 		return PTR_ERR(segs); | 
 | 	if (segs == NULL) | 
 | 		return -EINVAL; | 
 |  | 
 | 	do { | 
 | 		struct sk_buff *nskb = segs->next; | 
 | 		int err; | 
 |  | 
 | 		segs->next = NULL; | 
 | 		err = xfrm_output2(net, sk, segs); | 
 |  | 
 | 		if (unlikely(err)) { | 
 | 			kfree_skb_list(nskb); | 
 | 			return err; | 
 | 		} | 
 |  | 
 | 		segs = nskb; | 
 | 	} while (segs); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int xfrm_output(struct sock *sk, struct sk_buff *skb) | 
 | { | 
 | 	struct net *net = dev_net(skb_dst(skb)->dev); | 
 | 	struct xfrm_state *x = skb_dst(skb)->xfrm; | 
 | 	int err; | 
 |  | 
 | 	secpath_reset(skb); | 
 |  | 
 | 	if (xfrm_dev_offload_ok(skb, x)) { | 
 | 		struct sec_path *sp; | 
 |  | 
 | 		sp = secpath_dup(skb->sp); | 
 | 		if (!sp) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); | 
 | 			kfree_skb(skb); | 
 | 			return -ENOMEM; | 
 | 		} | 
 | 		if (skb->sp) | 
 | 			secpath_put(skb->sp); | 
 | 		skb->sp = sp; | 
 | 		skb->encapsulation = 1; | 
 |  | 
 | 		sp->olen++; | 
 | 		sp->xvec[skb->sp->len++] = x; | 
 | 		xfrm_state_hold(x); | 
 |  | 
 | 		if (skb_is_gso(skb)) { | 
 | 			skb_shinfo(skb)->gso_type |= SKB_GSO_ESP; | 
 |  | 
 | 			return xfrm_output2(net, sk, skb); | 
 | 		} | 
 |  | 
 | 		if (x->xso.dev && x->xso.dev->features & NETIF_F_HW_ESP_TX_CSUM) | 
 | 			goto out; | 
 | 	} | 
 |  | 
 | 	if (skb_is_gso(skb)) | 
 | 		return xfrm_output_gso(net, sk, skb); | 
 |  | 
 | 	if (skb->ip_summed == CHECKSUM_PARTIAL) { | 
 | 		err = skb_checksum_help(skb); | 
 | 		if (err) { | 
 | 			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); | 
 | 			kfree_skb(skb); | 
 | 			return err; | 
 | 		} | 
 | 	} | 
 |  | 
 | out: | 
 | 	return xfrm_output2(net, sk, skb); | 
 | } | 
 | EXPORT_SYMBOL_GPL(xfrm_output); | 
 |  | 
 | int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb) | 
 | { | 
 | 	struct xfrm_mode *inner_mode; | 
 | 	if (x->sel.family == AF_UNSPEC) | 
 | 		inner_mode = xfrm_ip2inner_mode(x, | 
 | 				xfrm_af2proto(skb_dst(skb)->ops->family)); | 
 | 	else | 
 | 		inner_mode = x->inner_mode; | 
 |  | 
 | 	if (inner_mode == NULL) | 
 | 		return -EAFNOSUPPORT; | 
 | 	return inner_mode->afinfo->extract_output(x, skb); | 
 | } | 
 | EXPORT_SYMBOL_GPL(xfrm_inner_extract_output); | 
 |  | 
 | void xfrm_local_error(struct sk_buff *skb, int mtu) | 
 | { | 
 | 	unsigned int proto; | 
 | 	struct xfrm_state_afinfo *afinfo; | 
 |  | 
 | 	if (skb->protocol == htons(ETH_P_IP)) | 
 | 		proto = AF_INET; | 
 | 	else if (skb->protocol == htons(ETH_P_IPV6)) | 
 | 		proto = AF_INET6; | 
 | 	else | 
 | 		return; | 
 |  | 
 | 	afinfo = xfrm_state_get_afinfo(proto); | 
 | 	if (afinfo) { | 
 | 		afinfo->local_error(skb, mtu); | 
 | 		rcu_read_unlock(); | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL_GPL(xfrm_local_error); |