本文将在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 代理服务器实现的更多相关文章

  1. java编程,通过代理服务器访问外网的FTP

    有些时候我们的网络不能直接连接到外网, 需要使用http或是https或是socket代理来连接到外网, 这里是java使用代理连接到外网的一些方法, 希望对你的程序有用.方法一:使用系统属性来完成代 ...

  2. FTP Proxy Server

    本文将在Linux环境下实现一个简单的FTP代理服务器,主要内容涉及FTP主动/被动模式和简单的Socket编程. 1. 主动模式和被动模式 FTP有两种模式,即主动模式(Active Mode)和被 ...

  3. 代理服务器基本知识普及代理IP使用方法!

    本文并未从专业角度进行详细讲解,而是从应用的角度出发来普及一些代理服务器的基本知识.文章明显是搜集多方资料的拼凑,而且比较老了,但往往越老的东西越接近事物的本质,更容易窥探到原理,对于刚接触的人来说, ...

  4. 大规模集群FTP代理(基于lvs的vsftpd网络负载均衡方案部署(PASV))

    [目的] 在日常工作中,我们经常需要在某服务器上开FTP(Server)服务.但就是这么简单的事情通常也会变得很复杂,原因如下:1.需要开通FTP的服务器没有公网IP地址:(即不能直接访问到)2.这样 ...

  5. 用C#实现WEB代理服务器

    用C#实现Web代理服务器 代理服务程序是一种广泛使用的网络应用程序.代理程序的种类非常多,根据协议不同可以分成HTTP代理服务程序.FTP代理服务程序等,而运行代理服务程序的服务器也就相应称为HTT ...

  6. 浅谈C#实现Web代理服务器的几大步骤

    代理服务程序是一种广泛使用的网络应用程序.代理程序的种类非常多,根据协议不同可以分成HTTP代理服务程序.FTP代理服务程序等,而运行代理服务程序的服务器也就相应称为HTTP代理服务器和FTP代理服务 ...

  7. java中设置代理的两种方式

    1 前言 有时候我们的程序中要提供可以使用代理访问网络,代理的方式包括http.https.ftp.socks代理.比如在IE浏览器设置代理. 那我们在我们的java程序中使用代理呢,有如下两种方式. ...

  8. Android之NDK开发(转)

    Android之NDK开发 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...

  9. cygwin-安装断点续传

    本文转载http://blog.chinaunix.net/uid-20178959-id-1731456.html 本文主要介绍正常下载安装(http://www.cnblogs.com/hwagg ...

随机推荐

  1. spring整合dubbo[单机版]

    Spring整合Dubbo,这个是用xml配置的 (方式一) 来梳理下步骤: 1. 安装zookeeper,在进行简单配置[这里使用单机模式,不用集群] 2. 创建maven项目,构建项目结构 3. ...

  2. 异常处理与网络基础中的tcp,udp协议

    # 异常处理: # 什么是异常?异常和错误的区别 # Error 语法错误 比较明显的错误 在编译代码阶段就能检测出来 # Iteration 异常 在执行代码的过程中引发的异常 # 异常发生之后的效 ...

  3. 关于PHP5.6连接SqlServer

    在做一个PHP报名系统的时候需要使用mssql来和winform结合起来使用, 但是发现我的php环境没有sqlsrv模块,于是乎,我就开始百度了 找到了微软官方下载地址,对照php版本,下载对应的模 ...

  4. EJB 笔记

    EJB(Enterprise JavaBean)是J2EE服务器端的组件模型,EJB包括会话Bean(Session Bean).实体Bean(Entity Bean).消息驱动Bean(Messag ...

  5. Uniprot数据库

    Uniprot数据库是Universal Protein的英文缩写,是信息最丰富.资源最广的蛋白质数据库. UniprotKB由两部分组成: UniProtKB/Swiss-Prot 高质量的.手工注 ...

  6. 用《捕鱼达人》去理解C#中的多线程

    线程是进程中某个单一顺序的控制流,是程序运行中的调度单位,是程序执行流的最小单位,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成. 线程自己不拥有系统资源,只拥有一点儿在运行中必 ...

  7. video 自动循环播放

    video 只加autoplay并不能自动播放,需要再加上muted   <video controls="controls" autoplay loop muted> ...

  8. Android 自定义类型文件与程序关联

    0x01 功能 实现在其他应用中打开某个后缀名的文件 可以直接跳转到本应用中的某个activity进行处理 0x01 实现 首先创建一个activity ,然后在manifest里对该activity ...

  9. Elasticsearch+Mongo亿级别数据导入及查询实践

    数据方案: 在Elasticsearch中通过code及time字段查询对应doc的mongo_id字段获得mongodb中的主键_id 通过获得id再进入mongodb进行查询   1,数据情况: ...

  10. erlang的热更新

    erlang作为一个为电信级别而出现的语言,热更新是其最重要的特性之一  热代码升级-Erlang允许程序代码在运行系统中被修改.旧代码能被逐步淘汰而后被新代码替换.在此过渡期间,新旧代码是共存的. ...