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

26 27
EXPORT_SYMBOL(xfrm6_find_1stfragopt);

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

	return ret;
}

46
static int xfrm6_output_one(struct sk_buff *skb)
L
Linus Torvalds 已提交
47 48 49 50
{
	struct dst_entry *dst = skb->dst;
	struct xfrm_state *x = dst->xfrm;
	int err;
51

52 53
	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		err = skb_checksum_help(skb);
L
Linus Torvalds 已提交
54 55 56 57
		if (err)
			goto error_nolock;
	}

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

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

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

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

78 79
		x->curlft.bytes += skb->len;
		x->curlft.packets++;
80
		if (x->props.mode == XFRM_MODE_ROUTEOPTIMIZATION)
81
			x->lastused = get_seconds();
L
Linus Torvalds 已提交
82

83
		spin_unlock_bh(&x->lock);
L
Linus Torvalds 已提交
84

85
		skb_reset_network_header(skb);
86

87 88 89 90 91 92
		if (!(skb->dst = dst_pop(dst))) {
			err = -EHOSTUNREACH;
			goto error_nolock;
		}
		dst = skb->dst;
		x = dst->xfrm;
93
	} while (x && (x->props.mode != XFRM_MODE_TUNNEL));
94

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

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

H
Herbert Xu 已提交
107
static int xfrm6_output_finish2(struct sk_buff *skb)
108 109 110 111 112
{
	int err;

	while (likely((err = xfrm6_output_one(skb)) == 0)) {
		nf_reset(skb);
113

114 115 116 117 118 119 120 121 122
		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 已提交
123
			      skb->dst->dev, xfrm6_output_finish2);
124 125 126 127 128 129 130
		if (unlikely(err != 1))
			break;
	}

	return err;
}

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

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

138
	skb->protocol = htons(ETH_P_IPV6);
H
Herbert Xu 已提交
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 164 165
	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;
}

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