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