Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* (C) 1999-2001 Paul `Rusty' Russell |
2 | * (C) 2002-2004 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 | ||
9 | /* Everything about the rules for NAT. */ | |
10 | #include <linux/types.h> | |
11 | #include <linux/ip.h> | |
12 | #include <linux/netfilter.h> | |
13 | #include <linux/netfilter_ipv4.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/kmod.h> | |
16 | #include <linux/skbuff.h> | |
17 | #include <linux/proc_fs.h> | |
18 | #include <net/checksum.h> | |
19 | #include <net/route.h> | |
20 | #include <linux/bitops.h> | |
21 | ||
1da177e4 LT |
22 | #include <linux/netfilter_ipv4/ip_tables.h> |
23 | #include <linux/netfilter_ipv4/ip_nat.h> | |
24 | #include <linux/netfilter_ipv4/ip_nat_core.h> | |
25 | #include <linux/netfilter_ipv4/ip_nat_rule.h> | |
1da177e4 LT |
26 | |
27 | #if 0 | |
28 | #define DEBUGP printk | |
29 | #else | |
30 | #define DEBUGP(format, args...) | |
31 | #endif | |
32 | ||
33 | #define NAT_VALID_HOOKS ((1<<NF_IP_PRE_ROUTING) | (1<<NF_IP_POST_ROUTING) | (1<<NF_IP_LOCAL_OUT)) | |
34 | ||
35 | static struct | |
36 | { | |
37 | struct ipt_replace repl; | |
38 | struct ipt_standard entries[3]; | |
39 | struct ipt_error term; | |
40 | } nat_initial_table __initdata | |
41 | = { { "nat", NAT_VALID_HOOKS, 4, | |
42 | sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error), | |
43 | { [NF_IP_PRE_ROUTING] = 0, | |
44 | [NF_IP_POST_ROUTING] = sizeof(struct ipt_standard), | |
45 | [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 }, | |
46 | { [NF_IP_PRE_ROUTING] = 0, | |
47 | [NF_IP_POST_ROUTING] = sizeof(struct ipt_standard), | |
48 | [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 }, | |
49 | 0, NULL, { } }, | |
50 | { | |
51 | /* PRE_ROUTING */ | |
52 | { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, | |
53 | 0, | |
54 | sizeof(struct ipt_entry), | |
55 | sizeof(struct ipt_standard), | |
56 | 0, { 0, 0 }, { } }, | |
57 | { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, | |
58 | -NF_ACCEPT - 1 } }, | |
59 | /* POST_ROUTING */ | |
60 | { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, | |
61 | 0, | |
62 | sizeof(struct ipt_entry), | |
63 | sizeof(struct ipt_standard), | |
64 | 0, { 0, 0 }, { } }, | |
65 | { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, | |
66 | -NF_ACCEPT - 1 } }, | |
67 | /* LOCAL_OUT */ | |
68 | { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, | |
69 | 0, | |
70 | sizeof(struct ipt_entry), | |
71 | sizeof(struct ipt_standard), | |
72 | 0, { 0, 0 }, { } }, | |
73 | { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, | |
74 | -NF_ACCEPT - 1 } } | |
75 | }, | |
76 | /* ERROR */ | |
77 | { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, | |
78 | 0, | |
79 | sizeof(struct ipt_entry), | |
80 | sizeof(struct ipt_error), | |
81 | 0, { 0, 0 }, { } }, | |
82 | { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } }, | |
83 | { } }, | |
84 | "ERROR" | |
85 | } | |
86 | } | |
87 | }; | |
88 | ||
e60a13e0 | 89 | static struct xt_table nat_table = { |
1da177e4 LT |
90 | .name = "nat", |
91 | .valid_hooks = NAT_VALID_HOOKS, | |
92 | .lock = RW_LOCK_UNLOCKED, | |
93 | .me = THIS_MODULE, | |
2e4e6a17 | 94 | .af = AF_INET, |
1da177e4 LT |
95 | }; |
96 | ||
97 | /* Source NAT */ | |
98 | static unsigned int ipt_snat_target(struct sk_buff **pskb, | |
99 | const struct net_device *in, | |
100 | const struct net_device *out, | |
101 | unsigned int hooknum, | |
6709dbbb | 102 | const struct xt_target *target, |
fe1cb108 | 103 | const void *targinfo) |
1da177e4 LT |
104 | { |
105 | struct ip_conntrack *ct; | |
106 | enum ip_conntrack_info ctinfo; | |
107 | const struct ip_nat_multi_range_compat *mr = targinfo; | |
108 | ||
109 | IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING); | |
110 | ||
111 | ct = ip_conntrack_get(*pskb, &ctinfo); | |
112 | ||
113 | /* Connection must be valid and new. */ | |
114 | IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED | |
e905a9ed | 115 | || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)); |
1da177e4 LT |
116 | IP_NF_ASSERT(out); |
117 | ||
118 | return ip_nat_setup_info(ct, &mr->range[0], hooknum); | |
119 | } | |
120 | ||
121 | /* Before 2.6.11 we did implicit source NAT if required. Warn about change. */ | |
a76b11dd | 122 | static void warn_if_extra_mangle(__be32 dstip, __be32 srcip) |
1da177e4 LT |
123 | { |
124 | static int warned = 0; | |
125 | struct flowi fl = { .nl_u = { .ip4_u = { .daddr = dstip } } }; | |
126 | struct rtable *rt; | |
127 | ||
128 | if (ip_route_output_key(&rt, &fl) != 0) | |
129 | return; | |
130 | ||
131 | if (rt->rt_src != srcip && !warned) { | |
132 | printk("NAT: no longer support implicit source local NAT\n"); | |
133 | printk("NAT: packet src %u.%u.%u.%u -> dst %u.%u.%u.%u\n", | |
134 | NIPQUAD(srcip), NIPQUAD(dstip)); | |
135 | warned = 1; | |
136 | } | |
137 | ip_rt_put(rt); | |
138 | } | |
139 | ||
140 | static unsigned int ipt_dnat_target(struct sk_buff **pskb, | |
141 | const struct net_device *in, | |
142 | const struct net_device *out, | |
143 | unsigned int hooknum, | |
6709dbbb | 144 | const struct xt_target *target, |
fe1cb108 | 145 | const void *targinfo) |
1da177e4 LT |
146 | { |
147 | struct ip_conntrack *ct; | |
148 | enum ip_conntrack_info ctinfo; | |
149 | const struct ip_nat_multi_range_compat *mr = targinfo; | |
150 | ||
151 | IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING | |
152 | || hooknum == NF_IP_LOCAL_OUT); | |
153 | ||
154 | ct = ip_conntrack_get(*pskb, &ctinfo); | |
155 | ||
156 | /* Connection must be valid and new. */ | |
157 | IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)); | |
158 | ||
159 | if (hooknum == NF_IP_LOCAL_OUT | |
160 | && mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) | |
eddc9ec5 | 161 | warn_if_extra_mangle(ip_hdr(*pskb)->daddr, |
1da177e4 LT |
162 | mr->range[0].min_ip); |
163 | ||
164 | return ip_nat_setup_info(ct, &mr->range[0], hooknum); | |
165 | } | |
166 | ||
167 | static int ipt_snat_checkentry(const char *tablename, | |
2e4e6a17 | 168 | const void *entry, |
6709dbbb | 169 | const struct xt_target *target, |
1da177e4 | 170 | void *targinfo, |
1da177e4 LT |
171 | unsigned int hook_mask) |
172 | { | |
173 | struct ip_nat_multi_range_compat *mr = targinfo; | |
174 | ||
175 | /* Must be a valid range */ | |
176 | if (mr->rangesize != 1) { | |
177 | printk("SNAT: multiple ranges no longer supported\n"); | |
178 | return 0; | |
179 | } | |
1da177e4 LT |
180 | return 1; |
181 | } | |
182 | ||
183 | static int ipt_dnat_checkentry(const char *tablename, | |
2e4e6a17 | 184 | const void *entry, |
6709dbbb | 185 | const struct xt_target *target, |
1da177e4 | 186 | void *targinfo, |
1da177e4 LT |
187 | unsigned int hook_mask) |
188 | { | |
189 | struct ip_nat_multi_range_compat *mr = targinfo; | |
190 | ||
191 | /* Must be a valid range */ | |
192 | if (mr->rangesize != 1) { | |
193 | printk("DNAT: multiple ranges no longer supported\n"); | |
194 | return 0; | |
195 | } | |
41f4689a EL |
196 | if (mr->range[0].flags & IP_NAT_RANGE_PROTO_RANDOM) { |
197 | printk("DNAT: port randomization not supported\n"); | |
198 | return 0; | |
199 | } | |
1da177e4 LT |
200 | return 1; |
201 | } | |
202 | ||
203 | inline unsigned int | |
204 | alloc_null_binding(struct ip_conntrack *conntrack, | |
205 | struct ip_nat_info *info, | |
206 | unsigned int hooknum) | |
207 | { | |
208 | /* Force range to this IP; let proto decide mapping for | |
209 | per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED). | |
210 | Use reply in case it's already been mangled (eg local packet). | |
211 | */ | |
a76b11dd | 212 | __be32 ip |
1da177e4 LT |
213 | = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC |
214 | ? conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip | |
215 | : conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip); | |
216 | struct ip_nat_range range | |
217 | = { IP_NAT_RANGE_MAP_IPS, ip, ip, { 0 }, { 0 } }; | |
218 | ||
219 | DEBUGP("Allocating NULL binding for %p (%u.%u.%u.%u)\n", conntrack, | |
220 | NIPQUAD(ip)); | |
221 | return ip_nat_setup_info(conntrack, &range, hooknum); | |
222 | } | |
223 | ||
03486a4f PM |
224 | unsigned int |
225 | alloc_null_binding_confirmed(struct ip_conntrack *conntrack, | |
e905a9ed YH |
226 | struct ip_nat_info *info, |
227 | unsigned int hooknum) | |
03486a4f | 228 | { |
a76b11dd | 229 | __be32 ip |
03486a4f PM |
230 | = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC |
231 | ? conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip | |
232 | : conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip); | |
233 | u_int16_t all | |
234 | = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC | |
235 | ? conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all | |
236 | : conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.u.all); | |
237 | struct ip_nat_range range | |
238 | = { IP_NAT_RANGE_MAP_IPS, ip, ip, { all }, { all } }; | |
239 | ||
240 | DEBUGP("Allocating NULL binding for confirmed %p (%u.%u.%u.%u)\n", | |
241 | conntrack, NIPQUAD(ip)); | |
242 | return ip_nat_setup_info(conntrack, &range, hooknum); | |
243 | } | |
244 | ||
1da177e4 LT |
245 | int ip_nat_rule_find(struct sk_buff **pskb, |
246 | unsigned int hooknum, | |
247 | const struct net_device *in, | |
248 | const struct net_device *out, | |
249 | struct ip_conntrack *ct, | |
250 | struct ip_nat_info *info) | |
251 | { | |
252 | int ret; | |
253 | ||
fe1cb108 | 254 | ret = ipt_do_table(pskb, hooknum, in, out, &nat_table); |
1da177e4 LT |
255 | |
256 | if (ret == NF_ACCEPT) { | |
257 | if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum))) | |
258 | /* NUL mapping */ | |
259 | ret = alloc_null_binding(ct, info, hooknum); | |
260 | } | |
261 | return ret; | |
262 | } | |
263 | ||
6709dbbb | 264 | static struct xt_target ipt_snat_reg = { |
1da177e4 | 265 | .name = "SNAT", |
6709dbbb | 266 | .family = AF_INET, |
1da177e4 | 267 | .target = ipt_snat_target, |
1d5cd909 PM |
268 | .targetsize = sizeof(struct ip_nat_multi_range_compat), |
269 | .table = "nat", | |
270 | .hooks = 1 << NF_IP_POST_ROUTING, | |
1da177e4 LT |
271 | .checkentry = ipt_snat_checkentry, |
272 | }; | |
273 | ||
6709dbbb | 274 | static struct xt_target ipt_dnat_reg = { |
1da177e4 | 275 | .name = "DNAT", |
6709dbbb | 276 | .family = AF_INET, |
1da177e4 | 277 | .target = ipt_dnat_target, |
1d5cd909 PM |
278 | .targetsize = sizeof(struct ip_nat_multi_range_compat), |
279 | .table = "nat", | |
19910d1a | 280 | .hooks = (1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT), |
1da177e4 LT |
281 | .checkentry = ipt_dnat_checkentry, |
282 | }; | |
283 | ||
284 | int __init ip_nat_rule_init(void) | |
285 | { | |
286 | int ret; | |
287 | ||
288 | ret = ipt_register_table(&nat_table, &nat_initial_table.repl); | |
289 | if (ret != 0) | |
290 | return ret; | |
6709dbbb | 291 | ret = xt_register_target(&ipt_snat_reg); |
1da177e4 LT |
292 | if (ret != 0) |
293 | goto unregister_table; | |
294 | ||
6709dbbb | 295 | ret = xt_register_target(&ipt_dnat_reg); |
1da177e4 LT |
296 | if (ret != 0) |
297 | goto unregister_snat; | |
298 | ||
299 | return ret; | |
300 | ||
301 | unregister_snat: | |
6709dbbb | 302 | xt_unregister_target(&ipt_snat_reg); |
1da177e4 | 303 | unregister_table: |
6709dbbb | 304 | xt_unregister_table(&nat_table); |
1da177e4 LT |
305 | |
306 | return ret; | |
307 | } | |
308 | ||
309 | void ip_nat_rule_cleanup(void) | |
310 | { | |
6709dbbb JE |
311 | xt_unregister_target(&ipt_dnat_reg); |
312 | xt_unregister_target(&ipt_snat_reg); | |
1da177e4 LT |
313 | ipt_unregister_table(&nat_table); |
314 | } |