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 }

 

你可能感兴趣的:(源码分析)