本文将在Linux环境下实现一个简单的FTP代理服务器,主要内容涉及FTP主动/被动模式和简单的Socket编程。

1. 主动模式和被动模式

FTP有两种模式,即主动模式(Active Mode)和被动模式(Passive Mode),主要区别在谁在监听数据端口。

1.1 主动模式

FTP服务器在开启后一直在监听21号端口等待客户端通过任意端口进行连接,客户端通过任意端口port1连接服务器21号端口成功后,服务器通过该命令套接字发送各种FTP命令(CD、DIR、QUIT...)。当要发送的命令涉及到数据传输的时候,服务器和客户端间就要开启数据通道,如果此时客户端处于主动模式时,客户端开启并监听一个大于1024的随机端口port2,并通过命令套接字向服务器发送PORT命令通告客户端处于主动模式且正在监听port2。服务器在收到客户端的PORT命令后,使用端口20连接客户端port2(数据端口使用20只是个惯例,其实不适用影响不大),并完成数据传输。

PORT命令的格式为” PORT 223,3,123,41,99,165 “,指示客户端ip为223.3.123.41,客户端开启的随机端口port2 = 99 * 265 + 165 = 26400,在服务器返回200 PORT command successful之后,客户端才发送获取文件命令RETR。

1.2 主动模式的缺陷

从1.1中可以看出FTP在采取主动模式时,客户端需要主动监听一个大于1024的随机端口,而一般客户端的防火墙不会开放对这样一个端口的连接。

1.3 被动模式

为了给客户端带来方便,FTP被动模式下,客户端发送Request: Pasv命令,服务器在接收到命令后,主动开启一个大于1024的端口port并发送响应Response: 227 Entering Passive Mode(...),客户端主动发起连接到服务器的port,并完成数据传输。在被动模式下客户端不需要监听任何端口,因此在客户端存在某些防火墙规则的情况下会更加适合。

2. FTP代理服务器架构

1. FTP代理服务器监听套接字proxy_cmd_socket监听21号端口,当客户端连接时,得到accept_cmd_socket,proxy主连接服务器21号端口得到connect_cmd_socket,proxy转发accept_cmd_socket和connect_cmd_socket之间除了情况2和情况3的通信。

2. 当处于主动模式下客户通过accept_cmd_socket发送PORT命令时,proxy需要把PORT命令的ip换成proxy的外网ip(指server看到的proxy的ip),并随机监听一个大于1024的端口port1,把PORT命令中的端口port改为port1。

3. 当处于被动模式下服务器响应客户端的Response: 227....命令时,proxy需要把Response中的ip换成proxy的内网ip(指client看到的proxy的ip),并随机监听一个大于1024的端口port2,把Response中的端口port改为port2。

4. 当处于主动模式下,服务器收到proxy修改过的PORT命令,会主动连接proxy的端口port1得到accept_data_socket,proxy主动连接客户端的port端口得到proxy_connect_socket,proxy转发accept_data_socket_socket和proxy_connect_socket间的通信。

5. 当处于被动模式下,客户端收到proxy修改过的Response: 227...后,会连接proxy的端口port2得到accept_data_socket,proxy连接服务器的port端口得到proxy_connect_socket,proxy转发accept_data_socket_socket和proxy_connect_socket间的通信。

3. FTP代理服务器实现
 #include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h> #define TRUE 1
