但wifidog启动时,会自动启动认证服务器心跳检测线程,此线程默认每隔60s与认证服务器交互一次,会将路由器的信息(系统启动时长,内存使用情况和系统平均负载)告知认证服务器,并通过一个"ping"字符串作为信号,而当认证服务器接收到此数据包后,会返回一个"pong"给路由器,具体我们看看代码。
代码片段1.1
此段代码很简单,就是调用ping函数,然后等待60s
1 void 2 thread_ping(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 /* 调用ping,具体代码看 代码片段1.2 */ 10 debug(LOG_DEBUG, "Running ping()"); 11 ping(); 12 13 /* 睡眠一个checkinterval,默认为60s */ 14 timeout.tv_sec = time(NULL) + config_get_config()->checkinterval; 15 timeout.tv_nsec = 0; 16 17 18 pthread_mutex_lock(&cond_mutex); 19 20 pthread_cond_timedwait(&cond, &cond_mutex, &timeout); 21 22 pthread_mutex_unlock(&cond_mutex); 23 }
代码片段1.2
1 static void 2 ping(void) 3 { 4 ssize_t numbytes; 5 size_t totalbytes; 6 int sockfd, nfds, done; 7 char request[MAX_BUF]; 8 fd_set readfds; 9 struct timeval timeout; 10 FILE * fh; 11 unsigned long int sys_uptime = 0; 12 unsigned int sys_memfree = 0; 13 float sys_load = 0; 14 t_auth_serv *auth_server = NULL; 15 auth_server = get_auth_server(); 16 17 debug(LOG_DEBUG, "Entering ping()"); 18 19 /* 其实认证服务器就是一个web服务器,路由器跟他做通信行为就是通过发送http请求进行通信,首先先连接认证服务器的http端口,获取其socket */ 20 sockfd = connect_auth_server(); 21 if (sockfd == -1) { 22 /* 无法连接认证服务器,connect_auth_server分析见 代码片段1.3 */ 23 return; 24 } 25 26 /* 27 * 从/proc文件系统获取路由器信息 28 */ 29 if ((fh = fopen("/proc/uptime", "r"))) { 30 fscanf(fh, "%lu", &sys_uptime); 31 fclose(fh); 32 } 33 if ((fh = fopen("/proc/meminfo", "r"))) { 34 while (!feof(fh)) { 35 if (fscanf(fh, "MemFree: %u", &sys_memfree) == 0) { 36 while (!feof(fh) && fgetc(fh) != '\n'); 37 } 38 else { 39 break; 40 } 41 } 42 fclose(fh); 43 } 44 if ((fh = fopen("/proc/loadavg", "r"))) { 45 fscanf(fh, "%f", &sys_load); 46 fclose(fh); 47 } 48 49 /* 50 * 准备http请求包 51 */ 52 snprintf(request, sizeof(request) - 1, 53 "GET %s%sgw_id=%s&sys_uptime=%lu&sys_memfree=%u&sys_load=%.2f&wifidog_uptime=%lu HTTP/1.0\r\n" 54 "User-Agent: WiFiDog %s\r\n" 55 "Host: %s\r\n" 56 "\r\n", 57 auth_server->authserv_path, 58 auth_server->authserv_ping_script_path_fragment, 59 config_get_config()->gw_id, 60 sys_uptime, 61 sys_memfree, 62 sys_load, 63 (long unsigned int)((long unsigned int)time(NULL) - (long unsigned int)started_time), 64 VERSION, 65 auth_server->authserv_hostname); 66 67 debug(LOG_DEBUG, "HTTP Request to Server: [%s]", request); 68 /* 发送 */ 69 send(sockfd, request, strlen(request), 0); 70 71 debug(LOG_DEBUG, "Reading response"); 72 73 numbytes = totalbytes = 0; 74 done = 0; 75 do { 76 FD_ZERO(&readfds); 77 FD_SET(sockfd, &readfds); 78 /* 设置超时30s */ 79 timeout.tv_sec = 30; 80 timeout.tv_usec = 0; 81 nfds = sockfd + 1; 82 83 nfds = select(nfds, &readfds, NULL, NULL, &timeout); 84 85 if (nfds > 0) { 86 /* 多路复用 */ 87 numbytes = read(sockfd, request + totalbytes, MAX_BUF - (totalbytes + 1)); 88 if (numbytes < 0) { 89 debug(LOG_ERR, "An error occurred while reading from auth server: %s", strerror(errno)); 90 close(sockfd); 91 return; 92 } 93 else if (numbytes == 0) { 94 done = 1; 95 } 96 else { 97 totalbytes += numbytes; 98 debug(LOG_DEBUG, "Read %d bytes, total now %d", numbytes, totalbytes); 99 } 100 } 101 else if (nfds == 0) { 102 debug(LOG_ERR, "Timed out reading data via select() from auth server"); 103 close(sockfd); 104 return; 105 } 106 else if (nfds < 0) { 107 debug(LOG_ERR, "Error reading data via select() from auth server: %s", strerror(errno)); 108 close(sockfd); 109 return; 110 } 111 } while (!done); 112 close(sockfd); 113 114 debug(LOG_DEBUG, "Done reading reply, total %d bytes", totalbytes); 115 116 request[totalbytes] = '\0'; 117 118 debug(LOG_DEBUG, "HTTP Response from Server: [%s]", request); 119 /* 判断认证服务器返回包中有没有"Pong"字符串 */ 120 if (strstr(request, "Pong") == 0) { 121 debug(LOG_WARNING, "Auth server did NOT say pong!"); 122 123 } 124 else { 125 debug(LOG_DEBUG, "Auth Server Says: Pong"); 126 } 127 128 return; 129 } 130
代码片段1.3
connect_auth_server函数用于连接认证服务器并返回socket套接字,其具体实现是通过_connect_auth_server实现的,而在_connect_auth_server中,递归认证服务器列表,每次递归中首先会根据认证服务器域名获取ip,如果失败,会通过公共网站判断是否为DNS问题,再判断是否为认证服务器问题,如果都失败,继续递归,否则返回认证服务器socket。
1 int connect_auth_server() { 2 int sockfd; 3 4 LOCK_CONFIG(); 5 /* 连接认证服务器 */ 6 sockfd = _connect_auth_server(0); 7 UNLOCK_CONFIG(); 8 9 if (sockfd == -1) { 10 debug(LOG_ERR, "Failed to connect to any of the auth servers"); 11 /* 标记认证服务器离线 */ 12 mark_auth_offline(); 13 } 14 else { 15 debug(LOG_DEBUG, "Connected to auth server"); 16 /* 标记认证服务器在线 */ 17 mark_auth_online(); 18 } 19 return (sockfd); 20 } 21 22 23 24 int _connect_auth_server(int level) { 25 s_config *config = config_get_config(); 26 t_auth_serv *auth_server = NULL; 27 struct in_addr *h_addr; 28 int num_servers = 0; 29 char * hostname = NULL; 30 /* 公共网站,用于判断DNS问题 */ 31 char * popular_servers[] = { 32 "www.google.com", 33 "www.yahoo.com", 34 NULL 35 }; 36 char ** popularserver; 37 char * ip; 38 struct sockaddr_in their_addr; 39 int sockfd; 40 41 /* 用于递归,因为可能会有多个认证服务器,如果第一个认证服务器无法连接,会递归尝试连接后面的认证服务器,此参数用于递归判断的,当成功连接任意一个认证服务器后停止 */ 42 level++; 43 44 /* 45 * 获取认证服务器数量 46 */ 47 for (auth_server = config->auth_servers; auth_server; auth_server = auth_server->next) { 48 num_servers++; 49 } 50 debug(LOG_DEBUG, "Level %d: Calculated %d auth servers in list", level, num_servers); 51 /* 已经尝试递归连接所有认证服务器,都不能连接 */ 52 if (level > num_servers) { 53 return (-1); 54 } 55 56 /* 57 * 获取认证服务器列表中的第一个认证服务器 58 */ 59 auth_server = config->auth_servers; 60 hostname = auth_server->authserv_hostname; 61 debug(LOG_DEBUG, "Level %d: Resolving auth server [%s]", level, hostname); 62 h_addr = wd_gethostbyname(hostname); 63 if (!h_addr) { 64 /* 65 * DNS解析错误,尝试解析公共网站判断是否为DNS错误 66 */ 67 debug(LOG_DEBUG, "Level %d: Resolving auth server [%s] failed", level, hostname); 68 69 for (popularserver = popular_servers; *popularserver; popularserver++) { 70 debug(LOG_DEBUG, "Level %d: Resolving popular server [%s]", level, *popularserver); 71 h_addr = wd_gethostbyname(*popularserver); 72 /* 公共网站DNS解析正确 */ 73 if (h_addr) { 74 debug(LOG_DEBUG, "Level %d: Resolving popular server [%s] succeeded = [%s]", level, *popularserver, inet_ntoa(*h_addr)); 75 break; 76 } 77 else { 78 debug(LOG_DEBUG, "Level %d: Resolving popular server [%s] failed", level, *popularserver); 79 } 80 } 81 82 if (h_addr) { 83 /* DNS正确,尝试递归下一个认证服务器 */ 84 free (h_addr); 85 86 debug(LOG_DEBUG, "Level %d: Marking auth server [%s] as bad and trying next if possible", level, hostname); 87 if (auth_server->last_ip) { 88 free(auth_server->last_ip); 89 auth_server->last_ip = NULL; 90 } 91 /* 将此认证服务器放入bad_server链表,并将config->auth_server指向认证服务器的下一个节点 */ 92 mark_auth_server_bad(auth_server); 93 /* 递归 */ 94 return _connect_auth_server(level); 95 } 96 else { 97 /* DNS问题,标记路由器离线 */ 98 mark_offline(); 99 debug(LOG_DEBUG, "Level %d: Failed to resolve auth server and all popular servers. " 100 "The internet connection is probably down", level); 101 return(-1); 102 } 103 } 104 else { 105 /* DNS解析成功 */ 106 ip = safe_strdup(inet_ntoa(*h_addr)); 107 debug(LOG_DEBUG, "Level %d: Resolving auth server [%s] succeeded = [%s]", level, hostname, ip); 108 109 if (!auth_server->last_ip || strcmp(auth_server->last_ip, ip) != 0) { 110 /* DNS解析到的IP与我们上一次连接的IP不同,更新上一次连接的IP */ 111 debug(LOG_DEBUG, "Level %d: Updating last_ip IP of server [%s] to [%s]", level, hostname, ip); 112 if (auth_server->last_ip) free(auth_server->last_ip); 113 auth_server->last_ip = ip; 114 115 /* 将此新的认证服务器IP添加到iptables中的可访问外网地址中 */ 116 fw_clear_authservers(); 117 fw_set_authservers(); 118 } 119 else { 120 /* 121 * DNS解析到的IP与我们上一次连接的IP相同 122 */ 123 free(ip); 124 } 125 126 /* 127 * 连接 128 */ 129 debug(LOG_DEBUG, "Level %d: Connecting to auth server %s:%d", level, hostname, auth_server->authserv_http_port); 130 their_addr.sin_family = AF_INET; 131 their_addr.sin_port = htons(auth_server->authserv_http_port); 132 their_addr.sin_addr = *h_addr; 133 memset(&(their_addr.sin_zero), '\0', sizeof(their_addr.sin_zero)); 134 free (h_addr); 135 136 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 137 debug(LOG_ERR, "Level %d: Failed to create a new SOCK_STREAM socket: %s", strerror(errno)); 138 return(-1); 139 } 140 141 if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { 142 /* 143 * 连接失败 144 * 将此认证服务器放入bad_server链表,并将config->auth_server指向认证服务器的下一个节点 145 */ 146 debug(LOG_DEBUG, "Level %d: Failed to connect to auth server %s:%d (%s). Marking it as bad and trying next if possible", level, hostname, auth_server->authserv_http_port, strerror(errno)); 147 close(sockfd); 148 mark_auth_server_bad(auth_server); 149 return _connect_auth_server(level); /* Yay recursion! */ 150 } 151 else { 152 /* 153 * 连接成功 154 */ 155 debug(LOG_DEBUG, "Level %d: Successfully connected to auth server %s:%d", level, hostname, auth_server->authserv_http_port); 156 return sockfd; 157 } 158 } 159 }