File: /usr/src/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     #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>
20     
21     #include <linux/netfilter_ipv4/lockhelp.h>
22     
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)
26     
27     #include <linux/netfilter_ipv4/listhelp.h>
28     
29     #if 0
30     #define DEBUGP printk
31     #else
32     #define DEBUGP(format, args...)
33     #endif
34     
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
47     
48     static u_int16_t
49     cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
50     {
51     	u_int32_t diffs[] = { oldvalinv, newval };
52     	return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
53     				      oldcheck^0xFFFF));
54     }
55     
56     struct redir_core {
57     	u_int32_t orig_srcip, orig_dstip;
58     	u_int16_t orig_sport, orig_dport;
59     
60     	u_int32_t new_dstip;
61     	u_int16_t new_dport;
62     };
63     
64     struct redir
65     {
66     	struct list_head list;
67     	struct redir_core core;
68     	struct timer_list destroyme;
69     };
70     
71     static LIST_HEAD(redirs);
72     
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)
77     {
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);
82     }
83     
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)
88     {
89     	return LIST_FIND(&redirs, redir_cmp, struct redir *,
90     			 orig_srcip, orig_dstip, orig_sport, orig_dport);
91     }
92     
93     static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
94     {
95     	struct iphdr *iph = skb->nh.iph;
96     	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
97     						+ iph->ihl);
98     
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;
108     
109     	skb->nfcache |= NFC_ALTERED;
110     }
111     
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)
116     {
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);
121     }
122     
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)
127     {
128     	return LIST_FIND(&redirs, unredir_cmp, struct redir *,
129     			 new_dstip, orig_srcip, new_dport, orig_sport);
130     }
131     
132     /* `unredir' a reply packet. */
133     static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
134     {
135     	struct iphdr *iph = skb->nh.iph;
136     	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
137     						+ iph->ihl);
138     
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;
149     
150     	skb->nfcache |= NFC_ALTERED;
151     }
152     
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)
158     {
159     	struct iphdr *iph = skb->nh.iph;
160     	u_int32_t newdst;
161     
162     	/* Figure out address: not loopback. */
163     	if (!dev)
164     		return NF_DROP;
165     
166     	/* Grab first address on interface. */
167     	newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
168     
169     	switch (iph->protocol) {
170     	case IPPROTO_UDP: {
171     		/* Simple mangle. */
172     		struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
173     							+ iph->ihl);
174     
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;
183     
184     		skb->nfcache |= NFC_ALTERED;
185     		return NF_ACCEPT;
186     	}
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;
193     
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);
200     
201     		if (!redir) {
202     			redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
203     			if (!redir) {
204     				ret = NF_DROP;
205     				goto out;
206     			}
207     			list_prepend(&redirs, redir);
208     			init_timer(&redir->destroyme);
209     		}
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;
217     
218     	out:
219     		UNLOCK_BH(&redir_lock);
220     		return ret;
221     	}
222     
223     	default: /* give up if not TCP or UDP. */
224     		return NF_DROP;
225     	}
226     }
227     
228     static void destroyme(unsigned long me)
229     {
230     	LOCK_BH(&redir_lock);
231     	LIST_DELETE(&redirs, (struct redir *)me);
232     	UNLOCK_BH(&redir_lock);
233     }
234     
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)
239     {
240     	struct iphdr *iph = skb->nh.iph;
241     	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
242     						+ iph->ihl);
243     	struct redir *redir;
244     
245     	if (iph->protocol != IPPROTO_TCP)
246     		return;
247     
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);
257     		}
258     	}
259     	UNLOCK_BH(&redir_lock);
260     }
261     
262     void
263     check_for_unredirect(struct sk_buff *skb)
264     {
265     	struct iphdr *iph = skb->nh.iph;
266     	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
267     						+ iph->ihl);
268     	struct redir *redir;
269     
270     	if (iph->protocol != IPPROTO_TCP)
271     		return;
272     
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);
282     		}
283     	}
284     	UNLOCK_BH(&redir_lock);
285     }
286