FTP Proxy Server
本文将在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,¶m);
//如果接收到内容,则对内容进行必要的处理,之后发送给服务器端(写入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的更多相关文章
- proxy server 代理服务器
有时候,我觉得自己需要去搞明白.搞清楚一个概念,帮我打通一下自己的知识体系,或者说,尝试联络起来. 1. 简介 突破自身IP限制,访问国外站点. 访问单位或者团体内部资源. 突破中国电信的IP封锁. ...
- Proxy Server代理服务器(轉載)
宽带IP城域网开通以来,单位连上了宽带网,10M的带宽让我们感受到了宽带的魅力.电信只提供7个IP地址,对任何一个单位来说都太少了,常用的解决办法是使用代理服务器.微软的MS Proxy Server ...
- 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 ...
- 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 ...
- 设置Proxy Server和SQL Server实现互联网上的数据库安全
◆首先,我们需要了解一下SQL Server在WinSock上定义协议的步骤: 1. 在”启动”菜单上,指向”程序/Microsoft Proxy Server”,然后点击”Microsoft Man ...
- 转:Jmeter 用户思考时间(User think time),定时器,和代理服务器(proxy server)
在负载测试中需要考虑的的一个重要要素是思考时间(think time), 也就是在两次成功的访问请求之间的暂停时间. 有多种情形挥发导致延迟的发生: 用户需要时间阅读文字内容,或者填表,或者查找正确的 ...
- 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问题处理 现象:访问订单的 ...
- 简单实现http proxy server怎么实现?
原文:https://blog.csdn.net/dolphin98629/article/details/54599850 简单实现http proxy server怎么实现?
- 架设FTP Server-Windows Server 2012
架设FTP Server-Windows Server 2012 https://jingyan.baidu.com/article/03b2f78c75b9b65ea237ae84.html 在 ...
随机推荐
- nginx基本配置和参数说明
#运行用户user nobody;#启动进程,通常设置成和cpu的数量相等worker_processes 1; #全局错误日志及PID文件#error_log logs/error.log;#err ...
- JS --- 原型模式
创建一个对象,先来看一段代码: // 例如创建一个 Person 的构造函数, 让人有名字, 可以说话 function Person ( name ) { this.name = name; // ...
- javascript字符转直接量和转义字符
直接量: 你可以用单引号或者双引号来表示字符串的直接量.但是js的字符串必须是Unicode 字符序列. 转义字符: 主要用在字符串中,包涵控制字符,以及当前操作系统余元所不允许直接输入的字符. 转义 ...
- hostapd与wpa_supplicant
hostapd与wpa_supplicant hostapd hostapd includes IEEE 802.11 access point management (authentication ...
- monkey 自己总结
adb shell monkey -p com.test.smstest --pct-touch 50 --pct-motion 30 --pct-nav 20 -s 10 -v --throttle ...
- Gvim常用命令
这是一篇较全的vim命令.特记录下来,有稍作修改说明.摘http://hi.baidu.com/ui176/item/b00ae7c0eeaba52847d5c0c5 Vim常用命令 跳到指定的行号: ...
- 使用requestAnimationFrame做动画效果一
最近学习了requestAnimationFrame,看了张鑫旭直白易懂,但是某些地方语言过于裸露的文章http://www.zhangxinxu.com/wordpress/2013/09/css3 ...
- css学习笔记 4
伪类可以用来指定一个或多个与其相关的选择符的状态,如a:link a:active a:hover a:visited :ie7及以下不支持. 注意:上面与超链接相关的伪类的顺序必须是:LVHA. 伪 ...
- Python学习笔记之条件、循环和其他语句
一.函数导入 1.为模块提供别名 >>> import math as foobar #设置math 别名为foobar >>> foobar.sqrt(4) 显示 ...
- java系统高并发解决方案-转
转载博客地址:http://blog.csdn.net/zxl333/article/details/8685157 一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图 ...
