diff options
Diffstat (limited to 'net/ipv4/netfilter/iptable_nat.c')
-rw-r--r-- | net/ipv4/netfilter/iptable_nat.c | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/net/ipv4/netfilter/iptable_nat.c b/net/ipv4/netfilter/iptable_nat.c new file mode 100644 index 00000000000..9e0ffaf1d94 --- /dev/null +++ b/net/ipv4/netfilter/iptable_nat.c | |||
@@ -0,0 +1,320 @@ | |||
1 | /* (C) 1999-2001 Paul `Rusty' Russell | ||
2 | * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org> | ||
3 | * (C) 2011 Patrick McHardy <kaber@trash.net> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/netfilter.h> | ||
12 | #include <linux/netfilter_ipv4.h> | ||
13 | #include <linux/netfilter_ipv4/ip_tables.h> | ||
14 | #include <linux/ip.h> | ||
15 | #include <net/ip.h> | ||
16 | |||
17 | #include <net/netfilter/nf_nat.h> | ||
18 | #include <net/netfilter/nf_nat_core.h> | ||
19 | #include <net/netfilter/nf_nat_l3proto.h> | ||
20 | |||
21 | static const struct xt_table nf_nat_ipv4_table = { | ||
22 | .name = "nat", | ||
23 | .valid_hooks = (1 << NF_INET_PRE_ROUTING) | | ||
24 | (1 << NF_INET_POST_ROUTING) | | ||
25 | (1 << NF_INET_LOCAL_OUT) | | ||
26 | (1 << NF_INET_LOCAL_IN), | ||
27 | .me = THIS_MODULE, | ||
28 | .af = NFPROTO_IPV4, | ||
29 | }; | ||
30 | |||
31 | static unsigned int alloc_null_binding(struct nf_conn *ct, unsigned int hooknum) | ||
32 | { | ||
33 | /* Force range to this IP; let proto decide mapping for | ||
34 | * per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED). | ||
35 | */ | ||
36 | struct nf_nat_range range; | ||
37 | |||
38 | range.flags = 0; | ||
39 | pr_debug("Allocating NULL binding for %p (%pI4)\n", ct, | ||
40 | HOOK2MANIP(hooknum) == NF_NAT_MANIP_SRC ? | ||
41 | &ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip : | ||
42 | &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip); | ||
43 | |||
44 | return nf_nat_setup_info(ct, &range, HOOK2MANIP(hooknum)); | ||
45 | } | ||
46 | |||
47 | static unsigned int nf_nat_rule_find(struct sk_buff *skb, unsigned int hooknum, | ||
48 | const struct net_device *in, | ||
49 | const struct net_device *out, | ||
50 | struct nf_conn *ct) | ||
51 | { | ||
52 | struct net *net = nf_ct_net(ct); | ||
53 | unsigned int ret; | ||
54 | |||
55 | ret = ipt_do_table(skb, hooknum, in, out, net->ipv4.nat_table); | ||
56 | if (ret == NF_ACCEPT) { | ||
57 | if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum))) | ||
58 | ret = alloc_null_binding(ct, hooknum); | ||
59 | } | ||
60 | return ret; | ||
61 | } | ||
62 | |||
63 | static unsigned int | ||
64 | nf_nat_ipv4_fn(unsigned int hooknum, | ||
65 | struct sk_buff *skb, | ||
66 | const struct net_device *in, | ||
67 | const struct net_device *out, | ||
68 | int (*okfn)(struct sk_buff *)) | ||
69 | { | ||
70 | struct nf_conn *ct; | ||
71 | enum ip_conntrack_info ctinfo; | ||
72 | struct nf_conn_nat *nat; | ||
73 | /* maniptype == SRC for postrouting. */ | ||
74 | enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum); | ||
75 | |||
76 | /* We never see fragments: conntrack defrags on pre-routing | ||
77 | * and local-out, and nf_nat_out protects post-routing. | ||
78 | */ | ||
79 | NF_CT_ASSERT(!ip_is_fragment(ip_hdr(skb))); | ||
80 | |||
81 | ct = nf_ct_get(skb, &ctinfo); | ||
82 | /* Can't track? It's not due to stress, or conntrack would | ||
83 | * have dropped it. Hence it's the user's responsibilty to | ||
84 | * packet filter it out, or implement conntrack/NAT for that | ||
85 | * protocol. 8) --RR | ||
86 | */ | ||
87 | if (!ct) | ||
88 | return NF_ACCEPT; | ||
89 | |||
90 | /* Don't try to NAT if this packet is not conntracked */ | ||
91 | if (nf_ct_is_untracked(ct)) | ||
92 | return NF_ACCEPT; | ||
93 | |||
94 | nat = nfct_nat(ct); | ||
95 | if (!nat) { | ||
96 | /* NAT module was loaded late. */ | ||
97 | if (nf_ct_is_confirmed(ct)) | ||
98 | return NF_ACCEPT; | ||
99 | nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC); | ||
100 | if (nat == NULL) { | ||
101 | pr_debug("failed to add NAT extension\n"); | ||
102 | return NF_ACCEPT; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | switch (ctinfo) { | ||
107 | case IP_CT_RELATED: | ||
108 | case IP_CT_RELATED_REPLY: | ||
109 | if (ip_hdr(skb)->protocol == IPPROTO_ICMP) { | ||
110 | if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo, | ||
111 | hooknum)) | ||
112 | return NF_DROP; | ||
113 | else | ||
114 | return NF_ACCEPT; | ||
115 | } | ||
116 | /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */ | ||
117 | case IP_CT_NEW: | ||
118 | /* Seen it before? This can happen for loopback, retrans, | ||
119 | * or local packets. | ||
120 | */ | ||
121 | if (!nf_nat_initialized(ct, maniptype)) { | ||
122 | unsigned int ret; | ||
123 | |||
124 | ret = nf_nat_rule_find(skb, hooknum, in, out, ct); | ||
125 | if (ret != NF_ACCEPT) | ||
126 | return ret; | ||
127 | } else | ||
128 | pr_debug("Already setup manip %s for ct %p\n", | ||
129 | maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST", | ||
130 | ct); | ||
131 | break; | ||
132 | |||
133 | default: | ||
134 | /* ESTABLISHED */ | ||
135 | NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED || | ||
136 | ctinfo == IP_CT_ESTABLISHED_REPLY); | ||
137 | } | ||
138 | |||
139 | return nf_nat_packet(ct, ctinfo, hooknum, skb); | ||
140 | } | ||
141 | |||
142 | static unsigned int | ||
143 | nf_nat_ipv4_in(unsigned int hooknum, | ||
144 | struct sk_buff *skb, | ||
145 | const struct net_device *in, | ||
146 | const struct net_device *out, | ||
147 | int (*okfn)(struct sk_buff *)) | ||
148 | { | ||
149 | unsigned int ret; | ||
150 | __be32 daddr = ip_hdr(skb)->daddr; | ||
151 | |||
152 | ret = nf_nat_ipv4_fn(hooknum, skb, in, out, okfn); | ||
153 | if (ret != NF_DROP && ret != NF_STOLEN && | ||
154 | daddr != ip_hdr(skb)->daddr) | ||
155 | skb_dst_drop(skb); | ||
156 | |||
157 | return ret; | ||
158 | } | ||
159 | |||
160 | static unsigned int | ||
161 | nf_nat_ipv4_out(unsigned int hooknum, | ||
162 | struct sk_buff *skb, | ||
163 | const struct net_device *in, | ||
164 | const struct net_device *out, | ||
165 | int (*okfn)(struct sk_buff *)) | ||
166 | { | ||
167 | #ifdef CONFIG_XFRM | ||
168 | const struct nf_conn *ct; | ||
169 | enum ip_conntrack_info ctinfo; | ||
170 | #endif | ||
171 | unsigned int ret; | ||
172 | |||
173 | /* root is playing with raw sockets. */ | ||
174 | if (skb->len < sizeof(struct iphdr) || | ||
175 | ip_hdrlen(skb) < sizeof(struct iphdr)) | ||
176 | return NF_ACCEPT; | ||
177 | |||
178 | ret = nf_nat_ipv4_fn(hooknum, skb, in, out, okfn); | ||
179 | #ifdef CONFIG_XFRM | ||
180 | if (ret != NF_DROP && ret != NF_STOLEN && | ||
181 | !(IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) && | ||
182 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { | ||
183 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | ||
184 | |||
185 | if ((ct->tuplehash[dir].tuple.src.u3.ip != | ||
186 | ct->tuplehash[!dir].tuple.dst.u3.ip) || | ||
187 | (ct->tuplehash[dir].tuple.src.u.all != | ||
188 | ct->tuplehash[!dir].tuple.dst.u.all)) | ||
189 | if (nf_xfrm_me_harder(skb, AF_INET) < 0) | ||
190 | ret = NF_DROP; | ||
191 | } | ||
192 | #endif | ||
193 | return ret; | ||
194 | } | ||
195 | |||
196 | static unsigned int | ||
197 | nf_nat_ipv4_local_fn(unsigned int hooknum, | ||
198 | struct sk_buff *skb, | ||
199 | const struct net_device *in, | ||
200 | const struct net_device *out, | ||
201 | int (*okfn)(struct sk_buff *)) | ||
202 | { | ||
203 | const struct nf_conn *ct; | ||
204 | enum ip_conntrack_info ctinfo; | ||
205 | unsigned int ret; | ||
206 | |||
207 | /* root is playing with raw sockets. */ | ||
208 | if (skb->len < sizeof(struct iphdr) || | ||
209 | ip_hdrlen(skb) < sizeof(struct iphdr)) | ||
210 | return NF_ACCEPT; | ||
211 | |||
212 | ret = nf_nat_ipv4_fn(hooknum, skb, in, out, okfn); | ||
213 | if (ret != NF_DROP && ret != NF_STOLEN && | ||
214 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { | ||
215 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | ||
216 | |||
217 | if (ct->tuplehash[dir].tuple.dst.u3.ip != | ||
218 | ct->tuplehash[!dir].tuple.src.u3.ip) { | ||
219 | if (ip_route_me_harder(skb, RTN_UNSPEC)) | ||
220 | ret = NF_DROP; | ||
221 | } | ||
222 | #ifdef CONFIG_XFRM | ||
223 | else if (!(IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) && | ||
224 | ct->tuplehash[dir].tuple.dst.u.all != | ||
225 | ct->tuplehash[!dir].tuple.src.u.all) | ||
226 | if (nf_xfrm_me_harder(skb, AF_INET) < 0) | ||
227 | ret = NF_DROP; | ||
228 | #endif | ||
229 | } | ||
230 | return ret; | ||
231 | } | ||
232 | |||
233 | static struct nf_hook_ops nf_nat_ipv4_ops[] __read_mostly = { | ||
234 | /* Before packet filtering, change destination */ | ||
235 | { | ||
236 | .hook = nf_nat_ipv4_in, | ||
237 | .owner = THIS_MODULE, | ||
238 | .pf = NFPROTO_IPV4, | ||
239 | .hooknum = NF_INET_PRE_ROUTING, | ||
240 | .priority = NF_IP_PRI_NAT_DST, | ||
241 | }, | ||
242 | /* After packet filtering, change source */ | ||
243 | { | ||
244 | .hook = nf_nat_ipv4_out, | ||
245 | .owner = THIS_MODULE, | ||
246 | .pf = NFPROTO_IPV4, | ||
247 | .hooknum = NF_INET_POST_ROUTING, | ||
248 | .priority = NF_IP_PRI_NAT_SRC, | ||
249 | }, | ||
250 | /* Before packet filtering, change destination */ | ||
251 | { | ||
252 | .hook = nf_nat_ipv4_local_fn, | ||
253 | .owner = THIS_MODULE, | ||
254 | .pf = NFPROTO_IPV4, | ||
255 | .hooknum = NF_INET_LOCAL_OUT, | ||
256 | .priority = NF_IP_PRI_NAT_DST, | ||
257 | }, | ||
258 | /* After packet filtering, change source */ | ||
259 | { | ||
260 | .hook = nf_nat_ipv4_fn, | ||
261 | .owner = THIS_MODULE, | ||
262 | .pf = NFPROTO_IPV4, | ||
263 | .hooknum = NF_INET_LOCAL_IN, | ||
264 | .priority = NF_IP_PRI_NAT_SRC, | ||
265 | }, | ||
266 | }; | ||
267 | |||
268 | static int __net_init iptable_nat_net_init(struct net *net) | ||
269 | { | ||
270 | struct ipt_replace *repl; | ||
271 | |||
272 | repl = ipt_alloc_initial_table(&nf_nat_ipv4_table); | ||
273 | if (repl == NULL) | ||
274 | return -ENOMEM; | ||
275 | net->ipv4.nat_table = ipt_register_table(net, &nf_nat_ipv4_table, repl); | ||
276 | kfree(repl); | ||
277 | if (IS_ERR(net->ipv4.nat_table)) | ||
278 | return PTR_ERR(net->ipv4.nat_table); | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | static void __net_exit iptable_nat_net_exit(struct net *net) | ||
283 | { | ||
284 | ipt_unregister_table(net, net->ipv4.nat_table); | ||
285 | } | ||
286 | |||
287 | static struct pernet_operations iptable_nat_net_ops = { | ||
288 | .init = iptable_nat_net_init, | ||
289 | .exit = iptable_nat_net_exit, | ||
290 | }; | ||
291 | |||
292 | static int __init iptable_nat_init(void) | ||
293 | { | ||
294 | int err; | ||
295 | |||
296 | err = register_pernet_subsys(&iptable_nat_net_ops); | ||
297 | if (err < 0) | ||
298 | goto err1; | ||
299 | |||
300 | err = nf_register_hooks(nf_nat_ipv4_ops, ARRAY_SIZE(nf_nat_ipv4_ops)); | ||
301 | if (err < 0) | ||
302 | goto err2; | ||
303 | return 0; | ||
304 | |||
305 | err2: | ||
306 | unregister_pernet_subsys(&iptable_nat_net_ops); | ||
307 | err1: | ||
308 | return err; | ||
309 | } | ||
310 | |||
311 | static void __exit iptable_nat_exit(void) | ||
312 | { | ||
313 | nf_unregister_hooks(nf_nat_ipv4_ops, ARRAY_SIZE(nf_nat_ipv4_ops)); | ||
314 | unregister_pernet_subsys(&iptable_nat_net_ops); | ||
315 | } | ||
316 | |||
317 | module_init(iptable_nat_init); | ||
318 | module_exit(iptable_nat_exit); | ||
319 | |||
320 | MODULE_LICENSE("GPL"); | ||