wifidog源码分析 - 客户端检测线程

1890阅读 0评论2015-03-22 Garfield_Trump
分类:嵌入式

引言

  当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.         /* 使用pthread_cond_timedwait必须先上锁 */
  13.         pthread_mutex_lock(&cond_mutex);
  14.         
  15.         /* 等待超时 */
  16.         pthread_cond_timedwait(&cond, &cond_mutex, &timeout);

  17.         /* 解锁 */
  18.         pthread_mutex_unlock(&cond_mutex);
  19.     
  20.         debug(LOG_DEBUG, "Running fw_counter()");
  21.     
  22.         /* 执行核心代码 */
  23.         fw_sync_with_authserver();
  24.     }
  25. }

fw_sync_with_authserver

  此函数是此线程的核心函数,维护客户端列表就在此中,其首先会遍历客户端列表,通过iptables获取每个客户端列表的出入流量,之后根据出口流量(入口流量不做判断,详见 代码片段1.3)更新客户端最近更新时间(last_updated),之后使用每个客户端最近更新时间与当前时间比较,如果超过超时间隔则判断为下线,而如果未超时,则还会从认证服务器中获取此客户端状态,确定其是否在线。具体流程如下

代码片段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.     /* 根据iptables流量更新最近更新时间,具体代码见 代码片段1.3 */
  10.     if (-1 == iptables_fw_counters_update()) {
  11.         debug(LOG_ERR, "Could not get counters from firewall!");
  12.         return;
  13.     }

  14.     LOCK_CLIENT_LIST();

  15.     /* 遍历客户端列表 */
  16.     for (p1 = p2 = client_get_first_client(); NULL != p1; p1 = p2) {
  17.         p2 = p1->next;

  18.         ip = safe_strdup(p1->ip);
  19.         token = safe_strdup(p1->token);
  20.         mac = safe_strdup(p1->mac);
  21.         outgoing = p1->counters.outgoing;
  22.         incoming = p1->counters.incoming;

  23.         UNLOCK_CLIENT_LIST();
  24.         /* ping一下此客户端,不清楚作用 */
  25.         icmp_ping(ip);
  26.         /* 将客户端的出入流量上传至认证服务器,此时如果此客户端在认证服务器上下线会返回告知wifidog */
  27.         if (config->auth_servers != NULL) {
  28.             auth_server_request(&authresponse, REQUEST_TYPE_COUNTERS, ip, mac, token, incoming, outgoing);
  29.         }
  30.         LOCK_CLIENT_LIST();

  31.         /* 从客户端列表获取IP,MAC对应客户端 */
  32.         if (!(p1 = client_list_find(ip, mac))) {
  33.             debug(LOG_ERR, "Node %s was freed while being re-validated!", ip);
  34.         } else {
  35.             time_t current_time=time(NULL);
  36.             debug(LOG_INFO, "Checking client %s for timeout: Last updated %ld (%ld seconds ago), timeout delay %ld seconds, current time %ld, ",
  37.                         p1->ip, p1->counters.last_updated, current_time-p1->counters.last_updated, config->checkinterval * config->clienttimeout, current_time);
  38.             /* 判断是否超时,(最近更新时间 + 超时时间 <= 当前时间) 表明以超过超时时间,下线 */
  39.             if (p1->counters.last_updated +
  40.                 (config->checkinterval * config->clienttimeout)
  41.                 <= current_time) {
  42.                 debug(LOG_INFO, "%s - Inactive for more than %ld seconds, removing client and denying in firewall",
  43.                         p1->ip, config->checkinterval * config->clienttimeout);
  44.                 /* 修改iptables禁止此客户端访问外网 */
  45.                 fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
  46.                 /* 从客户端列表中删除此客户端 */
  47.                 client_list_delete(p1);

  48.                 /* 通知认证服务器此客户端下线 */
  49.                 if (config->auth_servers != NULL) {
  50.                     UNLOCK_CLIENT_LIST();
  51.                     auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token, 0, 0);
  52.                     LOCK_CLIENT_LIST();
  53.                 }
  54.             } else {
  55.                 /* 未超时处理 */
  56.                 if (config->auth_servers != NULL) {
  57.                     /* 判断认证服务器返回信息 */
  58.                     switch (authresponse.authcode) {
  59.                         /* 认证服务器禁止其访问网络(下线或遭拒绝) */
  60.                         case AUTH_DENIED:
  61.                             debug(LOG_NOTICE, "%s - Denied. Removing client and firewall rules", p1->ip);
  62.                             fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
  63.                             client_list_delete(p1);
  64.                             break;

  65.                         case AUTH_VALIDATION_FAILED:
  66.                             debug(LOG_NOTICE, "%s - Validation timeout, now denied. Removing client and firewall rules", p1->ip);
  67.                             fw_deny(p1->ip, p1->mac, p1->fw_connection_state);
  68.                             client_list_delete(p1);
  69.                             break;

  70.                         /* 认证服务器允许其访问网络(在线) */
  71.                         case AUTH_ALLOWED:
  72.                             if (p1->fw_connection_state != FW_MARK_KNOWN) {
  73.                                 debug(LOG_INFO, "%s - Access has changed to allowed, refreshing firewall and clearing counters", p1->ip);
  74.                                 if (p1->fw_connection_state != FW_MARK_PROBATION) {
  75.                                     p1->counters.incoming = p1->counters.outgoing = 0;
  76.                                 }
  77.                                 else {
  78.                            
  79.                                     debug(LOG_INFO, "%s - Skipped clearing counters after all, the user was previously in validation", p1->ip);
  80.                                 }
  81.                                 p1->fw_connection_state = FW_MARK_KNOWN;
  82.                                 fw_allow(p1->ip, p1->mac, p1->fw_connection_state);
  83.                             }
  84.                             break;

  85.                         case AUTH_VALIDATION:
  86.                             debug(LOG_INFO, "%s - User in validation period", p1->ip);
  87.                             break;

  88.                             case AUTH_ERROR:
  89.                                     debug(LOG_WARNING, "Error communicating with auth server - leaving %s as-is for now", p1->ip);
  90.                                     break;

  91.                         default:
  92.                             debug(LOG_ERR, "I do not know about authentication code %d", authresponse.authcode);
  93.                             break;
  94.                     }
  95.                 }
  96.             }
  97.         }

  98.         free(token);
  99.         free(ip);
  100.         free(mac);
  101.     }
  102.     UNLOCK_CLIENT_LIST();
  103. }

