diff options
Diffstat (limited to 'net/ipv6/xfrm6_output.c')
-rw-r--r-- | net/ipv6/xfrm6_output.c | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/net/ipv6/xfrm6_output.c b/net/ipv6/xfrm6_output.c new file mode 100644 index 000000000000..601a148f60f3 --- /dev/null +++ b/net/ipv6/xfrm6_output.c | |||
@@ -0,0 +1,143 @@ | |||
1 | /* | ||
2 | * xfrm6_output.c - Common IPsec encapsulation code for IPv6. | ||
3 | * Copyright (C) 2002 USAGI/WIDE Project | ||
4 | * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/skbuff.h> | ||
13 | #include <linux/spinlock.h> | ||
14 | #include <linux/icmpv6.h> | ||
15 | #include <net/dsfield.h> | ||
16 | #include <net/inet_ecn.h> | ||
17 | #include <net/ipv6.h> | ||
18 | #include <net/xfrm.h> | ||
19 | |||
20 | /* Add encapsulation header. | ||
21 | * | ||
22 | * In transport mode, the IP header and mutable extension headers will be moved | ||
23 | * forward to make space for the encapsulation header. | ||
24 | * | ||
25 | * In tunnel mode, the top IP header will be constructed per RFC 2401. | ||
26 | * The following fields in it shall be filled in by x->type->output: | ||
27 | * payload_len | ||
28 | * | ||
29 | * On exit, skb->h will be set to the start of the encapsulation header to be | ||
30 | * filled in by x->type->output and skb->nh will be set to the nextheader field | ||
31 | * of the extension header directly preceding the encapsulation header, or in | ||
32 | * its absence, that of the top IP header. The value of skb->data will always | ||
33 | * point to the top IP header. | ||
34 | */ | ||
35 | static void xfrm6_encap(struct sk_buff *skb) | ||
36 | { | ||
37 | struct dst_entry *dst = skb->dst; | ||
38 | struct xfrm_state *x = dst->xfrm; | ||
39 | struct ipv6hdr *iph, *top_iph; | ||
40 | int dsfield; | ||
41 | |||
42 | skb_push(skb, x->props.header_len); | ||
43 | iph = skb->nh.ipv6h; | ||
44 | |||
45 | if (!x->props.mode) { | ||
46 | u8 *prevhdr; | ||
47 | int hdr_len; | ||
48 | |||
49 | hdr_len = ip6_find_1stfragopt(skb, &prevhdr); | ||
50 | skb->nh.raw = prevhdr - x->props.header_len; | ||
51 | skb->h.raw = skb->data + hdr_len; | ||
52 | memmove(skb->data, iph, hdr_len); | ||
53 | return; | ||
54 | } | ||
55 | |||
56 | skb->nh.raw = skb->data; | ||
57 | top_iph = skb->nh.ipv6h; | ||
58 | skb->nh.raw = &top_iph->nexthdr; | ||
59 | skb->h.ipv6h = top_iph + 1; | ||
60 | |||
61 | top_iph->version = 6; | ||
62 | top_iph->priority = iph->priority; | ||
63 | top_iph->flow_lbl[0] = iph->flow_lbl[0]; | ||
64 | top_iph->flow_lbl[1] = iph->flow_lbl[1]; | ||
65 | top_iph->flow_lbl[2] = iph->flow_lbl[2]; | ||
66 | dsfield = ipv6_get_dsfield(top_iph); | ||
67 | dsfield = INET_ECN_encapsulate(dsfield, dsfield); | ||
68 | if (x->props.flags & XFRM_STATE_NOECN) | ||
69 | dsfield &= ~INET_ECN_MASK; | ||
70 | ipv6_change_dsfield(top_iph, 0, dsfield); | ||
71 | top_iph->nexthdr = IPPROTO_IPV6; | ||
72 | top_iph->hop_limit = dst_metric(dst->child, RTAX_HOPLIMIT); | ||
73 | ipv6_addr_copy(&top_iph->saddr, (struct in6_addr *)&x->props.saddr); | ||
74 | ipv6_addr_copy(&top_iph->daddr, (struct in6_addr *)&x->id.daddr); | ||
75 | } | ||
76 | |||
77 | static int xfrm6_tunnel_check_size(struct sk_buff *skb) | ||
78 | { | ||
79 | int mtu, ret = 0; | ||
80 | struct dst_entry *dst = skb->dst; | ||
81 | |||
82 | mtu = dst_mtu(dst); | ||
83 | if (mtu < IPV6_MIN_MTU) | ||
84 | mtu = IPV6_MIN_MTU; | ||
85 | |||
86 | if (skb->len > mtu) { | ||
87 | icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, skb->dev); | ||
88 | ret = -EMSGSIZE; | ||
89 | } | ||
90 | |||
91 | return ret; | ||
92 | } | ||
93 | |||
94 | int xfrm6_output(struct sk_buff *skb) | ||
95 | { | ||
96 | struct dst_entry *dst = skb->dst; | ||
97 | struct xfrm_state *x = dst->xfrm; | ||
98 | int err; | ||
99 | |||
100 | if (skb->ip_summed == CHECKSUM_HW) { | ||
101 | err = skb_checksum_help(skb, 0); | ||
102 | if (err) | ||
103 | goto error_nolock; | ||
104 | } | ||
105 | |||
106 | if (x->props.mode) { | ||
107 | err = xfrm6_tunnel_check_size(skb); | ||
108 | if (err) | ||
109 | goto error_nolock; | ||
110 | } | ||
111 | |||
112 | spin_lock_bh(&x->lock); | ||
113 | err = xfrm_state_check(x, skb); | ||
114 | if (err) | ||
115 | goto error; | ||
116 | |||
117 | xfrm6_encap(skb); | ||
118 | |||
119 | err = x->type->output(x, skb); | ||
120 | if (err) | ||
121 | goto error; | ||
122 | |||
123 | x->curlft.bytes += skb->len; | ||
124 | x->curlft.packets++; | ||
125 | |||
126 | spin_unlock_bh(&x->lock); | ||
127 | |||
128 | skb->nh.raw = skb->data; | ||
129 | |||
130 | if (!(skb->dst = dst_pop(dst))) { | ||
131 | err = -EHOSTUNREACH; | ||
132 | goto error_nolock; | ||
133 | } | ||
134 | err = NET_XMIT_BYPASS; | ||
135 | |||
136 | out_exit: | ||
137 | return err; | ||
138 | error: | ||
139 | spin_unlock_bh(&x->lock); | ||
140 | error_nolock: | ||
141 | kfree_skb(skb); | ||
142 | goto out_exit; | ||
143 | } | ||