// SPDX-License-Identifier: GPL-2.0-or-later 
 | 
/* 
 | 
 * ip_vs_ftp.c: IPVS ftp application module 
 | 
 * 
 | 
 * Authors:    Wensong Zhang <wensong@linuxvirtualserver.org> 
 | 
 * 
 | 
 * Changes: 
 | 
 * 
 | 
 * Most code here is taken from ip_masq_ftp.c in kernel 2.2. The difference 
 | 
 * is that ip_vs_ftp module handles the reverse direction to ip_masq_ftp. 
 | 
 * 
 | 
 *        IP_MASQ_FTP ftp masquerading module 
 | 
 * 
 | 
 * Version:    @(#)ip_masq_ftp.c 0.04   02/05/96 
 | 
 * 
 | 
 * Author:    Wouter Gadeyne 
 | 
 */ 
 | 
  
 | 
#define KMSG_COMPONENT "IPVS" 
 | 
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 
 | 
  
 | 
#include <linux/module.h> 
 | 
#include <linux/moduleparam.h> 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/skbuff.h> 
 | 
#include <linux/ctype.h> 
 | 
#include <linux/inet.h> 
 | 
#include <linux/in.h> 
 | 
#include <linux/ip.h> 
 | 
#include <linux/netfilter.h> 
 | 
#include <net/netfilter/nf_conntrack.h> 
 | 
#include <net/netfilter/nf_conntrack_expect.h> 
 | 
#include <net/netfilter/nf_nat.h> 
 | 
#include <net/netfilter/nf_nat_helper.h> 
 | 
#include <linux/gfp.h> 
 | 
#include <net/protocol.h> 
 | 
#include <net/tcp.h> 
 | 
#include <asm/unaligned.h> 
 | 
  
 | 
#include <net/ip_vs.h> 
 | 
  
 | 
  
 | 
#define SERVER_STRING_PASV "227 " 
 | 
#define CLIENT_STRING_PORT "PORT" 
 | 
#define SERVER_STRING_EPSV "229 " 
 | 
#define CLIENT_STRING_EPRT "EPRT" 
 | 
  
 | 
enum { 
 | 
    IP_VS_FTP_ACTIVE = 0, 
 | 
    IP_VS_FTP_PORT = 0, 
 | 
    IP_VS_FTP_PASV, 
 | 
    IP_VS_FTP_EPRT, 
 | 
    IP_VS_FTP_EPSV, 
 | 
}; 
 | 
  
 | 
/* 
 | 
 * List of ports (up to IP_VS_APP_MAX_PORTS) to be handled by helper 
 | 
 * First port is set to the default port. 
 | 
 */ 
 | 
static unsigned int ports_count = 1; 
 | 
static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0}; 
 | 
module_param_array(ports, ushort, &ports_count, 0444); 
 | 
MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands"); 
 | 
  
 | 
  
 | 
static char *ip_vs_ftp_data_ptr(struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) 
 | 
{ 
 | 
    struct tcphdr *th = (struct tcphdr *)((char *)skb->data + ipvsh->len); 
 | 
  
 | 
    if ((th->doff << 2) < sizeof(struct tcphdr)) 
 | 
        return NULL; 
 | 
  
 | 
    return (char *)th + (th->doff << 2); 
 | 
} 
 | 
  
 | 
