aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6
diff options
context:
space:
mode:
Diffstat (limited to 'net/ipv6')
-rw-r--r--net/ipv6/netfilter/Kconfig12
-rw-r--r--net/ipv6/netfilter/Makefile4
-rw-r--r--net/ipv6/netfilter/ip6table_nat.c321
-rw-r--r--net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c37
-rw-r--r--net/ipv6/netfilter/nf_nat_l3proto_ipv6.c287
-rw-r--r--net/ipv6/netfilter/nf_nat_proto_icmpv6.c90
6 files changed, 749 insertions, 2 deletions
diff --git a/net/ipv6/netfilter/Kconfig b/net/ipv6/netfilter/Kconfig
index 10135342799..b27e0ad4b73 100644
--- a/net/ipv6/netfilter/Kconfig
+++ b/net/ipv6/netfilter/Kconfig
@@ -25,6 +25,18 @@ config NF_CONNTRACK_IPV6
25 25
26 To compile it as a module, choose M here. If unsure, say N. 26 To compile it as a module, choose M here. If unsure, say N.
27 27
28config NF_NAT_IPV6
29 tristate "IPv6 NAT"
30 depends on NF_CONNTRACK_IPV6
31 depends on NETFILTER_ADVANCED
32 select NF_NAT
33 help
34 The IPv6 NAT option allows masquerading, port forwarding and other
35 forms of full Network Address Port Translation. It is controlled by
36 the `nat' table in ip6tables, see the man page for ip6tables(8).
37
38 To compile it as a module, choose M here. If unsure, say N.
39
28config IP6_NF_IPTABLES 40config IP6_NF_IPTABLES
29 tristate "IP6 tables support (required for filtering)" 41 tristate "IP6 tables support (required for filtering)"
30 depends on INET && IPV6 42 depends on INET && IPV6
diff --git a/net/ipv6/netfilter/Makefile b/net/ipv6/netfilter/Makefile
index 534d3f216f7..76779376da4 100644
--- a/net/ipv6/netfilter/Makefile
+++ b/net/ipv6/netfilter/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_IP6_NF_FILTER) += ip6table_filter.o
8obj-$(CONFIG_IP6_NF_MANGLE) += ip6table_mangle.o 8obj-$(CONFIG_IP6_NF_MANGLE) += ip6table_mangle.o
9obj-$(CONFIG_IP6_NF_RAW) += ip6table_raw.o 9obj-$(CONFIG_IP6_NF_RAW) += ip6table_raw.o
10obj-$(CONFIG_IP6_NF_SECURITY) += ip6table_security.o 10obj-$(CONFIG_IP6_NF_SECURITY) += ip6table_security.o
11obj-$(CONFIG_NF_NAT_IPV6) += ip6table_nat.o
11 12
12# objects for l3 independent conntrack 13# objects for l3 independent conntrack
13nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o 14nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
@@ -15,6 +16,9 @@ nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
15# l3 independent conntrack 16# l3 independent conntrack
16obj-$(CONFIG_NF_CONNTRACK_IPV6) += nf_conntrack_ipv6.o nf_defrag_ipv6.o 17obj-$(CONFIG_NF_CONNTRACK_IPV6) += nf_conntrack_ipv6.o nf_defrag_ipv6.o
17 18
19nf_nat_ipv6-y := nf_nat_l3proto_ipv6.o nf_nat_proto_icmpv6.o
20obj-$(CONFIG_NF_NAT_IPV6) += nf_nat_ipv6.o
21
18# defrag 22# defrag
19nf_defrag_ipv6-y := nf_defrag_ipv6_hooks.o nf_conntrack_reasm.o 23nf_defrag_ipv6-y := nf_defrag_ipv6_hooks.o nf_conntrack_reasm.o
20obj-$(CONFIG_NF_DEFRAG_IPV6) += nf_defrag_ipv6.o 24obj-$(CONFIG_NF_DEFRAG_IPV6) += nf_defrag_ipv6.o
diff --git a/net/ipv6/netfilter/ip6table_nat.c b/net/ipv6/netfilter/ip6table_nat.c
new file mode 100644
index 00000000000..e418bd6350a
--- /dev/null
+++ b/net/ipv6/netfilter/ip6table_nat.c
@@ -0,0 +1,321 @@
1/*
2 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * Based on Rusty Russell's IPv4 NAT code. Development of IPv6 NAT
9 * funded by Astaro.
10 */
11
12#include <linux/module.h>
13#include <linux/netfilter.h>
14#include <linux/netfilter_ipv6.h>
15#include <linux/netfilter_ipv6/ip6_tables.h>
16#include <linux/ipv6.h>
17#include <net/ipv6.h>
18
19#include <net/netfilter/nf_nat.h>
20#include <net/netfilter/nf_nat_core.h>
21#include <net/netfilter/nf_nat_l3proto.h>
22
23static const struct xt_table nf_nat_ipv6_table = {
24 .name = "nat",
25 .valid_hooks = (1 << NF_INET_PRE_ROUTING) |
26 (1 << NF_INET_POST_ROUTING) |
27 (1 << NF_INET_LOCAL_OUT) |
28 (1 << NF_INET_LOCAL_IN),
29 .me = THIS_MODULE,
30 .af = NFPROTO_IPV6,
31};
32
33static unsigned int alloc_null_binding(struct nf_conn *ct, unsigned int hooknum)
34{
35 /* Force range to this IP; let proto decide mapping for
36 * per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
37 */
38 struct nf_nat_range range;
39
40 range.flags = 0;
41 pr_debug("Allocating NULL binding for %p (%pI6)\n", ct,
42 HOOK2MANIP(hooknum) == NF_NAT_MANIP_SRC ?
43 &ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip6 :
44 &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip6);
45
46 return nf_nat_setup_info(ct, &range, HOOK2MANIP(hooknum));
47}
48
49static unsigned int nf_nat_rule_find(struct sk_buff *skb, unsigned int hooknum,
50 const struct net_device *in,
51 const struct net_device *out,
52 struct nf_conn *ct)
53{
54 struct net *net = nf_ct_net(ct);
55 unsigned int ret;
56
57 ret = ip6t_do_table(skb, hooknum, in, out, net->ipv6.ip6table_nat);
58 if (ret == NF_ACCEPT) {
59 if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))
60 ret = alloc_null_binding(ct, hooknum);
61 }
62 return ret;
63}
64
65static unsigned int
66nf_nat_ipv6_fn(unsigned int hooknum,
67 struct sk_buff *skb,
68 const struct net_device *in,
69 const struct net_device *out,
70 int (*okfn)(struct sk_buff *))
71{
72 struct nf_conn *ct;
73 enum ip_conntrack_info ctinfo;
74 struct nf_conn_nat *nat;
75 enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
76 __be16 frag_off;
77 int hdrlen;
78 u8 nexthdr;
79
80 ct = nf_ct_get(skb, &ctinfo);
81 /* Can't track? It's not due to stress, or conntrack would
82 * have dropped it. Hence it's the user's responsibilty to
83 * packet filter it out, or implement conntrack/NAT for that
84 * protocol. 8) --RR
85 */
86 if (!ct)
87 return NF_ACCEPT;
88
89 /* Don't try to NAT if this packet is not conntracked */
90 if (nf_ct_is_untracked(ct))
91 return NF_ACCEPT;
92
93 nat = nfct_nat(ct);
94 if (!nat) {
95 /* NAT module was loaded late. */
96 if (nf_ct_is_confirmed(ct))
97 return NF_ACCEPT;
98 nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
99 if (nat == NULL) {
100 pr_debug("failed to add NAT extension\n");
101 return NF_ACCEPT;
102 }
103 }
104
105 switch (ctinfo) {
106 case IP_CT_RELATED:
107 case IP_CT_RELATED_REPLY:
108 nexthdr = ipv6_hdr(skb)->nexthdr;
109 hdrlen = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
110 &nexthdr, &frag_off);
111
112 if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) {
113 if (!nf_nat_icmpv6_reply_translation(skb, ct, ctinfo,
114 hooknum, hdrlen))
115 return NF_DROP;
116 else
117 return NF_ACCEPT;
118 }
119 /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
120 case IP_CT_NEW:
121 /* Seen it before? This can happen for loopback, retrans,
122 * or local packets.
123 */
124 if (!nf_nat_initialized(ct, maniptype)) {
125 unsigned int ret;
126
127 ret = nf_nat_rule_find(skb, hooknum, in, out, ct);
128 if (ret != NF_ACCEPT)
129 return ret;
130 } else
131 pr_debug("Already setup manip %s for ct %p\n",
132 maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST",
133 ct);
134 break;
135
136 default:
137 /* ESTABLISHED */
138 NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
139 ctinfo == IP_CT_ESTABLISHED_REPLY);
140 }
141
142 return nf_nat_packet(ct, ctinfo, hooknum, skb);
143}
144
145static unsigned int
146nf_nat_ipv6_in(unsigned int hooknum,
147 struct sk_buff *skb,
148 const struct net_device *in,
149 const struct net_device *out,
150 int (*okfn)(struct sk_buff *))
151{
152 unsigned int ret;
153 struct in6_addr daddr = ipv6_hdr(skb)->daddr;
154
155 ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
156 if (ret != NF_DROP && ret != NF_STOLEN &&
157 ipv6_addr_cmp(&daddr, &ipv6_hdr(skb)->daddr))
158 skb_dst_drop(skb);
159
160 return ret;
161}
162
163static unsigned int
164nf_nat_ipv6_out(unsigned int hooknum,
165 struct sk_buff *skb,
166 const struct net_device *in,
167 const struct net_device *out,
168 int (*okfn)(struct sk_buff *))
169{
170#ifdef CONFIG_XFRM
171 const struct nf_conn *ct;
172 enum ip_conntrack_info ctinfo;
173#endif
174 unsigned int ret;
175
176 /* root is playing with raw sockets. */
177 if (skb->len < sizeof(struct ipv6hdr))
178 return NF_ACCEPT;
179
180 ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
181#ifdef CONFIG_XFRM
182 if (ret != NF_DROP && ret != NF_STOLEN &&
183 !(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
184 (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
185 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
186
187 if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3,
188 &ct->tuplehash[!dir].tuple.dst.u3) ||
189 (ct->tuplehash[dir].tuple.src.u.all !=
190 ct->tuplehash[!dir].tuple.dst.u.all))
191 if (nf_xfrm_me_harder(skb, AF_INET6) < 0)
192 ret = NF_DROP;
193 }
194#endif
195 return ret;
196}
197
198static unsigned int
199nf_nat_ipv6_local_fn(unsigned int hooknum,
200 struct sk_buff *skb,
201 const struct net_device *in,
202 const struct net_device *out,
203 int (*okfn)(struct sk_buff *))
204{
205 const struct nf_conn *ct;
206 enum ip_conntrack_info ctinfo;
207 unsigned int ret;
208
209 /* root is playing with raw sockets. */
210 if (skb->len < sizeof(struct ipv6hdr))
211 return NF_ACCEPT;
212
213 ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
214 if (ret != NF_DROP && ret != NF_STOLEN &&
215 (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
216 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
217
218 if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3,
219 &ct->tuplehash[!dir].tuple.src.u3)) {
220 if (ip6_route_me_harder(skb))
221 ret = NF_DROP;
222 }
223#ifdef CONFIG_XFRM
224 else if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
225 ct->tuplehash[dir].tuple.dst.u.all !=
226 ct->tuplehash[!dir].tuple.src.u.all)
227 if (nf_xfrm_me_harder(skb, AF_INET6))
228 ret = NF_DROP;
229#endif
230 }
231 return ret;
232}
233
234static struct nf_hook_ops nf_nat_ipv6_ops[] __read_mostly = {
235 /* Before packet filtering, change destination */
236 {
237 .hook = nf_nat_ipv6_in,
238 .owner = THIS_MODULE,
239 .pf = NFPROTO_IPV6,
240 .hooknum = NF_INET_PRE_ROUTING,
241 .priority = NF_IP6_PRI_NAT_DST,
242 },
243 /* After packet filtering, change source */
244 {
245 .hook = nf_nat_ipv6_out,
246 .owner = THIS_MODULE,
247 .pf = NFPROTO_IPV6,
248 .hooknum = NF_INET_POST_ROUTING,
249 .priority = NF_IP6_PRI_NAT_SRC,
250 },
251 /* Before packet filtering, change destination */
252 {
253 .hook = nf_nat_ipv6_local_fn,
254 .owner = THIS_MODULE,
255 .pf = NFPROTO_IPV6,
256 .hooknum = NF_INET_LOCAL_OUT,
257 .priority = NF_IP6_PRI_NAT_DST,
258 },
259 /* After packet filtering, change source */
260 {
261 .hook = nf_nat_ipv6_fn,
262 .owner = THIS_MODULE,
263 .pf = NFPROTO_IPV6,
264 .hooknum = NF_INET_LOCAL_IN,
265 .priority = NF_IP6_PRI_NAT_SRC,
266 },
267};
268
269static int __net_init ip6table_nat_net_init(struct net *net)
270{
271 struct ip6t_replace *repl;
272
273 repl = ip6t_alloc_initial_table(&nf_nat_ipv6_table);
274 if (repl == NULL)
275 return -ENOMEM;
276 net->ipv6.ip6table_nat = ip6t_register_table(net, &nf_nat_ipv6_table, repl);
277 kfree(repl);
278 if (IS_ERR(net->ipv6.ip6table_nat))
279 return PTR_ERR(net->ipv6.ip6table_nat);
280 return 0;
281}
282
283static void __net_exit ip6table_nat_net_exit(struct net *net)
284{
285 ip6t_unregister_table(net, net->ipv6.ip6table_nat);
286}
287
288static struct pernet_operations ip6table_nat_net_ops = {
289 .init = ip6table_nat_net_init,
290 .exit = ip6table_nat_net_exit,
291};
292
293static int __init ip6table_nat_init(void)
294{
295 int err;
296
297 err = register_pernet_subsys(&ip6table_nat_net_ops);
298 if (err < 0)
299 goto err1;
300
301 err = nf_register_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops));
302 if (err < 0)
303 goto err2;
304 return 0;
305
306err2:
307 unregister_pernet_subsys(&ip6table_nat_net_ops);
308err1:
309 return err;
310}
311
312static void __exit ip6table_nat_exit(void)
313{
314 nf_unregister_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops));
315 unregister_pernet_subsys(&ip6table_nat_net_ops);
316}
317
318module_init(ip6table_nat_init);
319module_exit(ip6table_nat_exit);
320
321MODULE_LICENSE("GPL");
diff --git a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
index dcf6010f68d..8860d23e61c 100644
--- a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
+++ b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
@@ -28,6 +28,7 @@
28#include <net/netfilter/nf_conntrack_core.h> 28#include <net/netfilter/nf_conntrack_core.h>
29#include <net/netfilter/nf_conntrack_zones.h> 29#include <net/netfilter/nf_conntrack_zones.h>
30#include <net/netfilter/ipv6/nf_conntrack_ipv6.h> 30#include <net/netfilter/ipv6/nf_conntrack_ipv6.h>
31#include <net/netfilter/nf_nat_helper.h>
31#include <net/netfilter/ipv6/nf_defrag_ipv6.h> 32#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
32#include <net/netfilter/nf_log.h> 33#include <net/netfilter/nf_log.h>
33 34
@@ -142,6 +143,36 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
142 const struct net_device *out, 143 const struct net_device *out,
143 int (*okfn)(struct sk_buff *)) 144 int (*okfn)(struct sk_buff *))
144{ 145{
146 struct nf_conn *ct;
147 enum ip_conntrack_info ctinfo;
148 unsigned char pnum = ipv6_hdr(skb)->nexthdr;
149 int protoff;
150 __be16 frag_off;
151
152 ct = nf_ct_get(skb, &ctinfo);
153 if (!ct || ctinfo == IP_CT_RELATED_REPLY)
154 goto out;
155
156 protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &pnum,
157 &frag_off);
158 if (protoff < 0 || (frag_off & htons(~0x7)) != 0) {
159 pr_debug("proto header not found\n");
160 goto out;
161 }
162
163 /* adjust seqs for loopback traffic only in outgoing direction */
164 if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
165 !nf_is_loopback_packet(skb)) {
166 typeof(nf_nat_seq_adjust_hook) seq_adjust;
167
168 seq_adjust = rcu_dereference(nf_nat_seq_adjust_hook);
169 if (!seq_adjust ||
170 !seq_adjust(skb, ct, ctinfo, protoff)) {
171 NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
172 return NF_DROP;
173 }
174 }
175out:
145 /* We've seen it coming out the other side: confirm it */ 176 /* We've seen it coming out the other side: confirm it */
146 return nf_conntrack_confirm(skb); 177 return nf_conntrack_confirm(skb);
147} 178}
@@ -170,12 +201,14 @@ static unsigned int __ipv6_conntrack_in(struct net *net,
170 } 201 }
171 202
172 /* Conntrack helpers need the entire reassembled packet in the 203 /* Conntrack helpers need the entire reassembled packet in the
173 * POST_ROUTING hook. 204 * POST_ROUTING hook. In case of unconfirmed connections NAT
205 * might reassign a helper, so the entire packet is also
206 * required.
174 */ 207 */
175 ct = nf_ct_get(reasm, &ctinfo); 208 ct = nf_ct_get(reasm, &ctinfo);
176 if (ct != NULL && !nf_ct_is_untracked(ct)) { 209 if (ct != NULL && !nf_ct_is_untracked(ct)) {
177 help = nfct_help(ct); 210 help = nfct_help(ct);
178 if (help && help->helper) { 211 if ((help && help->helper) || !nf_ct_is_confirmed(ct)) {
179 nf_conntrack_get_reasm(skb); 212 nf_conntrack_get_reasm(skb);
180 NF_HOOK_THRESH(NFPROTO_IPV6, hooknum, reasm, 213 NF_HOOK_THRESH(NFPROTO_IPV6, hooknum, reasm,
181 (struct net_device *)in, 214 (struct net_device *)in,
diff --git a/net/ipv6/netfilter/nf_nat_l3proto_ipv6.c b/net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
new file mode 100644
index 00000000000..81a2d1c3da8
--- /dev/null
+++ b/net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
@@ -0,0 +1,287 @@
1/*
2 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * Development of IPv6 NAT funded by Astaro.
9 */
10#include <linux/types.h>
11#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/ipv6.h>
14#include <linux/netfilter.h>
15#include <linux/netfilter_ipv6.h>
16#include <net/secure_seq.h>
17#include <net/checksum.h>
18#include <net/ip6_route.h>
19#include <net/ipv6.h>
20
21#include <net/netfilter/nf_conntrack_core.h>
22#include <net/netfilter/nf_conntrack.h>
23#include <net/netfilter/nf_nat_core.h>
24#include <net/netfilter/nf_nat_l3proto.h>
25#include <net/netfilter/nf_nat_l4proto.h>
26
27static const struct nf_nat_l3proto nf_nat_l3proto_ipv6;
28
29#ifdef CONFIG_XFRM
30static void nf_nat_ipv6_decode_session(struct sk_buff *skb,
31 const struct nf_conn *ct,
32 enum ip_conntrack_dir dir,
33 unsigned long statusbit,
34 struct flowi *fl)
35{
36 const struct nf_conntrack_tuple *t = &ct->tuplehash[dir].tuple;
37 struct flowi6 *fl6 = &fl->u.ip6;
38
39 if (ct->status & statusbit) {
40 fl6->daddr = t->dst.u3.in6;
41 if (t->dst.protonum == IPPROTO_TCP ||
42 t->dst.protonum == IPPROTO_UDP ||
43 t->dst.protonum == IPPROTO_UDPLITE ||
44 t->dst.protonum == IPPROTO_DCCP ||
45 t->dst.protonum == IPPROTO_SCTP)
46 fl6->fl6_dport = t->dst.u.all;
47 }
48
49 statusbit ^= IPS_NAT_MASK;
50
51 if (ct->status & statusbit) {
52 fl6->saddr = t->src.u3.in6;
53 if (t->dst.protonum == IPPROTO_TCP ||
54 t->dst.protonum == IPPROTO_UDP ||
55 t->dst.protonum == IPPROTO_UDPLITE ||
56 t->dst.protonum == IPPROTO_DCCP ||
57 t->dst.protonum == IPPROTO_SCTP)
58 fl6->fl6_sport = t->src.u.all;
59 }
60}
61#endif
62
63static bool nf_nat_ipv6_in_range(const struct nf_conntrack_tuple *t,
64 const struct nf_nat_range *range)
65{
66 return ipv6_addr_cmp(&t->src.u3.in6, &range->min_addr.in6) >= 0 &&
67 ipv6_addr_cmp(&t->src.u3.in6, &range->max_addr.in6) <= 0;
68}
69
70static u32 nf_nat_ipv6_secure_port(const struct nf_conntrack_tuple *t,
71 __be16 dport)
72{
73 return secure_ipv6_port_ephemeral(t->src.u3.ip6, t->dst.u3.ip6, dport);
74}
75
76static bool nf_nat_ipv6_manip_pkt(struct sk_buff *skb,
77 unsigned int iphdroff,
78 const struct nf_nat_l4proto *l4proto,
79 const struct nf_conntrack_tuple *target,
80 enum nf_nat_manip_type maniptype)
81{
82 struct ipv6hdr *ipv6h;
83 __be16 frag_off;
84 int hdroff;
85 u8 nexthdr;
86
87 if (!skb_make_writable(skb, iphdroff + sizeof(*ipv6h)))
88 return false;
89
90 ipv6h = (void *)skb->data + iphdroff;
91 nexthdr = ipv6h->nexthdr;
92 hdroff = ipv6_skip_exthdr(skb, iphdroff + sizeof(*ipv6h),
93 &nexthdr, &frag_off);
94 if (hdroff < 0)
95 goto manip_addr;
96
97 if ((frag_off & htons(~0x7)) == 0 &&
98 !l4proto->manip_pkt(skb, &nf_nat_l3proto_ipv6, iphdroff, hdroff,
99 target, maniptype))
100 return false;
101manip_addr:
102 if (maniptype == NF_NAT_MANIP_SRC)
103 ipv6h->saddr = target->src.u3.in6;
104 else
105 ipv6h->daddr = target->dst.u3.in6;
106
107 return true;
108}
109
110static void nf_nat_ipv6_csum_update(struct sk_buff *skb,
111 unsigned int iphdroff, __sum16 *check,
112 const struct nf_conntrack_tuple *t,
113 enum nf_nat_manip_type maniptype)
114{
115 const struct ipv6hdr *ipv6h = (struct ipv6hdr *)(skb->data + iphdroff);
116 const struct in6_addr *oldip, *newip;
117
118 if (maniptype == NF_NAT_MANIP_SRC) {
119 oldip = &ipv6h->saddr;
120 newip = &t->src.u3.in6;
121 } else {
122 oldip = &ipv6h->daddr;
123 newip = &t->dst.u3.in6;
124 }
125 inet_proto_csum_replace16(check, skb, oldip->s6_addr32,
126 newip->s6_addr32, 1);
127}
128
129static void nf_nat_ipv6_csum_recalc(struct sk_buff *skb,
130 u8 proto, void *data, __sum16 *check,
131 int datalen, int oldlen)
132{
133 const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
134 struct rt6_info *rt = (struct rt6_info *)skb_dst(skb);
135
136 if (skb->ip_summed != CHECKSUM_PARTIAL) {
137 if (!(rt->rt6i_flags & RTF_LOCAL) &&
138 (!skb->dev || skb->dev->features & NETIF_F_V6_CSUM)) {
139 skb->ip_summed = CHECKSUM_PARTIAL;
140 skb->csum_start = skb_headroom(skb) +
141 skb_network_offset(skb) +
142 (data - (void *)skb->data);
143 skb->csum_offset = (void *)check - data;
144 *check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
145 datalen, proto, 0);
146 } else {
147 *check = 0;
148 *check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
149 datalen, proto,
150 csum_partial(data, datalen,
151 0));
152 if (proto == IPPROTO_UDP && !*check)
153 *check = CSUM_MANGLED_0;
154 }
155 } else
156 inet_proto_csum_replace2(check, skb,
157 htons(oldlen), htons(datalen), 1);
158}
159
160static int nf_nat_ipv6_nlattr_to_range(struct nlattr *tb[],
161 struct nf_nat_range *range)
162{
163 if (tb[CTA_NAT_V6_MINIP]) {
164 nla_memcpy(&range->min_addr.ip6, tb[CTA_NAT_V6_MINIP],
165 sizeof(struct in6_addr));
166 range->flags |= NF_NAT_RANGE_MAP_IPS;
167 }
168
169 if (tb[CTA_NAT_V6_MAXIP])
170 nla_memcpy(&range->max_addr.ip6, tb[CTA_NAT_V6_MAXIP],
171 sizeof(struct in6_addr));
172 else
173 range->max_addr = range->min_addr;
174
175 return 0;
176}
177
178static const struct nf_nat_l3proto nf_nat_l3proto_ipv6 = {
179 .l3proto = NFPROTO_IPV6,
180 .secure_port = nf_nat_ipv6_secure_port,
181 .in_range = nf_nat_ipv6_in_range,
182 .manip_pkt = nf_nat_ipv6_manip_pkt,
183 .csum_update = nf_nat_ipv6_csum_update,
184 .csum_recalc = nf_nat_ipv6_csum_recalc,
185 .nlattr_to_range = nf_nat_ipv6_nlattr_to_range,
186#ifdef CONFIG_XFRM
187 .decode_session = nf_nat_ipv6_decode_session,
188#endif
189};
190
191int nf_nat_icmpv6_reply_translation(struct sk_buff *skb,
192 struct nf_conn *ct,
193 enum ip_conntrack_info ctinfo,
194 unsigned int hooknum,
195 unsigned int hdrlen)
196{
197 struct {
198 struct icmp6hdr icmp6;
199 struct ipv6hdr ip6;
200 } *inside;
201 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
202 enum nf_nat_manip_type manip = HOOK2MANIP(hooknum);
203 const struct nf_nat_l4proto *l4proto;
204 struct nf_conntrack_tuple target;
205 unsigned long statusbit;
206
207 NF_CT_ASSERT(ctinfo == IP_CT_RELATED || ctinfo == IP_CT_RELATED_REPLY);
208
209 if (!skb_make_writable(skb, hdrlen + sizeof(*inside)))
210 return 0;
211 if (nf_ip6_checksum(skb, hooknum, hdrlen, IPPROTO_ICMPV6))
212 return 0;
213
214 inside = (void *)skb->data + hdrlen;
215 if (inside->icmp6.icmp6_type == NDISC_REDIRECT) {
216 if ((ct->status & IPS_NAT_DONE_MASK) != IPS_NAT_DONE_MASK)
217 return 0;
218 if (ct->status & IPS_NAT_MASK)
219 return 0;
220 }
221
222 if (manip == NF_NAT_MANIP_SRC)
223 statusbit = IPS_SRC_NAT;
224 else
225 statusbit = IPS_DST_NAT;
226
227 /* Invert if this is reply direction */
228 if (dir == IP_CT_DIR_REPLY)
229 statusbit ^= IPS_NAT_MASK;
230
231 if (!(ct->status & statusbit))
232 return 1;
233
234 l4proto = __nf_nat_l4proto_find(NFPROTO_IPV6, inside->ip6.nexthdr);
235 if (!nf_nat_ipv6_manip_pkt(skb, hdrlen + sizeof(inside->icmp6),
236 l4proto, &ct->tuplehash[!dir].tuple, !manip))
237 return 0;
238
239 if (skb->ip_summed != CHECKSUM_PARTIAL) {
240 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
241 inside = (void *)skb->data + hdrlen;
242 inside->icmp6.icmp6_cksum = 0;
243 inside->icmp6.icmp6_cksum =
244 csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
245 skb->len - hdrlen, IPPROTO_ICMPV6,
246 csum_partial(&inside->icmp6,
247 skb->len - hdrlen, 0));
248 }
249
250 nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
251 l4proto = __nf_nat_l4proto_find(NFPROTO_IPV6, IPPROTO_ICMPV6);
252 if (!nf_nat_ipv6_manip_pkt(skb, 0, l4proto, &target, manip))
253 return 0;
254
255 return 1;
256}
257EXPORT_SYMBOL_GPL(nf_nat_icmpv6_reply_translation);
258
259static int __init nf_nat_l3proto_ipv6_init(void)
260{
261 int err;
262
263 err = nf_nat_l4proto_register(NFPROTO_IPV6, &nf_nat_l4proto_icmpv6);
264 if (err < 0)
265 goto err1;
266 err = nf_nat_l3proto_register(&nf_nat_l3proto_ipv6);
267 if (err < 0)
268 goto err2;
269 return err;
270
271err2:
272 nf_nat_l4proto_unregister(NFPROTO_IPV6, &nf_nat_l4proto_icmpv6);
273err1:
274 return err;
275}
276
277static void __exit nf_nat_l3proto_ipv6_exit(void)
278{
279 nf_nat_l3proto_unregister(&nf_nat_l3proto_ipv6);
280 nf_nat_l4proto_unregister(NFPROTO_IPV6, &nf_nat_l4proto_icmpv6);
281}
282
283MODULE_LICENSE("GPL");
284MODULE_ALIAS("nf-nat-" __stringify(AF_INET6));
285
286module_init(nf_nat_l3proto_ipv6_init);
287module_exit(nf_nat_l3proto_ipv6_exit);
diff --git a/net/ipv6/netfilter/nf_nat_proto_icmpv6.c b/net/ipv6/netfilter/nf_nat_proto_icmpv6.c
new file mode 100644
index 00000000000..5d6da784305
--- /dev/null
+++ b/net/ipv6/netfilter/nf_nat_proto_icmpv6.c
@@ -0,0 +1,90 @@
1/*
2 * Copyright (c) 2011 Patrick Mchardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * Based on Rusty Russell's IPv4 ICMP NAT code. Development of IPv6
9 * NAT funded by Astaro.
10 */
11
12#include <linux/types.h>
13#include <linux/init.h>
14#include <linux/icmpv6.h>
15
16#include <linux/netfilter.h>
17#include <net/netfilter/nf_nat.h>
18#include <net/netfilter/nf_nat_core.h>
19#include <net/netfilter/nf_nat_l3proto.h>
20#include <net/netfilter/nf_nat_l4proto.h>
21
22static bool
23icmpv6_in_range(const struct nf_conntrack_tuple *tuple,
24 enum nf_nat_manip_type maniptype,
25 const union nf_conntrack_man_proto *min,
26 const union nf_conntrack_man_proto *max)
27{
28 return ntohs(tuple->src.u.icmp.id) >= ntohs(min->icmp.id) &&
29 ntohs(tuple->src.u.icmp.id) <= ntohs(max->icmp.id);
30}
31
32static void
33icmpv6_unique_tuple(const struct nf_nat_l3proto *l3proto,
34 struct nf_conntrack_tuple *tuple,
35 const struct nf_nat_range *range,
36 enum nf_nat_manip_type maniptype,
37 const struct nf_conn *ct)
38{
39 static u16 id;
40 unsigned int range_size;
41 unsigned int i;
42
43 range_size = ntohs(range->max_proto.icmp.id) -
44 ntohs(range->min_proto.icmp.id) + 1;
45
46 if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED))
47 range_size = 0xffff;
48
49 for (i = 0; ; ++id) {
50 tuple->src.u.icmp.id = htons(ntohs(range->min_proto.icmp.id) +
51 (id % range_size));
52 if (++i == range_size || !nf_nat_used_tuple(tuple, ct))
53 return;
54 }
55}
56
57static bool
58icmpv6_manip_pkt(struct sk_buff *skb,
59 const struct nf_nat_l3proto *l3proto,
60 unsigned int iphdroff, unsigned int hdroff,
61 const struct nf_conntrack_tuple *tuple,
62 enum nf_nat_manip_type maniptype)
63{
64 struct icmp6hdr *hdr;
65
66 if (!skb_make_writable(skb, hdroff + sizeof(*hdr)))
67 return false;
68
69 hdr = (struct icmp6hdr *)(skb->data + hdroff);
70 l3proto->csum_update(skb, iphdroff, &hdr->icmp6_cksum,
71 tuple, maniptype);
72 if (hdr->icmp6_code == ICMPV6_ECHO_REQUEST ||
73 hdr->icmp6_code == ICMPV6_ECHO_REPLY) {
74 inet_proto_csum_replace2(&hdr->icmp6_cksum, skb,
75 hdr->icmp6_identifier,
76 tuple->src.u.icmp.id, 0);
77 hdr->icmp6_identifier = tuple->src.u.icmp.id;
78 }
79 return true;
80}
81
82const struct nf_nat_l4proto nf_nat_l4proto_icmpv6 = {
83 .l4proto = IPPROTO_ICMPV6,
84 .manip_pkt = icmpv6_manip_pkt,
85 .in_range = icmpv6_in_range,
86 .unique_tuple = icmpv6_unique_tuple,
87#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
88 .nlattr_to_range = nf_nat_l4proto_nlattr_to_range,
89#endif
90};