代码片段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.     /* 通过iptables获取其出口流量 */
  12.     safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_OUTGOING);
  13.     iptables_insert_gateway_id(&script);
  14.     output = popen(script, "r");
  15.     free(script);
  16.     if (!output) {
  17.         debug(LOG_ERR, "popen(): %s", strerror(errno));
  18.         return -1;
  19.     }

  20.     /* iptables返回信息处理 */
  21.     while (('\n' != fgetc(output)) && !feof(output))
  22.         ;
  23.     while (('\n' != fgetc(output)) && !feof(output))
  24.         ;
  25.     while (output && !(feof(output))) {
  26.         rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s %*s", &counter, ip);
  27.         //rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s 0x%*u", &counter, ip);
  28.         if (2 == rc && EOF != rc) {
  29.             if (!inet_aton(ip, &tempaddr)) {
  30.                 debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);
  31.                 continue;
  32.             }
  33.             debug(LOG_DEBUG, "Read outgoing traffic for %s: Bytes=%llu", ip, counter);
  34.             LOCK_CLIENT_LIST();
  35.             /* 通过ip获取客户端信息结构 */
  36.             if ((p1 = client_list_find_by_ip(ip))) {
  37.                 /* (上一次出口总流量(outgoing) + wifidog启动时的出口总流量(outgoing_history) < iptables返回的出口总流量) 表示此客户端有新的出口流量 */
  38.                 if ((p1->counters.outgoing - p1->counters.outgoing_history) < counter) {
  39.                     /* 更新上一次出口总流量(outgoing)为wifidog启动时的出口总流量(outgoing_history) + iptables返回总流量(counter) */
  40.                     p1->counters.outgoing = p1->counters.outgoing_history + counter;
  41.                     /* 更新最近更新时间为当前时间 */
  42.                     p1->counters.last_updated = time(NULL);
  43.                     debug(LOG_DEBUG, "%s - Updated counter.outgoing to %llu bytes. Updated last_updated to %d", ip, counter, p1->counters.last_updated);
  44.                 }
  45.             } else {
  46.                 debug(LOG_ERR, "Could not find %s in client list", ip);
  47.             }
  48.             UNLOCK_CLIENT_LIST();
  49.         }
  50.     }
  51.     pclose(output);

  52.     /* 通过iptables获取其入口流量,入口流量不做更新最近更新时间参考,只用于更新后上传至认证服务器,其原理同上,后面的代码不做详细分析 */
  53.     safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_INCOMING);
  54.     iptables_insert_gateway_id(&script);
  55.     output = popen(script, "r");
  56.     free(script);
  57.     if (!output) {
  58.         debug(LOG_ERR, "popen(): %s", strerror(errno));
  59.         return -1;
  60.     }

  61.  
  62.     while (('\n' != fgetc(output)) && !feof(output))
  63.         ;
  64.     while (('\n' != fgetc(output)) && !feof(output))
  65.         ;
  66.     while (output && !(feof(output))) {
  67.         rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %*s %15[0-9.]", &counter, ip);
  68.         if (2 == rc && EOF != rc) {
  69.  
  70.             if (!inet_aton(ip, &tempaddr)) {
  71.                 debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);
  72.                 continue;
  73.             }
  74.             debug(LOG_DEBUG, "Read incoming traffic for %s: Bytes=%llu", ip, counter);
  75.             LOCK_CLIENT_LIST();
  76.             if ((p1 = client_list_find_by_ip(ip))) {
  77.                 if ((p1->counters.incoming - p1->counters.incoming_history) < counter) {
  78.                     p1->counters.incoming = p1->counters.incoming_history + counter;
  79.                     debug(LOG_DEBUG, "%s - Updated counter.incoming to %llu bytes", ip, counter);
  80.                 }
  81.             } else {
  82.                 debug(LOG_ERR, "Could not find %s in client list", ip);
  83.             }
  84.             UNLOCK_CLIENT_LIST();
  85.         }
  86.     }
  87.     pclose(output);

  88.     return 1;
  89. }

上一篇:wifidog源码分析 - 用户连接过程
下一篇:wifidog源码分析 - 认证服务器心跳检测线程