xref: /linux/net/ipv4/xfrm4_output.c (revision 2874c5fd284268364ece81a7bd936f3c8168e567)
1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * xfrm4_output.c - Common IPsec encapsulation code for IPv4.
41da177e4SLinus Torvalds  * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au>
51da177e4SLinus Torvalds  */
61da177e4SLinus Torvalds 
709b8f7a9SHerbert Xu #include <linux/if_ether.h>
809b8f7a9SHerbert Xu #include <linux/kernel.h>
936cf9acfSHerbert Xu #include <linux/module.h>
101da177e4SLinus Torvalds #include <linux/skbuff.h>
1116a6677fSPatrick McHardy #include <linux/netfilter_ipv4.h>
1236cf9acfSHerbert Xu #include <net/dst.h>
131da177e4SLinus Torvalds #include <net/ip.h>
141da177e4SLinus Torvalds #include <net/xfrm.h>
151da177e4SLinus Torvalds #include <net/icmp.h>
161da177e4SLinus Torvalds 
171da177e4SLinus Torvalds static int xfrm4_tunnel_check_size(struct sk_buff *skb)
181da177e4SLinus Torvalds {
191da177e4SLinus Torvalds 	int mtu, ret = 0;
201da177e4SLinus Torvalds 
211da177e4SLinus Torvalds 	if (IPCB(skb)->flags & IPSKB_XFRM_TUNNEL_SIZE)
221da177e4SLinus Torvalds 		goto out;
231da177e4SLinus Torvalds 
2460ff7467SWANG Cong 	if (!(ip_hdr(skb)->frag_off & htons(IP_DF)) || skb->ignore_df)
251da177e4SLinus Torvalds 		goto out;
261da177e4SLinus Torvalds 
275a25cf1eSHannes Frederic Sowa 	mtu = dst_mtu(skb_dst(skb));
28d77e38e6SSteffen Klassert 	if ((!skb_is_gso(skb) && skb->len > mtu) ||
2980f5974dSDaniel Axtens 	    (skb_is_gso(skb) &&
3080f5974dSDaniel Axtens 	     !skb_gso_validate_network_len(skb, ip_skb_dst_mtu(skb->sk, skb)))) {
31ca064bd8SSteffen Klassert 		skb->protocol = htons(ETH_P_IP);
32ca064bd8SSteffen Klassert 
33b00897b8SSteffen Klassert 		if (skb->sk)
34628e341fSHannes Frederic Sowa 			xfrm_local_error(skb, mtu);
35b00897b8SSteffen Klassert 		else
36b00897b8SSteffen Klassert 			icmp_send(skb, ICMP_DEST_UNREACH,
37b00897b8SSteffen Klassert 				  ICMP_FRAG_NEEDED, htonl(mtu));
381da177e4SLinus Torvalds 		ret = -EMSGSIZE;
391da177e4SLinus Torvalds 	}
401da177e4SLinus Torvalds out:
411da177e4SLinus Torvalds 	return ret;
421da177e4SLinus Torvalds }
431da177e4SLinus Torvalds 
4436cf9acfSHerbert Xu int xfrm4_extract_output(struct xfrm_state *x, struct sk_buff *skb)
451da177e4SLinus Torvalds {
461da177e4SLinus Torvalds 	int err;
471da177e4SLinus Torvalds 
481da177e4SLinus Torvalds 	err = xfrm4_tunnel_check_size(skb);
491da177e4SLinus Torvalds 	if (err)
5036cf9acfSHerbert Xu 		return err;
5136cf9acfSHerbert Xu 
5260d5fcfbSHerbert Xu 	XFRM_MODE_SKB_CB(skb)->protocol = ip_hdr(skb)->protocol;
5360d5fcfbSHerbert Xu 
5436cf9acfSHerbert Xu 	return xfrm4_extract_header(skb);
551da177e4SLinus Torvalds }
561da177e4SLinus Torvalds 
577026b1ddSDavid Miller int xfrm4_output_finish(struct sock *sk, struct sk_buff *skb)
5809b8f7a9SHerbert Xu {
595596732fSSteffen Klassert 	memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
60862b82c6SHerbert Xu 
615596732fSSteffen Klassert #ifdef CONFIG_NETFILTER
62862b82c6SHerbert Xu 	IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED;
6309b8f7a9SHerbert Xu #endif
6409b8f7a9SHerbert Xu 
657026b1ddSDavid Miller 	return xfrm_output(sk, skb);
6609b8f7a9SHerbert Xu }
6709b8f7a9SHerbert Xu 
680c4b51f0SEric W. Biederman static int __xfrm4_output(struct net *net, struct sock *sk, struct sk_buff *skb)
695596732fSSteffen Klassert {
705596732fSSteffen Klassert 	struct xfrm_state *x = skb_dst(skb)->xfrm;
71733a5facSFlorian Westphal 	const struct xfrm_state_afinfo *afinfo;
72733a5facSFlorian Westphal 	int ret = -EAFNOSUPPORT;
735596732fSSteffen Klassert 
745596732fSSteffen Klassert #ifdef CONFIG_NETFILTER
755596732fSSteffen Klassert 	if (!x) {
765596732fSSteffen Klassert 		IPCB(skb)->flags |= IPSKB_REROUTED;
7713206b6bSEric W. Biederman 		return dst_output(net, sk, skb);
785596732fSSteffen Klassert 	}
795596732fSSteffen Klassert #endif
805596732fSSteffen Klassert 
81733a5facSFlorian Westphal 	rcu_read_lock();
82c9500d7bSFlorian Westphal 	afinfo = xfrm_state_afinfo_get_rcu(x->outer_mode.family);
83733a5facSFlorian Westphal 	if (likely(afinfo))
84733a5facSFlorian Westphal 		ret = afinfo->output_finish(sk, skb);
85733a5facSFlorian Westphal 	else
86733a5facSFlorian Westphal 		kfree_skb(skb);
87733a5facSFlorian Westphal 	rcu_read_unlock();
88733a5facSFlorian Westphal 
89733a5facSFlorian Westphal 	return ret;
905596732fSSteffen Klassert }
915596732fSSteffen Klassert 
92ede2059dSEric W. Biederman int xfrm4_output(struct net *net, struct sock *sk, struct sk_buff *skb)
9316a6677fSPatrick McHardy {
9429a26a56SEric W. Biederman 	return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
9529a26a56SEric W. Biederman 			    net, sk, skb, NULL, skb_dst(skb)->dev,
9629a26a56SEric W. Biederman 			    __xfrm4_output,
9748d5cad8SPatrick McHardy 			    !(IPCB(skb)->flags & IPSKB_REROUTED));
9816a6677fSPatrick McHardy }
99628e341fSHannes Frederic Sowa 
100628e341fSHannes Frederic Sowa void xfrm4_local_error(struct sk_buff *skb, u32 mtu)
101628e341fSHannes Frederic Sowa {
102628e341fSHannes Frederic Sowa 	struct iphdr *hdr;
103628e341fSHannes Frederic Sowa 
104628e341fSHannes Frederic Sowa 	hdr = skb->encapsulation ? inner_ip_hdr(skb) : ip_hdr(skb);
105628e341fSHannes Frederic Sowa 	ip_local_error(skb->sk, EMSGSIZE, hdr->daddr,
106628e341fSHannes Frederic Sowa 		       inet_sk(skb->sk)->inet_dport, mtu);
107628e341fSHannes Frederic Sowa }
108