vsftpd中进程的创建

最近把好多年前的一个开源组件拿出来学习,顺便记录一下。以前一直很忙未深入看过这个代码。现在比较闲,来认真学习一下。全是个人理解。不知道对不对,希望大家批评指正。

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,发给子进程。

  }

  .....

}

这样的交互还比较多。

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值