wifidog源码分析 - 用户连接过程(1)
引言
之前的文章已经描述wifidog大概的一个工作流程,这里我们具体说说wifidog是怎么把一个新用户重定向到认证服务器中的,它又是怎么对一个已认证的用户实行放行操作的。我们已经知道wifidog在启动时会删除iptables中mangle、nat、filter表中的所有规则,并在这三个表中添加wifidog自己的规则,其规则简单来说就是将网关80端口重定向到指定端口(默认为2060),禁止非认证用户连接外网(除认证服务器外)。当有新用户连接路由器上网时,wifidog会通过监听2060端口获取新用户的http报文,通过报文即可知道报文是否携带token进行认证,如果没有token,wifidog会返回一个重定向的http报文给新用户,用户则会跳转到认证服务器进行认证,当认证成功后,认真服务器又会对用户重定向到网关,并在重定向报文中添加关键路径"/wifidog/auth"和token,wifidog重新获取到用户的http报文,检测到包含关键路径"/wifidog/auth"后,会通过认证服务器验证token是否有效,有效则修改iptables放行此客户端。
main_loop
此函数几乎相当于我们写程序的main函数,主要功能都是在这里面实现的,函数主要实现了主循环,并启动了三个线程,这三个线程的功能具体见wifidog源码分析 - wifidog原理,在此函数最后主循环中会等待用户连接,新用户只要通过浏览器打开非认证服务器网页时主循环就会监听到,监听到后会启动一个处理线程。其流程为
- 设置程序启动时间
- 获取网关信息
- 绑定http端口(80重定向到了2060)
- 设置关键路径和404错误的回调函数
- 重新建立iptables规则
- 启动客户端检测线程 (稍后文章分析)
- 启动wdctrl交互线程 (稍后文章分析)
- 认证服务器心跳检测线程 (稍后文章分析)
- 循环等待用户http请求,为每个请求启动一个处理线程。(代码片段1.2 代码片段1.3 代码片段1.4)
代码片段1.1
static void
main_loop(void)
{
int result;
pthread_t tid;
s_config *config = config_get_config();
request *r;
void **params;
/* 设置启动时间 */
if (!started_time) {
debug(LOG_INFO, "Setting started_time");
started_time = time(NULL);
}
else if (started_time < MINIMUM_STARTED_TIME) {
debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time");
started_time = time(NULL);
}
/* 获取网关IP,失败退出程序 */
if (!config->gw_address) {
debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface);
if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) {
debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface);
exit(1);
}
debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_address);
}
/* 获取网关ID,失败退出程序 */
if (!config->gw_id) {
debug(LOG_DEBUG, "Finding MAC address of %s", config->gw_interface);
if ((config->gw_id = get_iface_mac(config->gw_interface)) == NULL) {
debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface);
exit(1);
}
debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_id);
}
/* 初始化监听网关2060端口的socket */
debug(LOG_NOTICE, "Creating web server on %s:%d", config->gw_address, config->gw_port);
if ((webserver = httpdCreate(config->gw_address, config->gw_port)) == NULL) {
debug(LOG_ERR, "Could not create web server: %s", strerror(errno));
exit(1);
}
debug(LOG_DEBUG, "Assigning callbacks to web server");
/* 设置关键路径及其回调函数,在代码片段1.2中会使用到 */
httpdAddCContent(webserver, "/", "wifidog", 0, NULL, http_callback_wifidog);
httpdAddCContent(webserver, "/wifidog", "", 0, NULL, http_callback_wifidog);
httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about);
httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status);
httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth);
/* 设置404错误回调函数,在里面实现了重定向至认证服务器 */
httpdAddC404Content(webserver, http_callback_404);
/* 清除iptables规则 */
fw_destroy();
/* 重新设置iptables规则 */
if (!fw_init()) {
debug(LOG_ERR, "FATAL: Failed to initialize firewall");
exit(1);
}
/* 客户端检测线程 */
result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL);
if (result != 0) {
debug(LOG_ERR, "FATAL: Failed to create a new thread (fw_counter) - exiting");
termination_handler(0);
}
pthread_detach(tid_fw_counter);
/* wdctrl交互线程 */
result = pthread_create(&tid, NULL, (void *)thread_wdctl, (void *)safe_strdup(config->wdctl_sock));
if (result != 0) {
debug(LOG_ERR, "FATAL: Failed to create a new thread (wdctl) - exiting");
termination_handler(0);
}
pthread_detach(tid);
/* 认证服务器心跳检测线程 */
result = pthread_create(&tid_ping, NULL, (void *)thread_ping, NULL);
if (result != 0) {
debug(LOG_ERR, "FATAL: Failed to create a new thread (ping) - exiting");
termination_handler(0);
}
pthread_detach(tid_ping);
debug(LOG_NOTICE, "Waiting for connections");
while(1) {
/* 监听2060端口等待用户http请求 */
r = httpdGetConnection(webserver, NULL);
/* 错误处理 */
if (webserver->lastError == -1) {
/* Interrupted system call */
continue; /* restart loop */
}
else if (webserver->lastError < -1) {
debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError);
termination_handler(0);
}
else if (r != NULL) {
/* 用户http请求接收成功 */
debug(LOG_INFO, "Received connection from %s, spawning worker thread", r->clientAddr);
params = safe_malloc(2 * sizeof(void *));
*params = webserver;
*(params + 1) = r;
/* 开启http请求处理线程 */
result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);
if (result != 0) {
debug(LOG_ERR, "FATAL: Failed to create a new thread (httpd) - exiting");
termination_handler(0);
}
pthread_detach(tid);
}
else {
;
}
}
/* never reached */
}
用户连接启动线程(void thread_httpd(void * args))
代码片段1.2分析
此段代码是当有新用户(未认证的用户 代码片段1.3,已在认证服务器上认证但没有在wifidog认证的用户 代码片段1.4)连接时创建的线程,其主要功能为
- 获取用户浏览器发送过来的http报头
- 分析http报头,分析是否包含关键路径
- 不包含关键路径则调用404回调函数
- 包含关键路径则执行关键路径回调函数(这里主要讲解"/wifidog/auth"路径)
代码片段1.2
void
thread_httpd(void *args)
{
void **params;
httpd *webserver;
request *r;
params = (void **)args;
webserver = *params;
r = *(params + 1);
free(params);
/* 获取http报文 */
if (httpdReadRequest(webserver, r) == 0) {
debug(LOG_DEBUG, "Processing request from %s", r->clientAddr);
debug(LOG_DEBUG, "Calling httpdProcessRequest() for %s", r->clientAddr);
/* 分析http报文 */
httpdProcessRequest(webserver, r);
debug(LOG_DEBUG, "Returned from httpdProcessRequest() for %s", r->clientAddr);
}
else {
debug(LOG_DEBUG, "No valid request received from %s", r->clientAddr);
}
debug(LOG_DEBUG, "Closing connection with %s", r->clientAddr);
httpdEndRequest(r);
}
/* 被thread_httpd调用 */
void httpdProcessRequest(httpd *server, request *r)
{
char dirName[HTTP_MAX_URL],
entryName[HTTP_MAX_URL],
*cp;
httpDir *dir;
httpContent *entry;
r->response.responseLength = 0;
strncpy(dirName, httpdRequestPath(r), HTTP_MAX_URL);
dirName[HTTP_MAX_URL-1]=0;
cp = rindex(dirName, '/');
if (cp == NULL)
{
printf("Invalid request path '%s'\n",dirName);
return;
}
strncpy(entryName, cp + 1, HTTP_MAX_URL);
entryName[HTTP_MAX_URL-1]=0;
if (cp != dirName)
*cp = 0;
else
*(cp+1) = 0;
/* 获取http报文中的关键路径,在main_loop中已经设置 */
dir = _httpd_findContentDir(server, dirName, HTTP_FALSE);
if (dir == NULL)
{
/* http报文中未包含关键路径,执行404回调函数(在404回调函数中新用户被重定向到认证服务器),见代码片段1.3 */
_httpd_send404(server, r);
_httpd_writeAccessLog(server, r);
return;
}
/* 获取关键路径内容描述符 */
entry = _httpd_findContentEntry(r, dir, entryName);
if (entry == NULL)
{
_httpd_send404(server, r);
_httpd_writeAccessLog(server, r);
return;
}
if (entry->preload)
{
if ((entry->preload)(server) < 0)
{
_httpd_writeAccessLog(server, r);
return;
}
}
switch(entry->type)
{
case HTTP_C_FUNCT:
case HTTP_C_WILDCARD:
/* 如果是被认证服务器重定向到网关的用户,此处的关键路径为"/wifidog/auth",并执行回调函数 */
(entry->function)(server, r);
break;
case HTTP_STATIC:
_httpd_sendStatic(server, r, entry->data);
break;
case HTTP_FILE:
_httpd_sendFile(server, r, entry->path);
break;
case HTTP_WILDCARD:
if (_httpd_sendDirectoryEntry(server, r, entry,
entryName)<0)
{
_httpd_send404(server, r);
}
break;
}
_httpd_writeAccessLog(server, r);
}
本文章由 http://www.wifidog.pro/2015/02/03/wifidog%E7%94%A8%E6%88%B7%E8%BF%9E%E6%8E%A5%E8%BF%87%E7%A8%8B_1.html 整理编辑,转载请注明出处