static int 
 | 
ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) 
 | 
{ 
 | 
    /* We use connection tracking for the command connection */ 
 | 
    cp->flags |= IP_VS_CONN_F_NFCT; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
  
 | 
static int 
 | 
ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp) 
 | 
{ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
  
 | 
/* Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started 
 | 
 * with the "pattern". <addr,port> is in network order. 
 | 
 * Parse extended format depending on ext. In this case addr can be pre-set. 
 | 
 */ 
 | 
static int ip_vs_ftp_get_addrport(char *data, char *data_limit, 
 | 
                  const char *pattern, size_t plen, 
 | 
                  char skip, bool ext, int mode, 
 | 
                  union nf_inet_addr *addr, __be16 *port, 
 | 
                  __u16 af, char **start, char **end) 
 | 
{ 
 | 
    char *s, c; 
 | 
    unsigned char p[6]; 
 | 
    char edelim; 
 | 
    __u16 hport; 
 | 
    int i = 0; 
 | 
  
 | 
    if (data_limit - data < plen) { 
 | 
        /* check if there is partial match */ 
 | 
        if (strncasecmp(data, pattern, data_limit - data) == 0) 
 | 
            return -1; 
 | 
        else 
 | 
            return 0; 
 | 
    } 
 | 
  
 | 
    if (strncasecmp(data, pattern, plen) != 0) { 
 | 
        return 0; 
 | 
    } 
 | 
    s = data + plen; 
 | 
    if (skip) { 
 | 
        bool found = false; 
 | 
  
 | 
        for (;; s++) { 
 | 
            if (s == data_limit) 
 | 
                return -1; 
 | 
            if (!found) { 
 | 
                /* "(" is optional for non-extended format, 
 | 
                 * so catch the start of IPv4 address 
 | 
                 */ 
 | 
                if (!ext && isdigit(*s)) 
 | 
                    break; 
 | 
                if (*s == skip) 
 | 
                    found = true; 
 | 
            } else if (*s != skip) { 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
    /* Old IPv4-only format? */ 
 | 
    if (!ext) { 
 | 
        p[0] = 0; 
 | 
        for (data = s; ; data++) { 
 | 
            if (data == data_limit) 
 | 
                return -1; 
 | 
            c = *data; 
 | 
            if (isdigit(c)) { 
 | 
                p[i] = p[i]*10 + c - '0'; 
 | 
            } else if (c == ',' && i < 5) { 
 | 
                i++; 
 | 
                p[i] = 0; 
 | 
            } else { 
 | 
                /* unexpected character or terminator */ 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (i != 5) 
 | 
            return -1; 
 | 
  
 | 
        *start = s; 
 | 
        *end = data; 
 | 
        addr->ip = get_unaligned((__be32 *) p); 
 | 
        *port = get_unaligned((__be16 *) (p + 4)); 
 | 
        return 1; 
 | 
    } 
 | 
    if (s == data_limit) 
 | 
        return -1; 
 | 
    *start = s; 
 | 
    edelim = *s++; 
 | 
    if (edelim < 33 || edelim > 126) 
 | 
        return -1; 
 | 
    if (s == data_limit) 
 | 
        return -1; 
 | 
    if (*s == edelim) { 
 | 
        /* Address family is usually missing for EPSV response */ 
 | 
        if (mode != IP_VS_FTP_EPSV) 
 | 
            return -1; 
 | 
        s++; 
 | 
        if (s == data_limit) 
 | 
            return -1; 
 | 
        /* Then address should be missing too */ 
 | 
        if (*s != edelim) 
 | 
            return -1; 
 | 
        /* Caller can pre-set addr, if needed */ 
 | 
        s++; 
 | 
    } else { 
 | 
        const char *ep; 
 | 
  
 | 
        /* We allow address only from same family */ 
 | 
        if (af == AF_INET6 && *s != '2') 
 | 
            return -1; 
 | 
        if (af == AF_INET && *s != '1') 
 | 
            return -1; 
 | 
        s++; 
 | 
        if (s == data_limit) 
 | 
            return -1; 
 | 
        if (*s != edelim) 
 | 
            return -1; 
 | 
        s++; 
 | 
        if (s == data_limit) 
 | 
            return -1; 
 | 
        if (af == AF_INET6) { 
 | 
            if (in6_pton(s, data_limit - s, (u8 *)addr, edelim, 
 | 
                     &ep) <= 0) 
 | 
                return -1; 
 | 
        } else { 
 | 
            if (in4_pton(s, data_limit - s, (u8 *)addr, edelim, 
 | 
                     &ep) <= 0) 
 | 
                return -1; 
 | 
        } 
 | 
        s = (char *) ep; 
 | 
        if (s == data_limit) 
 | 
            return -1; 
 | 
        if (*s != edelim) 
 | 
            return -1; 
 | 
        s++; 
 | 
    } 
 | 
    for (hport = 0; ; s++) 
 | 
    { 
 | 
        if (s == data_limit) 
 | 
            return -1; 
 | 
        if (!isdigit(*s)) 
 | 
            break; 
 | 
        hport = hport * 10 + *s - '0'; 
 | 
    } 
 | 
    if (s == data_limit || !hport || *s != edelim) 
 | 
        return -1; 
 | 
    s++; 
 | 
    *end = s; 
 | 
    *port = htons(hport); 
 | 
    return 1; 
 | 
} 
 | 
  
 | 
/* Look at outgoing ftp packets to catch the response to a PASV/EPSV command 
 | 
 * from the server (inside-to-outside). 
 | 
 * When we see one, we build a connection entry with the client address, 
 | 
 * client port 0 (unknown at the moment), the server address and the 
 | 
 * server port.  Mark the current connection entry as a control channel 
 | 
 * of the new entry. All this work is just to make the data connection 
 | 
 * can be scheduled to the right server later. 
 | 
 * 
 | 
 * The outgoing packet should be something like 
 | 
 *   "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)". 
 | 
 * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number. 
 | 
 * The extended format for EPSV response provides usually only port: 
 | 
 *   "229 Entering Extended Passive Mode (|||ppp|)" 
 | 
 */ 
 | 
static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, 
 | 
             struct sk_buff *skb, int *diff, 
 | 
             struct ip_vs_iphdr *ipvsh) 
 | 
{ 
 | 
    char *data, *data_limit; 
 | 
    char *start, *end; 
 | 
    union nf_inet_addr from; 
 | 
    __be16 port; 
 | 
    struct ip_vs_conn *n_cp; 
 | 
    char buf[24];        /* xxx.xxx.xxx.xxx,ppp,ppp\000 */ 
 | 
    unsigned int buf_len; 
 | 
    int ret = 0; 
 | 
    enum ip_conntrack_info ctinfo; 
 | 
    struct nf_conn *ct; 
 | 
  
 | 
    *diff = 0; 
 | 
  
 | 
    /* Only useful for established sessions */ 
 | 
    if (cp->state != IP_VS_TCP_S_ESTABLISHED) 
 | 
        return 1; 
 | 
  
 | 
    /* Linear packets are much easier to deal with. */ 
 | 
    if (skb_ensure_writable(skb, skb->len)) 
 | 
        return 0; 
 | 
  
 | 
    if (cp->app_data == (void *) IP_VS_FTP_PASV) { 
 | 
        data = ip_vs_ftp_data_ptr(skb, ipvsh); 
 | 
        data_limit = skb_tail_pointer(skb); 
 | 
  
 | 
        if (!data || data >= data_limit) 
 | 
            return 1; 
 | 
  
 | 
        if (ip_vs_ftp_get_addrport(data, data_limit, 
 | 
                       SERVER_STRING_PASV, 
 | 
                       sizeof(SERVER_STRING_PASV)-1, 
 | 
                       '(', false, IP_VS_FTP_PASV, 
 | 
                       &from, &port, cp->af, 
 | 
                       &start, &end) != 1) 
 | 
            return 1; 
 | 
  
 | 
        IP_VS_DBG(7, "PASV response (%pI4:%u) -> %pI4:%u detected\n", 
 | 
              &from.ip, ntohs(port), &cp->caddr.ip, 0); 
 | 
    } else if (cp->app_data == (void *) IP_VS_FTP_EPSV) { 
 | 
        data = ip_vs_ftp_data_ptr(skb, ipvsh); 
 | 
        data_limit = skb_tail_pointer(skb); 
 | 
  
 | 
        if (!data || data >= data_limit) 
 | 
            return 1; 
 | 
  
 | 
        /* Usually, data address is not specified but 
 | 
         * we support different address, so pre-set it. 
 | 
         */ 
 | 
        from = cp->daddr; 
 | 
        if (ip_vs_ftp_get_addrport(data, data_limit, 
 | 
                       SERVER_STRING_EPSV, 
 | 
                       sizeof(SERVER_STRING_EPSV)-1, 
 | 
                       '(', true, IP_VS_FTP_EPSV, 
 | 
                       &from, &port, cp->af, 
 | 
                       &start, &end) != 1) 
 | 
            return 1; 
 | 
  
 | 
        IP_VS_DBG_BUF(7, "EPSV response (%s:%u) -> %s:%u detected\n", 
 | 
                  IP_VS_DBG_ADDR(cp->af, &from), ntohs(port), 
 | 
                  IP_VS_DBG_ADDR(cp->af, &cp->caddr), 0); 
 | 
    } else { 
 | 
        return 1; 
 | 
    } 
 | 
  
 | 
    /* Now update or create a connection entry for it */ 
 | 
    { 
 | 
        struct ip_vs_conn_param p; 
 | 
  
 | 
        ip_vs_conn_fill_param(cp->ipvs, cp->af, 
 | 
                      ipvsh->protocol, &from, port, 
 | 
                      &cp->caddr, 0, &p); 
 | 
        n_cp = ip_vs_conn_out_get(&p); 
 | 
    } 
 | 
    if (!n_cp) { 
 | 
        struct ip_vs_conn_param p; 
 | 
  
 | 
        ip_vs_conn_fill_param(cp->ipvs, 
 | 
                      cp->af, ipvsh->protocol, &cp->caddr, 
 | 
                      0, &cp->vaddr, port, &p); 
 | 
        n_cp = ip_vs_conn_new(&p, cp->af, &from, port, 
 | 
                      IP_VS_CONN_F_NO_CPORT | 
 | 
                      IP_VS_CONN_F_NFCT, 
 | 
                      cp->dest, skb->mark); 
 | 
        if (!n_cp) 
 | 
            return 0; 
 | 
  
 | 
        /* add its controller */ 
 | 
        ip_vs_control_add(n_cp, cp); 
 | 
    } 
 | 
  
 | 
    /* Replace the old passive address with the new one */ 
 | 
    if (cp->app_data == (void *) IP_VS_FTP_PASV) { 
 | 
        from.ip = n_cp->vaddr.ip; 
 | 
        port = n_cp->vport; 
 | 
        snprintf(buf, sizeof(buf), "%u,%u,%u,%u,%u,%u", 
 | 
             ((unsigned char *)&from.ip)[0], 
 | 
             ((unsigned char *)&from.ip)[1], 
 | 
             ((unsigned char *)&from.ip)[2], 
 | 
             ((unsigned char *)&from.ip)[3], 
 | 
             ntohs(port) >> 8, 
 | 
             ntohs(port) & 0xFF); 
 | 
    } else if (cp->app_data == (void *) IP_VS_FTP_EPSV) { 
 | 
        from = n_cp->vaddr; 
 | 
        port = n_cp->vport; 
 | 
        /* Only port, client will use VIP for the data connection */ 
 | 
        snprintf(buf, sizeof(buf), "|||%u|", 
 | 
             ntohs(port)); 
 | 
    } else { 
 | 
        *buf = 0; 
 | 
    } 
 | 
    buf_len = strlen(buf); 
 | 
  
 | 
    ct = nf_ct_get(skb, &ctinfo); 
 | 
    if (ct) { 
 | 
        bool mangled; 
 | 
  
 | 
        /* If mangling fails this function will return 0 
 | 
         * which will cause the packet to be dropped. 
 | 
         * Mangling can only fail under memory pressure, 
 | 
         * hopefully it will succeed on the retransmitted 
 | 
         * packet. 
 | 
         */ 
 | 
        mangled = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, 
 | 
                           ipvsh->len, 
 | 
                           start - data, 
 | 
                           end - start, 
 | 
                           buf, buf_len); 
 | 
        if (mangled) { 
 | 
            ip_vs_nfct_expect_related(skb, ct, n_cp, 
 | 
                          ipvsh->protocol, 0, 0); 
 | 
            if (skb->ip_summed == CHECKSUM_COMPLETE) 
 | 
                skb->ip_summed = CHECKSUM_UNNECESSARY; 
 | 
            /* csum is updated */ 
 | 
            ret = 1; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* Not setting 'diff' is intentional, otherwise the sequence 
 | 
     * would be adjusted twice. 
 | 
     */ 
 | 
  
 | 
    cp->app_data = (void *) IP_VS_FTP_ACTIVE; 
 | 
    ip_vs_tcp_conn_listen(n_cp); 
 | 
    ip_vs_conn_put(n_cp); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
  
 | 
/* Look at incoming ftp packets to catch the PASV/PORT/EPRT/EPSV command 
 | 
 * (outside-to-inside). 
 | 
 * 
 | 
 * The incoming packet having the PORT command should be something like 
 | 
 *      "PORT xxx,xxx,xxx,xxx,ppp,ppp\n". 
 | 
 * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number. 
 | 
 * In this case, we create a connection entry using the client address and 
 | 
 * port, so that the active ftp data connection from the server can reach 
 | 
 * the client. 
 | 
 * Extended format: 
 | 
 *    "EPSV\r\n" when client requests server address from same family 
 | 
 *    "EPSV 1\r\n" when client requests IPv4 server address 
 | 
 *    "EPSV 2\r\n" when client requests IPv6 server address 
 | 
 *    "EPSV ALL\r\n" - not supported 
 | 
 *    EPRT with specified delimiter (ASCII 33..126), "|" by default: 
 | 
 *    "EPRT |1|IPv4ADDR|PORT|\r\n" when client provides IPv4 addrport 
 | 
 *    "EPRT |2|IPv6ADDR|PORT|\r\n" when client provides IPv6 addrport 
 | 
 */ 
 | 
static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, 
 | 
            struct sk_buff *skb, int *diff, 
 | 
            struct ip_vs_iphdr *ipvsh) 
 | 
{ 
 | 
    char *data, *data_start, *data_limit; 
 | 
    char *start, *end; 
 | 
    union nf_inet_addr to; 
 | 
    __be16 port; 
 | 
    struct ip_vs_conn *n_cp; 
 | 
  
 | 
    /* no diff required for incoming packets */ 
 | 
    *diff = 0; 
 | 
  
 | 
    /* Only useful for established sessions */ 
 | 
    if (cp->state != IP_VS_TCP_S_ESTABLISHED) 
 | 
        return 1; 
 | 
  
 | 
    /* Linear packets are much easier to deal with. */ 
 | 
    if (skb_ensure_writable(skb, skb->len)) 
 | 
        return 0; 
 | 
  
 | 
    data = data_start = ip_vs_ftp_data_ptr(skb, ipvsh); 
 | 
    data_limit = skb_tail_pointer(skb); 
 | 
    if (!data || data >= data_limit) 
 | 
        return 1; 
 | 
  
 | 
    while (data <= data_limit - 6) { 
 | 
        if (cp->af == AF_INET && 
 | 
            strncasecmp(data, "PASV\r\n", 6) == 0) { 
 | 
            /* Passive mode on */ 
 | 
            IP_VS_DBG(7, "got PASV at %td of %td\n", 
 | 
                  data - data_start, 
 | 
                  data_limit - data_start); 
 | 
            cp->app_data = (void *) IP_VS_FTP_PASV; 
 | 
            return 1; 
 | 
        } 
 | 
  
 | 
        /* EPSV or EPSV<space><net-prt> */ 
 | 
        if (strncasecmp(data, "EPSV", 4) == 0 && 
 | 
            (data[4] == ' ' || data[4] == '\r')) { 
 | 
            if (data[4] == ' ') { 
 | 
                char proto = data[5]; 
 | 
  
 | 
                if (data > data_limit - 7 || data[6] != '\r') 
 | 
                    return 1; 
 | 
  
 | 
#ifdef CONFIG_IP_VS_IPV6 
 | 
                if (cp->af == AF_INET6 && proto == '2') { 
 | 
                } else 
 | 
#endif 
 | 
                if (cp->af == AF_INET && proto == '1') { 
 | 
                } else { 
 | 
                    return 1; 
 | 
                } 
 | 
            } 
 | 
            /* Extended Passive mode on */ 
 | 
            IP_VS_DBG(7, "got EPSV at %td of %td\n", 
 | 
                  data - data_start, 
 | 
                  data_limit - data_start); 
 | 
            cp->app_data = (void *) IP_VS_FTP_EPSV; 
 | 
            return 1; 
 | 
        } 
 | 
  
 | 
        data++; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * To support virtual FTP server, the scenerio is as follows: 
 | 
     *       FTP client ----> Load Balancer ----> FTP server 
 | 
     * First detect the port number in the application data, 
 | 
     * then create a new connection entry for the coming data 
 | 
     * connection. 
 | 
     */ 
 | 
    if (cp->af == AF_INET && 
 | 
        ip_vs_ftp_get_addrport(data_start, data_limit, 
 | 
                   CLIENT_STRING_PORT, 
 | 
                   sizeof(CLIENT_STRING_PORT)-1, 
 | 
                   ' ', false, IP_VS_FTP_PORT, 
 | 
                   &to, &port, cp->af, 
 | 
                   &start, &end) == 1) { 
 | 
  
 | 
        IP_VS_DBG(7, "PORT %pI4:%u detected\n", &to.ip, ntohs(port)); 
 | 
  
 | 
        /* Now update or create a connection entry for it */ 
 | 
        IP_VS_DBG(7, "protocol %s %pI4:%u %pI4:%u\n", 
 | 
              ip_vs_proto_name(ipvsh->protocol), 
 | 
              &to.ip, ntohs(port), &cp->vaddr.ip, 
 | 
              ntohs(cp->vport)-1); 
 | 
    } else if (ip_vs_ftp_get_addrport(data_start, data_limit, 
 | 
                      CLIENT_STRING_EPRT, 
 | 
                      sizeof(CLIENT_STRING_EPRT)-1, 
 | 
                      ' ', true, IP_VS_FTP_EPRT, 
 | 
                      &to, &port, cp->af, 
 | 
                      &start, &end) == 1) { 
 | 
  
 | 
        IP_VS_DBG_BUF(7, "EPRT %s:%u detected\n", 
 | 
                  IP_VS_DBG_ADDR(cp->af, &to), ntohs(port)); 
 | 
  
 | 
        /* Now update or create a connection entry for it */ 
 | 
        IP_VS_DBG_BUF(7, "protocol %s %s:%u %s:%u\n", 
 | 
                  ip_vs_proto_name(ipvsh->protocol), 
 | 
                  IP_VS_DBG_ADDR(cp->af, &to), ntohs(port), 
 | 
                  IP_VS_DBG_ADDR(cp->af, &cp->vaddr), 
 | 
                  ntohs(cp->vport)-1); 
 | 
    } else { 
 | 
        return 1; 
 | 
    } 
 | 
  
 | 
    /* Passive mode off */ 
 | 
    cp->app_data = (void *) IP_VS_FTP_ACTIVE; 
 | 
  
 | 
    { 
 | 
        struct ip_vs_conn_param p; 
 | 
        ip_vs_conn_fill_param(cp->ipvs, cp->af, 
 | 
                      ipvsh->protocol, &to, port, &cp->vaddr, 
 | 
                      htons(ntohs(cp->vport)-1), &p); 
 | 
        n_cp = ip_vs_conn_in_get(&p); 
 | 
        if (!n_cp) { 
 | 
            n_cp = ip_vs_conn_new(&p, cp->af, &cp->daddr, 
 | 
                          htons(ntohs(cp->dport)-1), 
 | 
                          IP_VS_CONN_F_NFCT, cp->dest, 
 | 
                          skb->mark); 
 | 
            if (!n_cp) 
 | 
                return 0; 
 | 
  
 | 
            /* add its controller */ 
 | 
            ip_vs_control_add(n_cp, cp); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     *    Move tunnel to listen state 
 | 
     */ 
 | 
    ip_vs_tcp_conn_listen(n_cp); 
 | 
    ip_vs_conn_put(n_cp); 
 | 
  
 | 
    return 1; 
 | 
} 
 | 
  
 | 
  
 | 
static struct ip_vs_app ip_vs_ftp = { 
 | 
    .name =        "ftp", 
 | 
    .type =        IP_VS_APP_TYPE_FTP, 
 | 
    .protocol =    IPPROTO_TCP, 
 | 
    .module =    THIS_MODULE, 
 | 
    .incs_list =    LIST_HEAD_INIT(ip_vs_ftp.incs_list), 
 | 
    .init_conn =    ip_vs_ftp_init_conn, 
 | 
    .done_conn =    ip_vs_ftp_done_conn, 
 | 
    .bind_conn =    NULL, 
 | 
    .unbind_conn =    NULL, 
 | 
    .pkt_out =    ip_vs_ftp_out, 
 | 
    .pkt_in =    ip_vs_ftp_in, 
 | 
}; 
 | 
  
 | 
/* 
 | 
 *    per netns ip_vs_ftp initialization 
 | 
 */ 
 | 
static int __net_init __ip_vs_ftp_init(struct net *net) 
 | 
{ 
 | 
    int i, ret; 
 | 
    struct ip_vs_app *app; 
 | 
    struct netns_ipvs *ipvs = net_ipvs(net); 
 | 
  
 | 
    if (!ipvs) 
 | 
        return -ENOENT; 
 | 
  
 | 
    app = register_ip_vs_app(ipvs, &ip_vs_ftp); 
 | 
    if (IS_ERR(app)) 
 | 
        return PTR_ERR(app); 
 | 
  
 | 
    for (i = 0; i < ports_count; i++) { 
 | 
        if (!ports[i]) 
 | 
            continue; 
 | 
        ret = register_ip_vs_app_inc(ipvs, app, app->protocol, ports[i]); 
 | 
        if (ret) 
 | 
            goto err_unreg; 
 | 
        pr_info("%s: loaded support on port[%d] = %u\n", 
 | 
            app->name, i, ports[i]); 
 | 
    } 
 | 
    return 0; 
 | 
  
 | 
err_unreg: 
 | 
    unregister_ip_vs_app(ipvs, &ip_vs_ftp); 
 | 
    return ret; 
 | 
} 
 | 
/* 
 | 
 *    netns exit 
 | 
 */ 
 | 
static void __ip_vs_ftp_exit(struct net *net) 
 | 
{ 
 | 
    struct netns_ipvs *ipvs = net_ipvs(net); 
 | 
  
 | 
    if (!ipvs) 
 | 
        return; 
 | 
  
 | 
    unregister_ip_vs_app(ipvs, &ip_vs_ftp); 
 | 
} 
 | 
  
 | 
static struct pernet_operations ip_vs_ftp_ops = { 
 | 
    .init = __ip_vs_ftp_init, 
 | 
    .exit = __ip_vs_ftp_exit, 
 | 
}; 
 | 
  
 | 
static int __init ip_vs_ftp_init(void) 
 | 
{ 
 | 
    /* rcu_barrier() is called by netns on error */ 
 | 
    return register_pernet_subsys(&ip_vs_ftp_ops); 
 | 
} 
 | 
  
 | 
/* 
 | 
 *    ip_vs_ftp finish. 
 | 
 */ 
 | 
static void __exit ip_vs_ftp_exit(void) 
 | 
{ 
 | 
    unregister_pernet_subsys(&ip_vs_ftp_ops); 
 | 
    /* rcu_barrier() is called by netns */ 
 | 
} 
 | 
  
 | 
  
 | 
module_init(ip_vs_ftp_init); 
 | 
module_exit(ip_vs_ftp_exit); 
 | 
MODULE_LICENSE("GPL"); 
 |