xfrm6_output.c 3.3 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11
/*
 * xfrm6_output.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.
 */

12
#include <linux/compiler.h>
L
Linus Torvalds 已提交
13 14 15
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/icmpv6.h>
16
#include <linux/netfilter_ipv6.h>
L
Linus Torvalds 已提交
17 18 19
#include <net/ipv6.h>
#include <net/xfrm.h>

20 21 22 23 24 25
int xfrm6_find_1stfragopt(struct xfrm_state *x, struct sk_buff *skb,
			  u8 **prevhdr)
{
	return ip6_find_1stfragopt(skb, prevhdr);
}

L
Linus Torvalds 已提交
26 27 28 29 30 31 32 33 34 35
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) {
36
		skb->dev = dst->dev;
L
Linus Torvalds 已提交
37 38 39 40 41 42 43
		icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, skb->dev);
		ret = -EMSGSIZE;
	}

	return ret;
}

44
static int xfrm6_output_one(struct sk_buff *skb)
L
Linus Torvalds 已提交
45 46 47 48 49
{
	struct dst_entry *dst = skb->dst;
	struct xfrm_state *x = dst->xfrm;
	int err;
	
50 51
	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		err = skb_checksum_help(skb);
L
Linus Torvalds 已提交
52 53 54 55
		if (err)
			goto error_nolock;
	}

56
	if (x->props.mode == XFRM_MODE_TUNNEL) {
L
Linus Torvalds 已提交
57 58 59 60 61
		err = xfrm6_tunnel_check_size(skb);
		if (err)
			goto error_nolock;
	}

62 63 64 65 66
	do {
		spin_lock_bh(&x->lock);
		err = xfrm_state_check(x, skb);
		if (err)
			goto error;
L
Linus Torvalds 已提交
67

68
		err = x->mode->output(x, skb);
69 70
		if (err)
			goto error;
L
Linus Torvalds 已提交
71

72 73 74
		err = x->type->output(x, skb);
		if (err)
			goto error;
L
Linus Torvalds 已提交
75

76 77
		x->curlft.bytes += skb->len;
		x->curlft.packets++;
78 79
		if (x->props.mode == XFRM_MODE_ROUTEOPTIMIZATION)
			x->lastused = (u64)xtime.tv_sec;
L
Linus Torvalds 已提交
80

81
		spin_unlock_bh(&x->lock);
L
Linus Torvalds 已提交
82

83 84 85 86 87 88 89 90
		skb->nh.raw = skb->data;
		
		if (!(skb->dst = dst_pop(dst))) {
			err = -EHOSTUNREACH;
			goto error_nolock;
		}
		dst = skb->dst;
		x = dst->xfrm;
91
	} while (x && (x->props.mode != XFRM_MODE_TUNNEL));
92

93
	IP6CB(skb)->flags |= IP6SKB_XFRM_TRANSFORMED;
94
	err = 0;
L
Linus Torvalds 已提交
95 96 97 98 99 100 101 102 103

out_exit:
	return err;
error:
	spin_unlock_bh(&x->lock);
error_nolock:
	kfree_skb(skb);
	goto out_exit;
}
104

H
Herbert Xu 已提交
105
static int xfrm6_output_finish2(struct sk_buff *skb)
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
{
	int err;

	while (likely((err = xfrm6_output_one(skb)) == 0)) {
		nf_reset(skb);
	
		err = nf_hook(PF_INET6, NF_IP6_LOCAL_OUT, &skb, NULL,
			      skb->dst->dev, dst_output);
		if (unlikely(err != 1))
			break;

		if (!skb->dst->xfrm)
			return dst_output(skb);

		err = nf_hook(PF_INET6, NF_IP6_POST_ROUTING, &skb, NULL,
H
Herbert Xu 已提交
121
			      skb->dst->dev, xfrm6_output_finish2);
122 123 124 125 126 127 128
		if (unlikely(err != 1))
			break;
	}

	return err;
}

H
Herbert Xu 已提交
129 130 131 132
static int xfrm6_output_finish(struct sk_buff *skb)
{
	struct sk_buff *segs;

H
Herbert Xu 已提交
133
	if (!skb_is_gso(skb))
H
Herbert Xu 已提交
134 135
		return xfrm6_output_finish2(skb);

136
	skb->protocol = htons(ETH_P_IPV6);
H
Herbert Xu 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	segs = skb_gso_segment(skb, 0);
	kfree_skb(skb);
	if (unlikely(IS_ERR(segs)))
		return PTR_ERR(segs);

	do {
		struct sk_buff *nskb = segs->next;
		int err;

		segs->next = NULL;
		err = xfrm6_output_finish2(segs);

		if (unlikely(err)) {
			while ((segs = nskb)) {
				nskb = segs->next;
				segs->next = NULL;
				kfree_skb(segs);
			}
			return err;
		}

		segs = nskb;
	} while (segs);

	return 0;
}

164 165 166 167 168
int xfrm6_output(struct sk_buff *skb)
{
	return NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, skb, NULL, skb->dst->dev,
		       xfrm6_output_finish);
}