]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * ip_vs_ftp.c: IPVS ftp application module | |
3 | * | |
1da177e4 LT |
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 | * | |
7f1c4075 HE |
23 | * |
24 | * Code for ip_vs_expect_related and ip_vs_expect_callback is taken from | |
25 | * http://www.ssi.bg/~ja/nfct/: | |
26 | * | |
27 | * ip_vs_nfct.c: Netfilter connection tracking support for IPVS | |
28 | * | |
29 | * Portions Copyright (C) 2001-2002 | |
30 | * Antefacto Ltd, 181 Parnell St, Dublin 1, Ireland. | |
31 | * | |
32 | * Portions Copyright (C) 2003-2008 | |
33 | * Julian Anastasov | |
1da177e4 LT |
34 | */ |
35 | ||
9aada7ac HE |
36 | #define KMSG_COMPONENT "IPVS" |
37 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
38 | ||
1da177e4 LT |
39 | #include <linux/module.h> |
40 | #include <linux/moduleparam.h> | |
41 | #include <linux/kernel.h> | |
42 | #include <linux/skbuff.h> | |
43 | #include <linux/in.h> | |
44 | #include <linux/ip.h> | |
af1e1cf0 | 45 | #include <linux/netfilter.h> |
7f1c4075 HE |
46 | #include <net/netfilter/nf_conntrack.h> |
47 | #include <net/netfilter/nf_conntrack_expect.h> | |
7bcbf81a | 48 | #include <net/netfilter/nf_nat.h> |
7f1c4075 | 49 | #include <net/netfilter/nf_nat_helper.h> |
5a0e3ad6 | 50 | #include <linux/gfp.h> |
1da177e4 LT |
51 | #include <net/protocol.h> |
52 | #include <net/tcp.h> | |
96d2ca4e | 53 | #include <asm/unaligned.h> |
1da177e4 LT |
54 | |
55 | #include <net/ip_vs.h> | |
56 | ||
57 | ||
58 | #define SERVER_STRING "227 Entering Passive Mode (" | |
59 | #define CLIENT_STRING "PORT " | |
60 | ||
7f1c4075 HE |
61 | #define FMT_TUPLE "%pI4:%u->%pI4:%u/%u" |
62 | #define ARG_TUPLE(T) &(T)->src.u3.ip, ntohs((T)->src.u.all), \ | |
63 | &(T)->dst.u3.ip, ntohs((T)->dst.u.all), \ | |
64 | (T)->dst.protonum | |
65 | ||
66 | #define FMT_CONN "%pI4:%u->%pI4:%u->%pI4:%u/%u:%u" | |
67 | #define ARG_CONN(C) &((C)->caddr.ip), ntohs((C)->cport), \ | |
68 | &((C)->vaddr.ip), ntohs((C)->vport), \ | |
69 | &((C)->daddr.ip), ntohs((C)->dport), \ | |
70 | (C)->protocol, (C)->state | |
1da177e4 LT |
71 | |
72 | /* | |
73 | * List of ports (up to IP_VS_APP_MAX_PORTS) to be handled by helper | |
74 | * First port is set to the default port. | |
75 | */ | |
28b06c38 SH |
76 | static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0}; |
77 | module_param_array(ports, ushort, NULL, 0); | |
70e76b76 | 78 | MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands"); |
1da177e4 | 79 | |
1da177e4 LT |
80 | |
81 | /* Dummy variable */ | |
82 | static int ip_vs_ftp_pasv; | |
83 | ||
84 | ||
85 | static int | |
86 | ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) | |
87 | { | |
88 | return 0; | |
89 | } | |
90 | ||
91 | ||
92 | static int | |
93 | ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) | |
94 | { | |
95 | return 0; | |
96 | } | |
97 | ||
98 | ||
99 | /* | |
100 | * Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started | |
101 | * with the "pattern" and terminated with the "term" character. | |
102 | * <addr,port> is in network order. | |
103 | */ | |
104 | static int ip_vs_ftp_get_addrport(char *data, char *data_limit, | |
105 | const char *pattern, size_t plen, char term, | |
014d730d | 106 | __be32 *addr, __be16 *port, |
1da177e4 LT |
107 | char **start, char **end) |
108 | { | |
109 | unsigned char p[6]; | |
110 | int i = 0; | |
111 | ||
112 | if (data_limit - data < plen) { | |
113 | /* check if there is partial match */ | |
114 | if (strnicmp(data, pattern, data_limit - data) == 0) | |
115 | return -1; | |
116 | else | |
117 | return 0; | |
118 | } | |
119 | ||
120 | if (strnicmp(data, pattern, plen) != 0) { | |
121 | return 0; | |
122 | } | |
123 | *start = data + plen; | |
124 | ||
125 | for (data = *start; *data != term; data++) { | |
126 | if (data == data_limit) | |
127 | return -1; | |
128 | } | |
129 | *end = data; | |
130 | ||
131 | memset(p, 0, sizeof(p)); | |
132 | for (data = *start; data != *end; data++) { | |
133 | if (*data >= '0' && *data <= '9') { | |
134 | p[i] = p[i]*10 + *data - '0'; | |
135 | } else if (*data == ',' && i < 5) { | |
136 | i++; | |
137 | } else { | |
138 | /* unexpected character */ | |
139 | return -1; | |
140 | } | |
141 | } | |
142 | ||
143 | if (i != 5) | |
144 | return -1; | |
145 | ||
96d2ca4e AV |
146 | *addr = get_unaligned((__be32 *)p); |
147 | *port = get_unaligned((__be16 *)(p + 4)); | |
1da177e4 LT |
148 | return 1; |
149 | } | |
150 | ||
7f1c4075 HE |
151 | /* |
152 | * Called from init_conntrack() as expectfn handler. | |
153 | */ | |
154 | static void | |
155 | ip_vs_expect_callback(struct nf_conn *ct, | |
156 | struct nf_conntrack_expect *exp) | |
157 | { | |
158 | struct nf_conntrack_tuple *orig, new_reply; | |
159 | struct ip_vs_conn *cp; | |
160 | ||
161 | if (exp->tuple.src.l3num != PF_INET) | |
162 | return; | |
163 | ||
164 | /* | |
165 | * We assume that no NF locks are held before this callback. | |
166 | * ip_vs_conn_out_get and ip_vs_conn_in_get should match their | |
167 | * expectations even if they use wildcard values, now we provide the | |
168 | * actual values from the newly created original conntrack direction. | |
169 | * The conntrack is confirmed when packet reaches IPVS hooks. | |
170 | */ | |
171 | ||
172 | /* RS->CLIENT */ | |
173 | orig = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; | |
174 | cp = ip_vs_conn_out_get(exp->tuple.src.l3num, orig->dst.protonum, | |
175 | &orig->src.u3, orig->src.u.tcp.port, | |
176 | &orig->dst.u3, orig->dst.u.tcp.port); | |
177 | if (cp) { | |
178 | /* Change reply CLIENT->RS to CLIENT->VS */ | |
179 | new_reply = ct->tuplehash[IP_CT_DIR_REPLY].tuple; | |
180 | IP_VS_DBG(7, "%s(): ct=%p, status=0x%lX, tuples=" FMT_TUPLE ", " | |
181 | FMT_TUPLE ", found inout cp=" FMT_CONN "\n", | |
182 | __func__, ct, ct->status, | |
183 | ARG_TUPLE(orig), ARG_TUPLE(&new_reply), | |
184 | ARG_CONN(cp)); | |
185 | new_reply.dst.u3 = cp->vaddr; | |
186 | new_reply.dst.u.tcp.port = cp->vport; | |
187 | IP_VS_DBG(7, "%s(): ct=%p, new tuples=" FMT_TUPLE ", " FMT_TUPLE | |
188 | ", inout cp=" FMT_CONN "\n", | |
189 | __func__, ct, | |
190 | ARG_TUPLE(orig), ARG_TUPLE(&new_reply), | |
191 | ARG_CONN(cp)); | |
192 | goto alter; | |
193 | } | |
194 | ||
195 | /* CLIENT->VS */ | |
196 | cp = ip_vs_conn_in_get(exp->tuple.src.l3num, orig->dst.protonum, | |
197 | &orig->src.u3, orig->src.u.tcp.port, | |
198 | &orig->dst.u3, orig->dst.u.tcp.port); | |
199 | if (cp) { | |
200 | /* Change reply VS->CLIENT to RS->CLIENT */ | |
201 | new_reply = ct->tuplehash[IP_CT_DIR_REPLY].tuple; | |
202 | IP_VS_DBG(7, "%s(): ct=%p, status=0x%lX, tuples=" FMT_TUPLE ", " | |
203 | FMT_TUPLE ", found outin cp=" FMT_CONN "\n", | |
204 | __func__, ct, ct->status, | |
205 | ARG_TUPLE(orig), ARG_TUPLE(&new_reply), | |
206 | ARG_CONN(cp)); | |
207 | new_reply.src.u3 = cp->daddr; | |
208 | new_reply.src.u.tcp.port = cp->dport; | |
209 | IP_VS_DBG(7, "%s(): ct=%p, new tuples=" FMT_TUPLE ", " | |
210 | FMT_TUPLE ", outin cp=" FMT_CONN "\n", | |
211 | __func__, ct, | |
212 | ARG_TUPLE(orig), ARG_TUPLE(&new_reply), | |
213 | ARG_CONN(cp)); | |
214 | goto alter; | |
215 | } | |
216 | ||
217 | IP_VS_DBG(7, "%s(): ct=%p, status=0x%lX, tuple=" FMT_TUPLE | |
218 | " - unknown expect\n", | |
219 | __func__, ct, ct->status, ARG_TUPLE(orig)); | |
220 | return; | |
221 | ||
222 | alter: | |
223 | /* Never alter conntrack for non-NAT conns */ | |
224 | if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ) | |
225 | nf_conntrack_alter_reply(ct, &new_reply); | |
226 | ip_vs_conn_put(cp); | |
227 | return; | |
228 | } | |
229 | ||
230 | /* | |
231 | * Create NF conntrack expectation with wildcard (optional) source port. | |
232 | * Then the default callback function will alter the reply and will confirm | |
233 | * the conntrack entry when the first packet comes. | |
234 | */ | |
235 | static void | |
236 | ip_vs_expect_related(struct sk_buff *skb, struct nf_conn *ct, | |
237 | struct ip_vs_conn *cp, u_int8_t proto, | |
238 | const __be16 *port, int from_rs) | |
239 | { | |
240 | struct nf_conntrack_expect *exp; | |
241 | ||
242 | BUG_ON(!ct || ct == &nf_conntrack_untracked); | |
243 | ||
244 | exp = nf_ct_expect_alloc(ct); | |
245 | if (!exp) | |
246 | return; | |
247 | ||
248 | if (from_rs) | |
249 | nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, | |
250 | nf_ct_l3num(ct), &cp->daddr, &cp->caddr, | |
251 | proto, port, &cp->cport); | |
252 | else | |
253 | nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, | |
254 | nf_ct_l3num(ct), &cp->caddr, &cp->vaddr, | |
255 | proto, port, &cp->vport); | |
256 | ||
257 | exp->expectfn = ip_vs_expect_callback; | |
258 | ||
259 | IP_VS_DBG(7, "%s(): ct=%p, expect tuple=" FMT_TUPLE "\n", | |
260 | __func__, ct, ARG_TUPLE(&exp->tuple)); | |
261 | nf_ct_expect_related(exp); | |
262 | nf_ct_expect_put(exp); | |
263 | } | |
1da177e4 LT |
264 | |
265 | /* | |
266 | * Look at outgoing ftp packets to catch the response to a PASV command | |
267 | * from the server (inside-to-outside). | |
268 | * When we see one, we build a connection entry with the client address, | |
269 | * client port 0 (unknown at the moment), the server address and the | |
270 | * server port. Mark the current connection entry as a control channel | |
271 | * of the new entry. All this work is just to make the data connection | |
272 | * can be scheduled to the right server later. | |
273 | * | |
274 | * The outgoing packet should be something like | |
275 | * "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)". | |
276 | * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number. | |
277 | */ | |
278 | static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, | |
3db05fea | 279 | struct sk_buff *skb, int *diff) |
1da177e4 LT |
280 | { |
281 | struct iphdr *iph; | |
282 | struct tcphdr *th; | |
283 | char *data, *data_limit; | |
284 | char *start, *end; | |
28364a59 | 285 | union nf_inet_addr from; |
014d730d | 286 | __be16 port; |
1da177e4 LT |
287 | struct ip_vs_conn *n_cp; |
288 | char buf[24]; /* xxx.xxx.xxx.xxx,ppp,ppp\000 */ | |
289 | unsigned buf_len; | |
7f1c4075 HE |
290 | int ret = 0; |
291 | enum ip_conntrack_info ctinfo; | |
292 | struct nf_conn *ct; | |
1da177e4 | 293 | |
a0eb662f JV |
294 | #ifdef CONFIG_IP_VS_IPV6 |
295 | /* This application helper doesn't work with IPv6 yet, | |
296 | * so turn this into a no-op for IPv6 packets | |
297 | */ | |
298 | if (cp->af == AF_INET6) | |
299 | return 1; | |
300 | #endif | |
301 | ||
1da177e4 LT |
302 | *diff = 0; |
303 | ||
304 | /* Only useful for established sessions */ | |
305 | if (cp->state != IP_VS_TCP_S_ESTABLISHED) | |
306 | return 1; | |
307 | ||
308 | /* Linear packets are much easier to deal with. */ | |
3db05fea | 309 | if (!skb_make_writable(skb, skb->len)) |
1da177e4 LT |
310 | return 0; |
311 | ||
312 | if (cp->app_data == &ip_vs_ftp_pasv) { | |
3db05fea | 313 | iph = ip_hdr(skb); |
1da177e4 LT |
314 | th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]); |
315 | data = (char *)th + (th->doff << 2); | |
3db05fea | 316 | data_limit = skb_tail_pointer(skb); |
1da177e4 LT |
317 | |
318 | if (ip_vs_ftp_get_addrport(data, data_limit, | |
319 | SERVER_STRING, | |
320 | sizeof(SERVER_STRING)-1, ')', | |
28364a59 | 321 | &from.ip, &port, |
1da177e4 LT |
322 | &start, &end) != 1) |
323 | return 1; | |
324 | ||
14d5e834 HH |
325 | IP_VS_DBG(7, "PASV response (%pI4:%d) -> %pI4:%d detected\n", |
326 | &from.ip, ntohs(port), &cp->caddr.ip, 0); | |
1da177e4 LT |
327 | |
328 | /* | |
329 | * Now update or create an connection entry for it | |
330 | */ | |
28364a59 JV |
331 | n_cp = ip_vs_conn_out_get(AF_INET, iph->protocol, &from, port, |
332 | &cp->caddr, 0); | |
1da177e4 | 333 | if (!n_cp) { |
28364a59 JV |
334 | n_cp = ip_vs_conn_new(AF_INET, IPPROTO_TCP, |
335 | &cp->caddr, 0, | |
336 | &cp->vaddr, port, | |
337 | &from, port, | |
1da177e4 LT |
338 | IP_VS_CONN_F_NO_CPORT, |
339 | cp->dest); | |
340 | if (!n_cp) | |
341 | return 0; | |
342 | ||
343 | /* add its controller */ | |
344 | ip_vs_control_add(n_cp, cp); | |
345 | } | |
346 | ||
347 | /* | |
348 | * Replace the old passive address with the new one | |
349 | */ | |
28364a59 | 350 | from.ip = n_cp->vaddr.ip; |
1da177e4 | 351 | port = n_cp->vport; |
1da05f50 JP |
352 | snprintf(buf, sizeof(buf), "%u,%u,%u,%u,%u,%u", |
353 | ((unsigned char *)&from.ip)[0], | |
354 | ((unsigned char *)&from.ip)[1], | |
355 | ((unsigned char *)&from.ip)[2], | |
356 | ((unsigned char *)&from.ip)[3], | |
357 | ntohs(port) >> 8, | |
358 | ntohs(port) & 0xFF); | |
359 | ||
1da177e4 LT |
360 | buf_len = strlen(buf); |
361 | ||
7f1c4075 | 362 | ct = nf_ct_get(skb, &ctinfo); |
7bcbf81a | 363 | if (ct && !nf_ct_is_untracked(ct) && nfct_nat(ct)) { |
7f1c4075 HE |
364 | /* If mangling fails this function will return 0 |
365 | * which will cause the packet to be dropped. | |
366 | * Mangling can only fail under memory pressure, | |
367 | * hopefully it will succeed on the retransmitted | |
368 | * packet. | |
369 | */ | |
370 | ret = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, | |
371 | start-data, end-start, | |
372 | buf, buf_len); | |
373 | if (ret) | |
374 | ip_vs_expect_related(skb, ct, n_cp, | |
375 | IPPROTO_TCP, NULL, 0); | |
376 | } | |
377 | ||
1da177e4 | 378 | /* |
7f1c4075 HE |
379 | * Not setting 'diff' is intentional, otherwise the sequence |
380 | * would be adjusted twice. | |
1da177e4 | 381 | */ |
1da177e4 LT |
382 | |
383 | cp->app_data = NULL; | |
384 | ip_vs_tcp_conn_listen(n_cp); | |
385 | ip_vs_conn_put(n_cp); | |
386 | return ret; | |
387 | } | |
388 | return 1; | |
389 | } | |
390 | ||
391 | ||
392 | /* | |
393 | * Look at incoming ftp packets to catch the PASV/PORT command | |
394 | * (outside-to-inside). | |
395 | * | |
396 | * The incoming packet having the PORT command should be something like | |
397 | * "PORT xxx,xxx,xxx,xxx,ppp,ppp\n". | |
398 | * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number. | |
399 | * In this case, we create a connection entry using the client address and | |
400 | * port, so that the active ftp data connection from the server can reach | |
401 | * the client. | |
402 | */ | |
403 | static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, | |
3db05fea | 404 | struct sk_buff *skb, int *diff) |
1da177e4 LT |
405 | { |
406 | struct iphdr *iph; | |
407 | struct tcphdr *th; | |
408 | char *data, *data_start, *data_limit; | |
409 | char *start, *end; | |
28364a59 | 410 | union nf_inet_addr to; |
014d730d | 411 | __be16 port; |
1da177e4 LT |
412 | struct ip_vs_conn *n_cp; |
413 | ||
a0eb662f JV |
414 | #ifdef CONFIG_IP_VS_IPV6 |
415 | /* This application helper doesn't work with IPv6 yet, | |
416 | * so turn this into a no-op for IPv6 packets | |
417 | */ | |
418 | if (cp->af == AF_INET6) | |
419 | return 1; | |
420 | #endif | |
421 | ||
1da177e4 LT |
422 | /* no diff required for incoming packets */ |
423 | *diff = 0; | |
424 | ||
425 | /* Only useful for established sessions */ | |
426 | if (cp->state != IP_VS_TCP_S_ESTABLISHED) | |
427 | return 1; | |
428 | ||
429 | /* Linear packets are much easier to deal with. */ | |
3db05fea | 430 | if (!skb_make_writable(skb, skb->len)) |
1da177e4 LT |
431 | return 0; |
432 | ||
433 | /* | |
434 | * Detecting whether it is passive | |
435 | */ | |
3db05fea | 436 | iph = ip_hdr(skb); |
1da177e4 LT |
437 | th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]); |
438 | ||
439 | /* Since there may be OPTIONS in the TCP packet and the HLEN is | |
440 | the length of the header in 32-bit multiples, it is accurate | |
441 | to calculate data address by th+HLEN*4 */ | |
442 | data = data_start = (char *)th + (th->doff << 2); | |
3db05fea | 443 | data_limit = skb_tail_pointer(skb); |
1da177e4 LT |
444 | |
445 | while (data <= data_limit - 6) { | |
446 | if (strnicmp(data, "PASV\r\n", 6) == 0) { | |
447 | /* Passive mode on */ | |
5e7ddac7 | 448 | IP_VS_DBG(7, "got PASV at %td of %td\n", |
1da177e4 LT |
449 | data - data_start, |
450 | data_limit - data_start); | |
451 | cp->app_data = &ip_vs_ftp_pasv; | |
452 | return 1; | |
453 | } | |
454 | data++; | |
455 | } | |
456 | ||
457 | /* | |
458 | * To support virtual FTP server, the scenerio is as follows: | |
459 | * FTP client ----> Load Balancer ----> FTP server | |
460 | * First detect the port number in the application data, | |
461 | * then create a new connection entry for the coming data | |
462 | * connection. | |
463 | */ | |
464 | if (ip_vs_ftp_get_addrport(data_start, data_limit, | |
465 | CLIENT_STRING, sizeof(CLIENT_STRING)-1, | |
28364a59 | 466 | '\r', &to.ip, &port, |
1da177e4 LT |
467 | &start, &end) != 1) |
468 | return 1; | |
469 | ||
14d5e834 | 470 | IP_VS_DBG(7, "PORT %pI4:%d detected\n", &to.ip, ntohs(port)); |
1da177e4 LT |
471 | |
472 | /* Passive mode off */ | |
473 | cp->app_data = NULL; | |
474 | ||
475 | /* | |
476 | * Now update or create a connection entry for it | |
477 | */ | |
14d5e834 | 478 | IP_VS_DBG(7, "protocol %s %pI4:%d %pI4:%d\n", |
1da177e4 | 479 | ip_vs_proto_name(iph->protocol), |
14d5e834 | 480 | &to.ip, ntohs(port), &cp->vaddr.ip, 0); |
1da177e4 | 481 | |
28364a59 JV |
482 | n_cp = ip_vs_conn_in_get(AF_INET, iph->protocol, |
483 | &to, port, | |
484 | &cp->vaddr, htons(ntohs(cp->vport)-1)); | |
1da177e4 | 485 | if (!n_cp) { |
28364a59 JV |
486 | n_cp = ip_vs_conn_new(AF_INET, IPPROTO_TCP, |
487 | &to, port, | |
488 | &cp->vaddr, htons(ntohs(cp->vport)-1), | |
489 | &cp->daddr, htons(ntohs(cp->dport)-1), | |
1da177e4 LT |
490 | 0, |
491 | cp->dest); | |
492 | if (!n_cp) | |
493 | return 0; | |
494 | ||
495 | /* add its controller */ | |
496 | ip_vs_control_add(n_cp, cp); | |
497 | } | |
498 | ||
499 | /* | |
500 | * Move tunnel to listen state | |
501 | */ | |
502 | ip_vs_tcp_conn_listen(n_cp); | |
503 | ip_vs_conn_put(n_cp); | |
504 | ||
505 | return 1; | |
506 | } | |
507 | ||
508 | ||
509 | static struct ip_vs_app ip_vs_ftp = { | |
510 | .name = "ftp", | |
511 | .type = IP_VS_APP_TYPE_FTP, | |
512 | .protocol = IPPROTO_TCP, | |
513 | .module = THIS_MODULE, | |
514 | .incs_list = LIST_HEAD_INIT(ip_vs_ftp.incs_list), | |
515 | .init_conn = ip_vs_ftp_init_conn, | |
516 | .done_conn = ip_vs_ftp_done_conn, | |
517 | .bind_conn = NULL, | |
518 | .unbind_conn = NULL, | |
519 | .pkt_out = ip_vs_ftp_out, | |
520 | .pkt_in = ip_vs_ftp_in, | |
521 | }; | |
522 | ||
523 | ||
524 | /* | |
525 | * ip_vs_ftp initialization | |
526 | */ | |
527 | static int __init ip_vs_ftp_init(void) | |
528 | { | |
529 | int i, ret; | |
530 | struct ip_vs_app *app = &ip_vs_ftp; | |
531 | ||
532 | ret = register_ip_vs_app(app); | |
533 | if (ret) | |
534 | return ret; | |
535 | ||
536 | for (i=0; i<IP_VS_APP_MAX_PORTS; i++) { | |
537 | if (!ports[i]) | |
538 | continue; | |
539 | ret = register_ip_vs_app_inc(app, app->protocol, ports[i]); | |
540 | if (ret) | |
541 | break; | |
1e3e238e HE |
542 | pr_info("%s: loaded support on port[%d] = %d\n", |
543 | app->name, i, ports[i]); | |
1da177e4 LT |
544 | } |
545 | ||
546 | if (ret) | |
547 | unregister_ip_vs_app(app); | |
548 | ||
549 | return ret; | |
550 | } | |
551 | ||
552 | ||
553 | /* | |
554 | * ip_vs_ftp finish. | |
555 | */ | |
556 | static void __exit ip_vs_ftp_exit(void) | |
557 | { | |
558 | unregister_ip_vs_app(&ip_vs_ftp); | |
559 | } | |
560 | ||
561 | ||
562 | module_init(ip_vs_ftp_init); | |
563 | module_exit(ip_vs_ftp_exit); | |
564 | MODULE_LICENSE("GPL"); |