#define FALSE 0 #define CMD_PORT 21
#define BUFFSIZE 4096
#define LISTENQ 5 int acceptSocket(int socket,struct sockaddr *addr,socklen_t *addrlen);
int connectToServerByAddr(struct sockaddr_in servaddr);
int connectToServer(char *ip,unsigned short port);
int bindAndListenSocket(unsigned short port);
void splitCmd(char *buff, char **cmd,char **param);
unsigned short getPortFromFtpParam(char *param);
void getSockLocalIp(int fd,char *ipStr,int buffsize);
unsigned short getSockLocalPort(int sockfd); int main(int argc, const char *argv[])
{
int i;
fd_set master_set, working_set; //文件描述符集合
struct timeval timeout; //select 参数中的超时结构体
int proxy_cmd_socket = ; //proxy listen控制连接
int accept_cmd_socket = ; //proxy accept客户端请求的控制连接
int connect_cmd_socket = ; //proxy connect服务器建立控制连接
int proxy_data_socket = ; //proxy listen数据连接
int accept_data_socket = ; //proxy accept得到请求的数据连接(主动模式时accept得到服务器数据连接的请求,被动模式时accept得到客户端数据连接的请求)
int connect_data_socket = ; //proxy connect建立数据连接 (主动模式时connect客户端建立数据连接,被动模式时connect服务器端建立数据连接)
int selectResult = ; //select函数返回值
int select_sd = ; //select 函数监听的最大文件描述符
int pasv_mode = ; char serverProxyIp[BUFFSIZE]; //待获得
char clientProxyIp[BUFFSIZE]; //待获得 serverProxyIp和clientProxyIp可能不一样
char serverIp[BUFFSIZE]; unsigned short proxy_data_port;
unsigned short data_port;
socklen_t clilen;
struct sockaddr_in cliaddr; if(argc != ){
printf("usage : proxy server_ip\n example: proxy 121.121.121.121\n");
}
strcpy(serverIp,argv[]); FD_ZERO(&master_set); //清空master_set集合
bzero(&timeout, sizeof(timeout)); proxy_cmd_socket = bindAndListenSocket(CMD_PORT); //开启proxy_cmd_socket、bind()、listen操作
FD_SET(proxy_cmd_socket, &master_set); //将proxy_cmd_socket加入master_set集合 while (TRUE) {
FD_ZERO(&working_set); //清空working_set文件描述符集合
memcpy(&working_set, &master_set, sizeof(master_set)); //将master_set集合copy到working_set集合
timeout.tv_sec = ; //Select的超时结束时间
timeout.tv_usec = ; //ms //select循环监听 这里只对读操作的变化进行监听(working_set为监视读操作描述符所建立的集合),第三和第四个参数的NULL代表不对写操作、和误操作进行监听
selectResult = select(select_sd, &working_set, NULL, NULL, &timeout); // fail
if (selectResult < ) {
perror("select() failed\n");
exit();
} // timeout
if (selectResult == ) {
printf("select() timed out.\n");
continue;
} // selectResult > 0 时 开启循环判断有变化的文件描述符为哪个socket
for (i = ; i < select_sd; i++) {
//判断变化的文件描述符是否存在于working_set集合
if (FD_ISSET(i, &working_set)) {
if (i == proxy_cmd_socket) { accept_cmd_socket = acceptSocket(proxy_cmd_socket,NULL,NULL); //执行accept操作,建立proxy和客户端之间的控制连接
connect_cmd_socket = connectToServer(serverIp,CMD_PORT); //执行connect操作,建立proxy和服务器端之间的控制连接 getSockLocalIp(connect_cmd_socket,serverProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
getSockLocalIp(accept_cmd_socket,clientProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
printf("proxy ip from server's view : %s\n",serverProxyIp);
printf("proxy ip from client's view : %s\n",clientProxyIp); //将新得到的socket加入到master_set结合中
FD_SET(accept_cmd_socket, &master_set);
FD_SET(connect_cmd_socket, &master_set);
} if (i == accept_cmd_socket) {
char buff[BUFFSIZE] = {};
char copy[BUFFSIZE] = {}; if (read(i, buff, BUFFSIZE) == ) {
close(i); //如果接收不到内容,则关闭Socket
close(connect_cmd_socket);
printf("client closed\n"); //socket关闭后,使用FD_CLR将关闭的socket从master_set集合中移去,使得select函数不再监听关闭的socket
FD_CLR(i, &master_set);
FD_CLR(connect_cmd_socket, &master_set); } else {
printf("command received from client : %s\n",buff);
char *cmd,*param;
strcpy(copy,buff);
splitCmd(copy,&cmd,&param);
//如果接收到内容,则对内容进行必要的处理,之后发送给服务器端(写入connect_cmd_socket) //处理客户端发给proxy的request,部分命令需要进行处理,如PORT、RETR、STOR
//PORT
//////////////
if(strcmp(cmd,"PORT") == ){ //修改ip & port
//在这儿应该让proxy_data_socket监听任意端口
proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
proxy_data_port = getSockLocalPort(proxy_data_socket);
FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
pasv_mode = ;
data_port = getPortFromFtpParam(param);
bzero(buff,BUFFSIZE);
sprintf(buff,"PORT %s,%d,%d\r\n",serverProxyIp,proxy_data_port / ,proxy_data_port % );
} //写入proxy与server建立的cmd连接,除了PORT之外,直接转发buff内容
printf("command sent to server : %s\n",buff);
write(connect_cmd_socket, buff, strlen(buff));
}
} if (i == connect_cmd_socket) {
//处理服务器端发给proxy的reply,写入accept_cmd_socket
char buff[BUFFSIZE] = {};
if(read(i,buff,BUFFSIZE) == ){
close(i);
close(accept_cmd_socket);
FD_CLR(i,&master_set);
FD_CLR(accept_cmd_socket,&master_set);
} printf("reply received from server : %s\n",buff);
//PASV收到的端口 227 (port)
//////////////
if(buff[] == '' && buff[] == '' && buff[] == ''){
proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
proxy_data_port = getSockLocalPort(proxy_data_socket);
FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
data_port = getPortFromFtpParam(buff + );
bzero(buff + ,BUFFSIZE - );
sprintf(buff + ,"%s,%d,%d).\r\n",clientProxyIp,proxy_data_port / ,proxy_data_port % );
}
printf("reply sent to client : %s\n",buff); write(accept_cmd_socket,buff,strlen(buff));
} if (i == proxy_data_socket) {
if(pasv_mode){ //clinet connect
accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //client <-> proxy
connect_data_socket = connectToServer(serverIp,data_port); //proxy <-> server
}
else{ //主动模式
accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //proxy <-> server
clilen = sizeof(cliaddr);
if(getpeername(accept_cmd_socket,(struct sockaddr *)&cliaddr,&clilen) < ){
perror("getpeername() failed: ");
}
cliaddr.sin_port = htons(data_port);
connect_data_socket = connectToServerByAddr(cliaddr); //client <-> proxy
} FD_SET(accept_data_socket, &master_set);
FD_SET(connect_data_socket, &master_set);
printf("data connectiong established\n");
//建立data连接(accept_data_socket、connect_data_socket)
} if (i == accept_data_socket) { int n;
char buff[BUFFSIZE] = {};
if((n = read(accept_data_socket,buff,BUFFSIZE)) == ){
close(accept_data_socket);
close(connect_data_socket);
close(proxy_data_socket);
FD_CLR(proxy_data_socket,&master_set);
FD_CLR(accept_data_socket, &master_set);
FD_CLR(connect_data_socket, &master_set);
}
else{
write(connect_data_socket,buff,n);
} //判断主被动和传输方式(上传、下载)决定如何传输数据
} if (i == connect_data_socket) {
int n;
char buff[BUFFSIZE] = {};
if((n = read(connect_data_socket,buff,BUFFSIZE)) == ){
close(accept_data_socket);
close(connect_data_socket);
close(proxy_data_socket);
FD_CLR(proxy_data_socket,&master_set);
FD_CLR(accept_data_socket, &master_set);
FD_CLR(connect_data_socket, &master_set);
}
else{
write(accept_data_socket,buff,n);
}
//判断主被动和传输方式(上传、下载)决定如何传输数据
}
}
}
} return ;
} unsigned short getSockLocalPort(int sockfd)
{
struct sockaddr_in addr;
socklen_t addrlen;
addrlen = sizeof(addr); if(getsockname(sockfd,(struct sockaddr *)&addr,&addrlen) < ){
perror("getsockname() failed: ");
exit();
} return ntohs(addr.sin_port);
} void getSockLocalIp(int fd,char *ipStr,int buffsize)
{ bzero(ipStr,buffsize); struct sockaddr_in addr;
socklen_t addrlen;
addrlen = sizeof(addr); if(getsockname(fd,(struct sockaddr *)&addr,&addrlen) < ){
perror("getsockname() failed: ");
exit();
} inet_ntop(AF_INET,&addr.sin_addr,ipStr,addrlen); char *p = ipStr;
while(*p){
if(*p == '.') *p = ',';
p++;
}
} unsigned short getPortFromFtpParam(char *param)
{
unsigned short port,t;
int count = ;
char *p = param; while(count < ){
if(*(p++) == ','){
count++;
}
} sscanf(p,"%hu",&port);
while(*p != ',' && *p != '\r' && *p != ')') p++;
if(*p == ','){
p++;
sscanf(p,"%hu",&t);
port = port * + t;
} return port;
} //从FTP命令行中解析出命令和参数
void splitCmd(char *buff, char **cmd,char **param)
{
int i;
char *p; while((p = &buff[strlen(buff) - ]) && (*p == '\r' || *p == '\n')) *p = ; p = strchr(buff,' ');
*cmd = buff; if(!p){
*param = NULL;
}else{
*p = ;
*param = p + ;
} for(i = ;i < strlen(*cmd);i++){
(*cmd)[i] = toupper((*cmd)[i]);
}
} int acceptSocket(int cmd_socket,struct sockaddr *addr,socklen_t *addrlen)
{
int fd = accept(cmd_socket,addr,addrlen);
if(fd < ){
perror("accept() failed:");
exit();
} return fd;
} int connectToServerByAddr(struct sockaddr_in servaddr)
{
int fd; struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//cliaddr.sin_port = htons(20); fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed :");
exit();
} if(bind(fd,(struct sockaddr *)&cliaddr,sizeof(cliaddr) ) < ){
perror("bind() failed :");
exit();
} servaddr.sin_family = AF_INET;
if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
perror("connect() failed :");
exit();
} return fd;
} int connectToServer(char *ip,unsigned short port)
{
int fd;
struct sockaddr_in servaddr; bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
inet_pton(AF_INET,ip,&servaddr.sin_addr); fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed :");
exit();
} if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
perror("connect() failed :");
exit();
} return fd;
} int bindAndListenSocket(unsigned short port)
{
int fd;
struct sockaddr_in addr; fd = socket(AF_INET,SOCK_STREAM,);
if(fd < ){
perror("socket() failed: ");
exit();
} bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port); if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < ){
perror("bind() failed: ");
exit();
} if(listen(fd,LISTENQ) < ){
perror("listen() failed: ");
exit();
} return fd;
}

