本文將在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 }