xref: /openbmc/linux/net/ipv4/xfrm4_output.c (revision 09b8f7a9)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * xfrm4_output.c - Common IPsec encapsulation code for IPv4.
31da177e4SLinus Torvalds  * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au>
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  * This program is free software; you can redistribute it and/or
61da177e4SLinus Torvalds  * modify it under the terms of the GNU General Public License
71da177e4SLinus Torvalds  * as published by the Free Software Foundation; either version
81da177e4SLinus Torvalds  * 2 of the License, or (at your option) any later version.
91da177e4SLinus Torvalds  */
101da177e4SLinus Torvalds 
1116a6677fSPatrick McHardy #include <linux/compiler.h>
1209b8f7a9SHerbert Xu #include <linux/if_ether.h>
1309b8f7a9SHerbert Xu #include <linux/kernel.h>
141da177e4SLinus Torvalds #include <linux/skbuff.h>
151da177e4SLinus Torvalds #include <linux/spinlock.h>
1616a6677fSPatrick McHardy #include <linux/netfilter_ipv4.h>
171da177e4SLinus Torvalds #include <net/ip.h>
181da177e4SLinus Torvalds #include <net/xfrm.h>
191da177e4SLinus Torvalds #include <net/icmp.h>
201da177e4SLinus Torvalds 
211da177e4SLinus Torvalds static int xfrm4_tunnel_check_size(struct sk_buff *skb)
221da177e4SLinus Torvalds {
231da177e4SLinus Torvalds 	int mtu, ret = 0;
241da177e4SLinus Torvalds 	struct dst_entry *dst;
251da177e4SLinus Torvalds 	struct iphdr *iph = skb->nh.iph;
261da177e4SLinus Torvalds 
271da177e4SLinus Torvalds 	if (IPCB(skb)->flags & IPSKB_XFRM_TUNNEL_SIZE)
281da177e4SLinus Torvalds 		goto out;
291da177e4SLinus Torvalds 
301da177e4SLinus Torvalds 	IPCB(skb)->flags |= IPSKB_XFRM_TUNNEL_SIZE;
311da177e4SLinus Torvalds 
321da177e4SLinus Torvalds 	if (!(iph->frag_off & htons(IP_DF)) || skb->local_df)
331da177e4SLinus Torvalds 		goto out;
341da177e4SLinus Torvalds 
351da177e4SLinus Torvalds 	dst = skb->dst;
361da177e4SLinus Torvalds 	mtu = dst_mtu(dst);
371da177e4SLinus Torvalds 	if (skb->len > mtu) {
381da177e4SLinus Torvalds 		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
391da177e4SLinus Torvalds 		ret = -EMSGSIZE;
401da177e4SLinus Torvalds 	}
411da177e4SLinus Torvalds out:
421da177e4SLinus Torvalds 	return ret;
431da177e4SLinus Torvalds }
441da177e4SLinus Torvalds 
4516a6677fSPatrick McHardy static int xfrm4_output_one(struct sk_buff *skb)
461da177e4SLinus Torvalds {
471da177e4SLinus Torvalds 	struct dst_entry *dst = skb->dst;
481da177e4SLinus Torvalds 	struct xfrm_state *x = dst->xfrm;
491da177e4SLinus Torvalds 	int err;
501da177e4SLinus Torvalds 
511da177e4SLinus Torvalds 	if (skb->ip_summed == CHECKSUM_HW) {
521da177e4SLinus Torvalds 		err = skb_checksum_help(skb, 0);
531da177e4SLinus Torvalds 		if (err)
541da177e4SLinus Torvalds 			goto error_nolock;
551da177e4SLinus Torvalds 	}
561da177e4SLinus Torvalds 
571da177e4SLinus Torvalds 	if (x->props.mode) {
581da177e4SLinus Torvalds 		err = xfrm4_tunnel_check_size(skb);
591da177e4SLinus Torvalds 		if (err)
601da177e4SLinus Torvalds 			goto error_nolock;
611da177e4SLinus Torvalds 	}
621da177e4SLinus Torvalds 
6316a6677fSPatrick McHardy 	do {
641da177e4SLinus Torvalds 		spin_lock_bh(&x->lock);
651da177e4SLinus Torvalds 		err = xfrm_state_check(x, skb);
661da177e4SLinus Torvalds 		if (err)
671da177e4SLinus Torvalds 			goto error;
681da177e4SLinus Torvalds 
69b59f45d0SHerbert Xu 		err = x->mode->output(skb);
70b59f45d0SHerbert Xu 		if (err)
71b59f45d0SHerbert Xu 			goto error;
721da177e4SLinus Torvalds 
731da177e4SLinus Torvalds 		err = x->type->output(x, skb);
741da177e4SLinus Torvalds 		if (err)
751da177e4SLinus Torvalds 			goto error;
761da177e4SLinus Torvalds 
771da177e4SLinus Torvalds 		x->curlft.bytes += skb->len;
781da177e4SLinus Torvalds 		x->curlft.packets++;
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds 		spin_unlock_bh(&x->lock);
811da177e4SLinus Torvalds 
821da177e4SLinus Torvalds 		if (!(skb->dst = dst_pop(dst))) {
831da177e4SLinus Torvalds 			err = -EHOSTUNREACH;
841da177e4SLinus Torvalds 			goto error_nolock;
851da177e4SLinus Torvalds 		}
8616a6677fSPatrick McHardy 		dst = skb->dst;
8716a6677fSPatrick McHardy 		x = dst->xfrm;
8816a6677fSPatrick McHardy 	} while (x && !x->props.mode);
8916a6677fSPatrick McHardy 
903e3850e9SPatrick McHardy 	IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED;
9116a6677fSPatrick McHardy 	err = 0;
921da177e4SLinus Torvalds 
931da177e4SLinus Torvalds out_exit:
941da177e4SLinus Torvalds 	return err;
951da177e4SLinus Torvalds error:
961da177e4SLinus Torvalds 	spin_unlock_bh(&x->lock);
971da177e4SLinus Torvalds error_nolock:
981da177e4SLinus Torvalds 	kfree_skb(skb);
991da177e4SLinus Torvalds 	goto out_exit;
1001da177e4SLinus Torvalds }
10116a6677fSPatrick McHardy 
10209b8f7a9SHerbert Xu static int xfrm4_output_finish2(struct sk_buff *skb)
10316a6677fSPatrick McHardy {
10416a6677fSPatrick McHardy 	int err;
10516a6677fSPatrick McHardy 
10616a6677fSPatrick McHardy 	while (likely((err = xfrm4_output_one(skb)) == 0)) {
10716a6677fSPatrick McHardy 		nf_reset(skb);
10816a6677fSPatrick McHardy 
10916a6677fSPatrick McHardy 		err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, &skb, NULL,
11016a6677fSPatrick McHardy 			      skb->dst->dev, dst_output);
11116a6677fSPatrick McHardy 		if (unlikely(err != 1))
11216a6677fSPatrick McHardy 			break;
11316a6677fSPatrick McHardy 
11416a6677fSPatrick McHardy 		if (!skb->dst->xfrm)
11516a6677fSPatrick McHardy 			return dst_output(skb);
11616a6677fSPatrick McHardy 
11716a6677fSPatrick McHardy 		err = nf_hook(PF_INET, NF_IP_POST_ROUTING, &skb, NULL,
11809b8f7a9SHerbert Xu 			      skb->dst->dev, xfrm4_output_finish2);
11916a6677fSPatrick McHardy 		if (unlikely(err != 1))
12016a6677fSPatrick McHardy 			break;
12116a6677fSPatrick McHardy 	}
12216a6677fSPatrick McHardy 
12316a6677fSPatrick McHardy 	return err;
12416a6677fSPatrick McHardy }
12516a6677fSPatrick McHardy 
12609b8f7a9SHerbert Xu static int xfrm4_output_finish(struct sk_buff *skb)
12709b8f7a9SHerbert Xu {
12809b8f7a9SHerbert Xu 	struct sk_buff *segs;
12909b8f7a9SHerbert Xu 
13009b8f7a9SHerbert Xu #ifdef CONFIG_NETFILTER
13109b8f7a9SHerbert Xu 	if (!skb->dst->xfrm) {
13209b8f7a9SHerbert Xu 		IPCB(skb)->flags |= IPSKB_REROUTED;
13309b8f7a9SHerbert Xu 		return dst_output(skb);
13409b8f7a9SHerbert Xu 	}
13509b8f7a9SHerbert Xu #endif
13609b8f7a9SHerbert Xu 
13709b8f7a9SHerbert Xu 	if (!skb_shinfo(skb)->gso_size)
13809b8f7a9SHerbert Xu 		return xfrm4_output_finish2(skb);
13909b8f7a9SHerbert Xu 
14009b8f7a9SHerbert Xu 	skb->protocol = htons(ETH_P_IP);
14109b8f7a9SHerbert Xu 	segs = skb_gso_segment(skb, 0);
14209b8f7a9SHerbert Xu 	kfree_skb(skb);
14309b8f7a9SHerbert Xu 	if (unlikely(IS_ERR(segs)))
14409b8f7a9SHerbert Xu 		return PTR_ERR(segs);
14509b8f7a9SHerbert Xu 
14609b8f7a9SHerbert Xu 	do {
14709b8f7a9SHerbert Xu 		struct sk_buff *nskb = segs->next;
14809b8f7a9SHerbert Xu 		int err;
14909b8f7a9SHerbert Xu 
15009b8f7a9SHerbert Xu 		segs->next = NULL;
15109b8f7a9SHerbert Xu 		err = xfrm4_output_finish2(segs);
15209b8f7a9SHerbert Xu 
15309b8f7a9SHerbert Xu 		if (unlikely(err)) {
15409b8f7a9SHerbert Xu 			while ((segs = nskb)) {
15509b8f7a9SHerbert Xu 				nskb = segs->next;
15609b8f7a9SHerbert Xu 				segs->next = NULL;
15709b8f7a9SHerbert Xu 				kfree_skb(segs);
15809b8f7a9SHerbert Xu 			}
15909b8f7a9SHerbert Xu 			return err;
16009b8f7a9SHerbert Xu 		}
16109b8f7a9SHerbert Xu 
16209b8f7a9SHerbert Xu 		segs = nskb;
16309b8f7a9SHerbert Xu 	} while (segs);
16409b8f7a9SHerbert Xu 
16509b8f7a9SHerbert Xu 	return 0;
16609b8f7a9SHerbert Xu }
16709b8f7a9SHerbert Xu 
16816a6677fSPatrick McHardy int xfrm4_output(struct sk_buff *skb)
16916a6677fSPatrick McHardy {
17048d5cad8SPatrick McHardy 	return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev,
17148d5cad8SPatrick McHardy 			    xfrm4_output_finish,
17248d5cad8SPatrick McHardy 			    !(IPCB(skb)->flags & IPSKB_REROUTED));
17316a6677fSPatrick McHardy }
174