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
1 void 2 thread_client_timeout_check(const void *arg) 3 { 4 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 5 pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER; 6 struct timespec timeout; 7 8 while (1) { 9 /* 设置超时时间 */ 10 timeout.tv_sec = time(NULL) + config_get_config()->checkinterval; 11 timeout.tv_nsec = 0; 12 13 /* 使用pthread_cond_timedwait必须先上锁 */ 14 pthread_mutex_lock(&cond_mutex); 15 16 /* 等待超时 */ 17 pthread_cond_timedwait(&cond, &cond_mutex, &timeout); 18 19 /* 解锁 */ 20 pthread_mutex_unlock(&cond_mutex); 21 22 debug(LOG_DEBUG, "Running fw_counter()"); 23 24 /* 执行核心代码 */ 25 fw_sync_with_authserver(); 26 } 27 }
fw_sync_with_authserver
此函数是此线程的核心函数,维护客户端列表就在此中,其首先会遍历客户端列表,通过iptables获取每个客户端列表的出入流量,之后根据出口流量(入口流量不做判断,详见 代码片段1.3)更新客户端最近更新时间(last_updated),之后使用每个客户端最近更新时间与当前时间比较,如果超过超时间隔则判断为下线,而如果未超时,则还会从认证服务器中获取此客户端状态,确定其是否在线。具体流程如下
- 更新客户端出入口流量,根据出口流量更新每个客户端的最近更新时间
- 客户端超时则从客户端列表中移除并通过iptables禁止其访问网络,并告知认证服务器此客户端下线
- 客户端未超时则从认证服务器获取此客户端信息,判断其是否通过认证服务器下线
代码片段1.2
1 void 2 fw_sync_with_authserver(void) 3 { 4 t_authresponse authresponse; 5 char *token, *ip, *mac; 6 t_client *p1, *p2; 7 unsigned long long incoming, outgoing; 8 s_config *config = config_get_config(); 9 10 /* 根据iptables流量更新最近更新时间,具体代码见 代码片段1.3 */ 11 if (-1 == iptables_fw_counters_update()) { 12 debug(LOG_ERR, "Could not get counters from firewall!"); 13 return; 14 } 15 16 LOCK_CLIENT_LIST(); 17 18 /* 遍历客户端列表 */ 19 for (p1 = p2 = client_get_first_client(); NULL != p1; p1 = p2) { 20 p2 = p1->next; 21 22 ip = safe_strdup(p1->ip); 23 token = safe_strdup(p1->token); 24 mac = safe_strdup(p1->mac); 25 outgoing = p1->counters.outgoing; 26 incoming = p1->counters.incoming; 27 28 UNLOCK_CLIENT_LIST(); 29 /* ping一下此客户端,不清楚作用 */ 30 icmp_ping(ip); 31 /* 将客户端的出入流量上传至认证服务器,此时如果此客户端在认证服务器上下线会返回告知wifidog */ 32 if (config->auth_servers != NULL) { 33 auth_server_request(&authresponse, REQUEST_TYPE_COUNTERS, ip, mac, token, incoming, outgoing); 34 } 35 LOCK_CLIENT_LIST(); 36 37 /* 从客户端列表获取IP,MAC对应客户端 */ 38 if (!(p1 = client_list_find(ip, mac))) { 39 debug(LOG_ERR, "Node %s was freed while being re-validated!", ip); 40 } else { 41 time_t current_time=time(NULL); 42 debug(LOG_INFO, "Checking client %s for timeout: Last updated %ld (%ld seconds ago), timeout delay %ld seconds, current time %ld, ", 43 p1->ip, p1->counters.last_updated, current_time-p1->counters.last_updated, config->checkinterval * config->clienttimeout, current_time); 44 /* 判断是否超时,(最近更新时间 + 超时时间 <= 当前时间) 表明以超过超时时间,下线 */ 45 if (p1->counters.last_updated + 46 (config->checkinterval * config->clienttimeout) 47 <= current_time) { 48 debug(LOG_INFO, "%s - Inactive for more than %ld seconds, removing client and denying in firewall", 49 p1->ip, config->checkinterval * config->clienttimeout); 50 /* 修改iptables禁止此客户端访问外网 */ 51 fw_deny(p1->ip, p1->mac, p1->fw_connection_state); 52 /* 从客户端列表中删除此客户端 */ 53 client_list_delete(p1); 54 55 /* 通知认证服务器此客户端下线 */ 56 if (config->auth_servers != NULL) { 57 UNLOCK_CLIENT_LIST(); 58 auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token, 0, 0); 59 LOCK_CLIENT_LIST(); 60 } 61 } else { 62 /* 未超时处理 */ 63 if (config->auth_servers != NULL) { 64 /* 判断认证服务器返回信息 */ 65 switch (authresponse.authcode) { 66 /* 认证服务器禁止其访问网络(下线或遭拒绝) */ 67 case AUTH_DENIED: 68 debug(LOG_NOTICE, "%s - Denied. Removing client and firewall rules", p1->ip); 69 fw_deny(p1->ip, p1->mac, p1->fw_connection_state); 70 client_list_delete(p1); 71 break; 72 73 case AUTH_VALIDATION_FAILED: 74 debug(LOG_NOTICE, "%s - Validation timeout, now denied. Removing client and firewall rules", p1->ip); 75 fw_deny(p1->ip, p1->mac, p1->fw_connection_state); 76 client_list_delete(p1); 77 break; 78 79 /* 认证服务器允许其访问网络(在线) */ 80 case AUTH_ALLOWED: 81 if (p1->fw_connection_state != FW_MARK_KNOWN) { 82 debug(LOG_INFO, "%s - Access has changed to allowed, refreshing firewall and clearing counters", p1->ip); 83 if (p1->fw_connection_state != FW_MARK_PROBATION) { 84 p1->counters.incoming = p1->counters.outgoing = 0; 85 } 86 else { 87 88 debug(LOG_INFO, "%s - Skipped clearing counters after all, the user was previously in validation", p1->ip); 89 } 90 p1->fw_connection_state = FW_MARK_KNOWN; 91 fw_allow(p1->ip, p1->mac, p1->fw_connection_state); 92 } 93 break; 94 95 case AUTH_VALIDATION: 96 debug(LOG_INFO, "%s - User in validation period", p1->ip); 97 break; 98 99 case AUTH_ERROR: 100 debug(LOG_WARNING, "Error communicating with auth server - leaving %s as-is for now", p1->ip); 101 break; 102 103 default: 104 debug(LOG_ERR, "I do not know about authentication code %d", authresponse.authcode); 105 break; 106 } 107 } 108 } 109 } 110 111 free(token); 112 free(ip); 113 free(mac); 114 } 115 UNLOCK_CLIENT_LIST(); 116 }
代码片段1.3
1 int 2 iptables_fw_counters_update(void) 3 { 4 FILE *output; 5 char *script, 6 ip[16], 7 rc; 8 unsigned long long int counter; 9 t_client *p1; 10 struct in_addr tempaddr; 11 12 /* 通过iptables获取其出口流量 */ 13 safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_OUTGOING); 14 iptables_insert_gateway_id(&script); 15 output = popen(script, "r"); 16 free(script); 17 if (!output) { 18 debug(LOG_ERR, "popen(): %s", strerror(errno)); 19 return -1; 20 } 21 22 /* iptables返回信息处理 */ 23 while ((‘\n‘ != fgetc(output)) && !feof(output)) 24 ; 25 while ((‘\n‘ != fgetc(output)) && !feof(output)) 26 ; 27 while (output && !(feof(output))) { 28 rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s %*s", &counter, ip); 29 //rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s 0x%*u", &counter, ip); 30 if (2 == rc && EOF != rc) { 31 if (!inet_aton(ip, &tempaddr)) { 32 debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip); 33 continue; 34 } 35 debug(LOG_DEBUG, "Read outgoing traffic for %s: Bytes=%llu", ip, counter); 36 LOCK_CLIENT_LIST(); 37 /* 通过ip获取客户端信息结构 */ 38 if ((p1 = client_list_find_by_ip(ip))) { 39 /* (上一次出口总流量(outgoing) + wifidog启动时的出口总流量(outgoing_history) < iptables返回的出口总流量) 表示此客户端有新的出口流量 */ 40 if ((p1->counters.outgoing - p1->counters.outgoing_history) < counter) { 41 /* 更新上一次出口总流量(outgoing)为wifidog启动时的出口总流量(outgoing_history) + iptables返回总流量(counter) */ 42 p1->counters.outgoing = p1->counters.outgoing_history + counter; 43 /* 更新最近更新时间为当前时间 */ 44 p1->counters.last_updated = time(NULL); 45 debug(LOG_DEBUG, "%s - Updated counter.outgoing to %llu bytes. Updated last_updated to %d", ip, counter, p1->counters.last_updated); 46 } 47 } else { 48 debug(LOG_ERR, "Could not find %s in client list", ip); 49 } 50 UNLOCK_CLIENT_LIST(); 51 } 52 } 53 pclose(output); 54 55 /* 通过iptables获取其入口流量,入口流量不做更新最近更新时间参考,只用于更新后上传至认证服务器,其原理同上,后面的代码不做详细分析 */ 56 safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_INCOMING); 57 iptables_insert_gateway_id(&script); 58 output = popen(script, "r"); 59 free(script); 60 if (!output) { 61 debug(LOG_ERR, "popen(): %s", strerror(errno)); 62 return -1; 63 } 64 65 66 while ((‘\n‘ != fgetc(output)) && !feof(output)) 67 ; 68 while ((‘\n‘ != fgetc(output)) && !feof(output)) 69 ; 70 while (output && !(feof(output))) { 71 rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %*s %15[0-9.]", &counter, ip); 72 if (2 == rc && EOF != rc) { 73 74 if (!inet_aton(ip, &tempaddr)) { 75 debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip); 76 continue; 77 } 78 debug(LOG_DEBUG, "Read incoming traffic for %s: Bytes=%llu", ip, counter); 79 LOCK_CLIENT_LIST(); 80 if ((p1 = client_list_find_by_ip(ip))) { 81 if ((p1->counters.incoming - p1->counters.incoming_history) < counter) { 82 p1->counters.incoming = p1->counters.incoming_history + counter; 83 debug(LOG_DEBUG, "%s - Updated counter.incoming to %llu bytes", ip, counter); 84 } 85 } else { 86 debug(LOG_ERR, "Could not find %s in client list", ip); 87 } 88 UNLOCK_CLIENT_LIST(); 89 } 90 } 91 pclose(output); 92 93 return 1; 94 }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。