ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / 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 <linux/timer.h>
26 #include <linux/netdevice.h>
27 #include <linux/if.h>
28 #include <linux/in.h>
29
30 #include <linux/netfilter_ipv4/lockhelp.h>
31
32 /* Very simple timeout pushed back by each packet */
33 #define REDIR_TIMEOUT (240*HZ)
34
35 static DECLARE_LOCK(redir_lock);
36 #define ASSERT_READ_LOCK(x) MUST_BE_LOCKED(&redir_lock)
37 #define ASSERT_WRITE_LOCK(x) MUST_BE_LOCKED(&redir_lock)
38
39 #include <linux/netfilter_ipv4/listhelp.h>
40 #include "ip_fw_compat.h"
41
42 #if 0
43 #define DEBUGP printk
44 #else
45 #define DEBUGP(format, args...)
46 #endif
47
48 #ifdef CONFIG_NETFILTER_DEBUG
49 #define IP_NF_ASSERT(x)                                                  \
50 do {                                                                     \
51         if (!(x))                                                        \
52                 /* Wooah!  I'm tripping my conntrack in a frenzy of      \
53                    netplay... */                                         \
54                 printk("ASSERT: %s:%i(%s)\n",                            \
55                        __FILE__, __LINE__, __FUNCTION__);                \
56 } while(0)
57 #else
58 #define IP_NF_ASSERT(x)
59 #endif
60
61 static u_int16_t
62 cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
63 {
64         u_int32_t diffs[] = { oldvalinv, newval };
65         return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
66                                       oldcheck^0xFFFF));
67 }
68
69 struct redir_core {
70         u_int32_t orig_srcip, orig_dstip;
71         u_int16_t orig_sport, orig_dport;
72
73         u_int32_t new_dstip;
74         u_int16_t new_dport;
75 };
76
77 struct redir
78 {
79         struct list_head list;
80         struct redir_core core;
81         struct timer_list destroyme;
82 };
83
84 static LIST_HEAD(redirs);
85
86 static int
87 redir_cmp(const struct redir *i,
88           u_int32_t orig_srcip, u_int32_t orig_dstip,
89           u_int16_t orig_sport, u_int16_t orig_dport)
90 {
91         return (i->core.orig_srcip == orig_srcip
92                 && i->core.orig_dstip == orig_dstip
93                 && i->core.orig_sport == orig_sport
94                 && i->core.orig_dport == orig_dport);
95 }
96
97 /* Search for an existing redirection of the TCP packet. */
98 static struct redir *
99 find_redir(u_int32_t orig_srcip, u_int32_t orig_dstip,
100            u_int16_t orig_sport, u_int16_t orig_dport)
101 {
102         return LIST_FIND(&redirs, redir_cmp, struct redir *,
103                          orig_srcip, orig_dstip, orig_sport, orig_dport);
104 }
105
106 static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
107 {
108         struct iphdr *iph = skb->nh.iph;
109         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
110                                                 + iph->ihl);
111
112         tcph->check = cheat_check(~redir->core.orig_dstip,
113                                   redir->core.new_dstip,
114                                   cheat_check(redir->core.orig_dport ^ 0xFFFF,
115                                               redir->core.new_dport,
116                                               tcph->check));
117         iph->check = cheat_check(~redir->core.orig_dstip,
118                                  redir->core.new_dstip, iph->check);
119         tcph->dest = redir->core.new_dport;
120         iph->daddr = redir->core.new_dstip;
121
122         skb->nfcache |= NFC_ALTERED;
123 }
124
125 static int
126 unredir_cmp(const struct redir *i,
127             u_int32_t new_dstip, u_int32_t orig_srcip,
128             u_int16_t new_dport, u_int16_t orig_sport)
129 {
130         return (i->core.orig_srcip == orig_srcip
131                 && i->core.new_dstip == new_dstip
132                 && i->core.orig_sport == orig_sport
133                 && i->core.new_dport == new_dport);
134 }
135
136 /* Match reply packet against redir */
137 static struct redir *
138 find_unredir(u_int32_t new_dstip, u_int32_t orig_srcip,
139              u_int16_t new_dport, u_int16_t orig_sport)
140 {
141         return LIST_FIND(&redirs, unredir_cmp, struct redir *,
142                          new_dstip, orig_srcip, new_dport, orig_sport);
143 }
144
145 /* `unredir' a reply packet. */
146 static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
147 {
148         struct iphdr *iph = skb->nh.iph;
149         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
150                                                 + iph->ihl);
151
152         tcph->check = cheat_check(~redir->core.new_dstip,
153                                   redir->core.orig_dstip,
154                                   cheat_check(redir->core.new_dport ^ 0xFFFF,
155                                               redir->core.orig_dport,
156                                               tcph->check));
157         iph->check = cheat_check(~redir->core.new_dstip,
158                                  redir->core.orig_dstip,
159                                  iph->check);
160         tcph->source = redir->core.orig_dport;
161         iph->saddr = redir->core.orig_dstip;
162
163         skb->nfcache |= NFC_ALTERED;
164 }
165
166 static void destroyme(unsigned long me)
167 {
168         LOCK_BH(&redir_lock);
169         LIST_DELETE(&redirs, (struct redir *)me);
170         UNLOCK_BH(&redir_lock);
171         kfree((struct redir *)me);
172 }
173
174 /* REDIRECT a packet. */
175 unsigned int
176 do_redirect(struct sk_buff *skb,
177             const struct net_device *dev,
178             u_int16_t redirpt)
179 {
180         struct iphdr *iph = skb->nh.iph;
181         u_int32_t newdst;
182
183         /* Figure out address: not loopback. */
184         if (!dev)
185                 return NF_DROP;
186
187         /* Grab first address on interface. */
188         newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
189
190         switch (iph->protocol) {
191         case IPPROTO_UDP: {
192                 /* Simple mangle. */
193                 struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
194                                                         + iph->ihl);
195
196                 /* Must have whole header */
197                 if (skb->len < iph->ihl*4 + sizeof(*udph))
198                         return NF_DROP;
199
200                 if (udph->check) /* 0 is a special case meaning no checksum */
201                         udph->check = cheat_check(~iph->daddr, newdst,
202                                           cheat_check(udph->dest ^ 0xFFFF,
203                                                       redirpt,
204                                                       udph->check));
205                 iph->check = cheat_check(~iph->daddr, newdst, iph->check);
206                 udph->dest = redirpt;
207                 iph->daddr = newdst;
208
209                 skb->nfcache |= NFC_ALTERED;
210                 return NF_ACCEPT;
211         }
212         case IPPROTO_TCP: {
213                 /* Mangle, maybe record. */
214                 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
215                                                         + iph->ihl);
216                 struct redir *redir;
217                 int ret;
218
219                 /* Must have whole header */
220                 if (skb->len < iph->ihl*4 + sizeof(*tcph))
221                         return NF_DROP;
222
223                 DEBUGP("Doing tcp redirect. %08X:%u %08X:%u -> %08X:%u\n",
224                        iph->saddr, tcph->source, iph->daddr, tcph->dest,
225                        newdst, redirpt);
226                 LOCK_BH(&redir_lock);
227                 redir = find_redir(iph->saddr, iph->daddr,
228                                    tcph->source, tcph->dest);
229
230                 if (!redir) {
231                         redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
232                         if (!redir) {
233                                 ret = NF_DROP;
234                                 goto out;
235                         }
236                         list_prepend(&redirs, redir);
237                         init_timer(&redir->destroyme);
238                         redir->destroyme.function = destroyme;
239                         redir->destroyme.data = (unsigned long)redir;
240                         redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
241                         add_timer(&redir->destroyme);
242                 }
243                 /* In case mangling has changed, rewrite this part. */
244                 redir->core = ((struct redir_core)
245                                { iph->saddr, iph->daddr,
246                                  tcph->source, tcph->dest,
247                                  newdst, redirpt });
248                 do_tcp_redir(skb, redir);
249                 ret = NF_ACCEPT;
250
251         out:
252                 UNLOCK_BH(&redir_lock);
253                 return ret;
254         }
255
256         default: /* give up if not TCP or UDP. */
257                 return NF_DROP;
258         }
259 }
260
261 /* Incoming packet: is it a reply to a masqueraded connection, or
262    part of an already-redirected TCP connection? */
263 void
264 check_for_redirect(struct sk_buff *skb)
265 {
266         struct iphdr *iph = skb->nh.iph;
267         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
268                                                 + iph->ihl);
269         struct redir *redir;
270
271         if (iph->protocol != IPPROTO_TCP)
272                 return;
273
274         /* Must have whole header */
275         if (skb->len < iph->ihl*4 + sizeof(*tcph))
276                 return;
277
278         LOCK_BH(&redir_lock);
279         redir = find_redir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
280         if (redir) {
281                 DEBUGP("Doing tcp redirect again.\n");
282                 do_tcp_redir(skb, redir);
283                 if (del_timer(&redir->destroyme)) {
284                         redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
285                         add_timer(&redir->destroyme);
286                 }
287         }
288         UNLOCK_BH(&redir_lock);
289 }
290
291 void
292 check_for_unredirect(struct sk_buff *skb)
293 {
294         struct iphdr *iph = skb->nh.iph;
295         struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
296                                                 + iph->ihl);
297         struct redir *redir;
298
299         if (iph->protocol != IPPROTO_TCP)
300                 return;
301
302         /* Must have whole header */
303         if (skb->len < iph->ihl*4 + sizeof(*tcph))
304                 return;
305
306         LOCK_BH(&redir_lock);
307         redir = find_unredir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
308         if (redir) {
309                 DEBUGP("Doing tcp unredirect.\n");
310                 do_tcp_unredir(skb, redir);
311                 if (del_timer(&redir->destroyme)) {
312                         redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
313                         add_timer(&redir->destroyme);
314                 }
315         }
316         UNLOCK_BH(&redir_lock);
317 }