diff options
author | Patrick McHardy <kaber@trash.net> | 2012-08-26 13:14:12 -0400 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2012-08-29 21:00:17 -0400 |
commit | 58a317f1061c894d2344c0b6a18ab4a64b69b815 (patch) | |
tree | 0e8c4dd2a74cff86cedca36de278e3eb42f939bd /net/ipv6/netfilter/ip6table_nat.c | |
parent | 2cf545e835aae92173ef0b1f4af385e9c40f21e8 (diff) |
netfilter: ipv6: add IPv6 NAT support
Signed-off-by: Patrick McHardy <kaber@trash.net>
Diffstat (limited to 'net/ipv6/netfilter/ip6table_nat.c')
-rw-r--r-- | net/ipv6/netfilter/ip6table_nat.c | 321 |
1 files changed, 321 insertions, 0 deletions
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 | |||
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"); | ||