proxy.c

FTP Proxy Server的更多相关文章

  1. proxy server 代理服务器

    有时候,我觉得自己需要去搞明白.搞清楚一个概念,帮我打通一下自己的知识体系,或者说,尝试联络起来. 1. 简介 突破自身IP限制,访问国外站点. 访问单位或者团体内部资源. 突破中国电信的IP封锁. ...

  2. Proxy Server代理服务器(轉載)

    宽带IP城域网开通以来,单位连上了宽带网,10M的带宽让我们感受到了宽带的魅力.电信只提供7个IP地址,对任何一个单位来说都太少了,常用的解决办法是使用代理服务器.微软的MS Proxy Server ...

  3. How to setup vsftpd FTP file Server on Redhat 7 Linux

    Forward from: https://linuxconfig.org/how-to-setup-vsftpd-ftp-file-server-on-redhat-7-linux How to s ...

  4. 502 Proxy Error The proxy server received an invalid response from an upstream server

    Proxy Error The proxy server received an invalid response from an upstream server. The proxy server ...

  5. 设置Proxy Server和SQL Server实现互联网上的数据库安全

    ◆首先,我们需要了解一下SQL Server在WinSock上定义协议的步骤: 1. 在”启动”菜单上,指向”程序/Microsoft Proxy Server”,然后点击”Microsoft Man ...

  6. 转:Jmeter 用户思考时间(User think time),定时器,和代理服务器(proxy server)

    在负载测试中需要考虑的的一个重要要素是思考时间(think time), 也就是在两次成功的访问请求之间的暂停时间. 有多种情形挥发导致延迟的发生: 用户需要时间阅读文字内容,或者填表,或者查找正确的 ...

  7. tengine2.2.3报错502的The proxy server received an invalid response from an upstream server问题处理

    tengine2.2.3报错502的The proxy server received an invalid response from an upstream server问题处理 现象:访问订单的 ...

  8. 简单实现http proxy server怎么实现?

    原文:https://blog.csdn.net/dolphin98629/article/details/54599850 简单实现http proxy server怎么实现?

  9. 架设FTP Server-Windows Server 2012

    架设FTP Server-Windows Server 2012 https://jingyan.baidu.com/article/03b2f78c75b9b65ea237ae84.html   在 ...

