initial commit with v2.6.9
[linux-2.6.9-moxart.git] / net / ipv4 / netfilter / ip_fw_compat_redir.c
blob7f68c1ed5a87c7636a728bbb974e372e97f4865e
1 /* This is a file to handle the "simple" NAT cases (redirect and
2 masquerade) required for the compatibility layer.
4 `bind to foreign address' and `getpeername' hacks are not
5 supported.
7 FIXME: Timing is overly simplistic. If anyone complains, make it
8 use conntrack.
9 */
11 /* (C) 1999-2001 Paul `Rusty' Russell
12 * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License version 2 as
16 * published by the Free Software Foundation.
19 #include <linux/config.h>
20 #include <linux/netfilter.h>
21 #include <linux/ip.h>
22 #include <linux/udp.h>
23 #include <linux/tcp.h>
24 #include <net/checksum.h>
25 #include <net/ip.h>
26 #include <linux/timer.h>
27 #include <linux/netdevice.h>
28 #include <linux/if.h>
29 #include <linux/in.h>
31 #include <linux/netfilter_ipv4/lockhelp.h>
33 /* Very simple timeout pushed back by each packet */
34 #define REDIR_TIMEOUT (240*HZ)
36 static DECLARE_LOCK(redir_lock);
37 #define ASSERT_READ_LOCK(x) MUST_BE_LOCKED(&redir_lock)
38 #define ASSERT_WRITE_LOCK(x) MUST_BE_LOCKED(&redir_lock)
40 #include <linux/netfilter_ipv4/listhelp.h>
41 #include "ip_fw_compat.h"
43 #if 0
44 #define DEBUGP printk
45 #else
46 #define DEBUGP(format, args...)
47 #endif
49 #ifdef CONFIG_NETFILTER_DEBUG
50 #define IP_NF_ASSERT(x) \
51 do { \
52 if (!(x)) \
53 /* Wooah! I'm tripping my conntrack in a frenzy of \
54 netplay... */ \
55 printk("ASSERT: %s:%i(%s)\n", \
56 __FILE__, __LINE__, __FUNCTION__); \
57 } while(0)
58 #else
59 #define IP_NF_ASSERT(x)
60 #endif
62 static u_int16_t
63 cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
65 u_int32_t diffs[] = { oldvalinv, newval };
66 return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
67 oldcheck^0xFFFF));
70 struct redir_core {
71 u_int32_t orig_srcip, orig_dstip;
72 u_int16_t orig_sport, orig_dport;
74 u_int32_t new_dstip;
75 u_int16_t new_dport;
78 struct redir
80 struct list_head list;
81 struct redir_core core;
82 struct timer_list destroyme;
85 static LIST_HEAD(redirs);
87 static int
88 redir_cmp(const struct redir *i,
89 u_int32_t orig_srcip, u_int32_t orig_dstip,
90 u_int16_t orig_sport, u_int16_t orig_dport)
92 return (i->core.orig_srcip == orig_srcip
93 && i->core.orig_dstip == orig_dstip
94 && i->core.orig_sport == orig_sport
95 && i->core.orig_dport == orig_dport);
98 /* Search for an existing redirection of the TCP packet. */
99 static struct redir *
100 find_redir(u_int32_t orig_srcip, u_int32_t orig_dstip,
101 u_int16_t orig_sport, u_int16_t orig_dport)
103 return LIST_FIND(&redirs, redir_cmp, struct redir *,
104 orig_srcip, orig_dstip, orig_sport, orig_dport);
107 static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
109 struct iphdr *iph = skb->nh.iph;
110 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
111 + iph->ihl);
113 tcph->check = cheat_check(~redir->core.orig_dstip,
114 redir->core.new_dstip,
115 cheat_check(redir->core.orig_dport ^ 0xFFFF,
116 redir->core.new_dport,
117 tcph->check));
118 iph->check = cheat_check(~redir->core.orig_dstip,
119 redir->core.new_dstip, iph->check);
120 tcph->dest = redir->core.new_dport;
121 iph->daddr = redir->core.new_dstip;
123 skb->nfcache |= NFC_ALTERED;
126 static int
127 unredir_cmp(const struct redir *i,
128 u_int32_t new_dstip, u_int32_t orig_srcip,
129 u_int16_t new_dport, u_int16_t orig_sport)
131 return (i->core.orig_srcip == orig_srcip
132 && i->core.new_dstip == new_dstip
133 && i->core.orig_sport == orig_sport
134 && i->core.new_dport == new_dport);
137 /* Match reply packet against redir */
138 static struct redir *
139 find_unredir(u_int32_t new_dstip, u_int32_t orig_srcip,
140 u_int16_t new_dport, u_int16_t orig_sport)
142 return LIST_FIND(&redirs, unredir_cmp, struct redir *,
143 new_dstip, orig_srcip, new_dport, orig_sport);
146 /* `unredir' a reply packet. */
147 static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
149 struct iphdr *iph = skb->nh.iph;
150 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
151 + iph->ihl);
153 tcph->check = cheat_check(~redir->core.new_dstip,
154 redir->core.orig_dstip,
155 cheat_check(redir->core.new_dport ^ 0xFFFF,
156 redir->core.orig_dport,
157 tcph->check));
158 iph->check = cheat_check(~redir->core.new_dstip,
159 redir->core.orig_dstip,
160 iph->check);
161 tcph->source = redir->core.orig_dport;
162 iph->saddr = redir->core.orig_dstip;
164 skb->nfcache |= NFC_ALTERED;
167 static void destroyme(unsigned long me)
169 LOCK_BH(&redir_lock);
170 LIST_DELETE(&redirs, (struct redir *)me);
171 UNLOCK_BH(&redir_lock);
172 kfree((struct redir *)me);
175 /* REDIRECT a packet. */
176 unsigned int
177 do_redirect(struct sk_buff *skb,
178 const struct net_device *dev,
179 u_int16_t redirpt)
181 struct iphdr *iph = skb->nh.iph;
182 u_int32_t newdst;
184 /* Figure out address: not loopback. */
185 if (!dev)
186 return NF_DROP;
188 /* Grab first address on interface. */
189 newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
191 switch (iph->protocol) {
192 case IPPROTO_UDP: {
193 /* Simple mangle. */
194 struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
195 + iph->ihl);
197 /* Must have whole header */
198 if (skb->len < iph->ihl*4 + sizeof(*udph))
199 return NF_DROP;
201 if (udph->check) /* 0 is a special case meaning no checksum */
202 udph->check = cheat_check(~iph->daddr, newdst,
203 cheat_check(udph->dest ^ 0xFFFF,
204 redirpt,
205 udph->check));
206 iph->check = cheat_check(~iph->daddr, newdst, iph->check);
207 udph->dest = redirpt;
208 iph->daddr = newdst;
210 skb->nfcache |= NFC_ALTERED;
211 return NF_ACCEPT;
213 case IPPROTO_TCP: {
214 /* Mangle, maybe record. */
215 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
216 + iph->ihl);
217 struct redir *redir;
218 int ret;
220 /* Must have whole header */
221 if (skb->len < iph->ihl*4 + sizeof(*tcph))
222 return NF_DROP;
224 DEBUGP("Doing tcp redirect. %08X:%u %08X:%u -> %08X:%u\n",
225 iph->saddr, tcph->source, iph->daddr, tcph->dest,
226 newdst, redirpt);
227 LOCK_BH(&redir_lock);
228 redir = find_redir(iph->saddr, iph->daddr,
229 tcph->source, tcph->dest);
231 if (!redir) {
232 redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
233 if (!redir) {
234 ret = NF_DROP;
235 goto out;
237 list_prepend(&redirs, redir);
238 init_timer(&redir->destroyme);
239 redir->destroyme.function = destroyme;
240 redir->destroyme.data = (unsigned long)redir;
241 redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
242 add_timer(&redir->destroyme);
244 /* In case mangling has changed, rewrite this part. */
245 redir->core = ((struct redir_core)
246 { iph->saddr, iph->daddr,
247 tcph->source, tcph->dest,
248 newdst, redirpt });
249 do_tcp_redir(skb, redir);
250 ret = NF_ACCEPT;
252 out:
253 UNLOCK_BH(&redir_lock);
254 return ret;
257 default: /* give up if not TCP or UDP. */
258 return NF_DROP;
262 /* Incoming packet: is it a reply to a masqueraded connection, or
263 part of an already-redirected TCP connection? */
264 void
265 check_for_redirect(struct sk_buff *skb)
267 struct iphdr *iph = skb->nh.iph;
268 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
269 + iph->ihl);
270 struct redir *redir;
272 if (iph->protocol != IPPROTO_TCP)
273 return;
275 /* Must have whole header */
276 if (skb->len < iph->ihl*4 + sizeof(*tcph))
277 return;
279 LOCK_BH(&redir_lock);
280 redir = find_redir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
281 if (redir) {
282 DEBUGP("Doing tcp redirect again.\n");
283 do_tcp_redir(skb, redir);
284 if (del_timer(&redir->destroyme)) {
285 redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
286 add_timer(&redir->destroyme);
289 UNLOCK_BH(&redir_lock);
292 void
293 check_for_unredirect(struct sk_buff *skb)
295 struct iphdr *iph = skb->nh.iph;
296 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
297 + iph->ihl);
298 struct redir *redir;
300 if (iph->protocol != IPPROTO_TCP)
301 return;
303 /* Must have whole header */
304 if (skb->len < iph->ihl*4 + sizeof(*tcph))
305 return;
307 LOCK_BH(&redir_lock);
308 redir = find_unredir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
309 if (redir) {
310 DEBUGP("Doing tcp unredirect.\n");
311 do_tcp_unredir(skb, redir);
312 if (del_timer(&redir->destroyme)) {
313 redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
314 add_timer(&redir->destroyme);
317 UNLOCK_BH(&redir_lock);