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 }
时间: 2024-11-09 05:03:34

wifidog源码分析 - 客户端检测线程的相关文章

wifidog源码分析 - wifidog原理

wifidog是一个用于配合认证服务器实现无线网页认证功能的程序,常见的情景就是使用于公共场合的无线wifi接入点,首先移动设备会连接公共wifi接入点,之后会弹出网页要求输入用户名密码,认证过后才能够连入外网.其主页是http://dev.wifidog.org/ 实现原理 其实wifidog原理很简单,主要是通过管控iptables,配合认证服务器进行客户端的放行操作.wifidog在启动后都会自动启动三个线程,分别为客户端检测线程.wdctrl交互线程.认证服务器心跳检测线程.每当新用户连

传奇源码分析-客户端(游戏逻辑处理源分析五 服务器端响应)

器执行流程:(玩家走动) GameSrv服务器ProcessUserHuman线程处理玩家消息:遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用.判断玩家if (!m_fIsDead),如果已死,则发送_MSG_FAIL消息.我们在前面看到过,该消息是被优先处理的.否则则调用WalkTo,并发送_MSG_GOOD消息给客户端.Walk

传奇源码分析-客户端(游戏逻辑处理源分析三)

6. 接收怪物,商人,其它玩家的消息:ProcessUserHuman:(其它玩家-服务器处理)CPlayerObject->SearchViewRange();CPlayerObject->Operate();遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用.根据分发消息(RM_TURN)向客户端发送SM_TURN消息.

传奇源码分析-客户端(游戏逻辑处理源分析二)

5.接受登录成功后,接收GameSrv服务器发送的消息:接收GameGate发送的消息:CClientSocket::OnSocketMessage的FD_READ事件中,PacketQ.PushQ((BYTE*)pszPacket);把接收到的消息,压入PacketQ队列中.处理PacketQ队列数据是由CGameProcess::Load()时调用OnTimer在CGameProcess::OnTimer中处理的, 处理过程为:OnMessageReceive; ProcessPacket(

《Java源码分析》:线程池 ThreadPoolExecutor

<Java源码分析>:线程池 ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一张实现,但是是间接实现. ThreadPoolExecutor是继承AbstractExecutorService.而AbstractExecutorService实现了ExecutorService接口. 在介绍细节的之前,先介绍下ThreadPoolExecutor的结构 1.线程池需要支持多个线程并发执行,因此有一个线程集合Collection来执行

基于TCP网络通信的自动升级程序源码分析-客户端接收文件

升级程序客户端接收文件 /// <summary> /// 文件数据缓存 索引是 ConnectionInfo对象 数据包的顺序号 值是数据 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>();

传奇源码分析-客户端(游戏逻辑处理源分析四)

现在假设玩家开始操作游戏:传奇的客户端源代码工程WindHorn一.CWHApp派生CWHWindow和CWHDXGraphicWindow.二.CWHDefProcess派生出CloginProcess.CcharacterProcess.CgameProcess客户端WinMain调用CWHDXGraphicWindow g_xMainWnd;创建一个窗口.客户端CWHDXGraphicWindow在自己的Create函数中调用了CWHWindow的Create来创建窗口,然后再调用自己的C

基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息

每次升级,客户端都会获取服务器端存放在upgradefile文件夹下的需要升级的文件和升级信息配置文件(即upgradeconfig.xml文件) 我们来看一下代码 //升级信息配置文件相对应的类 ( 升级信息配置文件是由这个类转化成的) private UpgradeConfig upgradeConfig = null; //客户端存储升级配置文件的地址 是放在客户端根目录下的 (就是把服务器 upgradefile/upgradeconfig.xml下载到客户端存放的位置) string

zeromq源码分析笔记之线程间收发命令(2)

在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socket_base_t实例与session的消息通信,消息的结构由msg_t确定.命令的发送与存储是通过mailbox_t实现的,消息的发送和存储是通过pipe_t实现的,这两个结构都会详细说到,今天先说一下线程间的收发命令. zeromq的线程可分为两类,一类是io线程,像reaper_t.io_thr