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

引言

但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整理编辑,转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 引言 当wifidog启动时,会启动一个线程(thread_client_timeout_check)维护客户端列...
    3c937c88e6c0阅读 439评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 1.ngnix介绍 ngnix www服务软件 俄罗斯人开发 开源 性能很高 本身是一款静态WWW软件 静态小文件...
    逗比punk阅读 2,032评论 1 6
  • 上一篇分析了 接入设备 在接入路由器,并发起首次 HTTP/80 请求到路由器上时,wifidog 是如何将此 H...
    3c937c88e6c0阅读 1,333评论 0 2
  • NAME dnsmasq - A lightweight DHCP and caching DNS server....
    ximitc阅读 2,652评论 0 0