Following the previous blog Post implementation of the shell under Linux, we further utilize the knowledge of network programming and system programming to implement Linux Ftpserver. We have VSFTPD as a prototype and implemented most of its functions. Because of the relationship between space and time, here is no longer one by one to repeat the detailed implementation process, but a brief overview of the concept of functional implementation and part of the core code.
(i) basic frameworks and processes
Solve two questions First:
(1) Why use the nobody process and the two processes of the service process?
In port mode, the server will actively establish a data channel connection Client,server may not have permission to do such a thing, you need to nobody process to help.
The nobody process passes the socket to the service process through the UNIX domain protocol (high efficiency of native communication).
Ordinary users do not have permission to bind 20port, need to nobody process assistance, so need to nobody process as the control process.
(2) Why use multiple processes instead of multithreading?
The reason is in the case of multithreading or IO multiplexing. The current folder is shared. You cannot have your own current folder on a per-connection basis. This means that the current User folder switch affects other users.
(ii) Implementation of the main passive mode
The primary passive is relative to the server:
Active mode: The server knocks at the client and then the client opens the door
Passive mode: Client knocks at server, then server opens
The emergence of passive mode is mainly to solve the problem caused by firewall or NAT. When converted through NAT. The server simply learns the address of the NAT and is not aware of the client's IP address, so the server makes a request to the NAT Portport at 20port, but Nat does not have portport enabled, so the connection is rejected.
int get_transfer_fd (session_t *sess) {//check whether you receive the port or PASV command if (!port_active (sess) &&!pasv_active (Sess)) {FTP_ Reply (Sess, ftp_badsendconn, "use PORT or PASV first."); return 0;} int ret = 1;//is assumed to be active mode if (Port_active (Sess)) {if (GET_PORT_FD (sess) = = 0) {ret = 0;}} if (pasv_active (Sess)) {if (GET_PASV_FD (sess) = = 0) {ret = 0;}} if (sess->port_addr) {free (SESS->PORT_ADDR); sess->port_addr = NULL;} if (ret) {//once again, install the SIGALRM signal. and start the alarm start_data_alarm ();} return ret;}
(iii) Implementation of BASIC commands
Follow the RFC specification and VSFTPD demo results, and then simulate the following commands in turn:
static void Do_user (session_t *sess), static void Do_pass (session_t *sess), static void Do_cwd (session_t *sess), static void Do_cdup (session_t *sess), static void Do_quit (session_t *sess), static void Do_port (session_t *sess), static void Do_ PASV (session_t *sess), static void Do_type (session_t *sess), static void Do_retr (session_t *sess), static void Do_stor ( Session_t *sess), static void Do_appe (session_t *sess), static void Do_list (session_t *sess), static void Do_nlst (Session_ T *sess), static void Do_rest (session_t *sess), static void Do_abor (session_t *sess), static void Do_pwd (session_t *sess); static void Do_mkd (session_t *sess), static void Do_rmd (session_t *sess), static void Do_dele (session_t *sess), static void DO_RNFR (session_t *sess), static void Do_rnto (session_t *sess), static void Do_site (session_t *sess), static void Do_syst ( Session_t *sess), static void Do_feat (session_t *sess), static void Do_size (session_t *sess), static void Do_stat (Session_ t *sess); static void Do_noop (session_t *sess); Static void Do_help (session_t *sess);
Note: The use of static is intended to be applied only in a single module.
(iv) upload/download interruption of the implementation of the continued transmission
The idea of the continuation of the breakpoint is easy, just need to use a global variable to record the offset in the file.
Continue uploading/downloading from the offset next time.
static void Do_retr (session_t *sess) {//download file//breakpoint reload//Create data connection if (GET_TRANSFER_FD (sess) = = 0) {return;} Long long offset = Sess->restart_pos;sess->restart_pos = 0;//Open file int fd = open (Sess->arg, o_rdonly); if (fd = =-1 {ftp_reply (Sess, Ftp_filefail, "Failed to open file."); return;} int ret;//read-LOCK ret = Lock_file_read (FD); if (ret = =-1) {ftp_reply (Sess, Ftp_filefail, "Failed to open file."); return;} Infer whether the normal file struct stat Sbuf;ret = Fstat (fd, &SBUF); S_isreg (Sbuf.st_mode)) {ftp_reply (Sess, Ftp_filefail, "Failed to open file."); return;} if (offset! = 0) {ret = Lseek (FD, offset, seek_set), if (ret = =-1) {ftp_reply (Sess, Ftp_filefail, "Failed to open file."); return;}} Opening BINARY Mode data connection for/home/jjl/tmp/echocli.c (1085 bytes).//150char text[1024] = {0};if (SESS-&G T;IS_ASCII) {sprintf (text, "Opening ASCII mode data connection for%s (%lld bytes).", Sess->arg, (Long Long) sbuf.st_size );} else{sprintf (text, "Opening BINARY mode data connection for%s (%lld bytes).", Sess->arg, (Long Long) sbuf.st_size);} Ftp_reply (Sess, ftp_dataconn, text); int flag = 0;//ssize_t sendfile (int out_fd, int in_fd, off_t *offset, size_t count); l Ong Long bytes_to_send = sbuf.st_size;if (Offset > bytes_to_send) {bytes_to_send = 0;} Else{bytes_to_send-= offset;} Sess->bw_transfer_start_sec = Get_time_sec (); sess->bw_transfer_start_usec = Get_time_usec (); while (bytes_to_ Send) {int num_this_time = bytes_to_send > 4096?4096:bytes_to_send;ret = Sendfile (SESS->DATA_FD, FD, NULL, num_this_time); if (ret = =-1) {flag = 2;break;} Limit_rate (sess, ret, 0); if (sess->abor_received) {flag = 2;break;} Bytes_to_send-= ret;} if (Bytes_to_send = = 0) {flag = 0;} Close the data socket close (SESS->DATA_FD); sess->data_fd = -1;close (FD); if (flag = = = 0 &&!sess->abor_received) {// 226ftp_reply (Sess, Ftp_transferok, "Transfer complete."); else if (flag = = 1) {//451ftp_reply (Sess, Ftp_badsendfile, "Failure reading from local file."); else if (flag = = 2) {//426ftp_reply (Sess, Ftp_badsendnet, "Failure writting to Network stream."); Check_abor (sess);//Once again open the control connection Channel Alarm Start_cmdio_alarm ();}
(v) The realization of speed limit
The speed limit is achieved by making the process sleep. Set a timer to calculate the current speed, assuming the discovery is greater than the defined speed. The current transfer time is calculated by the time of sleep = (current transfer speed/maximum transfer speed –1).
void Limit_rate (session_t *sess, int bytes_transfered, int is_upload) {sess->data_process = 1;//sleep time = (current transfer speed/MAX transfer speed) –1) * Current transmission time; long curr_sec = Get_time_sec (); Long curr_usec = Get_time_usec ();d ouble elapsed;elapsed = (double) (curr_sec -sess->bw_transfer_start_sec); elapsed + = (double) (CURR_USEC-SESS->BW_TRANSFER_START_USEC)/(double) 1000000; if (elapsed <= (double) 0) {elapsed = (double) 0.01;} Calculates the current transfer speed unsigned int bw_rate = (unsigned int) ((double) bytes_transfered/elapsed);d ouble rate_ratio;if (is_upload) {if (Bw_rate <= Sess->bw_upload_rate_max) {//Do not need speed limit sess->bw_transfer_start_sec = Curr_sec;sess->bw_transfer_start_usec = Curr_usec;return;} Rate_ratio = Bw_rate/sess->bw_upload_rate_max;} Else{if (bw_rate <= Sess->bw_download_rate_max) {//Do not require speed limit sess->bw_transfer_start_sec = Curr_sec;sess->bw_ Transfer_start_usec = Curr_usec;return;} Rate_ratio = Bw_rate/sess->bw_download_rate_max;} Sleep time = (current transfer speed/maximum transfer speed –1) * Current transmission time; double Pause_timE;pause_time = (Rate_ratio-(double) 1) * Elapsed;nano_sleep (pause_time); sess->bw_transfer_start_sec = Get_time_sec (); sess->bw_transfer_start_usec = Get_time_usec ();}
(vi) limit of maximum number of single IP connections
Implemented using a hash table.
After mapping, it is assumed that the number of connections to an IP exceeds the specified number. Do not agree to connect. It is important to note that there are two hash tables to build. The mapping between ip& processes and the number of ip& connections is recorded separately. Because we have to know the relationship between the process and the IP when the user disconnects.
Please refer to my blog post on the hash table: http://blog.csdn.net/nk_test/article/details/50526184
S_ip_count_hash = Hash_alloc (hash_func); S_pid_ip_hash = Hash_alloc (Hash_func);
void Check_limits (session_t *sess) {if (tunable_max_clients > 0 && sess->num_clients > Tunable_max_ Clients) {ftp_reply (Sess, Ftp_too_many_users, "There is TOO many connected USERS, please try later."); Exit (exit_failure);} if (tunable_max_per_ip > 0 && sess->num_this_ip > Tunable_max_per_ip) {ftp_reply (Sess, Ftp_ip_limit, " There is too many connections from your Internet address. "); Exit (Exit_failure);}}
For specific implementations of the project please download the source code to my
Github .
References:
FTP Official specification of the agreement:RFC 959
M.J "hands-on implementation of FTP"
Implementation of Ftpserver under Linux (Imitation vsftpd)