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"路径)
-
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);
-
}
代码片段1.3
此段代码表示wifidog是如何通过http 404回调函数实现客户端重定向了,实际上就是在404回调函数中封装了一个307状态的http报头,http的307状态在http协议中就是用于重定向的,封装完成后通过已经与客户端连接的socket返回给客户端。步骤流程为
-
判断本机是否处于离线状态
-
判断认证服务器是否在线
-
封装http 307报文
-
发送于目标客户端
-
void
-
http_callback_404(httpd *webserver, request *r)
-
{
-
char tmp_url[MAX_BUF],
-
*url;
-
s_config *config = config_get_config();
-
t_auth_serv *auth_server = get_auth_server();
-
-
memset(tmp_url, 0, sizeof(tmp_url));
-
-
snprintf(tmp_url, (sizeof(tmp_url) - 1), "http://%s%s%s%s",
-
r->request.host,
-
r->request.path,
-
r->request.query[0] ? "?" : "",
-
r->request.query);
-
url = httpdUrlEncode(tmp_url);
-
-
if (!is_online()) {
-
/* 本机处于离线状态,此函数调用结果由认证服务器检测线程设置 */
-
char * buf;
-
safe_asprintf(&buf,
-
"
We apologize, but it seems that the internet connection that powers this hotspot is temporarily unavailable.
"
-
"
If at all possible, please notify the owners of this hotspot that the internet connection is out of service.
"
-
"
The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.
"
-
"
In a while please click here to try your request again.
"
, tmp_url);
-
-
send_http_page(r, "Uh oh! Internet access unavailable!", buf);
-
free(buf);
-
debug(LOG_INFO, "Sent %s an apology since I am not online - no point sending them to auth server", r->clientAddr);
-
}
-
else if (!is_auth_online()) {
-
/* 认证服务器处于离线状态 */
-
char * buf;
-
safe_asprintf(&buf,
-
"
We apologize, but it seems that we are currently unable to re-direct you to the login screen.
"
-
"
The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.
"
-
"
In a couple of minutes please click here to try your request again.
"
, tmp_url);
-
-
send_http_page(r, "Uh oh! Login screen unavailable!", buf);
-
free(buf);
-
debug(LOG_INFO, "Sent %s an apology since auth server not online - no point sending them to auth server", r->clientAddr);
-
}
-
else {
-
/* 本机与认证服务器都在线,返回重定向包于客户端 */
-
char *urlFragment;
-
safe_asprintf(&urlFragment, "%sgw_address=%s&gw_port=%d&gw_id=%s&url=%s",
-
auth_server->authserv_login_script_path_fragment,
-
config->gw_address,
-
config->gw_port,
-
config->gw_id,
-
url);
-
debug(LOG_INFO, "Captured %s requesting [%s] and re-directing them to login page", r->clientAddr, url);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to login page"); /* 实际上此函数中通过socket返回一个307状态的http报头给客户端,里面包含有认证服务器地址 */
-
free(urlFragment);
-
}
-
free(url);
-
}
代码片段1.4
此段表明当客户端已经在认证服务器确认登陆,认证服务器将客户端重新重定向回网关,并在重定向包中包含关键路径"/wifidog/auth"和token,认证服务器所执行的操作。
-
void
-
http_callback_auth(httpd *webserver, request *r)
-
{
-
t_client *client;
-
httpVar * token;
-
char *mac;
-
/* 判断http报文是否包含登出logout */
-
httpVar *logout = httpdGetVariableByName(r, "logout");
-
if ((token = httpdGetVariableByName(r, "token"))) {
-
/* 获取http报文中的token */
-
if (!(mac = arp_get(r->clientAddr))) {
-
/* 获取客户端mac地址失败 */
-
debug(LOG_ERR, "Failed to retrieve MAC address for ip %s", r->clientAddr);
-
send_http_page(r, "WiFiDog Error", "Failed to retrieve your MAC address");
-
} else {
-
LOCK_CLIENT_LIST();
-
/* 判断客户端是否存在于列表中 */
-
if ((client = client_list_find(r->clientAddr, mac)) == NULL) {
-
debug(LOG_DEBUG, "New client for %s", r->clientAddr);
-
/* 将此客户端添加到客户端列表 */
-
client_list_append(r->clientAddr, mac, token->value);
-
} else if (logout) {
-
/* http报文为登出 */
-
t_authresponse authresponse;
-
s_config *config = config_get_config();
-
unsigned long long incoming = client->counters.incoming;
-
unsigned long long outgoing = client->counters.outgoing;
-
char *ip = safe_strdup(client->ip);
-
char *urlFragment = NULL;
-
t_auth_serv *auth_server = get_auth_server();
-
/* 修改iptables禁止客户端访问外网 */
-
fw_deny(client->ip, client->mac, client->fw_connection_state);
-
/* 从客户端列表中删除此客户端 */
-
client_list_delete(client);
-
debug(LOG_DEBUG, "Got logout from %s", client->ip);
-
-
if (config->auth_servers != NULL) {
-
UNLOCK_CLIENT_LIST();
-
/* 发送登出认证包给认证服务器 */
-
auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token->value,
-
incoming, outgoing);
-
LOCK_CLIENT_LIST();
-
-
/* 将客户端重定向到认证服务器 */
-
debug(LOG_INFO, "Got manual logout from client ip %s, mac %s, token %s"
-
"- redirecting them to logout message", client->ip, client->mac, client->token);
-
safe_asprintf(&urlFragment, "%smessage=%s",
-
auth_server->authserv_msg_script_path_fragment,
-
GATEWAY_MESSAGE_ACCOUNT_LOGGED_OUT
-
);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to logout message");
-
free(urlFragment);
-
}
-
free(ip);
-
}
-
else {
-
debug(LOG_DEBUG, "Client for %s is already in the client list", client->ip);
-
}
-
UNLOCK_CLIENT_LIST();
-
if (!logout) {
-
/* 通过认证服务器认证此客户端token */
-
authenticate_client(r);
-
}
-
free(mac);
-
}
-
} else {
-
send_http_page(r, "WiFiDog error", "Invalid token");
-
}
-
}
-
-
/* 此函数用于提交token到认证服务器进行认证 */
-
void
-
authenticate_client(request *r)
-
{
-
t_client *client;
-
t_authresponse auth_response;
-
char *mac,
-
*token;
-
char *urlFragment = NULL;
-
s_config *config = NULL;
-
t_auth_serv *auth_server = NULL;
-
-
LOCK_CLIENT_LIST();
-
-
client = client_list_find_by_ip(r->clientAddr);
-
/* 判断此客户端是否在列表中 */
-
if (client == NULL) {
-
debug(LOG_ERR, "Could not find client for %s", r->clientAddr);
-
UNLOCK_CLIENT_LIST();
-
return;
-
}
-
-
mac = safe_strdup(client->mac);
-
token = safe_strdup(client->token);
-
-
UNLOCK_CLIENT_LIST();
-
-
/* 提交token、客户端ip、客户端mac至认证服务器 */
-
auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0);
-
-
LOCK_CLIENT_LIST();
-
-
/*再次判断客户端是否存在于列表中,保险起见,因为有可能在于认证服务器认证过程中,客户端检测线程把此客户端下线 */
-
client = client_list_find(r->clientAddr, mac);
-
-
if (client == NULL) {
-
debug(LOG_ERR, "Could not find client node for %s (%s)", r->clientAddr, mac);
-
UNLOCK_CLIENT_LIST();
-
free(token);
-
free(mac);
-
return;
-
}
-
-
free(token);
-
free(mac);
-
-
config = config_get_config();
-
auth_server = get_auth_server();
-
-
/* 判断认证服务器认证结果 */
-
switch(auth_response.authcode) {
-
-
case AUTH_ERROR:
-
/* 认证错误 */
-
debug(LOG_ERR, "Got %d from central server authenticating token %s from %s at %s", auth_response, client->token, client->ip, client->mac);
-
send_http_page(r, "Error!", "Error: We did not get a valid answer from the central server");
-
break;
-
-
case AUTH_DENIED:
-
/* 认证服务器拒绝此客户端 */
-
debug(LOG_INFO, "Got DENIED from central server authenticating token %s from %s at %s - redirecting them to denied message", client->token, client->ip, client->mac);
-
safe_asprintf(&urlFragment, "%smessage=%s",
-
auth_server->authserv_msg_script_path_fragment,
-
GATEWAY_MESSAGE_DENIED
-
);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to denied message");
-
free(urlFragment);
-
break;
-
-
case AUTH_VALIDATION:
-
/* 认证服务器处于等待此客户端电子邮件确认回执状态 */
-
debug(LOG_INFO, "Got VALIDATION from central server authenticating token %s from %s at %s"
-
"- adding to firewall and redirecting them to activate message", client->token,
-
client->ip, client->mac);
-
client->fw_connection_state = FW_MARK_PROBATION;
-
fw_allow(client->ip, client->mac, FW_MARK_PROBATION);
-
safe_asprintf(&urlFragment, "%smessage=%s",
-
auth_server->authserv_msg_script_path_fragment,
-
GATEWAY_MESSAGE_ACTIVATE_ACCOUNT
-
);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to activate message");
-
free(urlFragment);
-
break;
-
-
case AUTH_ALLOWED:
-
/* 认证通过 */
-
debug(LOG_INFO, "Got ALLOWED from central server authenticating token %s from %s at %s - "
-
"adding to firewall and redirecting them to portal", client->token, client->ip, client->mac);
-
client->fw_connection_state = FW_MARK_KNOWN;
-
fw_allow(client->ip, client->mac, FW_MARK_KNOWN);
-
served_this_session++;
-
safe_asprintf(&urlFragment, "%sgw_id=%s",
-
auth_server->authserv_portal_script_path_fragment,
-
config->gw_id
-
);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to portal");
-
free(urlFragment);
-
break;
-
-
case AUTH_VALIDATION_FAILED:
-
/* 电子邮件确认回执超时 */
-
debug(LOG_INFO, "Got VALIDATION_FAILED from central server authenticating token %s from %s at %s "
-
"- redirecting them to failed_validation message", client->token, client->ip, client->mac);
-
safe_asprintf(&urlFragment, "%smessage=%s",
-
auth_server->authserv_msg_script_path_fragment,
-
GATEWAY_MESSAGE_ACCOUNT_VALIDATION_FAILED
-
);
-
http_send_redirect_to_auth(r, urlFragment, "Redirect to failed validation message");
-
free(urlFragment);
-
break;
-
-
default:
-
debug(LOG_WARNING, "I don't know what the validation code %d means for token %s from %s at %s - sending error message", auth_response.authcode, client->token, client->ip, client->mac);
-
send_http_page(r, "Internal Error", "We can not validate your request at this time");
-
break;
-
-
}
-
-
UNLOCK_CLIENT_LIST();
-
return;
-
}
阅读(1003) | 评论(0) | 转发(0) |