本文将在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代理服务器实现
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <arpa/inet.h>
  7. #include <string.h>
  8. #include <ctype.h>
  9.  
  10. #define TRUE 1
  11. #define FALSE 0
  12.  
  13. #define CMD_PORT 21
  14. #define BUFFSIZE 4096
  15. #define LISTENQ 5
  16.  
  17. int acceptSocket(int socket,struct sockaddr *addr,socklen_t *addrlen);
  18. int connectToServerByAddr(struct sockaddr_in servaddr);
  19. int connectToServer(char *ip,unsigned short port);
  20. int bindAndListenSocket(unsigned short port);
  21. void splitCmd(char *buff, char **cmd,char **param);
  22. unsigned short getPortFromFtpParam(char *param);
  23. void getSockLocalIp(int fd,char *ipStr,int buffsize);
  24. unsigned short getSockLocalPort(int sockfd);
  25.  
  26. int main(int argc, const char *argv[])
  27. {
  28. int i;
  29. fd_set master_set, working_set; //文件描述符集合
  30. struct timeval timeout; //select 参数中的超时结构体
  31. int proxy_cmd_socket = ; //proxy listen控制连接
  32. int accept_cmd_socket = ; //proxy accept客户端请求的控制连接
  33. int connect_cmd_socket = ; //proxy connect服务器建立控制连接
  34. int proxy_data_socket = ; //proxy listen数据连接
  35. int accept_data_socket = ; //proxy accept得到请求的数据连接(主动模式时accept得到服务器数据连接的请求,被动模式时accept得到客户端数据连接的请求)
  36. int connect_data_socket = ; //proxy connect建立数据连接 (主动模式时connect客户端建立数据连接,被动模式时connect服务器端建立数据连接)
  37. int selectResult = ; //select函数返回值
  38. int select_sd = ; //select 函数监听的最大文件描述符
  39. int pasv_mode = ;
  40.  
  41. char serverProxyIp[BUFFSIZE]; //待获得
  42. char clientProxyIp[BUFFSIZE]; //待获得 serverProxyIp和clientProxyIp可能不一样
  43. char serverIp[BUFFSIZE];
  44.  
  45. unsigned short proxy_data_port;
  46. unsigned short data_port;
  47. socklen_t clilen;
  48. struct sockaddr_in cliaddr;
  49.  
  50. if(argc != ){
  51. printf("usage : proxy server_ip\n example: proxy 121.121.121.121\n");
  52. }
  53. strcpy(serverIp,argv[]);
  54.  
  55. FD_ZERO(&master_set); //清空master_set集合
  56. bzero(&timeout, sizeof(timeout));
  57.  
  58. proxy_cmd_socket = bindAndListenSocket(CMD_PORT); //开启proxy_cmd_socket、bind()、listen操作
  59. FD_SET(proxy_cmd_socket, &master_set); //将proxy_cmd_socket加入master_set集合
  60.  
  61. while (TRUE) {
  62. FD_ZERO(&working_set); //清空working_set文件描述符集合
  63. memcpy(&working_set, &master_set, sizeof(master_set)); //将master_set集合copy到working_set集合
  64. timeout.tv_sec = ; //Select的超时结束时间
  65. timeout.tv_usec = ; //ms
  66.  
  67. //select循环监听 这里只对读操作的变化进行监听(working_set为监视读操作描述符所建立的集合),第三和第四个参数的NULL代表不对写操作、和误操作进行监听
  68. selectResult = select(select_sd, &working_set, NULL, NULL, &timeout);
  69.  
  70. // fail
  71. if (selectResult < ) {
  72. perror("select() failed\n");
  73. exit();
  74. }
  75.  
  76. // timeout
  77. if (selectResult == ) {
  78. printf("select() timed out.\n");
  79. continue;
  80. }
  81.  
  82. // selectResult > 0 时 开启循环判断有变化的文件描述符为哪个socket
  83. for (i = ; i < select_sd; i++) {
  84. //判断变化的文件描述符是否存在于working_set集合
  85. if (FD_ISSET(i, &working_set)) {
  86. if (i == proxy_cmd_socket) {
  87.  
  88. accept_cmd_socket = acceptSocket(proxy_cmd_socket,NULL,NULL); //执行accept操作,建立proxy和客户端之间的控制连接
  89. connect_cmd_socket = connectToServer(serverIp,CMD_PORT); //执行connect操作,建立proxy和服务器端之间的控制连接
  90.  
  91. getSockLocalIp(connect_cmd_socket,serverProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
  92. getSockLocalIp(accept_cmd_socket,clientProxyIp,BUFFSIZE); //获取本地ip,格式为port和pasv使用的格式
  93. printf("proxy ip from server's view : %s\n",serverProxyIp);
  94. printf("proxy ip from client's view : %s\n",clientProxyIp);
  95.  
  96. //将新得到的socket加入到master_set结合中
  97. FD_SET(accept_cmd_socket, &master_set);
  98. FD_SET(connect_cmd_socket, &master_set);
  99. }
  100.  
  101. if (i == accept_cmd_socket) {
  102. char buff[BUFFSIZE] = {};
  103. char copy[BUFFSIZE] = {};
  104.  
  105. if (read(i, buff, BUFFSIZE) == ) {
  106. close(i); //如果接收不到内容,则关闭Socket
  107. close(connect_cmd_socket);
  108. printf("client closed\n");
  109.  
  110. //socket关闭后,使用FD_CLR将关闭的socket从master_set集合中移去,使得select函数不再监听关闭的socket
  111. FD_CLR(i, &master_set);
  112. FD_CLR(connect_cmd_socket, &master_set);
  113.  
  114. } else {
  115. printf("command received from client : %s\n",buff);
  116. char *cmd,*param;
  117. strcpy(copy,buff);
  118. splitCmd(copy,&cmd,&param);
  119. //如果接收到内容,则对内容进行必要的处理,之后发送给服务器端(写入connect_cmd_socket)
  120.  
  121. //处理客户端发给proxy的request,部分命令需要进行处理,如PORT、RETR、STOR
  122. //PORT
  123. //////////////
  124. if(strcmp(cmd,"PORT") == ){ //修改ip & port
  125. //在这儿应该让proxy_data_socket监听任意端口
  126. proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
  127. proxy_data_port = getSockLocalPort(proxy_data_socket);
  128. FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
  129. pasv_mode = ;
  130. data_port = getPortFromFtpParam(param);
  131. bzero(buff,BUFFSIZE);
  132. sprintf(buff,"PORT %s,%d,%d\r\n",serverProxyIp,proxy_data_port / ,proxy_data_port % );
  133. }
  134.  
  135. //写入proxy与server建立的cmd连接,除了PORT之外,直接转发buff内容
  136. printf("command sent to server : %s\n",buff);
  137. write(connect_cmd_socket, buff, strlen(buff));
  138. }
  139. }
  140.  
  141. if (i == connect_cmd_socket) {
  142. //处理服务器端发给proxy的reply,写入accept_cmd_socket
  143. char buff[BUFFSIZE] = {};
  144. if(read(i,buff,BUFFSIZE) == ){
  145. close(i);
  146. close(accept_cmd_socket);
  147. FD_CLR(i,&master_set);
  148. FD_CLR(accept_cmd_socket,&master_set);
  149. }
  150.  
  151. printf("reply received from server : %s\n",buff);
  152. //PASV收到的端口 227 (port)
  153. //////////////
  154. if(buff[] == '' && buff[] == '' && buff[] == ''){
  155. proxy_data_socket = bindAndListenSocket(); //开启proxy_data_socket、bind()、listen操作
  156. proxy_data_port = getSockLocalPort(proxy_data_socket);
  157. FD_SET(proxy_data_socket, &master_set);//将proxy_data_socket加入master_set集合
  158. data_port = getPortFromFtpParam(buff + );
  159. bzero(buff + ,BUFFSIZE - );
  160. sprintf(buff + ,"%s,%d,%d).\r\n",clientProxyIp,proxy_data_port / ,proxy_data_port % );
  161. }
  162. printf("reply sent to client : %s\n",buff);
  163.  
  164. write(accept_cmd_socket,buff,strlen(buff));
  165. }
  166.  
  167. if (i == proxy_data_socket) {
  168. if(pasv_mode){ //clinet connect
  169. accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //client <-> proxy
  170. connect_data_socket = connectToServer(serverIp,data_port); //proxy <-> server
  171. }
  172. else{ //主动模式
  173. accept_data_socket = acceptSocket(proxy_data_socket,NULL,NULL); //proxy <-> server
  174. clilen = sizeof(cliaddr);
  175. if(getpeername(accept_cmd_socket,(struct sockaddr *)&cliaddr,&clilen) < ){
  176. perror("getpeername() failed: ");
  177. }
  178. cliaddr.sin_port = htons(data_port);
  179. connect_data_socket = connectToServerByAddr(cliaddr); //client <-> proxy
  180. }
  181.  
  182. FD_SET(accept_data_socket, &master_set);
  183. FD_SET(connect_data_socket, &master_set);
  184. printf("data connectiong established\n");
  185. //建立data连接(accept_data_socket、connect_data_socket)
  186. }
  187.  
  188. if (i == accept_data_socket) {
  189.  
  190. int n;
  191. char buff[BUFFSIZE] = {};
  192. if((n = read(accept_data_socket,buff,BUFFSIZE)) == ){
  193. close(accept_data_socket);
  194. close(connect_data_socket);
  195. close(proxy_data_socket);
  196. FD_CLR(proxy_data_socket,&master_set);
  197. FD_CLR(accept_data_socket, &master_set);
  198. FD_CLR(connect_data_socket, &master_set);
  199. }
  200. else{
  201. write(connect_data_socket,buff,n);
  202. }
  203.  
  204. //判断主被动和传输方式(上传、下载)决定如何传输数据
  205. }
  206.  
  207. if (i == connect_data_socket) {
  208. int n;
  209. char buff[BUFFSIZE] = {};
  210. if((n = read(connect_data_socket,buff,BUFFSIZE)) == ){
  211. close(accept_data_socket);
  212. close(connect_data_socket);
  213. close(proxy_data_socket);
  214. FD_CLR(proxy_data_socket,&master_set);
  215. FD_CLR(accept_data_socket, &master_set);
  216. FD_CLR(connect_data_socket, &master_set);
  217. }
  218. else{
  219. write(accept_data_socket,buff,n);
  220. }
  221. //判断主被动和传输方式(上传、下载)决定如何传输数据
  222. }
  223. }
  224. }
  225. }
  226.  
  227. return ;
  228. }
  229.  
  230. unsigned short getSockLocalPort(int sockfd)
  231. {
  232. struct sockaddr_in addr;
  233. socklen_t addrlen;
  234. addrlen = sizeof(addr);
  235.  
  236. if(getsockname(sockfd,(struct sockaddr *)&addr,&addrlen) < ){
  237. perror("getsockname() failed: ");
  238. exit();
  239. }
  240.  
  241. return ntohs(addr.sin_port);
  242. }
  243.  
  244. void getSockLocalIp(int fd,char *ipStr,int buffsize)
  245. {
  246.  
  247. bzero(ipStr,buffsize);
  248.  
  249. struct sockaddr_in addr;
  250. socklen_t addrlen;
  251. addrlen = sizeof(addr);
  252.  
  253. if(getsockname(fd,(struct sockaddr *)&addr,&addrlen) < ){
  254. perror("getsockname() failed: ");
  255. exit();
  256. }
  257.  
  258. inet_ntop(AF_INET,&addr.sin_addr,ipStr,addrlen);
  259.  
  260. char *p = ipStr;
  261. while(*p){
  262. if(*p == '.') *p = ',';
  263. p++;
  264. }
  265. }
  266.  
  267. unsigned short getPortFromFtpParam(char *param)
  268. {
  269. unsigned short port,t;
  270. int count = ;
  271. char *p = param;
  272.  
  273. while(count < ){
  274. if(*(p++) == ','){
  275. count++;
  276. }
  277. }
  278.  
  279. sscanf(p,"%hu",&port);
  280. while(*p != ',' && *p != '\r' && *p != ')') p++;
  281. if(*p == ','){
  282. p++;
  283. sscanf(p,"%hu",&t);
  284. port = port * + t;
  285. }
  286.  
  287. return port;
  288. }
  289.  
  290. //从FTP命令行中解析出命令和参数
  291. void splitCmd(char *buff, char **cmd,char **param)
  292. {
  293. int i;
  294. char *p;
  295.  
  296. while((p = &buff[strlen(buff) - ]) && (*p == '\r' || *p == '\n')) *p = ;
  297.  
  298. p = strchr(buff,' ');
  299. *cmd = buff;
  300.  
  301. if(!p){
  302. *param = NULL;
  303. }else{
  304. *p = ;
  305. *param = p + ;
  306. }
  307.  
  308. for(i = ;i < strlen(*cmd);i++){
  309. (*cmd)[i] = toupper((*cmd)[i]);
  310. }
  311. }
  312.  
  313. int acceptSocket(int cmd_socket,struct sockaddr *addr,socklen_t *addrlen)
  314. {
  315. int fd = accept(cmd_socket,addr,addrlen);
  316. if(fd < ){
  317. perror("accept() failed:");
  318. exit();
  319. }
  320.  
  321. return fd;
  322. }
  323.  
  324. int connectToServerByAddr(struct sockaddr_in servaddr)
  325. {
  326. int fd;
  327.  
  328. struct sockaddr_in cliaddr;
  329. bzero(&cliaddr,sizeof(cliaddr));
  330. cliaddr.sin_family = AF_INET;
  331. cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  332. //cliaddr.sin_port = htons(20);
  333.  
  334. fd = socket(AF_INET,SOCK_STREAM,);
  335. if(fd < ){
  336. perror("socket() failed :");
  337. exit();
  338. }
  339.  
  340. if(bind(fd,(struct sockaddr *)&cliaddr,sizeof(cliaddr) ) < ){
  341. perror("bind() failed :");
  342. exit();
  343. }
  344.  
  345. servaddr.sin_family = AF_INET;
  346. if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
  347. perror("connect() failed :");
  348. exit();
  349. }
  350.  
  351. return fd;
  352. }
  353.  
  354. int connectToServer(char *ip,unsigned short port)
  355. {
  356. int fd;
  357. struct sockaddr_in servaddr;
  358.  
  359. bzero(&servaddr,sizeof(servaddr));
  360. servaddr.sin_family = AF_INET;
  361. servaddr.sin_port = htons(port);
  362. inet_pton(AF_INET,ip,&servaddr.sin_addr);
  363.  
  364. fd = socket(AF_INET,SOCK_STREAM,);
  365. if(fd < ){
  366. perror("socket() failed :");
  367. exit();
  368. }
  369.  
  370. if(connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
  371. perror("connect() failed :");
  372. exit();
  373. }
  374.  
  375. return fd;
  376. }
  377.  
  378. int bindAndListenSocket(unsigned short port)
  379. {
  380. int fd;
  381. struct sockaddr_in addr;
  382.  
  383. fd = socket(AF_INET,SOCK_STREAM,);
  384. if(fd < ){
  385. perror("socket() failed: ");
  386. exit();
  387. }
  388.  
  389. bzero(&addr,sizeof(addr));
  390. addr.sin_family = AF_INET;
  391. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  392. addr.sin_port = htons(port);
  393.  
  394. if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < ){
  395. perror("bind() failed: ");
  396. exit();
  397. }
  398.  
  399. if(listen(fd,LISTENQ) < ){
  400. perror("listen() failed: ");
  401. exit();
  402. }
  403.  
  404. return fd;
  405. }

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. hdfs性能调优(cloudera)

    参照官方文档:http://www.cloudera.com/content/cloudera/en/documentation/core/latest/topics/cdh_ig_yarn_tuni ...

  2. HDU 2665 Kth number(主席树静态区间第K大)题解

    题意:问你区间第k大是谁 思路:主席树就是可持久化线段树,他是由多个历史版本的权值线段树(不是普通线段树)组成的. 具体可以看q学姐的B站视频 代码: #include<cmath> #i ...

  3. 使用Mongo进行分页

    MongoDB’s pipeline aggregation is – like most things in application development these days – confusi ...

  4. 使用ODP.NET连接Oracle数据库

    项目需要和第三方进行对接,奇葩的是地方没给提供接口,却提供了一个Oracle的视图 所以有了C#访问Oracle数据库的需求 1.打开nuget,安装驱动 https://www.nuget.org/ ...

  5. Flutter安装之后cmd运行错误解决

    当把Flutter环境变量配置之后,打开cmd输入:flutter 出现如下错误: 'MySQL' is not recognized as an internal or external comma ...

  6. Lintcode228-Middle of Linked List-Naive

    228. Middle of Linked List Find the middle node of a linked list. Example Example 1: Input: 1->2- ...

  7. mybatis-ehcache整合中出现的异常 ibatis处理器异常(executor.ExecutorException)解决方法

    今天学习mabatis时出现了,ibatis处理器处理器异常,显示原因是Executor was closed.则很有可能是ibatis的session被关闭了, 后面看了一下测试程序其实是把sqlS ...

  8. oledb

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data; ...

  9. linux运行进程实时监控pidstat详解

  10. Spring MyBatis多数据源(同包)

    创建基本的包 entity service dao 为了区分多数据源 一个用的是Mysql 一个是Oracle 方便测试, 创建MyBatis dao 映射 xml 文件 创建db.propertie ...