随机推荐

  1. provider 设计模式

    相关介绍文章: Provider Model Design Pattern and Specification, Part 1 (old but detailed). The ASP.NET 2.0 ...

  2. php : 常用函数

    常用函数: <?php /** * 获取客户端IP * @return [string] [description] */ function getClientIp() { $ip = NULL ...

  3. 复利计算器(4)——jQuery界面美化、自动补全

    一.分工 这次终于可以跟小伙伴合作了,经过讨论,我负责界面的美化和输入框自动补全,小伙伴擅长安卓,于是将复利计算器弄成app的任务就交给了小伙伴.为了我们两人团队,我们都好奋斗哈哈哈!! 二.界面美化 ...

  4. js 中与元素有关的高度

    1, 平常都经常用 document.documentElement.clientWidth 或 document.documentElement.clientHeight 来获取页面的宽度和高度, ...

  5. springmvc 用注解方式添加事务不生效解决方法

    springmvc 事务注册有很多种方法,在此我只mark 用注解方式添加transaction不生效的解决办法. springmvc 注解方法添加事务步骤: 1.在 spring的 root-con ...

  6. asp TreeView控件的使用

    相对于之前发过一个TreeView控件的使用方法 本次利用js操作,页面无刷新,性能提高 Css编码可能时我的模板页样式被继承下来,导致页面变乱,不需要的可以去掉 前台 <style> . ...

  7. contiki-事件调度

    事件驱动机制广泛应用于嵌入式系统,类似于中断机制,当有事件到来时(比如按键.数据到达),系统响应并处理该事件.相对于轮询机制,事件机制优势很明星,低功耗(系统处于休眠状态,当有事件到达时才被唤醒)和M ...

  8. 行内js函数调用

    <ul> <li onclick=abc(this);><a href="javascript:void(0);">12234588</a ...

  9. centos5.11 repo 安装mysql5.7

    http://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html mysql yum repo 安装说明 http://d ...

  10. iOS CommonCrypto 对称加密 AES ecb,cbc

    CommonCrypto 为苹果提供的系统加密接口,支持iOS 和 mac 开发: 不仅限于AES加密,提供的接口还支持其他DES,3DES,RC4,BLOWFISH等算法, 本文章主要讨论AES在i ...