本文将在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. Cheatsheet: 2016 09.01 ~ 09.30

    Web Is JavaScript Single-Threaded? Quill 1.0 – Better Rich Text Editor for Web Apps Next Generation ...

  2. fzu1036四塔问题(汉诺塔问题拓展)

    #include<iostream> #include<cstdio> #include<cmath> using namespace std; ]; int ru ...

  3. 如何将推送证书p12导出为pem

    1. 在Mac上启动Keychain助手,然后在login keychain中选择 Certificates分类.你将看到一个可展开的“Apple Development Push Services” ...

  4. windos系统定时执行批处理文件(bat文件)

    Win7怎么设置定时自动执行任务? 点击开始按钮,依次选择打开“所有程序—附件—系统工具”,找到“任务计划程序”即可打开Win7系统的任务计划设置面板.也可以点击Win7开始按钮,在多功能搜索框中输入 ...

  5. Chrome谷歌浏览器下不支持css字体小于12px的解决办法

    先来看下 ie.火狐.谷歌浏览器下各个字体显示情况 ie下: 火狐下: 谷歌下: 从上面的图可以很明显看出谷歌下 css设置字体大小为12px及以下时,显示都是一样大小,都是默认12px; 那么网上一 ...

  6. iOS - Card Identification 银行卡号识别

    1.CardIO 识别 框架 GitHub 下载地址 配置 1.把框架整个拉进自己的工程,然后在 TARGETS => Build Phases => Link Binary With L ...

  7. 说说focus /focusin /focusout /blur 事件

    事件触发时间 focus:当focusable元素获得焦点时,不支持冒泡:focusin:和focus一样,只是此事件支持冒泡:blur:当focusable元素失去焦点时,不支持冒泡:focusou ...

  8. SwitchyOmega 在线调试

    1,chrome 安装 Proxy SwitchyOmega 扩展程序 2,新建情景模式,输入模式名称"例如:new proxy1",选择"请选择情景模式的类型:代理服务 ...

  9. 构建高可用集群Keepalived+Haproxy负载均衡

    重点概念vrrp_script中节点权重改变算法vrrp_script 里的script返回值为0时认为检测成功,其它值都会当成检测失败:weight 为正时,脚本检测成功时此weight会加到pri ...

  10. 可以结合react的ui组件

    https://ant.design/components/switch-cn/