Commit | Line | Data |
---|---|---|
58a317f1 PM |
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; | |
c65ef8dc | 130 | } else { |
58a317f1 PM |
131 | pr_debug("Already setup manip %s for ct %p\n", |
132 | maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST", | |
133 | ct); | |
c65ef8dc AC |
134 | if (nf_nat_oif_changed(hooknum, ctinfo, nat, out)) |
135 | goto oif_changed; | |
136 | } | |
58a317f1 PM |
137 | break; |
138 | ||
139 | default: | |
140 | /* ESTABLISHED */ | |
141 | NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED || | |
142 | ctinfo == IP_CT_ESTABLISHED_REPLY); | |
c65ef8dc AC |
143 | if (nf_nat_oif_changed(hooknum, ctinfo, nat, out)) |
144 | goto oif_changed; | |
58a317f1 PM |
145 | } |
146 | ||
147 | return nf_nat_packet(ct, ctinfo, hooknum, skb); | |
c65ef8dc AC |
148 | |
149 | oif_changed: | |
150 | nf_ct_kill_acct(ct, ctinfo, skb); | |
151 | return NF_DROP; | |
58a317f1 PM |
152 | } |
153 | ||
154 | static unsigned int | |
155 | nf_nat_ipv6_in(unsigned int hooknum, | |
156 | struct sk_buff *skb, | |
157 | const struct net_device *in, | |
158 | const struct net_device *out, | |
159 | int (*okfn)(struct sk_buff *)) | |
160 | { | |
161 | unsigned int ret; | |
162 | struct in6_addr daddr = ipv6_hdr(skb)->daddr; | |
163 | ||
164 | ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn); | |
165 | if (ret != NF_DROP && ret != NF_STOLEN && | |
166 | ipv6_addr_cmp(&daddr, &ipv6_hdr(skb)->daddr)) | |
167 | skb_dst_drop(skb); | |
168 | ||
169 | return ret; | |
170 | } | |
171 | ||
172 | static unsigned int | |
173 | nf_nat_ipv6_out(unsigned int hooknum, | |
174 | struct sk_buff *skb, | |
175 | const struct net_device *in, | |
176 | const struct net_device *out, | |
177 | int (*okfn)(struct sk_buff *)) | |
178 | { | |
179 | #ifdef CONFIG_XFRM | |
180 | const struct nf_conn *ct; | |
181 | enum ip_conntrack_info ctinfo; | |
182 | #endif | |
183 | unsigned int ret; | |
184 | ||
185 | /* root is playing with raw sockets. */ | |
186 | if (skb->len < sizeof(struct ipv6hdr)) | |
187 | return NF_ACCEPT; | |
188 | ||
189 | ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn); | |
190 | #ifdef CONFIG_XFRM | |
191 | if (ret != NF_DROP && ret != NF_STOLEN && | |
192 | !(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) && | |
193 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { | |
194 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | |
195 | ||
196 | if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, | |
197 | &ct->tuplehash[!dir].tuple.dst.u3) || | |
38fe36a2 UW |
198 | (ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMPV6 && |
199 | ct->tuplehash[dir].tuple.src.u.all != | |
58a317f1 PM |
200 | ct->tuplehash[!dir].tuple.dst.u.all)) |
201 | if (nf_xfrm_me_harder(skb, AF_INET6) < 0) | |
202 | ret = NF_DROP; | |
203 | } | |
204 | #endif | |
205 | return ret; | |
206 | } | |
207 | ||
208 | static unsigned int | |
209 | nf_nat_ipv6_local_fn(unsigned int hooknum, | |
210 | struct sk_buff *skb, | |
211 | const struct net_device *in, | |
212 | const struct net_device *out, | |
213 | int (*okfn)(struct sk_buff *)) | |
214 | { | |
215 | const struct nf_conn *ct; | |
216 | enum ip_conntrack_info ctinfo; | |
217 | unsigned int ret; | |
218 | ||
219 | /* root is playing with raw sockets. */ | |
220 | if (skb->len < sizeof(struct ipv6hdr)) | |
221 | return NF_ACCEPT; | |
222 | ||
223 | ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn); | |
224 | if (ret != NF_DROP && ret != NF_STOLEN && | |
225 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { | |
226 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | |
227 | ||
228 | if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3, | |
229 | &ct->tuplehash[!dir].tuple.src.u3)) { | |
230 | if (ip6_route_me_harder(skb)) | |
231 | ret = NF_DROP; | |
232 | } | |
233 | #ifdef CONFIG_XFRM | |
234 | else if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) && | |
38fe36a2 | 235 | ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMPV6 && |
58a317f1 PM |
236 | ct->tuplehash[dir].tuple.dst.u.all != |
237 | ct->tuplehash[!dir].tuple.src.u.all) | |
238 | if (nf_xfrm_me_harder(skb, AF_INET6)) | |
239 | ret = NF_DROP; | |
240 | #endif | |
241 | } | |
242 | return ret; | |
243 | } | |
244 | ||
245 | static struct nf_hook_ops nf_nat_ipv6_ops[] __read_mostly = { | |
246 | /* Before packet filtering, change destination */ | |
247 | { | |
248 | .hook = nf_nat_ipv6_in, | |
249 | .owner = THIS_MODULE, | |
250 | .pf = NFPROTO_IPV6, | |
251 | .hooknum = NF_INET_PRE_ROUTING, | |
252 | .priority = NF_IP6_PRI_NAT_DST, | |
253 | }, | |
254 | /* After packet filtering, change source */ | |
255 | { | |
256 | .hook = nf_nat_ipv6_out, | |
257 | .owner = THIS_MODULE, | |
258 | .pf = NFPROTO_IPV6, | |
259 | .hooknum = NF_INET_POST_ROUTING, | |
260 | .priority = NF_IP6_PRI_NAT_SRC, | |
261 | }, | |
262 | /* Before packet filtering, change destination */ | |
263 | { | |
264 | .hook = nf_nat_ipv6_local_fn, | |
265 | .owner = THIS_MODULE, | |
266 | .pf = NFPROTO_IPV6, | |
267 | .hooknum = NF_INET_LOCAL_OUT, | |
268 | .priority = NF_IP6_PRI_NAT_DST, | |
269 | }, | |
270 | /* After packet filtering, change source */ | |
271 | { | |
272 | .hook = nf_nat_ipv6_fn, | |
273 | .owner = THIS_MODULE, | |
274 | .pf = NFPROTO_IPV6, | |
275 | .hooknum = NF_INET_LOCAL_IN, | |
276 | .priority = NF_IP6_PRI_NAT_SRC, | |
277 | }, | |
278 | }; | |
279 | ||
280 | static int __net_init ip6table_nat_net_init(struct net *net) | |
281 | { | |
282 | struct ip6t_replace *repl; | |
283 | ||
284 | repl = ip6t_alloc_initial_table(&nf_nat_ipv6_table); | |
285 | if (repl == NULL) | |
286 | return -ENOMEM; | |
287 | net->ipv6.ip6table_nat = ip6t_register_table(net, &nf_nat_ipv6_table, repl); | |
288 | kfree(repl); | |
6229b75d | 289 | return PTR_RET(net->ipv6.ip6table_nat); |
58a317f1 PM |
290 | } |
291 | ||
292 | static void __net_exit ip6table_nat_net_exit(struct net *net) | |
293 | { | |
294 | ip6t_unregister_table(net, net->ipv6.ip6table_nat); | |
295 | } | |
296 | ||
297 | static struct pernet_operations ip6table_nat_net_ops = { | |
298 | .init = ip6table_nat_net_init, | |
299 | .exit = ip6table_nat_net_exit, | |
300 | }; | |
301 | ||
302 | static int __init ip6table_nat_init(void) | |
303 | { | |
304 | int err; | |
305 | ||
306 | err = register_pernet_subsys(&ip6table_nat_net_ops); | |
307 | if (err < 0) | |
308 | goto err1; | |
309 | ||
310 | err = nf_register_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops)); | |
311 | if (err < 0) | |
312 | goto err2; | |
313 | return 0; | |
314 | ||
315 | err2: | |
316 | unregister_pernet_subsys(&ip6table_nat_net_ops); | |
317 | err1: | |
318 | return err; | |
319 | } | |
320 | ||
321 | static void __exit ip6table_nat_exit(void) | |
322 | { | |
323 | nf_unregister_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops)); | |
324 | unregister_pernet_subsys(&ip6table_nat_net_ops); | |
325 | } | |
326 | ||
327 | module_init(ip6table_nat_init); | |
328 | module_exit(ip6table_nat_exit); | |
329 | ||
330 | MODULE_LICENSE("GPL"); |