• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * ip_vs_ftp.c: IPVS ftp application module
3  *
4  * Authors:	Wensong Zhang <wensong@linuxvirtualserver.org>
5  *
6  * Changes:
7  *
8  *
9  *	This program is free software; you can redistribute it and/or
10  *	modify it under the terms of the GNU General Public License
11  *	as published by the Free Software Foundation; either version
12  *	2 of the License, or (at your option) any later version.
13  *
14  * Most code here is taken from ip_masq_ftp.c in kernel 2.2. The difference
15  * is that ip_vs_ftp module handles the reverse direction to ip_masq_ftp.
16  *
17  *		IP_MASQ_FTP ftp masquerading module
18  *
19  * Version:	@(#)ip_masq_ftp.c 0.04   02/05/96
20  *
21  * Author:	Wouter Gadeyne
22  *
23  */
24 
25 #include <linux/module.h>
26 #include <linux/moduleparam.h>
27 #include <linux/kernel.h>
28 #include <linux/skbuff.h>
29 #include <linux/in.h>
30 #include <linux/ip.h>
31 #include <linux/netfilter.h>
32 #include <net/protocol.h>
33 #include <net/tcp.h>
34 #include <asm/unaligned.h>
35 
36 #include <net/ip_vs.h>
37 
38 
39 #define SERVER_STRING "227 Entering Passive Mode ("
40 #define CLIENT_STRING "PORT "
41 
42 
43 /*
44  * List of ports (up to IP_VS_APP_MAX_PORTS) to be handled by helper
45  * First port is set to the default port.
46  */
47 static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0};
48 module_param_array(ports, ushort, NULL, 0);
49 MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands");
50 
51 
52 /*	Dummy variable */
53 static int ip_vs_ftp_pasv;
54 
55 
56 static int
ip_vs_ftp_init_conn(struct ip_vs_app * app,struct ip_vs_conn * cp)57 ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
58 {
59 	return 0;
60 }
61 
62 
63 static int
ip_vs_ftp_done_conn(struct ip_vs_app * app,struct ip_vs_conn * cp)64 ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
65 {
66 	return 0;
67 }
68 
69 
70 /*
71  * Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started
72  * with the "pattern" and terminated with the "term" character.
73  * <addr,port> is in network order.
74  */
ip_vs_ftp_get_addrport(char * data,char * data_limit,const char * pattern,size_t plen,char term,__be32 * addr,__be16 * port,char ** start,char ** end)75 static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
76 				  const char *pattern, size_t plen, char term,
77 				  __be32 *addr, __be16 *port,
78 				  char **start, char **end)
79 {
80 	unsigned char p[6];
81 	int i = 0;
82 
83 	if (data_limit - data < plen) {
84 		/* check if there is partial match */
85 		if (strnicmp(data, pattern, data_limit - data) == 0)
86 			return -1;
87 		else
88 			return 0;
89 	}
90 
91 	if (strnicmp(data, pattern, plen) != 0) {
92 		return 0;
93 	}
94 	*start = data + plen;
95 
96 	for (data = *start; *data != term; data++) {
97 		if (data == data_limit)
98 			return -1;
99 	}
100 	*end = data;
101 
102 	memset(p, 0, sizeof(p));
103 	for (data = *start; data != *end; data++) {
104 		if (*data >= '0' && *data <= '9') {
105 			p[i] = p[i]*10 + *data - '0';
106 		} else if (*data == ',' && i < 5) {
107 			i++;
108 		} else {
109 			/* unexpected character */
110 			return -1;
111 		}
112 	}
113 
114 	if (i != 5)
115 		return -1;
116 
117 	*addr = get_unaligned((__be32 *)p);
118 	*port = get_unaligned((__be16 *)(p + 4));
119 	return 1;
120 }
121 
122 
123 /*
124  * Look at outgoing ftp packets to catch the response to a PASV command
125  * from the server (inside-to-outside).
126  * When we see one, we build a connection entry with the client address,
127  * client port 0 (unknown at the moment), the server address and the
128  * server port.  Mark the current connection entry as a control channel
129  * of the new entry. All this work is just to make the data connection
130  * can be scheduled to the right server later.
131  *
132  * The outgoing packet should be something like
133  *   "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)".
134  * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number.
135  */
ip_vs_ftp_out(struct ip_vs_app * app,struct ip_vs_conn * cp,struct sk_buff * skb,int * diff)136 static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp,
137 			 struct sk_buff *skb, int *diff)
138 {
139 	struct iphdr *iph;
140 	struct tcphdr *th;
141 	char *data, *data_limit;
142 	char *start, *end;
143 	union nf_inet_addr from;
144 	__be16 port;
145 	struct ip_vs_conn *n_cp;
146 	char buf[24];		/* xxx.xxx.xxx.xxx,ppp,ppp\000 */
147 	unsigned buf_len;
148 	int ret;
149 
150 #ifdef CONFIG_IP_VS_IPV6
151 	/* This application helper doesn't work with IPv6 yet,
152 	 * so turn this into a no-op for IPv6 packets
153 	 */
154 	if (cp->af == AF_INET6)
155 		return 1;
156 #endif
157 
158 	*diff = 0;
159 
160 	/* Only useful for established sessions */
161 	if (cp->state != IP_VS_TCP_S_ESTABLISHED)
162 		return 1;
163 
164 	/* Linear packets are much easier to deal with. */
165 	if (!skb_make_writable(skb, skb->len))
166 		return 0;
167 
168 	if (cp->app_data == &ip_vs_ftp_pasv) {
169 		iph = ip_hdr(skb);
170 		th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
171 		data = (char *)th + (th->doff << 2);
172 		data_limit = skb_tail_pointer(skb);
173 
174 		if (ip_vs_ftp_get_addrport(data, data_limit,
175 					   SERVER_STRING,
176 					   sizeof(SERVER_STRING)-1, ')',
177 					   &from.ip, &port,
178 					   &start, &end) != 1)
179 			return 1;
180 
181 		IP_VS_DBG(7, "PASV response (%pI4:%d) -> %pI4:%d detected\n",
182 			  &from.ip, ntohs(port), &cp->caddr.ip, 0);
183 
184 		/*
185 		 * Now update or create an connection entry for it
186 		 */
187 		n_cp = ip_vs_conn_out_get(AF_INET, iph->protocol, &from, port,
188 					  &cp->caddr, 0);
189 		if (!n_cp) {
190 			n_cp = ip_vs_conn_new(AF_INET, IPPROTO_TCP,
191 					      &cp->caddr, 0,
192 					      &cp->vaddr, port,
193 					      &from, port,
194 					      IP_VS_CONN_F_NO_CPORT,
195 					      cp->dest);
196 			if (!n_cp)
197 				return 0;
198 
199 			/* add its controller */
200 			ip_vs_control_add(n_cp, cp);
201 		}
202 
203 		/*
204 		 * Replace the old passive address with the new one
205 		 */
206 		from.ip = n_cp->vaddr.ip;
207 		port = n_cp->vport;
208 		sprintf(buf, "%d,%d,%d,%d,%d,%d", NIPQUAD(from.ip),
209 			(ntohs(port)>>8)&255, ntohs(port)&255);
210 		buf_len = strlen(buf);
211 
212 		/*
213 		 * Calculate required delta-offset to keep TCP happy
214 		 */
215 		*diff = buf_len - (end-start);
216 
217 		if (*diff == 0) {
218 			/* simply replace it with new passive address */
219 			memcpy(start, buf, buf_len);
220 			ret = 1;
221 		} else {
222 			ret = !ip_vs_skb_replace(skb, GFP_ATOMIC, start,
223 					  end-start, buf, buf_len);
224 		}
225 
226 		cp->app_data = NULL;
227 		ip_vs_tcp_conn_listen(n_cp);
228 		ip_vs_conn_put(n_cp);
229 		return ret;
230 	}
231 	return 1;
232 }
233 
234 
235 /*
236  * Look at incoming ftp packets to catch the PASV/PORT command
237  * (outside-to-inside).
238  *
239  * The incoming packet having the PORT command should be something like
240  *      "PORT xxx,xxx,xxx,xxx,ppp,ppp\n".
241  * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number.
242  * In this case, we create a connection entry using the client address and
243  * port, so that the active ftp data connection from the server can reach
244  * the client.
245  */
ip_vs_ftp_in(struct ip_vs_app * app,struct ip_vs_conn * cp,struct sk_buff * skb,int * diff)246 static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp,
247 			struct sk_buff *skb, int *diff)
248 {
249 	struct iphdr *iph;
250 	struct tcphdr *th;
251 	char *data, *data_start, *data_limit;
252 	char *start, *end;
253 	union nf_inet_addr to;
254 	__be16 port;
255 	struct ip_vs_conn *n_cp;
256 
257 #ifdef CONFIG_IP_VS_IPV6
258 	/* This application helper doesn't work with IPv6 yet,
259 	 * so turn this into a no-op for IPv6 packets
260 	 */
261 	if (cp->af == AF_INET6)
262 		return 1;
263 #endif
264 
265 	/* no diff required for incoming packets */
266 	*diff = 0;
267 
268 	/* Only useful for established sessions */
269 	if (cp->state != IP_VS_TCP_S_ESTABLISHED)
270 		return 1;
271 
272 	/* Linear packets are much easier to deal with. */
273 	if (!skb_make_writable(skb, skb->len))
274 		return 0;
275 
276 	/*
277 	 * Detecting whether it is passive
278 	 */
279 	iph = ip_hdr(skb);
280 	th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
281 
282 	/* Since there may be OPTIONS in the TCP packet and the HLEN is
283 	   the length of the header in 32-bit multiples, it is accurate
284 	   to calculate data address by th+HLEN*4 */
285 	data = data_start = (char *)th + (th->doff << 2);
286 	data_limit = skb_tail_pointer(skb);
287 
288 	while (data <= data_limit - 6) {
289 		if (strnicmp(data, "PASV\r\n", 6) == 0) {
290 			/* Passive mode on */
291 			IP_VS_DBG(7, "got PASV at %td of %td\n",
292 				  data - data_start,
293 				  data_limit - data_start);
294 			cp->app_data = &ip_vs_ftp_pasv;
295 			return 1;
296 		}
297 		data++;
298 	}
299 
300 	/*
301 	 * To support virtual FTP server, the scenerio is as follows:
302 	 *       FTP client ----> Load Balancer ----> FTP server
303 	 * First detect the port number in the application data,
304 	 * then create a new connection entry for the coming data
305 	 * connection.
306 	 */
307 	if (ip_vs_ftp_get_addrport(data_start, data_limit,
308 				   CLIENT_STRING, sizeof(CLIENT_STRING)-1,
309 				   '\r', &to.ip, &port,
310 				   &start, &end) != 1)
311 		return 1;
312 
313 	IP_VS_DBG(7, "PORT %pI4:%d detected\n", &to.ip, ntohs(port));
314 
315 	/* Passive mode off */
316 	cp->app_data = NULL;
317 
318 	/*
319 	 * Now update or create a connection entry for it
320 	 */
321 	IP_VS_DBG(7, "protocol %s %pI4:%d %pI4:%d\n",
322 		  ip_vs_proto_name(iph->protocol),
323 		  &to.ip, ntohs(port), &cp->vaddr.ip, 0);
324 
325 	n_cp = ip_vs_conn_in_get(AF_INET, iph->protocol,
326 				 &to, port,
327 				 &cp->vaddr, htons(ntohs(cp->vport)-1));
328 	if (!n_cp) {
329 		n_cp = ip_vs_conn_new(AF_INET, IPPROTO_TCP,
330 				      &to, port,
331 				      &cp->vaddr, htons(ntohs(cp->vport)-1),
332 				      &cp->daddr, htons(ntohs(cp->dport)-1),
333 				      0,
334 				      cp->dest);
335 		if (!n_cp)
336 			return 0;
337 
338 		/* add its controller */
339 		ip_vs_control_add(n_cp, cp);
340 	}
341 
342 	/*
343 	 *	Move tunnel to listen state
344 	 */
345 	ip_vs_tcp_conn_listen(n_cp);
346 	ip_vs_conn_put(n_cp);
347 
348 	return 1;
349 }
350 
351 
352 static struct ip_vs_app ip_vs_ftp = {
353 	.name =		"ftp",
354 	.type =		IP_VS_APP_TYPE_FTP,
355 	.protocol =	IPPROTO_TCP,
356 	.module =	THIS_MODULE,
357 	.incs_list =	LIST_HEAD_INIT(ip_vs_ftp.incs_list),
358 	.init_conn =	ip_vs_ftp_init_conn,
359 	.done_conn =	ip_vs_ftp_done_conn,
360 	.bind_conn =	NULL,
361 	.unbind_conn =	NULL,
362 	.pkt_out =	ip_vs_ftp_out,
363 	.pkt_in =	ip_vs_ftp_in,
364 };
365 
366 
367 /*
368  *	ip_vs_ftp initialization
369  */
ip_vs_ftp_init(void)370 static int __init ip_vs_ftp_init(void)
371 {
372 	int i, ret;
373 	struct ip_vs_app *app = &ip_vs_ftp;
374 
375 	ret = register_ip_vs_app(app);
376 	if (ret)
377 		return ret;
378 
379 	for (i=0; i<IP_VS_APP_MAX_PORTS; i++) {
380 		if (!ports[i])
381 			continue;
382 		ret = register_ip_vs_app_inc(app, app->protocol, ports[i]);
383 		if (ret)
384 			break;
385 		IP_VS_INFO("%s: loaded support on port[%d] = %d\n",
386 			   app->name, i, ports[i]);
387 	}
388 
389 	if (ret)
390 		unregister_ip_vs_app(app);
391 
392 	return ret;
393 }
394 
395 
396 /*
397  *	ip_vs_ftp finish.
398  */
ip_vs_ftp_exit(void)399 static void __exit ip_vs_ftp_exit(void)
400 {
401 	unregister_ip_vs_app(&ip_vs_ftp);
402 }
403 
404 
405 module_init(ip_vs_ftp_init);
406 module_exit(ip_vs_ftp_exit);
407 MODULE_LICENSE("GPL");
408