]>
Commit | Line | Data |
---|---|---|
5b1158e9 JK |
1 | /* (C) 1999-2001 Paul `Rusty' Russell |
2 | * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org> | |
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 | #include <linux/types.h> | |
9 | #include <linux/icmp.h> | |
10 | #include <linux/ip.h> | |
11 | #include <linux/netfilter.h> | |
12 | #include <linux/netfilter_ipv4.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/skbuff.h> | |
15 | #include <linux/proc_fs.h> | |
16 | #include <net/ip.h> | |
17 | #include <net/checksum.h> | |
18 | #include <linux/spinlock.h> | |
19 | ||
20 | #include <net/netfilter/nf_conntrack.h> | |
21 | #include <net/netfilter/nf_conntrack_core.h> | |
2d59e5ca | 22 | #include <net/netfilter/nf_conntrack_extend.h> |
5b1158e9 JK |
23 | #include <net/netfilter/nf_nat.h> |
24 | #include <net/netfilter/nf_nat_rule.h> | |
25 | #include <net/netfilter/nf_nat_protocol.h> | |
26 | #include <net/netfilter/nf_nat_core.h> | |
27 | #include <net/netfilter/nf_nat_helper.h> | |
28 | #include <linux/netfilter_ipv4/ip_tables.h> | |
29 | ||
5b1158e9 JK |
30 | #ifdef CONFIG_XFRM |
31 | static void nat_decode_session(struct sk_buff *skb, struct flowi *fl) | |
32 | { | |
33 | struct nf_conn *ct; | |
34 | struct nf_conntrack_tuple *t; | |
35 | enum ip_conntrack_info ctinfo; | |
36 | enum ip_conntrack_dir dir; | |
37 | unsigned long statusbit; | |
38 | ||
39 | ct = nf_ct_get(skb, &ctinfo); | |
40 | if (ct == NULL) | |
41 | return; | |
42 | dir = CTINFO2DIR(ctinfo); | |
43 | t = &ct->tuplehash[dir].tuple; | |
44 | ||
45 | if (dir == IP_CT_DIR_ORIGINAL) | |
46 | statusbit = IPS_DST_NAT; | |
47 | else | |
48 | statusbit = IPS_SRC_NAT; | |
49 | ||
50 | if (ct->status & statusbit) { | |
51 | fl->fl4_dst = t->dst.u3.ip; | |
52 | if (t->dst.protonum == IPPROTO_TCP || | |
53 | t->dst.protonum == IPPROTO_UDP) | |
54 | fl->fl_ip_dport = t->dst.u.tcp.port; | |
55 | } | |
56 | ||
57 | statusbit ^= IPS_NAT_MASK; | |
58 | ||
59 | if (ct->status & statusbit) { | |
60 | fl->fl4_src = t->src.u3.ip; | |
61 | if (t->dst.protonum == IPPROTO_TCP || | |
62 | t->dst.protonum == IPPROTO_UDP) | |
63 | fl->fl_ip_sport = t->src.u.tcp.port; | |
64 | } | |
65 | } | |
66 | #endif | |
67 | ||
68 | static unsigned int | |
69 | nf_nat_fn(unsigned int hooknum, | |
3db05fea | 70 | struct sk_buff *skb, |
5b1158e9 JK |
71 | const struct net_device *in, |
72 | const struct net_device *out, | |
73 | int (*okfn)(struct sk_buff *)) | |
74 | { | |
75 | struct nf_conn *ct; | |
76 | enum ip_conntrack_info ctinfo; | |
77 | struct nf_conn_nat *nat; | |
5b1158e9 JK |
78 | /* maniptype == SRC for postrouting. */ |
79 | enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum); | |
80 | ||
81 | /* We never see fragments: conntrack defrags on pre-routing | |
82 | and local-out, and nf_nat_out protects post-routing. */ | |
3db05fea | 83 | NF_CT_ASSERT(!(ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET))); |
5b1158e9 | 84 | |
3db05fea | 85 | ct = nf_ct_get(skb, &ctinfo); |
5b1158e9 JK |
86 | /* Can't track? It's not due to stress, or conntrack would |
87 | have dropped it. Hence it's the user's responsibilty to | |
88 | packet filter it out, or implement conntrack/NAT for that | |
89 | protocol. 8) --RR */ | |
90 | if (!ct) { | |
91 | /* Exception: ICMP redirect to new connection (not in | |
e905a9ed YH |
92 | hash table yet). We must not let this through, in |
93 | case we're doing NAT to the same network. */ | |
3db05fea | 94 | if (ip_hdr(skb)->protocol == IPPROTO_ICMP) { |
5b1158e9 JK |
95 | struct icmphdr _hdr, *hp; |
96 | ||
3db05fea | 97 | hp = skb_header_pointer(skb, ip_hdrlen(skb), |
5b1158e9 JK |
98 | sizeof(_hdr), &_hdr); |
99 | if (hp != NULL && | |
100 | hp->type == ICMP_REDIRECT) | |
101 | return NF_DROP; | |
102 | } | |
103 | return NF_ACCEPT; | |
104 | } | |
105 | ||
106 | /* Don't try to NAT if this packet is not conntracked */ | |
107 | if (ct == &nf_conntrack_untracked) | |
108 | return NF_ACCEPT; | |
109 | ||
110 | nat = nfct_nat(ct); | |
2d59e5ca YK |
111 | if (!nat) { |
112 | nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC); | |
113 | if (nat == NULL) { | |
0d53778e | 114 | pr_debug("failed to add NAT extension\n"); |
2d59e5ca YK |
115 | return NF_ACCEPT; |
116 | } | |
117 | } | |
5b1158e9 JK |
118 | |
119 | switch (ctinfo) { | |
120 | case IP_CT_RELATED: | |
121 | case IP_CT_RELATED+IP_CT_IS_REPLY: | |
3db05fea | 122 | if (ip_hdr(skb)->protocol == IPPROTO_ICMP) { |
5b1158e9 | 123 | if (!nf_nat_icmp_reply_translation(ct, ctinfo, |
3db05fea | 124 | hooknum, skb)) |
5b1158e9 JK |
125 | return NF_DROP; |
126 | else | |
127 | return NF_ACCEPT; | |
128 | } | |
129 | /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */ | |
130 | case IP_CT_NEW: | |
5b1158e9 JK |
131 | |
132 | /* Seen it before? This can happen for loopback, retrans, | |
133 | or local packets.. */ | |
134 | if (!nf_nat_initialized(ct, maniptype)) { | |
135 | unsigned int ret; | |
136 | ||
137 | if (unlikely(nf_ct_is_confirmed(ct))) | |
138 | /* NAT module was loaded late */ | |
ba4c7cba | 139 | ret = alloc_null_binding_confirmed(ct, hooknum); |
5b1158e9 JK |
140 | else if (hooknum == NF_IP_LOCAL_IN) |
141 | /* LOCAL_IN hook doesn't have a chain! */ | |
ba4c7cba | 142 | ret = alloc_null_binding(ct, hooknum); |
5b1158e9 | 143 | else |
3db05fea | 144 | ret = nf_nat_rule_find(skb, hooknum, in, out, |
ba4c7cba | 145 | ct); |
5b1158e9 JK |
146 | |
147 | if (ret != NF_ACCEPT) { | |
148 | return ret; | |
149 | } | |
150 | } else | |
0d53778e PM |
151 | pr_debug("Already setup manip %s for ct %p\n", |
152 | maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", | |
153 | ct); | |
5b1158e9 JK |
154 | break; |
155 | ||
156 | default: | |
157 | /* ESTABLISHED */ | |
158 | NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED || | |
159 | ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY)); | |
5b1158e9 JK |
160 | } |
161 | ||
3db05fea | 162 | return nf_nat_packet(ct, ctinfo, hooknum, skb); |
5b1158e9 JK |
163 | } |
164 | ||
165 | static unsigned int | |
166 | nf_nat_in(unsigned int hooknum, | |
3db05fea | 167 | struct sk_buff *skb, |
e905a9ed YH |
168 | const struct net_device *in, |
169 | const struct net_device *out, | |
170 | int (*okfn)(struct sk_buff *)) | |
5b1158e9 JK |
171 | { |
172 | unsigned int ret; | |
3db05fea | 173 | __be32 daddr = ip_hdr(skb)->daddr; |
5b1158e9 | 174 | |
3db05fea | 175 | ret = nf_nat_fn(hooknum, skb, in, out, okfn); |
5b1158e9 | 176 | if (ret != NF_DROP && ret != NF_STOLEN && |
3db05fea HX |
177 | daddr != ip_hdr(skb)->daddr) { |
178 | dst_release(skb->dst); | |
179 | skb->dst = NULL; | |
5b1158e9 JK |
180 | } |
181 | return ret; | |
182 | } | |
183 | ||
184 | static unsigned int | |
185 | nf_nat_out(unsigned int hooknum, | |
3db05fea | 186 | struct sk_buff *skb, |
5b1158e9 JK |
187 | const struct net_device *in, |
188 | const struct net_device *out, | |
189 | int (*okfn)(struct sk_buff *)) | |
190 | { | |
191 | #ifdef CONFIG_XFRM | |
192 | struct nf_conn *ct; | |
193 | enum ip_conntrack_info ctinfo; | |
194 | #endif | |
195 | unsigned int ret; | |
196 | ||
197 | /* root is playing with raw sockets. */ | |
3db05fea HX |
198 | if (skb->len < sizeof(struct iphdr) || |
199 | ip_hdrlen(skb) < sizeof(struct iphdr)) | |
5b1158e9 JK |
200 | return NF_ACCEPT; |
201 | ||
3db05fea | 202 | ret = nf_nat_fn(hooknum, skb, in, out, okfn); |
5b1158e9 JK |
203 | #ifdef CONFIG_XFRM |
204 | if (ret != NF_DROP && ret != NF_STOLEN && | |
3db05fea | 205 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { |
5b1158e9 JK |
206 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
207 | ||
208 | if (ct->tuplehash[dir].tuple.src.u3.ip != | |
209 | ct->tuplehash[!dir].tuple.dst.u3.ip | |
210 | || ct->tuplehash[dir].tuple.src.u.all != | |
211 | ct->tuplehash[!dir].tuple.dst.u.all | |
212 | ) | |
3db05fea | 213 | return ip_xfrm_me_harder(skb) == 0 ? ret : NF_DROP; |
5b1158e9 JK |
214 | } |
215 | #endif | |
216 | return ret; | |
217 | } | |
218 | ||
219 | static unsigned int | |
220 | nf_nat_local_fn(unsigned int hooknum, | |
3db05fea | 221 | struct sk_buff *skb, |
5b1158e9 JK |
222 | const struct net_device *in, |
223 | const struct net_device *out, | |
224 | int (*okfn)(struct sk_buff *)) | |
225 | { | |
226 | struct nf_conn *ct; | |
227 | enum ip_conntrack_info ctinfo; | |
228 | unsigned int ret; | |
229 | ||
230 | /* root is playing with raw sockets. */ | |
3db05fea HX |
231 | if (skb->len < sizeof(struct iphdr) || |
232 | ip_hdrlen(skb) < sizeof(struct iphdr)) | |
5b1158e9 JK |
233 | return NF_ACCEPT; |
234 | ||
3db05fea | 235 | ret = nf_nat_fn(hooknum, skb, in, out, okfn); |
5b1158e9 | 236 | if (ret != NF_DROP && ret != NF_STOLEN && |
3db05fea | 237 | (ct = nf_ct_get(skb, &ctinfo)) != NULL) { |
5b1158e9 JK |
238 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
239 | ||
240 | if (ct->tuplehash[dir].tuple.dst.u3.ip != | |
848c29fd | 241 | ct->tuplehash[!dir].tuple.src.u3.ip) { |
3db05fea | 242 | if (ip_route_me_harder(skb, RTN_UNSPEC)) |
5b1158e9 | 243 | ret = NF_DROP; |
848c29fd PM |
244 | } |
245 | #ifdef CONFIG_XFRM | |
246 | else if (ct->tuplehash[dir].tuple.dst.u.all != | |
247 | ct->tuplehash[!dir].tuple.src.u.all) | |
3db05fea | 248 | if (ip_xfrm_me_harder(skb)) |
848c29fd PM |
249 | ret = NF_DROP; |
250 | #endif | |
5b1158e9 JK |
251 | } |
252 | return ret; | |
253 | } | |
254 | ||
255 | static unsigned int | |
256 | nf_nat_adjust(unsigned int hooknum, | |
3db05fea | 257 | struct sk_buff *skb, |
5b1158e9 JK |
258 | const struct net_device *in, |
259 | const struct net_device *out, | |
260 | int (*okfn)(struct sk_buff *)) | |
261 | { | |
262 | struct nf_conn *ct; | |
263 | enum ip_conntrack_info ctinfo; | |
264 | ||
3db05fea | 265 | ct = nf_ct_get(skb, &ctinfo); |
5b1158e9 | 266 | if (ct && test_bit(IPS_SEQ_ADJUST_BIT, &ct->status)) { |
0d53778e | 267 | pr_debug("nf_nat_standalone: adjusting sequence number\n"); |
3db05fea | 268 | if (!nf_nat_seq_adjust(skb, ct, ctinfo)) |
e905a9ed | 269 | return NF_DROP; |
5b1158e9 JK |
270 | } |
271 | return NF_ACCEPT; | |
272 | } | |
273 | ||
274 | /* We must be after connection tracking and before packet filtering. */ | |
275 | ||
276 | static struct nf_hook_ops nf_nat_ops[] = { | |
277 | /* Before packet filtering, change destination */ | |
278 | { | |
279 | .hook = nf_nat_in, | |
280 | .owner = THIS_MODULE, | |
281 | .pf = PF_INET, | |
282 | .hooknum = NF_IP_PRE_ROUTING, | |
283 | .priority = NF_IP_PRI_NAT_DST, | |
284 | }, | |
285 | /* After packet filtering, change source */ | |
286 | { | |
287 | .hook = nf_nat_out, | |
288 | .owner = THIS_MODULE, | |
289 | .pf = PF_INET, | |
290 | .hooknum = NF_IP_POST_ROUTING, | |
291 | .priority = NF_IP_PRI_NAT_SRC, | |
292 | }, | |
293 | /* After conntrack, adjust sequence number */ | |
294 | { | |
295 | .hook = nf_nat_adjust, | |
296 | .owner = THIS_MODULE, | |
297 | .pf = PF_INET, | |
298 | .hooknum = NF_IP_POST_ROUTING, | |
299 | .priority = NF_IP_PRI_NAT_SEQ_ADJUST, | |
300 | }, | |
301 | /* Before packet filtering, change destination */ | |
302 | { | |
303 | .hook = nf_nat_local_fn, | |
304 | .owner = THIS_MODULE, | |
305 | .pf = PF_INET, | |
306 | .hooknum = NF_IP_LOCAL_OUT, | |
307 | .priority = NF_IP_PRI_NAT_DST, | |
308 | }, | |
309 | /* After packet filtering, change source */ | |
310 | { | |
311 | .hook = nf_nat_fn, | |
312 | .owner = THIS_MODULE, | |
313 | .pf = PF_INET, | |
314 | .hooknum = NF_IP_LOCAL_IN, | |
315 | .priority = NF_IP_PRI_NAT_SRC, | |
316 | }, | |
317 | /* After conntrack, adjust sequence number */ | |
318 | { | |
319 | .hook = nf_nat_adjust, | |
320 | .owner = THIS_MODULE, | |
321 | .pf = PF_INET, | |
322 | .hooknum = NF_IP_LOCAL_IN, | |
323 | .priority = NF_IP_PRI_NAT_SEQ_ADJUST, | |
324 | }, | |
325 | }; | |
326 | ||
327 | static int __init nf_nat_standalone_init(void) | |
328 | { | |
2d59e5ca | 329 | int ret = 0; |
5b1158e9 | 330 | |
591e6206 | 331 | need_ipv4_conntrack(); |
5b1158e9 | 332 | |
5b1158e9 JK |
333 | #ifdef CONFIG_XFRM |
334 | BUG_ON(ip_nat_decode_session != NULL); | |
335 | ip_nat_decode_session = nat_decode_session; | |
336 | #endif | |
337 | ret = nf_nat_rule_init(); | |
338 | if (ret < 0) { | |
339 | printk("nf_nat_init: can't setup rules.\n"); | |
340 | goto cleanup_decode_session; | |
341 | } | |
342 | ret = nf_register_hooks(nf_nat_ops, ARRAY_SIZE(nf_nat_ops)); | |
343 | if (ret < 0) { | |
344 | printk("nf_nat_init: can't register hooks.\n"); | |
345 | goto cleanup_rule_init; | |
346 | } | |
5b1158e9 JK |
347 | return ret; |
348 | ||
349 | cleanup_rule_init: | |
350 | nf_nat_rule_cleanup(); | |
351 | cleanup_decode_session: | |
352 | #ifdef CONFIG_XFRM | |
353 | ip_nat_decode_session = NULL; | |
354 | synchronize_net(); | |
355 | #endif | |
5b1158e9 JK |
356 | return ret; |
357 | } | |
358 | ||
359 | static void __exit nf_nat_standalone_fini(void) | |
360 | { | |
361 | nf_unregister_hooks(nf_nat_ops, ARRAY_SIZE(nf_nat_ops)); | |
362 | nf_nat_rule_cleanup(); | |
5b1158e9 JK |
363 | #ifdef CONFIG_XFRM |
364 | ip_nat_decode_session = NULL; | |
365 | synchronize_net(); | |
366 | #endif | |
367 | /* Conntrack caches are unregistered in nf_conntrack_cleanup */ | |
368 | } | |
369 | ||
370 | module_init(nf_nat_standalone_init); | |
371 | module_exit(nf_nat_standalone_fini); | |
372 | ||
373 | MODULE_LICENSE("GPL"); | |
374 | MODULE_ALIAS("ip_nat"); |