Commit 58a317f1061c894d2344c0b6a18ab4a64b69b815
Committed by
Pablo Neira Ayuso
1 parent
2cf545e835
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
netfilter: ipv6: add IPv6 NAT support
Signed-off-by: Patrick McHardy <kaber@trash.net>
Showing 13 changed files with 764 additions and 2 deletions Side-by-side Diff
- include/linux/netfilter/nfnetlink_conntrack.h
- include/net/netfilter/nf_nat_l3proto.h
- include/net/netfilter/nf_nat_l4proto.h
- include/net/netns/ipv6.h
- net/core/secure_seq.c
- net/ipv6/netfilter/Kconfig
- net/ipv6/netfilter/Makefile
- net/ipv6/netfilter/ip6table_nat.c
- net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
- net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
- net/ipv6/netfilter/nf_nat_proto_icmpv6.c
- net/netfilter/nf_nat_core.c
- net/netfilter/xt_nat.c
include/linux/netfilter/nfnetlink_conntrack.h
include/net/netfilter/nf_nat_l3proto.h
... | ... | @@ -43,6 +43,11 @@ |
43 | 43 | struct nf_conn *ct, |
44 | 44 | enum ip_conntrack_info ctinfo, |
45 | 45 | unsigned int hooknum); |
46 | +extern int nf_nat_icmpv6_reply_translation(struct sk_buff *skb, | |
47 | + struct nf_conn *ct, | |
48 | + enum ip_conntrack_info ctinfo, | |
49 | + unsigned int hooknum, | |
50 | + unsigned int hdrlen); | |
46 | 51 | |
47 | 52 | #endif /* _NF_NAT_L3PROTO_H */ |
include/net/netfilter/nf_nat_l4proto.h
... | ... | @@ -51,6 +51,7 @@ |
51 | 51 | extern const struct nf_nat_l4proto nf_nat_l4proto_tcp; |
52 | 52 | extern const struct nf_nat_l4proto nf_nat_l4proto_udp; |
53 | 53 | extern const struct nf_nat_l4proto nf_nat_l4proto_icmp; |
54 | +extern const struct nf_nat_l4proto nf_nat_l4proto_icmpv6; | |
54 | 55 | extern const struct nf_nat_l4proto nf_nat_l4proto_unknown; |
55 | 56 | |
56 | 57 | extern bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, |
include/net/netns/ipv6.h
net/core/secure_seq.c
net/ipv6/netfilter/Kconfig
... | ... | @@ -25,6 +25,18 @@ |
25 | 25 | |
26 | 26 | To compile it as a module, choose M here. If unsure, say N. |
27 | 27 | |
28 | +config 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 | + | |
28 | 40 | config IP6_NF_IPTABLES |
29 | 41 | tristate "IP6 tables support (required for filtering)" |
30 | 42 | depends on INET && IPV6 |
net/ipv6/netfilter/Makefile
... | ... | @@ -8,12 +8,16 @@ |
8 | 8 | obj-$(CONFIG_IP6_NF_MANGLE) += ip6table_mangle.o |
9 | 9 | obj-$(CONFIG_IP6_NF_RAW) += ip6table_raw.o |
10 | 10 | obj-$(CONFIG_IP6_NF_SECURITY) += ip6table_security.o |
11 | +obj-$(CONFIG_NF_NAT_IPV6) += ip6table_nat.o | |
11 | 12 | |
12 | 13 | # objects for l3 independent conntrack |
13 | 14 | nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o |
14 | 15 | |
15 | 16 | # l3 independent conntrack |
16 | 17 | obj-$(CONFIG_NF_CONNTRACK_IPV6) += nf_conntrack_ipv6.o nf_defrag_ipv6.o |
18 | + | |
19 | +nf_nat_ipv6-y := nf_nat_l3proto_ipv6.o nf_nat_proto_icmpv6.o | |
20 | +obj-$(CONFIG_NF_NAT_IPV6) += nf_nat_ipv6.o | |
17 | 21 | |
18 | 22 | # defrag |
19 | 23 | nf_defrag_ipv6-y := nf_defrag_ipv6_hooks.o nf_conntrack_reasm.o |
net/ipv6/netfilter/ip6table_nat.c
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 | + | |
23 | +static 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 | + | |
33 | +static 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 | + | |
49 | +static 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 | + | |
65 | +static unsigned int | |
66 | +nf_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 | + | |
145 | +static unsigned int | |
146 | +nf_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 | + | |
163 | +static unsigned int | |
164 | +nf_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 | + | |
198 | +static unsigned int | |
199 | +nf_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 | + | |
234 | +static 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 | + | |
269 | +static 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 | + | |
283 | +static void __net_exit ip6table_nat_net_exit(struct net *net) | |
284 | +{ | |
285 | + ip6t_unregister_table(net, net->ipv6.ip6table_nat); | |
286 | +} | |
287 | + | |
288 | +static struct pernet_operations ip6table_nat_net_ops = { | |
289 | + .init = ip6table_nat_net_init, | |
290 | + .exit = ip6table_nat_net_exit, | |
291 | +}; | |
292 | + | |
293 | +static 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 | + | |
306 | +err2: | |
307 | + unregister_pernet_subsys(&ip6table_nat_net_ops); | |
308 | +err1: | |
309 | + return err; | |
310 | +} | |
311 | + | |
312 | +static 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 | + | |
318 | +module_init(ip6table_nat_init); | |
319 | +module_exit(ip6table_nat_exit); | |
320 | + | |
321 | +MODULE_LICENSE("GPL"); |
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
... | ... | @@ -28,6 +28,7 @@ |
28 | 28 | #include <net/netfilter/nf_conntrack_core.h> |
29 | 29 | #include <net/netfilter/nf_conntrack_zones.h> |
30 | 30 | #include <net/netfilter/ipv6/nf_conntrack_ipv6.h> |
31 | +#include <net/netfilter/nf_nat_helper.h> | |
31 | 32 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
32 | 33 | #include <net/netfilter/nf_log.h> |
33 | 34 | |
... | ... | @@ -142,6 +143,36 @@ |
142 | 143 | const struct net_device *out, |
143 | 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 | + } | |
175 | +out: | |
145 | 176 | /* We've seen it coming out the other side: confirm it */ |
146 | 177 | return nf_conntrack_confirm(skb); |
147 | 178 | } |
148 | 179 | |
... | ... | @@ -170,12 +201,14 @@ |
170 | 201 | } |
171 | 202 | |
172 | 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 | 208 | ct = nf_ct_get(reasm, &ctinfo); |
176 | 209 | if (ct != NULL && !nf_ct_is_untracked(ct)) { |
177 | 210 | help = nfct_help(ct); |
178 | - if (help && help->helper) { | |
211 | + if ((help && help->helper) || !nf_ct_is_confirmed(ct)) { | |
179 | 212 | nf_conntrack_get_reasm(skb); |
180 | 213 | NF_HOOK_THRESH(NFPROTO_IPV6, hooknum, reasm, |
181 | 214 | (struct net_device *)in, |
net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
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 | + | |
27 | +static const struct nf_nat_l3proto nf_nat_l3proto_ipv6; | |
28 | + | |
29 | +#ifdef CONFIG_XFRM | |
30 | +static 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 | + | |
63 | +static 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 | + | |
70 | +static 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 | + | |
76 | +static 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; | |
101 | +manip_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 | + | |
110 | +static 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 | + | |
129 | +static 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 | + | |
160 | +static 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 | + | |
178 | +static 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 | + | |
191 | +int 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 | +} | |
257 | +EXPORT_SYMBOL_GPL(nf_nat_icmpv6_reply_translation); | |
258 | + | |
259 | +static 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 | + | |
271 | +err2: | |
272 | + nf_nat_l4proto_unregister(NFPROTO_IPV6, &nf_nat_l4proto_icmpv6); | |
273 | +err1: | |
274 | + return err; | |
275 | +} | |
276 | + | |
277 | +static 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 | + | |
283 | +MODULE_LICENSE("GPL"); | |
284 | +MODULE_ALIAS("nf-nat-" __stringify(AF_INET6)); | |
285 | + | |
286 | +module_init(nf_nat_l3proto_ipv6_init); | |
287 | +module_exit(nf_nat_l3proto_ipv6_exit); |
net/ipv6/netfilter/nf_nat_proto_icmpv6.c
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 | + | |
22 | +static bool | |
23 | +icmpv6_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 | + | |
32 | +static void | |
33 | +icmpv6_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 | + | |
57 | +static bool | |
58 | +icmpv6_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 | + | |
82 | +const 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 | +}; |
net/netfilter/nf_nat_core.c
... | ... | @@ -696,6 +696,8 @@ |
696 | 696 | static const struct nla_policy nat_nla_policy[CTA_NAT_MAX+1] = { |
697 | 697 | [CTA_NAT_V4_MINIP] = { .type = NLA_U32 }, |
698 | 698 | [CTA_NAT_V4_MAXIP] = { .type = NLA_U32 }, |
699 | + [CTA_NAT_V6_MINIP] = { .len = sizeof(struct in6_addr) }, | |
700 | + [CTA_NAT_V6_MAXIP] = { .len = sizeof(struct in6_addr) }, | |
699 | 701 | [CTA_NAT_PROTO] = { .type = NLA_NESTED }, |
700 | 702 | }; |
701 | 703 |
net/netfilter/xt_nat.c