IPVS系统的FTP应用模块

FTP应用模块没有全局初始化操作,仅是注册了网络命名空间操作ip_vs_ftp_ops,初始化init函数为__ip_vs_ftp_init。

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);
}

当初始化网络命名空间时,执行此函数。全局变量ports_count指定了FTP应用使用的端口数量,当前为1。数组ports定义了FTP应用使用的端口号列表,当前只有FTP的有名端口号21。

static unsigned int ports_count = 1;
static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0};

关于register_ip_vs_app和register_ip_vs_app_inc的详细解释可参见:。

static int __net_init __ip_vs_ftp_init(struct net *net)
{
    struct ip_vs_app *app;
    struct netns_ipvs *ipvs = net_ipvs(net);

    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] = %d\n", app->name, i, ports[i]);
    }
}

函数register_ip_vs_app注册的FTP应用的操作集ip_vs_ftp,定义如下。

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,
};

连接初始化函数。

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;
}

FTP输入处理

函数ip_vs_ftp_in将由函数ip_vs_app_pkt_in调用,用于执行特定于应用(如ftp)的操作。目前不支持IPv6相关操作,遇到IPv6类型的连接返回。

static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff *skb, int *diff)
{
    struct iphdr *iph;
    struct tcphdr *th;
    char *data, *data_start, *data_limit;
    struct ip_vs_conn *n_cp;

    /* no diff required for incoming packets */
    *diff = 0;

#ifdef CONFIG_IP_VS_IPV6
    /* This application helper doesn't work with IPv6 yet, so turn this into a no-op for IPv6 packets */
    if (cp->af == AF_INET6)
        return 1;
#endif

首先验证是否为被动模式,通过在数据包中查找字符串:"PASV\r\n"实现。如果找到此字符串说明此FTP连接为被动模式,使用app_data进行标记。

    iph = ip_hdr(skb);
    th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
    data = data_start = (char *)th + (th->doff << 2);
    data_limit = skb_tail_pointer(skb);

    while (data <= data_limit - 6) {
        if (strncasecmp(data, "PASV\r\n", 6) == 0) {
            /* Passive mode on */
            cp->app_data = &ip_vs_ftp_pasv;
            return 1;
        }
        data++;
    }

如果不是被动模式,即主动模式,通过查找数据报文中的命令字符串:“PORT”(CLIENT_STRING),来获取客户端FTP数据通道的地址和端口号。如果成功的话,清除被动模式表示:app_date等于空。

    /*
     * 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 (ip_vs_ftp_get_addrport(data_start, data_limit, CLIENT_STRING, sizeof(CLIENT_STRING)-1, ' ', '\r', &to.ip, &port,
                   &start, &end) != 1)
        return 1;

    /* Passive mode off */
    cp->app_data = NULL;

为了保证随后的FTP数据通道的连接与控制通道的连接都由同一个真实服务器进行处理,之后,内核根据以上获取到的地址和端口号,在加上当前连接的目的地址和目的端口号,创建一个新的连接。并且,将此新连接的控制连接设置为当前的连接,表明新连接是由当前连接派生出来的。

    {
        struct ip_vs_conn_param p;
        ip_vs_conn_fill_param(cp->ipvs, AF_INET, iph->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, AF_INET, &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);
}

最后,由于在以上的过程中,没有对实际的报文进行任何修改,所以不存在任何差值:diff等于0。以下为FTP主动模式的报文交互流程:

在这里插入图片描述

FTP输出处理

对于被动模式的FTP连接,即在上节的函数ip_vs_ftp_in中将连接的app_data设置为ip_vs_ftp_pasv的连接。通过在数据包中查找字符串:"227 "(SERVER_STRING)来获取FTP服务端通告的地址和端口号。

static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff *skb, int *diff)
{
    struct iphdr *iph;
    struct tcphdr *th;

    if (cp->app_data == &ip_vs_ftp_pasv) {
        iph = ip_hdr(skb);
        th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
        data = (char *)th + (th->doff << 2);
        data_limit = skb_tail_pointer(skb);

        if (ip_vs_ftp_get_addrport(data, data_limit, SERVER_STRING, sizeof(SERVER_STRING)-1, '(', ')', &from.ip, &port,
                       &start, &end) != 1)
            return 1;

获取到服务器的地址和端口之后,更新或者创建新的连接。

        /* Now update or create an connection entry for it */
        {
            struct ip_vs_conn_param p;
            ip_vs_conn_fill_param(cp->ipvs, AF_INET, iph->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, AF_INET, IPPROTO_TCP, &cp->caddr, 0, &cp->vaddr, port, &p);
            /* As above, this is ipv4 only */
            n_cp = ip_vs_conn_new(&p, AF_INET, &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 */
        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);

        buf_len = strlen(buf);

使用nf_nat_mangle_tcp_packet函数将数据包中的地址和端口信息替换为虚拟服务器的地址和端口信息。由于在此函数中已经调整了报文的TCP头部中的序列号,因此返回报文长度差值参数应设置为0,即diff = 0。

        ct = nf_ct_get(skb, &ctinfo);
        if (ct && (ct->status & IPS_NAT_MASK)) {
            bool mangled;

            mangled = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, iph->ihl * 4, start - data, end - start, buf, buf_len);
            if (mangled) {
                ip_vs_nfct_expect_related(skb, ct, n_cp, IPPROTO_TCP, 0, 0);
                if (skb->ip_summed == CHECKSUM_COMPLETE)
                    skb->ip_summed = CHECKSUM_UNNECESSARY;
                ret = 1;
            }
        }

        cp->app_data = NULL;
        ip_vs_tcp_conn_listen(n_cp);
        ip_vs_conn_put(n_cp);
        return ret;
    }
    return 1;
}

