Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* This file contains all the functions required for the standalone |
2 | ip_nat module. | |
3 | ||
4 | These are not required by the compatibility layer. | |
5 | */ | |
6 | ||
7 | /* (C) 1999-2001 Paul `Rusty' Russell | |
8 | * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | */ | |
14 | ||
15 | /* | |
16 | * 23 Apr 2001: Harald Welte <laforge@gnumonks.org> | |
17 | * - new API and handling of conntrack/nat helpers | |
18 | * - now capable of multiple expectations for one master | |
19 | * */ | |
20 | ||
1da177e4 LT |
21 | #include <linux/types.h> |
22 | #include <linux/icmp.h> | |
23 | #include <linux/ip.h> | |
24 | #include <linux/netfilter.h> | |
25 | #include <linux/netfilter_ipv4.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/skbuff.h> | |
28 | #include <linux/proc_fs.h> | |
29 | #include <net/ip.h> | |
30 | #include <net/checksum.h> | |
31 | #include <linux/spinlock.h> | |
32 | ||
1da177e4 LT |
33 | #include <linux/netfilter_ipv4/ip_nat.h> |
34 | #include <linux/netfilter_ipv4/ip_nat_rule.h> | |
35 | #include <linux/netfilter_ipv4/ip_nat_protocol.h> | |
36 | #include <linux/netfilter_ipv4/ip_nat_core.h> | |
37 | #include <linux/netfilter_ipv4/ip_nat_helper.h> | |
38 | #include <linux/netfilter_ipv4/ip_tables.h> | |
39 | #include <linux/netfilter_ipv4/ip_conntrack_core.h> | |
1da177e4 LT |
40 | |
41 | #if 0 | |
42 | #define DEBUGP printk | |
43 | #else | |
44 | #define DEBUGP(format, args...) | |
45 | #endif | |
46 | ||
eb9c7ebe PM |
47 | #ifdef CONFIG_XFRM |
48 | static void nat_decode_session(struct sk_buff *skb, struct flowi *fl) | |
49 | { | |
50 | struct ip_conntrack *ct; | |
51 | struct ip_conntrack_tuple *t; | |
52 | enum ip_conntrack_info ctinfo; | |
53 | enum ip_conntrack_dir dir; | |
54 | unsigned long statusbit; | |
55 | ||
56 | ct = ip_conntrack_get(skb, &ctinfo); | |
57 | if (ct == NULL) | |
58 | return; | |
59 | dir = CTINFO2DIR(ctinfo); | |
60 | t = &ct->tuplehash[dir].tuple; | |
61 | ||
62 | if (dir == IP_CT_DIR_ORIGINAL) | |
63 | statusbit = IPS_DST_NAT; | |
64 | else | |
65 | statusbit = IPS_SRC_NAT; | |
66 | ||
67 | if (ct->status & statusbit) { | |
68 | fl->fl4_dst = t->dst.ip; | |
69 | if (t->dst.protonum == IPPROTO_TCP || | |
70 | t->dst.protonum == IPPROTO_UDP) | |
71 | fl->fl_ip_dport = t->dst.u.tcp.port; | |
72 | } | |
73 | ||
74 | statusbit ^= IPS_NAT_MASK; | |
75 | ||
76 | if (ct->status & statusbit) { | |
77 | fl->fl4_src = t->src.ip; | |
78 | if (t->dst.protonum == IPPROTO_TCP || | |
79 | t->dst.protonum == IPPROTO_UDP) | |
80 | fl->fl_ip_sport = t->src.u.tcp.port; | |
81 | } | |
82 | } | |
83 | #endif | |
e905a9ed | 84 | |
1da177e4 LT |
85 | static unsigned int |
86 | ip_nat_fn(unsigned int hooknum, | |
87 | struct sk_buff **pskb, | |
88 | const struct net_device *in, | |
89 | const struct net_device *out, | |
90 | int (*okfn)(struct sk_buff *)) | |
91 | { | |
92 | struct ip_conntrack *ct; | |
93 | enum ip_conntrack_info ctinfo; | |
94 | struct ip_nat_info *info; | |
95 | /* maniptype == SRC for postrouting. */ | |
96 | enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum); | |
97 | ||
98 | /* We never see fragments: conntrack defrags on pre-routing | |
99 | and local-out, and ip_nat_out protects post-routing. */ | |
eddc9ec5 | 100 | IP_NF_ASSERT(!(ip_hdr(*pskb)->frag_off |
1da177e4 LT |
101 | & htons(IP_MF|IP_OFFSET))); |
102 | ||
1da177e4 LT |
103 | ct = ip_conntrack_get(*pskb, &ctinfo); |
104 | /* Can't track? It's not due to stress, or conntrack would | |
105 | have dropped it. Hence it's the user's responsibilty to | |
106 | packet filter it out, or implement conntrack/NAT for that | |
107 | protocol. 8) --RR */ | |
108 | if (!ct) { | |
109 | /* Exception: ICMP redirect to new connection (not in | |
e905a9ed YH |
110 | hash table yet). We must not let this through, in |
111 | case we're doing NAT to the same network. */ | |
eddc9ec5 | 112 | if (ip_hdr(*pskb)->protocol == IPPROTO_ICMP) { |
1da177e4 LT |
113 | struct icmphdr _hdr, *hp; |
114 | ||
c9bdd4b5 | 115 | hp = skb_header_pointer(*pskb, ip_hdrlen(*pskb), |
1da177e4 LT |
116 | sizeof(_hdr), &_hdr); |
117 | if (hp != NULL && | |
118 | hp->type == ICMP_REDIRECT) | |
119 | return NF_DROP; | |
120 | } | |
121 | return NF_ACCEPT; | |
122 | } | |
123 | ||
8b83bc77 HW |
124 | /* Don't try to NAT if this packet is not conntracked */ |
125 | if (ct == &ip_conntrack_untracked) | |
126 | return NF_ACCEPT; | |
127 | ||
1da177e4 LT |
128 | switch (ctinfo) { |
129 | case IP_CT_RELATED: | |
130 | case IP_CT_RELATED+IP_CT_IS_REPLY: | |
eddc9ec5 | 131 | if (ip_hdr(*pskb)->protocol == IPPROTO_ICMP) { |
4cf411de PM |
132 | if (!ip_nat_icmp_reply_translation(ct, ctinfo, |
133 | hooknum, pskb)) | |
1da177e4 LT |
134 | return NF_DROP; |
135 | else | |
136 | return NF_ACCEPT; | |
137 | } | |
138 | /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */ | |
139 | case IP_CT_NEW: | |
140 | info = &ct->nat.info; | |
141 | ||
142 | /* Seen it before? This can happen for loopback, retrans, | |
143 | or local packets.. */ | |
144 | if (!ip_nat_initialized(ct, maniptype)) { | |
145 | unsigned int ret; | |
146 | ||
03486a4f PM |
147 | if (unlikely(is_confirmed(ct))) |
148 | /* NAT module was loaded late */ | |
149 | ret = alloc_null_binding_confirmed(ct, info, | |
e905a9ed | 150 | hooknum); |
03486a4f PM |
151 | else if (hooknum == NF_IP_LOCAL_IN) |
152 | /* LOCAL_IN hook doesn't have a chain! */ | |
1da177e4 LT |
153 | ret = alloc_null_binding(ct, info, hooknum); |
154 | else | |
155 | ret = ip_nat_rule_find(pskb, hooknum, | |
156 | in, out, ct, | |
157 | info); | |
158 | ||
159 | if (ret != NF_ACCEPT) { | |
160 | return ret; | |
161 | } | |
162 | } else | |
163 | DEBUGP("Already setup manip %s for ct %p\n", | |
164 | maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", | |
165 | ct); | |
166 | break; | |
167 | ||
168 | default: | |
169 | /* ESTABLISHED */ | |
170 | IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED | |
171 | || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY)); | |
172 | info = &ct->nat.info; | |
173 | } | |
174 | ||
175 | IP_NF_ASSERT(info); | |
188bab3a | 176 | return ip_nat_packet(ct, ctinfo, hooknum, pskb); |
1da177e4 LT |
177 | } |
178 | ||
179 | static unsigned int | |
180 | ip_nat_in(unsigned int hooknum, | |
e905a9ed YH |
181 | struct sk_buff **pskb, |
182 | const struct net_device *in, | |
183 | const struct net_device *out, | |
184 | int (*okfn)(struct sk_buff *)) | |
1da177e4 | 185 | { |
1da177e4 | 186 | unsigned int ret; |
eddc9ec5 | 187 | __be32 daddr = ip_hdr(*pskb)->daddr; |
1da177e4 | 188 | |
1da177e4 LT |
189 | ret = ip_nat_fn(hooknum, pskb, in, out, okfn); |
190 | if (ret != NF_DROP && ret != NF_STOLEN | |
eddc9ec5 | 191 | && daddr != ip_hdr(*pskb)->daddr) { |
8e249f08 PM |
192 | dst_release((*pskb)->dst); |
193 | (*pskb)->dst = NULL; | |
1da177e4 LT |
194 | } |
195 | return ret; | |
196 | } | |
197 | ||
198 | static unsigned int | |
199 | ip_nat_out(unsigned int hooknum, | |
200 | struct sk_buff **pskb, | |
201 | const struct net_device *in, | |
202 | const struct net_device *out, | |
203 | int (*okfn)(struct sk_buff *)) | |
204 | { | |
2354feae | 205 | #ifdef CONFIG_XFRM |
5c901daa PM |
206 | struct ip_conntrack *ct; |
207 | enum ip_conntrack_info ctinfo; | |
2354feae | 208 | #endif |
5c901daa PM |
209 | unsigned int ret; |
210 | ||
1da177e4 LT |
211 | /* root is playing with raw sockets. */ |
212 | if ((*pskb)->len < sizeof(struct iphdr) | |
c9bdd4b5 | 213 | || ip_hdrlen(*pskb) < sizeof(struct iphdr)) |
1da177e4 LT |
214 | return NF_ACCEPT; |
215 | ||
5c901daa | 216 | ret = ip_nat_fn(hooknum, pskb, in, out, okfn); |
ee68cea2 | 217 | #ifdef CONFIG_XFRM |
5c901daa PM |
218 | if (ret != NF_DROP && ret != NF_STOLEN |
219 | && (ct = ip_conntrack_get(*pskb, &ctinfo)) != NULL) { | |
220 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | |
221 | ||
222 | if (ct->tuplehash[dir].tuple.src.ip != | |
223 | ct->tuplehash[!dir].tuple.dst.ip | |
5c901daa PM |
224 | || ct->tuplehash[dir].tuple.src.u.all != |
225 | ct->tuplehash[!dir].tuple.dst.u.all | |
5c901daa | 226 | ) |
ee68cea2 | 227 | return ip_xfrm_me_harder(pskb) == 0 ? ret : NF_DROP; |
5c901daa | 228 | } |
ee68cea2 | 229 | #endif |
5c901daa | 230 | return ret; |
1da177e4 LT |
231 | } |
232 | ||
233 | static unsigned int | |
234 | ip_nat_local_fn(unsigned int hooknum, | |
235 | struct sk_buff **pskb, | |
236 | const struct net_device *in, | |
237 | const struct net_device *out, | |
238 | int (*okfn)(struct sk_buff *)) | |
239 | { | |
4e8e9de7 PM |
240 | struct ip_conntrack *ct; |
241 | enum ip_conntrack_info ctinfo; | |
1da177e4 LT |
242 | unsigned int ret; |
243 | ||
244 | /* root is playing with raw sockets. */ | |
245 | if ((*pskb)->len < sizeof(struct iphdr) | |
c9bdd4b5 | 246 | || ip_hdrlen(*pskb) < sizeof(struct iphdr)) |
1da177e4 LT |
247 | return NF_ACCEPT; |
248 | ||
1da177e4 LT |
249 | ret = ip_nat_fn(hooknum, pskb, in, out, okfn); |
250 | if (ret != NF_DROP && ret != NF_STOLEN | |
4e8e9de7 PM |
251 | && (ct = ip_conntrack_get(*pskb, &ctinfo)) != NULL) { |
252 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); | |
253 | ||
254 | if (ct->tuplehash[dir].tuple.dst.ip != | |
848c29fd | 255 | ct->tuplehash[!dir].tuple.src.ip) { |
b4c4ed17 SH |
256 | if (ip_route_me_harder(pskb, RTN_UNSPEC)) |
257 | ret = NF_DROP; | |
848c29fd PM |
258 | } |
259 | #ifdef CONFIG_XFRM | |
260 | else if (ct->tuplehash[dir].tuple.dst.u.all != | |
261 | ct->tuplehash[!dir].tuple.src.u.all) | |
262 | if (ip_xfrm_me_harder(pskb)) | |
263 | ret = NF_DROP; | |
264 | #endif | |
265 | ||
4e8e9de7 | 266 | } |
1da177e4 LT |
267 | return ret; |
268 | } | |
269 | ||
e281e3ac PM |
270 | static unsigned int |
271 | ip_nat_adjust(unsigned int hooknum, | |
272 | struct sk_buff **pskb, | |
273 | const struct net_device *in, | |
274 | const struct net_device *out, | |
275 | int (*okfn)(struct sk_buff *)) | |
276 | { | |
277 | struct ip_conntrack *ct; | |
278 | enum ip_conntrack_info ctinfo; | |
279 | ||
280 | ct = ip_conntrack_get(*pskb, &ctinfo); | |
281 | if (ct && test_bit(IPS_SEQ_ADJUST_BIT, &ct->status)) { | |
e905a9ed YH |
282 | DEBUGP("ip_nat_standalone: adjusting sequence number\n"); |
283 | if (!ip_nat_seq_adjust(pskb, ct, ctinfo)) | |
284 | return NF_DROP; | |
e281e3ac PM |
285 | } |
286 | return NF_ACCEPT; | |
287 | } | |
288 | ||
1da177e4 LT |
289 | /* We must be after connection tracking and before packet filtering. */ |
290 | ||
964ddaa1 PM |
291 | static struct nf_hook_ops ip_nat_ops[] = { |
292 | /* Before packet filtering, change destination */ | |
293 | { | |
294 | .hook = ip_nat_in, | |
295 | .owner = THIS_MODULE, | |
296 | .pf = PF_INET, | |
297 | .hooknum = NF_IP_PRE_ROUTING, | |
298 | .priority = NF_IP_PRI_NAT_DST, | |
299 | }, | |
300 | /* After packet filtering, change source */ | |
301 | { | |
302 | .hook = ip_nat_out, | |
303 | .owner = THIS_MODULE, | |
304 | .pf = PF_INET, | |
305 | .hooknum = NF_IP_POST_ROUTING, | |
306 | .priority = NF_IP_PRI_NAT_SRC, | |
307 | }, | |
308 | /* After conntrack, adjust sequence number */ | |
309 | { | |
310 | .hook = ip_nat_adjust, | |
311 | .owner = THIS_MODULE, | |
312 | .pf = PF_INET, | |
313 | .hooknum = NF_IP_POST_ROUTING, | |
314 | .priority = NF_IP_PRI_NAT_SEQ_ADJUST, | |
315 | }, | |
316 | /* Before packet filtering, change destination */ | |
317 | { | |
318 | .hook = ip_nat_local_fn, | |
319 | .owner = THIS_MODULE, | |
320 | .pf = PF_INET, | |
321 | .hooknum = NF_IP_LOCAL_OUT, | |
322 | .priority = NF_IP_PRI_NAT_DST, | |
323 | }, | |
324 | /* After packet filtering, change source */ | |
325 | { | |
326 | .hook = ip_nat_fn, | |
327 | .owner = THIS_MODULE, | |
328 | .pf = PF_INET, | |
329 | .hooknum = NF_IP_LOCAL_IN, | |
330 | .priority = NF_IP_PRI_NAT_SRC, | |
331 | }, | |
332 | /* After conntrack, adjust sequence number */ | |
333 | { | |
334 | .hook = ip_nat_adjust, | |
335 | .owner = THIS_MODULE, | |
336 | .pf = PF_INET, | |
337 | .hooknum = NF_IP_LOCAL_IN, | |
338 | .priority = NF_IP_PRI_NAT_SEQ_ADJUST, | |
339 | }, | |
1da177e4 LT |
340 | }; |
341 | ||
32292a7f | 342 | static int __init ip_nat_standalone_init(void) |
1da177e4 LT |
343 | { |
344 | int ret = 0; | |
345 | ||
2e4e6a17 | 346 | need_conntrack(); |
1da177e4 | 347 | |
eb9c7ebe PM |
348 | #ifdef CONFIG_XFRM |
349 | BUG_ON(ip_nat_decode_session != NULL); | |
350 | ip_nat_decode_session = nat_decode_session; | |
351 | #endif | |
1da177e4 LT |
352 | ret = ip_nat_rule_init(); |
353 | if (ret < 0) { | |
354 | printk("ip_nat_init: can't setup rules.\n"); | |
eb9c7ebe | 355 | goto cleanup_decode_session; |
1da177e4 | 356 | } |
964ddaa1 | 357 | ret = nf_register_hooks(ip_nat_ops, ARRAY_SIZE(ip_nat_ops)); |
1da177e4 | 358 | if (ret < 0) { |
964ddaa1 | 359 | printk("ip_nat_init: can't register hooks.\n"); |
188bab3a | 360 | goto cleanup_rule_init; |
1da177e4 | 361 | } |
1da177e4 LT |
362 | return ret; |
363 | ||
1da177e4 LT |
364 | cleanup_rule_init: |
365 | ip_nat_rule_cleanup(); | |
eb9c7ebe PM |
366 | cleanup_decode_session: |
367 | #ifdef CONFIG_XFRM | |
368 | ip_nat_decode_session = NULL; | |
369 | synchronize_net(); | |
370 | #endif | |
1da177e4 LT |
371 | return ret; |
372 | } | |
373 | ||
65b4b4e8 | 374 | static void __exit ip_nat_standalone_fini(void) |
1da177e4 | 375 | { |
32292a7f PM |
376 | nf_unregister_hooks(ip_nat_ops, ARRAY_SIZE(ip_nat_ops)); |
377 | ip_nat_rule_cleanup(); | |
378 | #ifdef CONFIG_XFRM | |
379 | ip_nat_decode_session = NULL; | |
380 | synchronize_net(); | |
381 | #endif | |
1da177e4 LT |
382 | } |
383 | ||
65b4b4e8 AM |
384 | module_init(ip_nat_standalone_init); |
385 | module_exit(ip_nat_standalone_fini); | |
1da177e4 | 386 | |
1da177e4 | 387 | MODULE_LICENSE("GPL"); |