美文网首页wifidog
wifidog源码分析 - 认证服务器心跳检测线程

wifidog源码分析 - 认证服务器心跳检测线程

作者: 3c937c88e6c0 | 来源:发表于2015-02-02 14:11 被阅读145次

    引言

    但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;

    }

    }

    }

    本文由http://www.wifidog.pro/2015/02/02/wifidog%E5%BF%83%E8%B7%B3.html整理编辑,转载请注明出处

    相关文章

      网友评论

        本文标题:wifidog源码分析 - 认证服务器心跳检测线程

        本文链接:https://www.haomeiwen.com/subject/ebfrxttx.html