wifidog源码分析 - 客户端检测线程
引言
当wifidog启动时,会启动一个线程(thread_client_timeout_check)维护客户端列表,具体就是wifidog必须定时检测客户端列表中的每个客户端是否在线,而wifidog是通过两种方式进行检测客户端在线情况,一种是定时通过iptables获取客户端出入总流量更新客户端时间,通过最近更新时间进行判断(有新的出入流量则更新客户端时间,之后使用最新客户端时间与当前时间判断),一种是查询认证服务器,通过认证服务器的返回信息进行判断(将客户端IP和状态请求发送给认证服务器,认证服务器会返回客户端是否在线,这种情况是用于客户端是在认证服务器上正常登出)。
thread_client_timeout_check
此线程执行函数用于维护客户端列表,此线程是一个while (1)循环,每隔一个配置文件中的checkinterval时间间隔执行一次fw_sync_with_authserver函数,核心代码处于fw_sync_with_authserver函数中,我们先具体代码
代码片段1.1
void
thread_client_timeout_check(const void *arg)
{
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
struct timespec timeout; while () {
/* 设置超时时间 */
timeout.tv_sec = time(NULL) + config_get_config()->checkinterval;
timeout.tv_nsec = ; /* 使用pthread_cond_timedwait必须先上锁 */
pthread_mutex_lock(&cond_mutex); /* 等待超时 */
pthread_cond_timedwait(&cond, &cond_mutex, &timeout); /* 解锁 */
pthread_mutex_unlock(&cond_mutex); debug(LOG_DEBUG, "Running fw_counter()"); /* 执行核心代码 */
fw_sync_with_authserver();
}
}
fw_sync_with_authserver
此函数是此线程的核心函数,维护客户端列表就在此中,其首先会遍历客户端列表,通过iptables获取每个客户端列表的出入流量,之后根据出口流量(入口流量不做判断,详见 代码片段1.3)更新客户端最近更新时间(last_updated),之后使用每个客户端最近更新时间与当前时间比较,如果超过超时间隔则判断为下线,而如果未超时,则还会从认证服务器中获取此客户端状态,确定其是否在线。具体流程如下
- 更新客户端出入口流量,根据出口流量更新每个客户端的最近更新时间
- 客户端超时则从客户端列表中移除并通过iptables禁止其访问网络,并告知认证服务器此客户端下线
- 客户端未超时则从认证服务器获取此客户端信息,判断其是否通过认证服务器下线
代码片段1.2
void
fw_sync_with_authserver(void)
{
t_authresponse authresponse;
char *token, *ip, *mac;
t_client *p1, *p2;
unsigned long long incoming, outgoing;
s_config *config = config_get_config(); /* 根据iptables流量更新最近更新时间,具体代码见 代码片段1.3 */
if (- == iptables_fw_counters_update()) {
debug(LOG_ERR, "Could not get counters from firewall!");
return;
} LOCK_CLIENT_LIST(); /* 遍历客户端列表 */
for (p1 = p2 = client_get_first_client(); NULL != p1; p1 = p2) {
p2 = p1->next; ip = safe_strdup(p1->ip);
token = safe_strdup(p1->token);
mac = safe_strdup(p1->mac);
outgoing = p1->counters.outgoing;
incoming = p1->counters.incoming; UNLOCK_CLIENT_LIST();
/* ping一下此客户端,不清楚作用 */
icmp_ping(ip);
/* 将客户端的出入流量上传至认证服务器,此时如果此客户端在认证服务器上下线会返回告知wifidog */
if (config->auth_servers != NULL) {
auth_server_request(&authresponse, REQUEST_TYPE_COUNTERS, ip, mac, token, incoming, outgoing);
}
LOCK_CLIENT_LIST(); /* 从客户端列表获取IP,MAC对应客户端 */
if (!(p1 = client_list_find(ip, mac))) {
debug(LOG_ERR, "Node %s was freed while being re-validated!", ip);
} else {
time_t current_time=time(NULL);
debug(LOG_INFO, "Checking client %s for timeout: Last updated %ld (%ld seconds ago), timeout delay %ld seconds, current time %ld, ",
p1->ip, p1->counters.last_updated, current_time-p1->counters.last_updated, config->checkinterval * config->clienttimeout, current_time);
/* 判断是否超时,(最近更新时间 + 超时时间 <= 当前时间) 表明以超过超时时间,下线 */
if (p1->counters.last_updated +
(config->checkinterval * config->clienttimeout)
<= current_time) {
debug(LOG_INFO, "%s - Inactive for more than %ld seconds, removing client and denying in firewall",
p1->ip, config->checkinterval * config->clienttimeout);
/* 修改iptables禁止此客户端访问外网 */
fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
/* 从客户端列表中删除此客户端 */
client_list_delete(p1); /* 通知认证服务器此客户端下线 */
if (config->auth_servers != NULL) {
UNLOCK_CLIENT_LIST();
auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token, , );
LOCK_CLIENT_LIST();
}
} else {
/* 未超时处理 */
if (config->auth_servers != NULL) {
/* 判断认证服务器返回信息 */
switch (authresponse.authcode) {
/* 认证服务器禁止其访问网络(下线或遭拒绝) */
case AUTH_DENIED:
debug(LOG_NOTICE, "%s - Denied. Removing client and firewall rules", p1->ip);
fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
client_list_delete(p1);
break; case AUTH_VALIDATION_FAILED:
debug(LOG_NOTICE, "%s - Validation timeout, now denied. Removing client and firewall rules", p1->ip);
fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
client_list_delete(p1);
break; /* 认证服务器允许其访问网络(在线) */
case AUTH_ALLOWED:
if (p1->fw_connection_state != FW_MARK_KNOWN) {
debug(LOG_INFO, "%s - Access has changed to allowed, refreshing firewall and clearing counters", p1->ip);
if (p1->fw_connection_state != FW_MARK_PROBATION) {
p1->counters.incoming = p1->counters.outgoing = ;
}
else { debug(LOG_INFO, "%s - Skipped clearing counters after all, the user was previously in validation", p1->ip);
}
p1->fw_connection_state = FW_MARK_KNOWN;
fw_allow(p1->ip, p1->mac, p1->fw_connection_state);
}
break; case AUTH_VALIDATION:
debug(LOG_INFO, "%s - User in validation period", p1->ip);
break; case AUTH_ERROR:
debug(LOG_WARNING, "Error communicating with auth server - leaving %s as-is for now", p1->ip);
break; default:
debug(LOG_ERR, "I do not know about authentication code %d", authresponse.authcode);
break;
}
}
}
} free(token);
free(ip);
free(mac);
}
UNLOCK_CLIENT_LIST();
}
代码片段1.3
int
iptables_fw_counters_update(void)
{
FILE *output;
char *script,
ip[],
rc;
unsigned long long int counter;
t_client *p1;
struct in_addr tempaddr; /* 通过iptables获取其出口流量 */
safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_OUTGOING);
iptables_insert_gateway_id(&script);
output = popen(script, "r");
free(script);
if (!output) {
debug(LOG_ERR, "popen(): %s", strerror(errno));
return -;
} /* iptables返回信息处理 */
while (('\n' != fgetc(output)) && !feof(output))
;
while (('\n' != fgetc(output)) && !feof(output))
;
while (output && !(feof(output))) {
rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s %*s", &counter, ip);
//rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s 0x%*u", &counter, ip);
if ( == rc && EOF != rc) {
if (!inet_aton(ip, &tempaddr)) {
debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);
continue;
}
debug(LOG_DEBUG, "Read outgoing traffic for %s: Bytes=%llu", ip, counter);
LOCK_CLIENT_LIST();
/* 通过ip获取客户端信息结构 */
if ((p1 = client_list_find_by_ip(ip))) {
/* (上一次出口总流量(outgoing) + wifidog启动时的出口总流量(outgoing_history) < iptables返回的出口总流量) 表示此客户端有新的出口流量 */
if ((p1->counters.outgoing - p1->counters.outgoing_history) < counter) {
/* 更新上一次出口总流量(outgoing)为wifidog启动时的出口总流量(outgoing_history) + iptables返回总流量(counter) */
p1->counters.outgoing = p1->counters.outgoing_history + counter;
/* 更新最近更新时间为当前时间 */
p1->counters.last_updated = time(NULL);
debug(LOG_DEBUG, "%s - Updated counter.outgoing to %llu bytes. Updated last_updated to %d", ip, counter, p1->counters.last_updated);
}
} else {
debug(LOG_ERR, "Could not find %s in client list", ip);
}
UNLOCK_CLIENT_LIST();
}
}
pclose(output); /* 通过iptables获取其入口流量,入口流量不做更新最近更新时间参考,只用于更新后上传至认证服务器,其原理同上,后面的代码不做详细分析 */
safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_INCOMING);
iptables_insert_gateway_id(&script);
output = popen(script, "r");
free(script);
if (!output) {
debug(LOG_ERR, "popen(): %s", strerror(errno));
return -;
} while (('\n' != fgetc(output)) && !feof(output))
;
while (('\n' != fgetc(output)) && !feof(output))
;
while (output && !(feof(output))) {
rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %*s %15[0-9.]", &counter, ip);
if ( == rc && EOF != rc) { if (!inet_aton(ip, &tempaddr)) {
debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);
continue;
}
debug(LOG_DEBUG, "Read incoming traffic for %s: Bytes=%llu", ip, counter);
LOCK_CLIENT_LIST();
if ((p1 = client_list_find_by_ip(ip))) {
if ((p1->counters.incoming - p1->counters.incoming_history) < counter) {
p1->counters.incoming = p1->counters.incoming_history + counter;
debug(LOG_DEBUG, "%s - Updated counter.incoming to %llu bytes", ip, counter);
}
} else {
debug(LOG_ERR, "Could not find %s in client list", ip);
}
UNLOCK_CLIENT_LIST();
}
}
pclose(output); return ;
}
wifidog源码分析 - 客户端检测线程的更多相关文章
- wifidog源码分析 - 用户连接过程
引言 之前的文章已经描述wifidog大概的一个工作流程,这里我们具体说说wifidog是怎么把一个新用户重定向到认证服务器中的,它又是怎么对一个已认证的用户实行放行操作的.我们已经知道wifidog ...
- wifidog源码分析 - wifidog原理
wifidog是一个用于配合认证服务器实现无线网页认证功能的程序,常见的情景就是使用于公共场合的无线wifi接入点,首先移动设备会连接公共wifi接入点,之后会弹出网页要求输入用户名密码,认证过后才能 ...
- wifidog源码分析 - wifidog原理 tiger
转:http://www.cnblogs.com/tolimit/p/4223644.html wifidog源码分析 - wifidog原理 wifidog是一个用于配合认证服务器实现无线网页认证功 ...
- Netty源码分析之Reactor线程模型详解
上一篇文章,分析了Netty服务端启动的初始化过程,今天我们来分析一下Netty中的Reactor线程模型 在分析源码之前,我们先分析,哪些地方用到了EventLoop? NioServerSocke ...
- zeromq源码分析笔记之线程间收发命令(2)
在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socke ...
- Android多线程之(一)View.post()源码分析——在子线程中更新UI
提起View.post(),相信不少童鞋一点都不陌生,它用得最多的有两个功能,使用简便而且实用: 1)在子线程中更新UI.从子线程中切换到主线程更新UI,不需要额外new一个Handler实例来实现. ...
- wifidog源码分析 - 认证服务器心跳检测线程
引言 但wifidog启动时,会自动启动认证服务器心跳检测线程,此线程默认每隔60s与认证服务器交互一次,会将路由器的信息(系统启动时长,内存使用情况和系统平均负载)告知认证服务器,并通过一个&quo ...
- 【Netty源码分析】Reactor线程模型
1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...
- wifidog源码分析 - 初始化阶段
Wifidog是一个linux下开源的认证网关软件,它主要用于配合认证服务器实现无线路由器的认证放行功能. wifidog是一个后台的服务程序,可以通过wdctrl命令对wifidog主程序进行控制. ...
随机推荐
- Python编程-从入门到实践 Eric Matthes 著 袁国忠 译 - - 第二章 动手试一试
因为第一章的动手试一试基本都是探索性的,所以直接进入第二章. # 2.2 动手试一试 # 2_1 简单消息: 将一条消息存储到变量中,再将其打印出来. message = 'python 编程从入门到 ...
- 2017-07-29 中文代码示例教程之Java编程一天入门
Java编程一天入门 v0.0.1 alpha 共享协议 本作使用署名-非商业使用-禁止演绎协议共享. 前言 Java入门代码用中文写(举例如下)更能被新手理解. 由于至今没有看到类似教程, 在此抛砖 ...
- docker-使用Dockerfile制作镜像
最近项目中有使用docker,组内做了关于docker的培训,然后自己跟着研究了一下,大概了解如何使用.我是基于tomcat镜像制作(不需要安装jdk,配置环境变量),基于centos镜像制作需要安装 ...
- Jmeter进阶篇之保存测试结果
Jmeter现在真的是一款越来越流行的接口测试工具. 但是通过和老大哥LR相比较,可能有部分同学觉得,LR的图表功能,报告功能不要太强大. 但是小弟jmeter在这方面其实并不差... 今天我们就来学 ...
- Angular 2基础(一) 环境搭建
Angular2是一款开源JavaScript库,由Google维护,用来创建页面应用程序.正式发布于2016年9月,基于ES6开发. 一.准备工作 使用Angular2开发,需要预先做一些配置上的配 ...
- 安卓APP应用在各大应用市场上架方法整理
想要把APP上架到应用市场都要先注册开发者账号才可以.这里的方法包括注册帐号和后期上架及一些需要注意的问题.注意:首次提交应用绝对不能随便删除,否则后面再提交会显示应用APP冲突,会要求走应用认领流程 ...
- (网页)javascript小技巧(非常全)
事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcE ...
- windows下安装Erlang
由于RabbitMQ是用Erlang编写的,因此需要先安装Erlang环境,建议安装的版本新一点.下载地址点我试试 我这里下载的V20.3 x64版本,下载后点击开始安装,基本是一路next(默认设置 ...
- 1.Spring MVC详解
目录 1.SpringMVC 详细介绍 2.SpringMVC 处理请求流程 3.配置前端控制器 4.配置处理器适配器 5.编写 Handler 6.配置处理器映射器 7.配置视图解析器 8.Disp ...
- CSS| 框模型-輪廓
轮廓(outline)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用.CSS outline 属性规定元素轮廓的样式.颜色和宽度. 相關屬性 outline-color 属性 ou ...