第三部分:FTP服务器最大连接数的限制
服务端的最大连接数
将当前的连接数保存在会话结构体成员中的max_clients,当有一个客户登录的时候对应的max_clients的值会加1。客户退出的时候,max_client的值会减少1个。
需要注意的是,当前的max_clients的值如果大于配置文件中的tunable_max_clients,那么客户端就无法登录。
连接的限制的函数是位于主函数miniftp.c中。
代码如下所示:
while(1)
{
sockConn = accept(listenfd, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn < 0)
{
perror("accept");
continue;
}
//最大连接数
s_children_nums++;
sess.max_clients = s_children_nums;
pid_t pid = fork();
// 进程创建失败的话,此时的连接数需要减少1.
if(pid == -1)
{
s_children_nums--;
ERR_EXIT("fork");
}
if(pid == 0)
{
//Child Process
close(listenfd);
sess.ctrl_fd = sockConn;
//连接限制
check_limit(&sess);
begin_session(&sess);
exit(EXIT_SUCCESS);
}
else
{
close(sockConn);
}
}
handle_limit函数如下所示:
static void check_limit(session_t *sess)
{
if(tunable_max_clients!=0 && sess->max_clients>tunable_max_clients)
{
//421 There are too many connected users, please try later.
ftp_reply(sess, FTP_TOO_MANY_USERS, "There are too many connected users, please try later.");
exit(EXIT_FAILURE);
}
}
客户端退出时,对应的max_client需要减少。要实现这个功能就需要对子进程进行回收,即nobody进程退出之后,会向主进程发送SIGCHID信号,为该信号定义自定义处理函数sig_child,在该函数内部将client的数值减去1即可。
服务端的每ip连接数
服务器的每ip连接数是使用hash表来实现的。hash表的具体实现详见下面这篇博客。本文不再赘述原理部分,直接使用函数接口开发相应的功能。
这里需要注意的是:
- 必须使用两个hash表来维护,因为进程退出之后,对应的count值需要减一,我们可以很容易的建立ip到count的正向关系。一旦子进程退出之后,父进程只能通过waitpid函数获取到子进程对应的pid号,我们可以建立子进程的PID和IP的对应关系,继而再通过ip和count的关系实现对count的减少。
- 在获取客户端的ip地址时,需要对addrlen进行初始化,因为自己写代码的时候没有对socklen_t addrlen进行初始化,导致一开始的客户端ip地址获取错误。
- 添加pid和ip的映射关系是在父进程中完成滴。
- 增加ip和count的映射关系定义了handle_ip_count函数。
- 删除ip和count的映射关系定义了drop_ip_count函数
代码如下所示:
//申请hash表
s_ip_count_hash = hash_alloc(MAX_BUCKET_SIZE, hash_func);
s_pid_ip_hash = hash_alloc(MAX_BUCKET_SIZE, hash_func);
while(1)
{
sockConn = accept(listenfd, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn < 0)
{
perror("accept");
continue;
}
//最大连接数
s_children_nums++;
sess.max_clients = s_children_nums;
//每IP连接数
unsigned int ip = addrCli.sin_addr.s_addr;
sess.max_per_ip = handle_ip_count(&ip);
pid_t pid = fork();
// 进程创建失败的话,此时的连接数需要减少1.
if(pid == -1)
{
s_children_nums--;
ERR_EXIT("fork");
}
if(pid == 0)
{
//Child Process
close(listenfd);
sess.ctrl_fd = sockConn;
//连接限制
check_limit(&sess);
begin_session(&sess);
exit(EXIT_SUCCESS);
}
//父进程
else
{
//Parent Process
//增加pid跟ip的映射
hash_add_entry(s_pid_ip_hash, &pid, sizeof(pid), &ip, sizeof(ip));
close(sockConn);
}
信号处理函数如下所示:
static void handle_sigchld(int sig)
{
//减少最大连接数
s_children_nums--;
//减少每ip的连接数
pid_t pid;
while((pid = waitpid(-1, NULL, WNOHANG)) > 0)
{
unsigned int *ip = (unsigned int *)hash_lookup_entry(s_pid_ip_hash, &pid, sizeof(pid));
if(ip == NULL)
continue;
drop_ip_count(ip);
// 由于当前的子进程已经退出咯,因此删除对应的pid和ip的哈希结点。
hash_free_entry(s_pid_ip_hash, &pid, sizeof(pid));
}
}
unsigned int hash_func(unsigned int buket_size, void *key)
{
return (*(unsigned int*)key) % buket_size;
}
unsigned int handle_ip_count(void *ip)
{
unsigned int *p_count = (unsigned int *)hash_lookup_entry(s_ip_count_hash, ip, sizeof(unsigned int));
if(p_count == NULL)
{
unsigned int count = 1;
hash_add_entry(s_ip_count_hash, ip, sizeof(unsigned int), &count, sizeof(unsigned int));
return count;
}
(*p_count)++;
return *p_count;
}
void drop_ip_count(unsigned int *ip)
{
unsigned int *p_count = (unsigned int*)hash_lookup_entry(s_ip_count_hash, ip, sizeof(unsigned int));
if(p_count == NULL)
return;
//查找到对应的ip地址,对应的count要减一。
(*p_count)--;
// 当前ip用户减少为0,所以删除对应的hash表。
if(*p_count == 0)
hash_free_entry(s_ip_count_hash, ip, sizeof(unsigned int));
}
绑定20端口
需要注意的是,nobody进程没有权限去绑定20端口的,需要提升权限到root之后,才可以绑定20端口。
使用capset这个函数,对进程进行能力的限制。
int capset(cap_user_header_t hdrp, const cap_user_data_t datap)
{
//通过系统调用函数
return syscall(__NR_capset, hdrp, datap);
}
static void minimize_privilege()
{
//更改nobody进程
struct passwd *pwd = getpwnam("nobody"); //lyx
if(pwd == NULL)
ERR_EXIT("getpwnam");
if(setegid(pwd->pw_gid) < 0)
ERR_EXIT("setegid");
if(seteuid(pwd->pw_uid) < 0)
ERR_EXIT("seteuid");
struct __user_cap_header_struct cap_header;
struct __user_cap_data_struct cap_data;
memset(&cap_header, 0, sizeof(cap_header));
memset(&cap_data, 0, sizeof(cap_data));
//设置头结构
cap_header.version = _LINUX_CAPABILITY_VERSION_2;
cap_header.pid = 0; //提升为root用户
//设置数据结构
unsigned int cap_mask = 0;
// 提升相应的权限
cap_mask |= (1<<CAP_NET_BIND_SERVICE); // 0000 0000 0000 0000 1000 0000 0000 0000
cap_data.effective = cap_data.permitted = cap_mask;
cap_data.inheritable = 0;
//设置特殊能力
capset(&cap_header, &cap_data);
}