1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ip_vs_ftp.c: IPVS ftp application module |
4 | * |
5 | * Authors: Wensong Zhang <wensong@linuxvirtualserver.org> |
6 | * |
7 | * Changes: |
8 | * |
9 | * Most code here is taken from ip_masq_ftp.c in kernel 2.2. The difference |
10 | * is that ip_vs_ftp module handles the reverse direction to ip_masq_ftp. |
11 | * |
12 | * IP_MASQ_FTP ftp masquerading module |
13 | * |
14 | * Version: @(#)ip_masq_ftp.c 0.04 02/05/96 |
15 | * |
16 | * Author: Wouter Gadeyne |
17 | */ |
18 | |
19 | #define KMSG_COMPONENT "IPVS" |
20 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
21 | |
22 | #include <linux/module.h> |
23 | #include <linux/moduleparam.h> |
24 | #include <linux/kernel.h> |
25 | #include <linux/skbuff.h> |
26 | #include <linux/ctype.h> |
27 | #include <linux/inet.h> |
28 | #include <linux/in.h> |
29 | #include <linux/ip.h> |
30 | #include <linux/netfilter.h> |
31 | #include <net/netfilter/nf_conntrack.h> |
32 | #include <net/netfilter/nf_conntrack_expect.h> |
33 | #include <net/netfilter/nf_nat.h> |
34 | #include <net/netfilter/nf_nat_helper.h> |
35 | #include <linux/gfp.h> |
36 | #include <net/protocol.h> |
37 | #include <net/tcp.h> |
38 | #include <asm/unaligned.h> |
39 | |
40 | #include <net/ip_vs.h> |
41 | |
42 | |
43 | #define SERVER_STRING_PASV "227 " |
44 | #define CLIENT_STRING_PORT "PORT" |
45 | #define SERVER_STRING_EPSV "229 " |
46 | #define CLIENT_STRING_EPRT "EPRT" |
47 | |
48 | enum { |
49 | IP_VS_FTP_ACTIVE = 0, |
50 | IP_VS_FTP_PORT = 0, |
51 | IP_VS_FTP_PASV, |
52 | IP_VS_FTP_EPRT, |
53 | IP_VS_FTP_EPSV, |
54 | }; |
55 | |
56 | /* |
57 | * List of ports (up to IP_VS_APP_MAX_PORTS) to be handled by helper |
58 | * First port is set to the default port. |
59 | */ |
60 | static unsigned int ports_count = 1; |
61 | static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0}; |
62 | module_param_array(ports, ushort, &ports_count, 0444); |
63 | MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands" ); |
64 | |
65 | |
66 | static char *ip_vs_ftp_data_ptr(struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) |
67 | { |
68 | struct tcphdr *th = (struct tcphdr *)((char *)skb->data + ipvsh->len); |
69 | |
70 | if ((th->doff << 2) < sizeof(struct tcphdr)) |
71 | return NULL; |
72 | |
73 | return (char *)th + (th->doff << 2); |
74 | } |
75 | |
76 | static int |
77 | ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) |
78 | { |
79 | /* We use connection tracking for the command connection */ |
80 | cp->flags |= IP_VS_CONN_F_NFCT; |
81 | return 0; |
82 | } |
83 | |
84 | |
85 | static int |
86 | ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) |
87 | { |
88 | return 0; |
89 | } |
90 | |
91 | |
92 | /* Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started |
93 | * with the "pattern". <addr,port> is in network order. |
94 | * Parse extended format depending on ext. In this case addr can be pre-set. |
95 | */ |
96 | static int ip_vs_ftp_get_addrport(char *data, char *data_limit, |
97 | const char *pattern, size_t plen, |
98 | char skip, bool ext, int mode, |
99 | union nf_inet_addr *addr, __be16 *port, |
100 | __u16 af, char **start, char **end) |
101 | { |
102 | char *s, c; |
103 | unsigned char p[6]; |
104 | char edelim; |
105 | __u16 hport; |
106 | int i = 0; |
107 | |
108 | if (data_limit - data < plen) { |
109 | /* check if there is partial match */ |
110 | if (strncasecmp(s1: data, s2: pattern, n: data_limit - data) == 0) |
111 | return -1; |
112 | else |
113 | return 0; |
114 | } |
115 | |
116 | if (strncasecmp(s1: data, s2: pattern, n: plen) != 0) { |
117 | return 0; |
118 | } |
119 | s = data + plen; |
120 | if (skip) { |
121 | bool found = false; |
122 | |
123 | for (;; s++) { |
124 | if (s == data_limit) |
125 | return -1; |
126 | if (!found) { |
127 | /* "(" is optional for non-extended format, |
128 | * so catch the start of IPv4 address |
129 | */ |
130 | if (!ext && isdigit(c: *s)) |
131 | break; |
132 | if (*s == skip) |
133 | found = true; |
134 | } else if (*s != skip) { |
135 | break; |
136 | } |
137 | } |
138 | } |
139 | /* Old IPv4-only format? */ |
140 | if (!ext) { |
141 | p[0] = 0; |
142 | for (data = s; ; data++) { |
143 | if (data == data_limit) |
144 | return -1; |
145 | c = *data; |
146 | if (isdigit(c)) { |
147 | p[i] = p[i]*10 + c - '0'; |
148 | } else if (c == ',' && i < 5) { |
149 | i++; |
150 | p[i] = 0; |
151 | } else { |
152 | /* unexpected character or terminator */ |
153 | break; |
154 | } |
155 | } |
156 | |
157 | if (i != 5) |
158 | return -1; |
159 | |
160 | *start = s; |
161 | *end = data; |
162 | addr->ip = get_unaligned((__be32 *) p); |
163 | *port = get_unaligned((__be16 *) (p + 4)); |
164 | return 1; |
165 | } |
166 | if (s == data_limit) |
167 | return -1; |
168 | *start = s; |
169 | edelim = *s++; |
170 | if (edelim < 33 || edelim > 126) |
171 | return -1; |
172 | if (s == data_limit) |
173 | return -1; |
174 | if (*s == edelim) { |
175 | /* Address family is usually missing for EPSV response */ |
176 | if (mode != IP_VS_FTP_EPSV) |
177 | return -1; |
178 | s++; |
179 | if (s == data_limit) |
180 | return -1; |
181 | /* Then address should be missing too */ |
182 | if (*s != edelim) |
183 | return -1; |
184 | /* Caller can pre-set addr, if needed */ |
185 | s++; |
186 | } else { |
187 | const char *ep; |
188 | |
189 | /* We allow address only from same family */ |
190 | if (af == AF_INET6 && *s != '2') |
191 | return -1; |
192 | if (af == AF_INET && *s != '1') |
193 | return -1; |
194 | s++; |
195 | if (s == data_limit) |
196 | return -1; |
197 | if (*s != edelim) |
198 | return -1; |
199 | s++; |
200 | if (s == data_limit) |
201 | return -1; |
202 | if (af == AF_INET6) { |
203 | if (in6_pton(src: s, srclen: data_limit - s, dst: (u8 *)addr, delim: edelim, |
204 | end: &ep) <= 0) |
205 | return -1; |
206 | } else { |
207 | if (in4_pton(src: s, srclen: data_limit - s, dst: (u8 *)addr, delim: edelim, |
208 | end: &ep) <= 0) |
209 | return -1; |
210 | } |
211 | s = (char *) ep; |
212 | if (s == data_limit) |
213 | return -1; |
214 | if (*s != edelim) |
215 | return -1; |
216 | s++; |
217 | } |
218 | for (hport = 0; ; s++) |
219 | { |
220 | if (s == data_limit) |
221 | return -1; |
222 | if (!isdigit(c: *s)) |
223 | break; |
224 | hport = hport * 10 + *s - '0'; |
225 | } |
226 | if (s == data_limit || !hport || *s != edelim) |
227 | return -1; |
228 | s++; |
229 | *end = s; |
230 | *port = htons(hport); |
231 | return 1; |
232 | } |
233 | |
234 | /* Look at outgoing ftp packets to catch the response to a PASV/EPSV command |
235 | * from the server (inside-to-outside). |
236 | * When we see one, we build a connection entry with the client address, |
237 | * client port 0 (unknown at the moment), the server address and the |
238 | * server port. Mark the current connection entry as a control channel |
239 | * of the new entry. All this work is just to make the data connection |
240 | * can be scheduled to the right server later. |
241 | * |
242 | * The outgoing packet should be something like |
243 | * "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)". |
244 | * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number. |
245 | * The extended format for EPSV response provides usually only port: |
246 | * "229 Entering Extended Passive Mode (|||ppp|)" |
247 | */ |
248 | static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, |
249 | struct sk_buff *skb, int *diff, |
250 | struct ip_vs_iphdr *ipvsh) |
251 | { |
252 | char *data, *data_limit; |
253 | char *start, *end; |
254 | union nf_inet_addr from; |
255 | __be16 port; |
256 | struct ip_vs_conn *n_cp; |
257 | char buf[24]; /* xxx.xxx.xxx.xxx,ppp,ppp\000 */ |
258 | unsigned int buf_len; |
259 | int ret = 0; |
260 | enum ip_conntrack_info ctinfo; |
261 | struct nf_conn *ct; |
262 | |
263 | *diff = 0; |
264 | |
265 | /* Only useful for established sessions */ |
266 | if (cp->state != IP_VS_TCP_S_ESTABLISHED) |
267 | return 1; |
268 | |
269 | /* Linear packets are much easier to deal with. */ |
270 | if (skb_ensure_writable(skb, write_len: skb->len)) |
271 | return 0; |
272 | |
273 | if (cp->app_data == (void *) IP_VS_FTP_PASV) { |
274 | data = ip_vs_ftp_data_ptr(skb, ipvsh); |
275 | data_limit = skb_tail_pointer(skb); |
276 | |
277 | if (!data || data >= data_limit) |
278 | return 1; |
279 | |
280 | if (ip_vs_ftp_get_addrport(data, data_limit, |
281 | SERVER_STRING_PASV, |
282 | plen: sizeof(SERVER_STRING_PASV)-1, |
283 | skip: '(', ext: false, mode: IP_VS_FTP_PASV, |
284 | addr: &from, port: &port, af: cp->af, |
285 | start: &start, end: &end) != 1) |
286 | return 1; |
287 | |
288 | IP_VS_DBG(7, "PASV response (%pI4:%u) -> %pI4:%u detected\n" , |
289 | &from.ip, ntohs(port), &cp->caddr.ip, 0); |
290 | } else if (cp->app_data == (void *) IP_VS_FTP_EPSV) { |
291 | data = ip_vs_ftp_data_ptr(skb, ipvsh); |
292 | data_limit = skb_tail_pointer(skb); |
293 | |
294 | if (!data || data >= data_limit) |
295 | return 1; |
296 | |
297 | /* Usually, data address is not specified but |
298 | * we support different address, so pre-set it. |
299 | */ |
300 | from = cp->daddr; |
301 | if (ip_vs_ftp_get_addrport(data, data_limit, |
302 | SERVER_STRING_EPSV, |
303 | plen: sizeof(SERVER_STRING_EPSV)-1, |
304 | skip: '(', ext: true, mode: IP_VS_FTP_EPSV, |
305 | addr: &from, port: &port, af: cp->af, |
306 | start: &start, end: &end) != 1) |
307 | return 1; |
308 | |
309 | IP_VS_DBG_BUF(7, "EPSV response (%s:%u) -> %s:%u detected\n" , |
310 | IP_VS_DBG_ADDR(cp->af, &from), ntohs(port), |
311 | IP_VS_DBG_ADDR(cp->af, &cp->caddr), 0); |
312 | } else { |
313 | return 1; |
314 | } |
315 | |
316 | /* Now update or create a connection entry for it */ |
317 | { |
318 | struct ip_vs_conn_param p; |
319 | |
320 | ip_vs_conn_fill_param(ipvs: cp->ipvs, af: cp->af, |
321 | protocol: ipvsh->protocol, caddr: &from, cport: port, |
322 | vaddr: &cp->caddr, vport: 0, p: &p); |
323 | n_cp = ip_vs_conn_out_get(p: &p); |
324 | } |
325 | if (!n_cp) { |
326 | struct ip_vs_conn_param p; |
327 | |
328 | ip_vs_conn_fill_param(ipvs: cp->ipvs, |
329 | af: cp->af, protocol: ipvsh->protocol, caddr: &cp->caddr, |
330 | cport: 0, vaddr: &cp->vaddr, vport: port, p: &p); |
331 | n_cp = ip_vs_conn_new(p: &p, dest_af: cp->af, daddr: &from, dport: port, |
332 | IP_VS_CONN_F_NO_CPORT | |
333 | IP_VS_CONN_F_NFCT, |
334 | dest: cp->dest, fwmark: skb->mark); |
335 | if (!n_cp) |
336 | return 0; |
337 | |
338 | /* add its controller */ |
339 | ip_vs_control_add(cp: n_cp, ctl_cp: cp); |
340 | } |
341 | |
342 | /* Replace the old passive address with the new one */ |
343 | if (cp->app_data == (void *) IP_VS_FTP_PASV) { |
344 | from.ip = n_cp->vaddr.ip; |
345 | port = n_cp->vport; |
346 | snprintf(buf, size: sizeof(buf), fmt: "%u,%u,%u,%u,%u,%u" , |
347 | ((unsigned char *)&from.ip)[0], |
348 | ((unsigned char *)&from.ip)[1], |
349 | ((unsigned char *)&from.ip)[2], |
350 | ((unsigned char *)&from.ip)[3], |
351 | ntohs(port) >> 8, |
352 | ntohs(port) & 0xFF); |
353 | } else if (cp->app_data == (void *) IP_VS_FTP_EPSV) { |
354 | from = n_cp->vaddr; |
355 | port = n_cp->vport; |
356 | /* Only port, client will use VIP for the data connection */ |
357 | snprintf(buf, size: sizeof(buf), fmt: "|||%u|" , |
358 | ntohs(port)); |
359 | } else { |
360 | *buf = 0; |
361 | } |
362 | buf_len = strlen(buf); |
363 | |
364 | ct = nf_ct_get(skb, ctinfo: &ctinfo); |
365 | if (ct) { |
366 | bool mangled; |
367 | |
368 | /* If mangling fails this function will return 0 |
369 | * which will cause the packet to be dropped. |
370 | * Mangling can only fail under memory pressure, |
371 | * hopefully it will succeed on the retransmitted |
372 | * packet. |
373 | */ |
374 | mangled = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, |
375 | protoff: ipvsh->len, |
376 | match_offset: start - data, |
377 | match_len: end - start, |
378 | rep_buffer: buf, rep_len: buf_len); |
379 | if (mangled) { |
380 | ip_vs_nfct_expect_related(skb, ct, cp: n_cp, |
381 | proto: ipvsh->protocol, port: 0, from_rs: 0); |
382 | if (skb->ip_summed == CHECKSUM_COMPLETE) |
383 | skb->ip_summed = CHECKSUM_UNNECESSARY; |
384 | /* csum is updated */ |
385 | ret = 1; |
386 | } |
387 | } |
388 | |
389 | /* Not setting 'diff' is intentional, otherwise the sequence |
390 | * would be adjusted twice. |
391 | */ |
392 | |
393 | cp->app_data = (void *) IP_VS_FTP_ACTIVE; |
394 | ip_vs_tcp_conn_listen(cp: n_cp); |
395 | ip_vs_conn_put(cp: n_cp); |
396 | return ret; |
397 | } |
398 | |
399 | |
400 | /* Look at incoming ftp packets to catch the PASV/PORT/EPRT/EPSV command |
401 | * (outside-to-inside). |
402 | * |
403 | * The incoming packet having the PORT command should be something like |
404 | * "PORT xxx,xxx,xxx,xxx,ppp,ppp\n". |
405 | * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number. |
406 | * In this case, we create a connection entry using the client address and |
407 | * port, so that the active ftp data connection from the server can reach |
408 | * the client. |
409 | * Extended format: |
410 | * "EPSV\r\n" when client requests server address from same family |
411 | * "EPSV 1\r\n" when client requests IPv4 server address |
412 | * "EPSV 2\r\n" when client requests IPv6 server address |
413 | * "EPSV ALL\r\n" - not supported |
414 | * EPRT with specified delimiter (ASCII 33..126), "|" by default: |
415 | * "EPRT |1|IPv4ADDR|PORT|\r\n" when client provides IPv4 addrport |
416 | * "EPRT |2|IPv6ADDR|PORT|\r\n" when client provides IPv6 addrport |
417 | */ |
418 | static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, |
419 | struct sk_buff *skb, int *diff, |
420 | struct ip_vs_iphdr *ipvsh) |
421 | { |
422 | char *data, *data_start, *data_limit; |
423 | char *start, *end; |
424 | union nf_inet_addr to; |
425 | __be16 port; |
426 | struct ip_vs_conn *n_cp; |
427 | |
428 | /* no diff required for incoming packets */ |
429 | *diff = 0; |
430 | |
431 | /* Only useful for established sessions */ |
432 | if (cp->state != IP_VS_TCP_S_ESTABLISHED) |
433 | return 1; |
434 | |
435 | /* Linear packets are much easier to deal with. */ |
436 | if (skb_ensure_writable(skb, write_len: skb->len)) |
437 | return 0; |
438 | |
439 | data = data_start = ip_vs_ftp_data_ptr(skb, ipvsh); |
440 | data_limit = skb_tail_pointer(skb); |
441 | if (!data || data >= data_limit) |
442 | return 1; |
443 | |
444 | while (data <= data_limit - 6) { |
445 | if (cp->af == AF_INET && |
446 | strncasecmp(s1: data, s2: "PASV\r\n" , n: 6) == 0) { |
447 | /* Passive mode on */ |
448 | IP_VS_DBG(7, "got PASV at %td of %td\n" , |
449 | data - data_start, |
450 | data_limit - data_start); |
451 | cp->app_data = (void *) IP_VS_FTP_PASV; |
452 | return 1; |
453 | } |
454 | |
455 | /* EPSV or EPSV<space><net-prt> */ |
456 | if (strncasecmp(s1: data, s2: "EPSV" , n: 4) == 0 && |
457 | (data[4] == ' ' || data[4] == '\r')) { |
458 | if (data[4] == ' ') { |
459 | char proto = data[5]; |
460 | |
461 | if (data > data_limit - 7 || data[6] != '\r') |
462 | return 1; |
463 | |
464 | #ifdef CONFIG_IP_VS_IPV6 |
465 | if (cp->af == AF_INET6 && proto == '2') { |
466 | } else |
467 | #endif |
468 | if (cp->af == AF_INET && proto == '1') { |
469 | } else { |
470 | return 1; |
471 | } |
472 | } |
473 | /* Extended Passive mode on */ |
474 | IP_VS_DBG(7, "got EPSV at %td of %td\n" , |
475 | data - data_start, |
476 | data_limit - data_start); |
477 | cp->app_data = (void *) IP_VS_FTP_EPSV; |
478 | return 1; |
479 | } |
480 | |
481 | data++; |
482 | } |
483 | |
484 | /* |
485 | * To support virtual FTP server, the scenerio is as follows: |
486 | * FTP client ----> Load Balancer ----> FTP server |
487 | * First detect the port number in the application data, |
488 | * then create a new connection entry for the coming data |
489 | * connection. |
490 | */ |
491 | if (cp->af == AF_INET && |
492 | ip_vs_ftp_get_addrport(data: data_start, data_limit, |
493 | CLIENT_STRING_PORT, |
494 | plen: sizeof(CLIENT_STRING_PORT)-1, |
495 | skip: ' ', ext: false, mode: IP_VS_FTP_PORT, |
496 | addr: &to, port: &port, af: cp->af, |
497 | start: &start, end: &end) == 1) { |
498 | |
499 | IP_VS_DBG(7, "PORT %pI4:%u detected\n" , &to.ip, ntohs(port)); |
500 | |
501 | /* Now update or create a connection entry for it */ |
502 | IP_VS_DBG(7, "protocol %s %pI4:%u %pI4:%u\n" , |
503 | ip_vs_proto_name(ipvsh->protocol), |
504 | &to.ip, ntohs(port), &cp->vaddr.ip, |
505 | ntohs(cp->vport)-1); |
506 | } else if (ip_vs_ftp_get_addrport(data: data_start, data_limit, |
507 | CLIENT_STRING_EPRT, |
508 | plen: sizeof(CLIENT_STRING_EPRT)-1, |
509 | skip: ' ', ext: true, mode: IP_VS_FTP_EPRT, |
510 | addr: &to, port: &port, af: cp->af, |
511 | start: &start, end: &end) == 1) { |
512 | |
513 | IP_VS_DBG_BUF(7, "EPRT %s:%u detected\n" , |
514 | IP_VS_DBG_ADDR(cp->af, &to), ntohs(port)); |
515 | |
516 | /* Now update or create a connection entry for it */ |
517 | IP_VS_DBG_BUF(7, "protocol %s %s:%u %s:%u\n" , |
518 | ip_vs_proto_name(ipvsh->protocol), |
519 | IP_VS_DBG_ADDR(cp->af, &to), ntohs(port), |
520 | IP_VS_DBG_ADDR(cp->af, &cp->vaddr), |
521 | ntohs(cp->vport)-1); |
522 | } else { |
523 | return 1; |
524 | } |
525 | |
526 | /* Passive mode off */ |
527 | cp->app_data = (void *) IP_VS_FTP_ACTIVE; |
528 | |
529 | { |
530 | struct ip_vs_conn_param p; |
531 | ip_vs_conn_fill_param(ipvs: cp->ipvs, af: cp->af, |
532 | protocol: ipvsh->protocol, caddr: &to, cport: port, vaddr: &cp->vaddr, |
533 | htons(ntohs(cp->vport)-1), p: &p); |
534 | n_cp = ip_vs_conn_in_get(p: &p); |
535 | if (!n_cp) { |
536 | n_cp = ip_vs_conn_new(p: &p, dest_af: cp->af, daddr: &cp->daddr, |
537 | htons(ntohs(cp->dport)-1), |
538 | IP_VS_CONN_F_NFCT, dest: cp->dest, |
539 | fwmark: skb->mark); |
540 | if (!n_cp) |
541 | return 0; |
542 | |
543 | /* add its controller */ |
544 | ip_vs_control_add(cp: n_cp, ctl_cp: cp); |
545 | } |
546 | } |
547 | |
548 | /* |
549 | * Move tunnel to listen state |
550 | */ |
551 | ip_vs_tcp_conn_listen(cp: n_cp); |
552 | ip_vs_conn_put(cp: n_cp); |
553 | |
554 | return 1; |
555 | } |
556 | |
557 | |
558 | static struct ip_vs_app ip_vs_ftp = { |
559 | .name = "ftp" , |
560 | .type = IP_VS_APP_TYPE_FTP, |
561 | .protocol = IPPROTO_TCP, |
562 | .module = THIS_MODULE, |
563 | .incs_list = LIST_HEAD_INIT(ip_vs_ftp.incs_list), |
564 | .init_conn = ip_vs_ftp_init_conn, |
565 | .done_conn = ip_vs_ftp_done_conn, |
566 | .bind_conn = NULL, |
567 | .unbind_conn = NULL, |
568 | .pkt_out = ip_vs_ftp_out, |
569 | .pkt_in = ip_vs_ftp_in, |
570 | }; |
571 | |
572 | /* |
573 | * per netns ip_vs_ftp initialization |
574 | */ |
575 | static int __net_init __ip_vs_ftp_init(struct net *net) |
576 | { |
577 | int i, ret; |
578 | struct ip_vs_app *app; |
579 | struct netns_ipvs *ipvs = net_ipvs(net); |
580 | |
581 | if (!ipvs) |
582 | return -ENOENT; |
583 | |
584 | app = register_ip_vs_app(ipvs, app: &ip_vs_ftp); |
585 | if (IS_ERR(ptr: app)) |
586 | return PTR_ERR(ptr: app); |
587 | |
588 | for (i = 0; i < ports_count; i++) { |
589 | if (!ports[i]) |
590 | continue; |
591 | ret = register_ip_vs_app_inc(ipvs, app, proto: app->protocol, port: ports[i]); |
592 | if (ret) |
593 | goto err_unreg; |
594 | } |
595 | return 0; |
596 | |
597 | err_unreg: |
598 | unregister_ip_vs_app(ipvs, app: &ip_vs_ftp); |
599 | return ret; |
600 | } |
601 | /* |
602 | * netns exit |
603 | */ |
604 | static void __ip_vs_ftp_exit(struct net *net) |
605 | { |
606 | struct netns_ipvs *ipvs = net_ipvs(net); |
607 | |
608 | if (!ipvs) |
609 | return; |
610 | |
611 | unregister_ip_vs_app(ipvs, app: &ip_vs_ftp); |
612 | } |
613 | |
614 | static struct pernet_operations ip_vs_ftp_ops = { |
615 | .init = __ip_vs_ftp_init, |
616 | .exit = __ip_vs_ftp_exit, |
617 | }; |
618 | |
619 | static int __init ip_vs_ftp_init(void) |
620 | { |
621 | /* rcu_barrier() is called by netns on error */ |
622 | return register_pernet_subsys(&ip_vs_ftp_ops); |
623 | } |
624 | |
625 | /* |
626 | * ip_vs_ftp finish. |
627 | */ |
628 | static void __exit ip_vs_ftp_exit(void) |
629 | { |
630 | unregister_pernet_subsys(&ip_vs_ftp_ops); |
631 | /* rcu_barrier() is called by netns */ |
632 | } |
633 | |
634 | |
635 | module_init(ip_vs_ftp_init); |
636 | module_exit(ip_vs_ftp_exit); |
637 | MODULE_LICENSE("GPL" ); |
638 | MODULE_DESCRIPTION("ipvs ftp helper" ); |
639 | |