Import 2.4.0-test6pre3
[davej-history.git] / net / ipv4 / netfilter / ip_fw_compat_redir.c
blob274bb1162265e09aef2a9ab6d90fcc4e8e9e166b
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 */
10 #include <linux/config.h>
11 #include <linux/netfilter.h>
12 #include <linux/ip.h>
13 #include <linux/udp.h>
14 #include <linux/tcp.h>
15 #include <net/checksum.h>
16 #include <linux/timer.h>
17 #include <linux/netdevice.h>
18 #include <linux/if.h>
19 #include <linux/in.h>
21 #include <linux/netfilter_ipv4/lockhelp.h>
23 static DECLARE_LOCK(redir_lock);
24 #define ASSERT_READ_LOCK(x) MUST_BE_LOCKED(&redir_lock)
25 #define ASSERT_WRITE_LOCK(x) MUST_BE_LOCKED(&redir_lock)
27 #include <linux/netfilter_ipv4/listhelp.h>
29 #if 0
30 #define DEBUGP printk
31 #else
32 #define DEBUGP(format, args...)
33 #endif
35 #ifdef CONFIG_NETFILTER_DEBUG
36 #define IP_NF_ASSERT(x) \
37 do { \
38 if (!(x)) \
39 /* Wooah! I'm tripping my conntrack in a frenzy of \
40 netplay... */ \
41 printk("ASSERT: %s:%i(%s)\n", \
42 __FILE__, __LINE__, __FUNCTION__); \
43 } while(0);
44 #else
45 #define IP_NF_ASSERT(x)
46 #endif
48 static u_int16_t
49 cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
51 u_int32_t diffs[] = { oldvalinv, newval };
52 return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
53 oldcheck^0xFFFF));
56 struct redir_core {
57 u_int32_t orig_srcip, orig_dstip;
58 u_int16_t orig_sport, orig_dport;
60 u_int32_t new_dstip;
61 u_int16_t new_dport;
64 struct redir
66 struct list_head list;
67 struct redir_core core;
68 struct timer_list destroyme;
71 static LIST_HEAD(redirs);
73 static int
74 redir_cmp(const struct redir *i,
75 u_int32_t orig_srcip, u_int32_t orig_dstip,
76 u_int16_t orig_sport, u_int16_t orig_dport)
78 return (i->core.orig_srcip == orig_srcip
79 && i->core.orig_dstip == orig_dstip
80 && i->core.orig_sport == orig_sport
81 && i->core.orig_dport == orig_dport);
84 /* Search for an existing redirection of the TCP packet. */
85 static struct redir *
86 find_redir(u_int32_t orig_srcip, u_int32_t orig_dstip,
87 u_int16_t orig_sport, u_int16_t orig_dport)
89 return LIST_FIND(&redirs, redir_cmp, struct redir *,
90 orig_srcip, orig_dstip, orig_sport, orig_dport);
93 static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
95 struct iphdr *iph = skb->nh.iph;
96 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
97 + iph->ihl);
99 tcph->check = cheat_check(~redir->core.orig_dstip,
100 redir->core.new_dstip,
101 cheat_check(redir->core.orig_dport ^ 0xFFFF,
102 redir->core.new_dport,
103 tcph->check));
104 iph->check = cheat_check(~redir->core.orig_dstip,
105 redir->core.new_dstip, iph->check);
106 tcph->dest = redir->core.new_dport;
107 iph->daddr = redir->core.new_dstip;
109 skb->nfcache |= NFC_ALTERED;
112 static int
113 unredir_cmp(const struct redir *i,
114 u_int32_t new_dstip, u_int32_t orig_srcip,
115 u_int16_t new_dport, u_int16_t orig_sport)
117 return (i->core.orig_srcip == orig_srcip
118 && i->core.new_dstip == new_dstip
119 && i->core.orig_sport == orig_sport
120 && i->core.new_dport == new_dport);
123 /* Match reply packet against redir */
124 static struct redir *
125 find_unredir(u_int32_t new_dstip, u_int32_t orig_srcip,
126 u_int16_t new_dport, u_int16_t orig_sport)
128 return LIST_FIND(&redirs, unredir_cmp, struct redir *,
129 new_dstip, orig_srcip, new_dport, orig_sport);
132 /* `unredir' a reply packet. */
133 static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
135 struct iphdr *iph = skb->nh.iph;
136 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
137 + iph->ihl);
139 tcph->check = cheat_check(~redir->core.new_dstip,
140 redir->core.orig_dstip,
141 cheat_check(redir->core.new_dport ^ 0xFFFF,
142 redir->core.orig_dport,
143 tcph->check));
144 iph->check = cheat_check(~redir->core.new_dstip,
145 redir->core.orig_dstip,
146 iph->check);
147 tcph->source = redir->core.orig_dport;
148 iph->saddr = redir->core.orig_dstip;
150 skb->nfcache |= NFC_ALTERED;
153 /* REDIRECT a packet. */
154 unsigned int
155 do_redirect(struct sk_buff *skb,
156 const struct net_device *dev,
157 u_int16_t redirpt)
159 struct iphdr *iph = skb->nh.iph;
160 u_int32_t newdst;
162 /* Figure out address: not loopback. */
163 if (!dev)
164 return NF_DROP;
166 /* Grab first address on interface. */
167 newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
169 switch (iph->protocol) {
170 case IPPROTO_UDP: {
171 /* Simple mangle. */
172 struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
173 + iph->ihl);
175 if (udph->check) /* 0 is a special case meaning no checksum */
176 udph->check = cheat_check(~iph->daddr, newdst,
177 cheat_check(udph->dest ^ 0xFFFF,
178 redirpt,
179 udph->check));
180 iph->check = cheat_check(~iph->daddr, newdst, iph->check);
181 udph->dest = redirpt;
182 iph->daddr = newdst;
184 skb->nfcache |= NFC_ALTERED;
185 return NF_ACCEPT;
187 case IPPROTO_TCP: {
188 /* Mangle, maybe record. */
189 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
190 + iph->ihl);
191 struct redir *redir;
192 int ret;
194 DEBUGP("Doing tcp redirect. %08X:%u %08X:%u -> %08X:%u\n",
195 iph->saddr, tcph->source, iph->daddr, tcph->dest,
196 newdst, redirpt);
197 LOCK_BH(&redir_lock);
198 redir = find_redir(iph->saddr, iph->daddr,
199 tcph->source, tcph->dest);
201 if (!redir) {
202 redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
203 if (!redir) {
204 ret = NF_DROP;
205 goto out;
207 list_prepend(&redirs, redir);
208 init_timer(&redir->destroyme);
210 /* In case mangling has changed, rewrite this part. */
211 redir->core = ((struct redir_core)
212 { iph->saddr, iph->daddr,
213 tcph->source, tcph->dest,
214 newdst, redirpt });
215 do_tcp_redir(skb, redir);
216 ret = NF_ACCEPT;
218 out:
219 UNLOCK_BH(&redir_lock);
220 return ret;
223 default: /* give up if not TCP or UDP. */
224 return NF_DROP;
228 static void destroyme(unsigned long me)
230 LOCK_BH(&redir_lock);
231 LIST_DELETE(&redirs, (struct redir *)me);
232 UNLOCK_BH(&redir_lock);
235 /* Incoming packet: is it a reply to a masqueraded connection, or
236 part of an already-redirected TCP connection? */
237 void
238 check_for_redirect(struct sk_buff *skb)
240 struct iphdr *iph = skb->nh.iph;
241 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
242 + iph->ihl);
243 struct redir *redir;
245 if (iph->protocol != IPPROTO_TCP)
246 return;
248 LOCK_BH(&redir_lock);
249 redir = find_redir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
250 if (redir) {
251 DEBUGP("Doing tcp redirect again.\n");
252 do_tcp_redir(skb, redir);
253 if (tcph->rst || tcph->fin) {
254 redir->destroyme.function = destroyme;
255 redir->destroyme.data = (unsigned long)redir;
256 mod_timer(&redir->destroyme, 75*HZ);
259 UNLOCK_BH(&redir_lock);
262 void
263 check_for_unredirect(struct sk_buff *skb)
265 struct iphdr *iph = skb->nh.iph;
266 struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
267 + iph->ihl);
268 struct redir *redir;
270 if (iph->protocol != IPPROTO_TCP)
271 return;
273 LOCK_BH(&redir_lock);
274 redir = find_unredir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
275 if (redir) {
276 DEBUGP("Doing tcp unredirect.\n");
277 do_tcp_unredir(skb, redir);
278 if (tcph->rst || tcph->fin) {
279 redir->destroyme.function = destroyme;
280 redir->destroyme.data = (unsigned long)redir;
281 mod_timer(&redir->destroyme, 75*HZ);
284 UNLOCK_BH(&redir_lock);