finally in sync with archive
[bcm963xx.git] / kernel / linux / net / ipv4 / netfilter / ip_fw_compat_redir.c
1 /* This is a file to handle the "simple" NAT cases (redirect and
2    masquerade) required for the compatibility layer.
3
4    `bind to foreign address' and `getpeername' hacks are not
5    supported.
6
7    FIXME: Timing is overly simplistic.  If anyone complains, make it
8    use conntrack.
9 */
10
11 /* (C) 1999-2001 Paul `Rusty' Russell
12  * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
13  *
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.
17  */
18
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>
30
31 #include <linux/netfilter_ipv4/lockhelp.h>
32
33 /* Very simple timeout pushed back by each packet */
34 #define REDIR_TIMEOUT (240*HZ)
35
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)
39
40 #include <linux/netfilter_ipv4/listhelp.h>
41 #include "ip_fw_compat.h"
42
43 #if 0
44 #define DEBUGP printk
45 #else
46 #define DEBUGP(format, args...)
47 #endif
48
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
61
62 static u_int16_t
63 cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
64 {
65         u_int32_t diffs[] = { oldvalinv, newval };
66         return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
67                                       oldcheck^0xFFFF));
68 }
69
70 struct redir_core {
71         u_int32_t orig_srcip, orig_dstip;
72         u_int16_t orig_sport, orig_dport;
73
74         u_int32_t new_dstip;
75         u_int16_t new_dport;
76 };
77
78 struct redir
79 {
80         struct list_head list;
81         struct redir_core core;
82         struct timer_list destroyme;
83 };
84
85 static LIST_HEAD(redirs);
86
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)
91 {
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);
96 }
97
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)
102 {
103         return LIST_FIND(&redirs, redir_cmp, struct redir *,
104                          orig_srcip, orig_dstip, orig_sport, orig_dport);
105 }
106
107 static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
108 {
109         struct iphdr *iph = skb->nh.iph;
110         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
111                                                 + iph->ihl);
112
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;
122
123         skb->nfcache |= NFC_ALTERED;
124 }
125
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)
130 {
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);
135 }
136
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)
141 {
142         return LIST_FIND(&redirs, unredir_cmp, struct redir *,
143                          new_dstip, orig_srcip, new_dport, orig_sport);
144 }
145
146 /* `unredir' a reply packet. */
147 static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
148 {
149         struct iphdr *iph = skb->nh.iph;
150         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
151                                                 + iph->ihl);
152
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;
163
164         skb->nfcache |= NFC_ALTERED;
165 }
166
167 static void destroyme(unsigned long me)
168 {
169         LOCK_BH(&redir_lock);
170         LIST_DELETE(&redirs, (struct redir *)me);
171         UNLOCK_BH(&redir_lock);
172         kfree((struct redir *)me);
173 }
174
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)
180 {
181         struct iphdr *iph = skb->nh.iph;
182         u_int32_t newdst;
183
184         /* Figure out address: not loopback. */
185         if (!dev)
186                 return NF_DROP;
187
188         /* Grab first address on interface. */
189         newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
190
191         switch (iph->protocol) {
192         case IPPROTO_UDP: {
193                 /* Simple mangle. */
194                 struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
195                                                         + iph->ihl);
196
197                 /* Must have whole header */
198                 if (skb->len < iph->ihl*4 + sizeof(*udph))
199                         return NF_DROP;
200
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;
209
210                 skb->nfcache |= NFC_ALTERED;
211                 return NF_ACCEPT;
212         }
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;
219
220                 /* Must have whole header */
221                 if (skb->len < iph->ihl*4 + sizeof(*tcph))
222                         return NF_DROP;
223
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);
230
231                 if (!redir) {
232                         redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
233                         if (!redir) {
234                                 ret = NF_DROP;
235                                 goto out;
236                         }
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);
243                 }
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;
251
252         out:
253                 UNLOCK_BH(&redir_lock);
254                 return ret;
255         }
256
257         default: /* give up if not TCP or UDP. */
258                 return NF_DROP;
259         }
260 }
261
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)
266 {
267         struct iphdr *iph = skb->nh.iph;
268         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
269                                                 + iph->ihl);
270         struct redir *redir;
271
272         if (iph->protocol != IPPROTO_TCP)
273                 return;
274
275         /* Must have whole header */
276         if (skb->len < iph->ihl*4 + sizeof(*tcph))
277                 return;
278
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);
287                 }
288         }
289         UNLOCK_BH(&redir_lock);
290 }
291
292 void
293 check_for_unredirect(struct sk_buff *skb)
294 {
295         struct iphdr *iph = skb->nh.iph;
296         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
297                                                 + iph->ihl);
298         struct redir *redir;
299
300         if (iph->protocol != IPPROTO_TCP)
301                 return;
302
303         /* Must have whole header */
304         if (skb->len < iph->ihl*4 + sizeof(*tcph))
305                 return;
306
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);
315                 }
316         }
317         UNLOCK_BH(&redir_lock);
318 }