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函数中,我们先具体代码,

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 (1) {

/* 设置超时时间 */

timeout.tv_sec = time(NULL) + config_get_config()->checkinterval;

timeout.tv_nsec = 0;

/* 使用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 (-1 == 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, 0, 0);

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 = 0;

}

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[16],

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 -1;

}

/* 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 (2 == 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 -1;

}

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 (2 == 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 1;

}

本文由http://www.wifidog.pro/2015/02/02/wifidog%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A3%80%E6%B5%8B.html整理编辑,转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容