FTP 代理服务器实现
本文将在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 代理服务器实现的更多相关文章
- java编程,通过代理服务器访问外网的FTP
有些时候我们的网络不能直接连接到外网, 需要使用http或是https或是socket代理来连接到外网, 这里是java使用代理连接到外网的一些方法, 希望对你的程序有用.方法一:使用系统属性来完成代 ...
- FTP Proxy Server
本文将在Linux环境下实现一个简单的FTP代理服务器,主要内容涉及FTP主动/被动模式和简单的Socket编程. 1. 主动模式和被动模式 FTP有两种模式,即主动模式(Active Mode)和被 ...
- 代理服务器基本知识普及代理IP使用方法!
本文并未从专业角度进行详细讲解,而是从应用的角度出发来普及一些代理服务器的基本知识.文章明显是搜集多方资料的拼凑,而且比较老了,但往往越老的东西越接近事物的本质,更容易窥探到原理,对于刚接触的人来说, ...
- 大规模集群FTP代理(基于lvs的vsftpd网络负载均衡方案部署(PASV))
[目的] 在日常工作中,我们经常需要在某服务器上开FTP(Server)服务.但就是这么简单的事情通常也会变得很复杂,原因如下:1.需要开通FTP的服务器没有公网IP地址:(即不能直接访问到)2.这样 ...
- 用C#实现WEB代理服务器
用C#实现Web代理服务器 代理服务程序是一种广泛使用的网络应用程序.代理程序的种类非常多,根据协议不同可以分成HTTP代理服务程序.FTP代理服务程序等,而运行代理服务程序的服务器也就相应称为HTT ...
- 浅谈C#实现Web代理服务器的几大步骤
代理服务程序是一种广泛使用的网络应用程序.代理程序的种类非常多,根据协议不同可以分成HTTP代理服务程序.FTP代理服务程序等,而运行代理服务程序的服务器也就相应称为HTTP代理服务器和FTP代理服务 ...
- java中设置代理的两种方式
1 前言 有时候我们的程序中要提供可以使用代理访问网络,代理的方式包括http.https.ftp.socks代理.比如在IE浏览器设置代理. 那我们在我们的java程序中使用代理呢,有如下两种方式. ...
- Android之NDK开发(转)
Android之NDK开发 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...
- cygwin-安装断点续传
本文转载http://blog.chinaunix.net/uid-20178959-id-1731456.html 本文主要介绍正常下载安装(http://www.cnblogs.com/hwagg ...
随机推荐
- hdfs性能调优(cloudera)
参照官方文档:http://www.cloudera.com/content/cloudera/en/documentation/core/latest/topics/cdh_ig_yarn_tuni ...
- HDU 2665 Kth number(主席树静态区间第K大)题解
题意:问你区间第k大是谁 思路:主席树就是可持久化线段树,他是由多个历史版本的权值线段树(不是普通线段树)组成的. 具体可以看q学姐的B站视频 代码: #include<cmath> #i ...
- 使用Mongo进行分页
MongoDB’s pipeline aggregation is – like most things in application development these days – confusi ...
- 使用ODP.NET连接Oracle数据库
项目需要和第三方进行对接,奇葩的是地方没给提供接口,却提供了一个Oracle的视图 所以有了C#访问Oracle数据库的需求 1.打开nuget,安装驱动 https://www.nuget.org/ ...
- Flutter安装之后cmd运行错误解决
当把Flutter环境变量配置之后,打开cmd输入:flutter 出现如下错误: 'MySQL' is not recognized as an internal or external comma ...
- 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- ...
- mybatis-ehcache整合中出现的异常 ibatis处理器异常(executor.ExecutorException)解决方法
今天学习mabatis时出现了,ibatis处理器处理器异常,显示原因是Executor was closed.则很有可能是ibatis的session被关闭了, 后面看了一下测试程序其实是把sqlS ...
- oledb
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data; ...
- linux运行进程实时监控pidstat详解
- Spring MyBatis多数据源(同包)
创建基本的包 entity service dao 为了区分多数据源 一个用的是Mysql 一个是Oracle 方便测试, 创建MyBatis dao 映射 xml 文件 创建db.propertie ...