wifidog源码分析 - 认证服务器心跳检测线程
2015-01-20 10:19
423 查看
引言
但wifidog启动时,会自动启动认证服务器心跳检测线程,此线程默认每隔60s与认证服务器交互一次,会将路由器的信息(系统启动时长,内存使用情况和系统平均负载)告知认证服务器,并通过一个"ping"字符串作为信号,而当认证服务器接收到此数据包后,会返回一个"pong"给路由器,具体我们看看代码。代码片段1.1
此段代码很简单,就是调用ping函数,然后等待60s
void thread_ping(void *arg) { pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER; struct timespec timeout; while (1) { /* 调用ping,具体代码看 代码片段1.2 */ debug(LOG_DEBUG, "Running ping()"); ping(); /* 睡眠一个checkinterval,默认为60s */ timeout.tv_sec = time(NULL) + config_get_config()->checkinterval; timeout.tv_nsec = 0; pthread_mutex_lock(&cond_mutex); pthread_cond_timedwait(&cond, &cond_mutex, &timeout); pthread_mutex_unlock(&cond_mutex); }
代码片段1.2
static void ping(void) { ssize_t numbytes; size_t totalbytes; int sockfd, nfds, done; char request[MAX_BUF]; fd_set readfds; struct timeval timeout; FILE * fh; unsigned long int sys_uptime = 0; unsigned int sys_memfree = 0; float sys_load = 0; t_auth_serv *auth_server = NULL; auth_server = get_auth_server(); debug(LOG_DEBUG, "Entering ping()"); /* 其实认证服务器就是一个web服务器,路由器跟他做通信行为就是通过发送http请求进行通信,首先先连接认证服务器的http端口,获取其socket */ sockfd = connect_auth_server(); if (sockfd == -1) { /* 无法连接认证服务器,connect_auth_server分析见 代码片段1.3 */ return; } /* * 从/proc文件系统获取路由器信息 */ if ((fh = fopen("/proc/uptime", "r"))) { fscanf(fh, "%lu", &sys_uptime); fclose(fh); } if ((fh = fopen("/proc/meminfo", "r"))) { while (!feof(fh)) { if (fscanf(fh, "MemFree: %u", &sys_memfree) == 0) { while (!feof(fh) && fgetc(fh) != '\n'); } else { break; } } fclose(fh); } if ((fh = fopen("/proc/loadavg", "r"))) { fscanf(fh, "%f", &sys_load); fclose(fh); } /* * 准备http请求包 */ snprintf(request, sizeof(request) - 1, "GET %s%sgw_id=%s&sys_uptime=%lu&sys_memfree=%u&sys_load=%.2f&wifidog_uptime=%lu HTTP/1.0\r\n" "User-Agent: WiFiDog %s\r\n" "Host: %s\r\n" "\r\n", auth_server->authserv_path, auth_server->authserv_ping_script_path_fragment, config_get_config()->gw_id, sys_uptime, sys_memfree, sys_load, (long unsigned int)((long unsigned int)time(NULL) - (long unsigned int)started_time), VERSION, auth_server->authserv_hostname); debug(LOG_DEBUG, "HTTP Request to Server: [%s]", request); /* 发送 */ send(sockfd, request, strlen(request), 0); debug(LOG_DEBUG, "Reading response"); numbytes = totalbytes = 0; done = 0; do { FD_ZERO(&readfds); FD_SET(sockfd, &readfds); /* 设置超时30s */ timeout.tv_sec = 30; timeout.tv_usec = 0; nfds = sockfd + 1; nfds = select(nfds, &readfds, NULL, NULL, &timeout); if (nfds > 0) { /* 多路复用 */ numbytes = read(sockfd, request + totalbytes, MAX_BUF - (totalbytes + 1)); if (numbytes < 0) { debug(LOG_ERR, "An error occurred while reading from auth server: %s", strerror(errno)); close(sockfd); return; } else if (numbytes == 0) { done = 1; } else { totalbytes += numbytes; debug(LOG_DEBUG, "Read %d bytes, total now %d", numbytes, totalbytes); } } else if (nfds == 0) { debug(LOG_ERR, "Timed out reading data via select() from auth server"); close(sockfd); return; } else if (nfds < 0) { debug(LOG_ERR, "Error reading data via select() from auth server: %s", strerror(errno)); close(sockfd); return; } } while (!done); close(sockfd); debug(LOG_DEBUG, "Done reading reply, total %d bytes", totalbytes); request[totalbytes] = '\0'; debug(LOG_DEBUG, "HTTP Response from Server: [%s]", request); /* 判断认证服务器返回包中有没有"Pong"字符串 */ if (strstr(request, "Pong") == 0) { debug(LOG_WARNING, "Auth server did NOT say pong!"); } else { debug(LOG_DEBUG, "Auth Server Says: Pong"); } return; }
代码片段1.3
connect_auth_server函数用于连接认证服务器并返回socket套接字,其具体实现是通过_connect_auth_server实现的,而在_connect_auth_server中,递归认证服务器列表,每次递归中首先会根据认证服务器域名获取ip,如果失败,会通过公共网站判断是否为DNS问题,再判断是否为认证服务器问题,如果都失败,继续递归,否则返回认证服务器socket。
int connect_auth_server() { int sockfd; LOCK_CONFIG(); /* 连接认证服务器 */ sockfd = _connect_auth_server(0); UNLOCK_CONFIG(); if (sockfd == -1) { debug(LOG_ERR, "Failed to connect to any of the auth servers"); /* 标记认证服务器离线 */ mark_auth_offline(); } else { debug(LOG_DEBUG, "Connected to auth server"); /* 标记认证服务器在线 */ mark_auth_online(); } return (sockfd); } int _connect_auth_server(int level) { s_config *config = config_get_config(); t_auth_serv *auth_server = NULL; struct in_addr *h_addr; int num_servers = 0; char * hostname = NULL; /* 公共网站,用于判断DNS问题 */ char * popular_servers[] = { "www.google.com", "www.yahoo.com", NULL }; char ** popularserver; char * ip; struct sockaddr_in their_addr; int sockfd; /* 用于递归,因为可能会有多个认证服务器,如果第一个认证服务器无法连接,会递归尝试连接后面的认证服务器,此参数用于递归判断的,当成功连接任意一个认证服务器后停止 */ level++; /* * 获取认证服务器数量 */ for (auth_server = config->auth_servers; auth_server; auth_server = auth_server->next) { num_servers++; } debug(LOG_DEBUG, "Level %d: Calculated %d auth servers in list", level, num_servers); /* 已经尝试递归连接所有认证服务器,都不能连接 */ if (level > num_servers) { return (-1); } /* * 获取认证服务器列表中的第一个认证服务器 */ auth_server = config->auth_servers; hostname = auth_server->authserv_hostname; debug(LOG_DEBUG, "Level %d: Resolving auth server [%s]", level, hostname); h_addr = wd_gethostbyname(hostname); if (!h_addr) { /* * DNS解析错误,尝试解析公共网站判断是否为DNS错误 */ debug(LOG_DEBUG, "Level %d: Resolving auth server [%s] failed", level, hostname); for (popularserver = popular_servers; *popularserver; popularserver++) { debug(LOG_DEBUG, "Level %d: Resolving popular server [%s]", level, *popularserver); h_addr = wd_gethostbyname(*popularserver); /* 公共网站DNS解析正确 */ if (h_addr) { debug(LOG_DEBUG, "Level %d: Resolving popular server [%s] succeeded = [%s]", level, *popularserver, inet_ntoa(*h_addr)); break; } else { debug(LOG_DEBUG, "Level %d: Resolving popular server [%s] failed", level, *popularserver); } } if (h_addr) { /* DNS正确,尝试递归下一个认证服务器 */ free (h_addr); debug(LOG_DEBUG, "Level %d: Marking auth server [%s] as bad and trying next if possible", level, hostname); if (auth_server->last_ip) { free(auth_server->last_ip); auth_server->last_ip = NULL; } /* 将此认证服务器放入bad_server链表,并将config->auth_server指向认证服务器的下一个节点 */ mark_auth_server_bad(auth_server); /* 递归 */ return _connect_auth_server(level); } else { /* DNS问题,标记路由器离线 */ mark_offline(); debug(LOG_DEBUG, "Level %d: Failed to resolve auth server and all popular servers. " "The internet connection is probably down", level); return(-1); } } else { /* DNS解析成功 */ ip = safe_strdup(inet_ntoa(*h_addr)); debug(LOG_DEBUG, "Level %d: Resolving auth server [%s] succeeded = [%s]", level, hostname, ip); if (!auth_server->last_ip || strcmp(auth_server->last_ip, ip) != 0) { /* DNS解析到的IP与我们上一次连接的IP不同,更新上一次连接的IP */ debug(LOG_DEBUG, "Level %d: Updating last_ip IP of server [%s] to [%s]", level, hostname, ip); if (auth_server->last_ip) free(auth_server->last_ip); auth_server->last_ip = ip; /* 将此新的认证服务器IP添加到iptables中的可访问外网地址中 */ fw_clear_authservers(); fw_set_authservers(); } else { /* * DNS解析到的IP与我们上一次连接的IP相同 */ free(ip); } /* * 连接 */ debug(LOG_DEBUG, "Level %d: Connecting to auth server %s:%d", level, hostname, auth_server->authserv_http_port); their_addr.sin_family = AF_INET; their_addr.sin_port = htons(auth_server->authserv_http_port); their_addr.sin_addr = *h_addr; memset(&(their_addr.sin_zero), '\0', sizeof(their_addr.sin_zero)); free (h_addr); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { debug(LOG_ERR, "Level %d: Failed to create a new SOCK_STREAM socket: %s", strerror(errno)); return(-1); } if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { /* * 连接失败 * 将此认证服务器放入bad_server链表,并将config->auth_server指向认证服务器的下一个节点 */ 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)); close(sockfd); mark_auth_server_bad(auth_server); return _connect_auth_server(level); /* Yay recursion! */ } else { /* * 连接成功 */ debug(LOG_DEBUG, "Level %d: Successfully connected to auth server %s:%d", level, hostname, auth_server->authserv_http_port); return sockfd; } } }
相关文章推荐
- wifidog源码分析 - 客户端检测线程
- wifidog源码分析 - 客户端检测线程
- wifidog源码分析 - wifidog原理
- wifidog源码分析 - wifidog原理
- Wifidog及认证过程初分析
- wifidog源码分析 - 认证服务器心跳检测线程
- HDFS源码分析心跳汇报之BPServiceActor工作线程运行流程
- 第二人生的源码分析(四十九)接收服务器回应的纹理图片数据
- 第二人生的源码分析(四十)创建多个工作线程
- 第二人生的源码分析(四十)创建多个工作线程
- 第二人生的源码分析(四十八)接收服务器回应的纹理图片头数据
- 第二人生的源码分析(四十二)实现消息处理的线程类
- 第二人生的源码分析(四十三)虚拟文件系统线程
- 第二人生的源码分析(二十五)人物行走与服务器同步
- 第二人生的源码分析(四十六)获取纹理图片的线程
- 第二人生的源码分析(四十八)接收服务器回应的纹理图片头数据
- 第二人生的源码分析(四十五)图像解压线程
- NT_DB服务器性能分析检测项目表
- 第二人生的源码分析(四十五)图像解压线程
- 第二人生的源码分析(四十一)使用Apache运行库线程