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