最近把好多年前的一个开源组件拿出来学习,顺便记录一下。以前一直很忙未深入看过这个代码。现在比较闲,来认真学习一下。全是个人理解。不知道对不对,希望大家批评指正。
1. 入口函数main
先处理入口命令,解析配置文件。
2. 起服务的函数vsf_standalone_main
里面类似创建一个守护进程,先1). 创建一个子进程,父进程退出,2).setsid 3)在子进程中创建一个服务器(创建socket, reuse,bind,listen,accept)。4)在死循环中创建子进程,后面的动作都交给子进程了
struct vsf_client_launch
vsf_standalone_main(void)
{
......
if (tunable_background)
{
int forkret = vsf_sysutil_fork();//-----创建一个新进程,父进程退出,一般的守护进程创建方式
if (forkret > 0)
{
/* Parent, just exit */
vsf_sysutil_exit(0);
}
/* Son, close standard FDs to avoid SSH hang-on-exit */
vsf_sysutil_reopen_standard_fds();
vsf_sysutil_make_session_leader();//setsid
}
if (tunable_listen)
{
listen_sock = vsf_sysutil_get_ipv4_sock();
}
else
{
listen_sock = vsf_sysutil_get_ipv6_sock(); //-----创建监听进程
}
vsf_sysutil_activate_reuseaddr(listen_sock);//设置复用setsockopt
s_p_ip_count_hash = hash_alloc(256, s_ipaddr_size,
sizeof(unsigned int), hash_ip);
s_p_pid_ip_hash = hash_alloc(256, sizeof(int),
s_ipaddr_size, hash_pid);///---确实不知道这是干什么?
if (tunable_setproctitle_enable)
{
vsf_sysutil_setproctitle("LISTENER");
}
vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1);
vsf_sysutil_install_sighandler(kVSFSysUtilSigHUP, handle_sighup, 0, 1); //注册信号
if (tunable_listen)
{
struct vsf_sysutil_sockaddr *p_sockaddr = 0;
vsf_sysutil_sockaddr_alloc_ipv4(&p_sockaddr);
vsf_sysutil_sockaddr_set_port(p_sockaddr,
(unsigned short)tunable_listen_port);
if (!tunable_listen_address)
{
vsf_sysutil_sockaddr_set_any(p_sockaddr);
}
else
{
if (!vsf_sysutil_inet_aton(tunable_listen_address, p_sockaddr))
{
die2("bad listen_address: ", tunable_listen_address);
}
}
retval = vsf_sysutil_bind(listen_sock, p_sockaddr);//bind
vsf_sysutil_free(p_sockaddr);
if (vsf_sysutil_retval_is_error(retval))
{
die("could not bind listening IPv4 socket");
}
}
else
{
.......
}
retval = vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG);//监听
if (vsf_sysutil_retval_is_error(retval))
{
die("could not listen");
}
vsf_sysutil_sockaddr_alloc(&p_accept_addr);
while (1)
{
struct vsf_client_launch child_info;
void *p_raw_addr;
int new_child;
int new_client_sock;
new_client_sock = vsf_sysutil_accept_timeout(
listen_sock, p_accept_addr, 0); //在死循环中accept,从完成三次握手的队列中取socket
if (vsf_sysutil_retval_is_error(new_client_sock))
{
continue;
}
++s_children;
child_info.num_children = s_children;
child_info.num_this_ip = 0;
p_raw_addr = vsf_sysutil_sockaddr_get_raw_addr(p_accept_addr);
child_info.num_this_ip = handle_ip_count(p_raw_addr);
if (tunable_isolate)
{
......
}
else
{
new_child = vsf_sysutil_fork_failok();
}
if (new_child != 0)
{
/* Parent context 在父进程中一直循环accept*/
vsf_sysutil_close(new_client_sock);
if (new_child > 0)
{
hash_add_entry(s_p_pid_ip_hash, (void *)&new_child, p_raw_addr);
}
else
{
/* fork() failed, clear up! */
--s_children;
drop_ip_count(p_raw_addr);
}
/* Fall through to while() loop and accept() again */
}
else
{
/* Child context 在子进程中就直接返回,后面的操作其实都是在这个创建的子进程中做的 */
vsf_set_die_if_parent_dies();
vsf_sysutil_close(listen_sock);
prepare_child(new_client_sock);
/* By returning here we "launch" the child process with the same
* contract as xinetd would provide.
*/
return child_info;
}
}
}
3. 中间的常规操作暂时不看了,到了真正的启动:vsf_two_process_start
void
vsf_two_process_start(struct vsf_session* p_sess)
{
.....
/* Create the comms channel between privileged parent and no-priv child */
priv_sock_init(p_sess); //---很重要。。。。
.....
vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1);
{
int newpid;
if (tunable_isolate_network)
{
newpid = vsf_sysutil_fork_newnet();
}
else
{
newpid = vsf_sysutil_fork();
}
if (newpid != 0)
{
priv_sock_set_parent_context(p_sess);
if (tunable_ssl_enable)
{
ssl_comm_channel_set_consumer_context(p_sess);
}
/* Parent - go into pre-login parent process mode */
while (1)
{
process_login_req(p_sess); //在这个父进程处理命令,在这里面还会在创建一次,
}
}
}
/* Child process - time to lose as much privilege as possible and do the
* login processing//在子进程中处理链接信息
*/
vsf_set_die_if_parent_dies();
priv_sock_set_child_context(p_sess);
if (tunable_ssl_enable)
{
ssl_comm_channel_set_producer_context(p_sess);
}
if (tunable_local_enable && tunable_userlist_enable)
{
int retval = -1;
if (tunable_userlist_file)
{
retval = str_fileread(&p_sess->userlist_str, tunable_userlist_file,
VSFTP_CONF_FILE_MAX);
}
if (vsf_sysutil_retval_is_error(retval))
{
die2("cannot read user list file:", tunable_userlist_file);
}
}
drop_all_privs();
seccomp_sandbox_init();
(p_sess);
seccomp_sandbox_lockdown();
init_connection(p_sess);
/* NOTREACHED */
}
总体:
vsf_standalone_main 中创建子进程p1,父进程死
然后在P1中创建服务器,并在accept后fork一个新的子线程P2, P1 中一直accept.
在P2 中做一些常规处理,并调用vsf_two_process_start
在vsf_two_process_start中创建一个子进程P3 处理连接请求,220回复,用户名密码鉴权等一些登录的事情。
继续在P2中处理请求命令:最要调用 process_login_req这个函数,来处理请求的其他命令,按道理需要子进程中的login请求处理完了才能进行其他处理的。那么子进程怎么通知到父进程的呢?主要是看vsf_two_process_start函数中的 priv_sock_init(p_sess),创建了一个socketpair。
在process_login_req中把切换到用户的chroot 目录,用户名密码校验完成以后。创建一个新的进程P4,来处理RETR, STORE之类的读写命令。
这个时候其实P2 还在干活的。
所以,一般情况下一个链接里面会有3个进程。P3和P4 的父进程是P2。P3和P4不直接通信,都是直接跟P2 通信,使用的是socketpair。跟管道比较像参考:socketpair的用法和理解-CSDN博客
P1(主要是一直accept) | P2 主要是读取各种信息。处理登录相关命令的响应等。以下举例说明 | P3 处理login相关,包括命令,鉴权等,处理控制通道的一些事情。 |
P4 在确认login之后响应RETR等命令,主要是处理数据传输。 |
数据传输过程中。父子进程之间的交互:
比如说:PASV命令,客户端要求使用被动模式,此时在P4 里面处理该命令:在响应函数
static void handle_pasv(struct vsf_session* p_sess, int is_epsv) 中给父进程发一个消息:PRIV_SOCK_PASV_LISTEN
unsigned short vsf_two_process_listen(struct vsf_session* p_sess)
{
priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_PASV_LISTEN);
return (unsigned short) priv_sock_get_int(p_sess->child_fd);
}
然后父进程P2中收到这个PRIV_SOCK_PASV_LISTEN之后就会cmd_process_pasv_listen函数取获取端口号。然后发给子进程。
static void process_post_login_req(struct vsf_session* p_sess)
{
char cmd;
.....
else if (cmd == PRIV_SOCK_PASV_LISTEN)
{
cmd_process_pasv_listen(p_sess); //这里面获取端口号,并创建好被动socket,发给子进程。
}
.....
}
这样的交互还比较多。