以下为FTP被动模式的报文交互流程:

在这里插入图片描述

地址端口解析

函数ip_vs_ftp_get_addrport负责解析报文中的地址和端口号,在以上的FTP输出输入处理中都会使用到。在FTP被动模式下,FTP服务端将发送自身的地址和数据通道端口号到客户端,并被动等待客户端的连接,其格式为:227 ÕýÔÚ½øÈë±»¶¯Ä£Ê½ (192,168,1,115,246,168) 。

对于FTP主动模式,FTP客户端使用命令PORT发送自身的地址和数据通道端口号到服务器,其格式为:PORT 192,168,1,114,140,247 。以下为在函数ip_vs_ftp_out和ip_vs_ftp_in中的调用参数。

static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff *skb, int *diff)
{
    if (ip_vs_ftp_get_addrport(data, data_limit, SERVER_STRING, sizeof(SERVER_STRING)-1, '(', ')', &from.ip, &port,
                   &start, &end) != 1)
        return 1;
}

static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff *skb, int *diff)
{
    if (ip_vs_ftp_get_addrport(data_start, data_limit, CLIENT_STRING, sizeof(CLIENT_STRING)-1, ' ', '\r', &to.ip, &port,
                   &start, &end) != 1)
        return 1;
}

函数ip_vs_ftp_get_addrport执行字符串解析操作,首先数据报文的开头需要匹配指定长度的pattern字符串,对于FTP被动模式,需要匹配字符串:"227 ";对于主动模式需要匹配字符串:"PORT "。

static int ip_vs_ftp_get_addrport(char *data, char *data_limit, const char *pattern, size_t plen, char skip, char term,
                  __be32 *addr, __be16 *port, char **start, char **end)
{
    char *s, c;
    unsigned char p[6];
    int i = 0;

    if (strncasecmp(data, pattern, plen) != 0) {
        return 0;
    }

参数skip用于跳过其所指定的字符,对于FTP被动模式,需要跳过的是:’(‘字符;对于主动模式,需要跳过的是:’ '空格字符。

    s = data + plen;
    if (skip) {
        int found = 0;

        for (;; s++) {
            if (s == data_limit)
                return -1;
            if (!found) {
                if (*s == skip)
                    found = 1;
            } else if (*s != skip) {
                break;
            }
        }
    }

参数term指定了要解析的数据中结尾的字符。对于FTP被动模式,结尾字符为:’)’;对于主动模式,结尾字符为:’\r’。

    for (data = s; ; data++) {
        if (data == data_limit)
            return -1;
        if (*data == term)
            break;
    }
    *end = data;

以下循环将地址和端口号组成的字符串解析为整数型数字,保存在数组p中。其中前4个数组元素保存IP地址,后2个数组元素保存端口号。

    memset(p, 0, sizeof(p));
    for (data = s; ; data++) {
        c = *data;
        if (c == term)
            break;
        if (c >= '0' && c <= '9') {
            p[i] = p[i]*10 + c - '0';
        } else if (c == ',' && i < 5) {
            i++;
        } else {
            /* unexpected character */
            return -1;
        }
    }

    if (i != 5)
        return -1;

    *start = s;
    *addr = get_unaligned((__be32 *) p);
    *port = get_unaligned((__be16 *) (p + 4));
    return 1;
}

内核版本 4.15

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页