Chinaunix首页 | 论坛 | 博客
  • 博客访问: 818907
  • 博文数量: 264
  • 博客积分: 592
  • 博客等级: 中士
  • 技术积分: 1574
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-24 22:02
文章分类

全部博文(264)

文章存档

2019年(2)

2018年(1)

2017年(1)

2016年(4)

2015年(14)

2014年(57)

2013年(88)

2012年(97)

分类: 嵌入式

2015-06-24 20:50:58

main_loop

  此函数几乎相当于我们写程序的main函数,主要功能都是在这里面实现的,函数主要实现了主循环,并启动了三个线程,这三个线程的功能具体见wifidog源码分析 - wifidog原理,在此函数最后主循环中会等待用户连接,新用户只要通过浏览器打开非认证服务器网页时主循环就会监听到,监听到后会启动一个处理线程。其流程为

  • 设置程序启动时间
  • 获取网关信息
  • 绑定http端口(80重定向到了2060)
  • 设置关键路径和404错误的回调函数
  • 重新建立iptables规则
  • 启动客户端检测线程 (稍后文章分析)
  • 启动wdctrl交互线程 (稍后文章分析)
  • 认证服务器心跳检测线程 (稍后文章分析)
  • 循环等待用户http请求,为每个请求启动一个处理线程。(代码片段1.2 代码片段1.3 代码片段1.4
代码片段1.1

  1. static void
  2. main_loop(void)
  3. {
  4.     int result;
  5.     pthread_t tid;
  6.     s_config *config = config_get_config();
  7.     request *r;
  8.     void **params;

  9.     /* 设置启动时间 */
  10.     if (!started_time) {
  11.         debug(LOG_INFO, "Setting started_time");
  12.         started_time = time(NULL);
  13.     }
  14.     else if (started_time < MINIMUM_STARTED_TIME) {
  15.         debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time");
  16.         started_time = time(NULL);
  17.     }

  18.     /* 获取网关IP,失败退出程序 */
  19.     if (!config->gw_address) {
  20.         debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface);
  21.         if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) {
  22.             debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface);
  23.             exit(1);
  24.         }
  25.         debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_address);
  26.     }

  27.     /* 获取网关ID,失败退出程序 */
  28.     if (!config->gw_id) {
  29.         debug(LOG_DEBUG, "Finding MAC address of %s", config->gw_interface);
  30.         if ((config->gw_id = get_iface_mac(config->gw_interface)) == NULL) {
  31.             debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface);
  32.             exit(1);
  33.         }
  34.         debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_id);
  35.     }

  36.     /* 初始化监听网关2060端口的socket */
  37.     debug(LOG_NOTICE, "Creating web server on %s:%d", config->gw_address, config->gw_port);
  38.     
  39.     if ((webserver = httpdCreate(config->gw_address, config->gw_port)) == NULL) {
  40.         debug(LOG_ERR, "Could not create web server: %s", strerror(errno));
  41.         exit(1);
  42.     }

  43.     debug(LOG_DEBUG, "Assigning callbacks to web server");
  44.     /* 设置关键路径及其回调函数,在代码片段1.2中会使用到 */
  45.     httpdAddCContent(webserver, "/", "wifidog", 0, NULL, http_callback_wifidog);
  46.     httpdAddCContent(webserver, "/wifidog", "", 0, NULL, http_callback_wifidog);
  47.     httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about);
  48.     httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status);
  49.     httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth);
  50.     /* 设置404错误回调函数,在里面实现了重定向至认证服务器 */
  51.     httpdAddC404Content(webserver, http_callback_404);

  52.     /* 清除iptables规则 */
  53.     fw_destroy();
  54.     /* 重新设置iptables规则 */
  55.     if (!fw_init()) {
  56.         debug(LOG_ERR, "FATAL: Failed to initialize firewall");
  57.         exit(1);
  58.     }

  59.     /* 客户端检测线程 */
  60.     result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL);
  61.     if (result != 0) {
  62.         debug(LOG_ERR, "FATAL: Failed to create a new thread (fw_counter) - exiting");
  63.         termination_handler(0);
  64.     }
  65.     pthread_detach(tid_fw_counter);

  66.     /* wdctrl交互线程 */
  67.     result = pthread_create(&tid, NULL, (void *)thread_wdctl, (void *)safe_strdup(config->wdctl_sock));
  68.     if (result != 0) {
  69.         debug(LOG_ERR, "FATAL: Failed to create a new thread (wdctl) - exiting");
  70.         termination_handler(0);
  71.     }
  72.     pthread_detach(tid);
  73.     
  74.     /* 认证服务器心跳检测线程 */
  75.     result = pthread_create(&tid_ping, NULL, (void *)thread_ping, NULL);
  76.     if (result != 0) {
  77.         debug(LOG_ERR, "FATAL: Failed to create a new thread (ping) - exiting");
  78.         termination_handler(0);
  79.     }
  80.     pthread_detach(tid_ping);
  81.     
  82.     debug(LOG_NOTICE, "Waiting for connections");
  83.     while(1) {
  84.         /* 监听2060端口等待用户http请求 */
  85.         r = httpdGetConnection(webserver, NULL);

  86.         /* 错误处理 */
  87.         if (webserver->lastError == -1) {
  88.             /* Interrupted system call */
  89.             continue; /* restart loop */
  90.         }
  91.         else if (webserver->lastError < -1) {
  92.             debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError);
  93.             termination_handler(0);
  94.         }
  95.         else if (r != NULL) {
  96.             /* 用户http请求接收成功 */
  97.             debug(LOG_INFO, "Received connection from %s, spawning worker thread", r->clientAddr);
  98.             params = safe_malloc(2 * sizeof(void *));
  99.             *params = webserver;
  100.             *(params + 1) = r;

  101.             /* 开启http请求处理线程 */
  102.             result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);
  103.             if (result != 0) {
  104.                 debug(LOG_ERR, "FATAL: Failed to create a new thread (httpd) - exiting");
  105.                 termination_handler(0);
  106.             }
  107.             pthread_detach(tid);
  108.         }
  109.         else {
  110.             ;
  111.         }
  112.     }

  113.     /* never reached */
  114. }

用户连接启动线程(void thread_httpd(void * args))

代码片段1.2

此段代码是当有新用户(未认证的用户 代码片段1.3,已在认证服务器上认证但没有在wifidog认证的用户 代码片段1.4)连接时创建的线程,其主要功能为

  • 获取用户浏览器发送过来的http报头
  • 分析http报头,分析是否包含关键路径
  • 不包含关键路径则调用404回调函数
  • 包含关键路径则执行关键路径回调函数(这里主要讲解"/wifidog/auth"路径)

  1. void
  2. thread_httpd(void *args)
  3. {
  4.     void **params;
  5.     httpd *webserver;
  6.     request *r;
  7.     
  8.     params = (void **)args;
  9.     webserver = *params;
  10.     r = *(params + 1);
  11.     free(params);
  12.     
  13.     /* 获取http报文 */
  14.     if (httpdReadRequest(webserver, r) == 0) {
  15.         debug(LOG_DEBUG, "Processing request from %s", r->clientAddr);
  16.         debug(LOG_DEBUG, "Calling httpdProcessRequest() for %s", r->clientAddr);
  17.         /* 分析http报文 */
  18.         httpdProcessRequest(webserver, r);
  19.         debug(LOG_DEBUG, "Returned from httpdProcessRequest() for %s", r->clientAddr);
  20.     }
  21.     else {
  22.         debug(LOG_DEBUG, "No valid request received from %s", r->clientAddr);
  23.     }
  24.     debug(LOG_DEBUG, "Closing connection with %s", r->clientAddr);
  25.     httpdEndRequest(r);
  26. }



  27. /* 被thread_httpd调用 */
  28. void httpdProcessRequest(httpd *server, request *r)
  29. {
  30.     char dirName[HTTP_MAX_URL],
  31.         entryName[HTTP_MAX_URL],
  32.         *cp;
  33.     httpDir *dir;
  34.     httpContent *entry;

  35.     r->response.responseLength = 0;
  36.     strncpy(dirName, httpdRequestPath(r), HTTP_MAX_URL);
  37.     dirName[HTTP_MAX_URL-1]=0;
  38.     cp = rindex(dirName, '/');
  39.     if (cp == NULL)
  40.     {
  41.         printf("Invalid request path '%s'\n",dirName);
  42.         return;
  43.     }
  44.     strncpy(entryName, cp + 1, HTTP_MAX_URL);
  45.     entryName[HTTP_MAX_URL-1]=0;
  46.     if (cp != dirName)
  47.         *cp = 0;
  48.     else
  49.         *(cp+1) = 0;

  50.      /* 获取http报文中的关键路径,在main_loop中已经设置 */
  51.     dir = _httpd_findContentDir(server, dirName, HTTP_FALSE);
  52.     if (dir == NULL)
  53.     {
  54.         /* http报文中未包含关键路径,执行404回调函数(在404回调函数中新用户被重定向到认证服务器),见代码片段1.3 */
  55.         _httpd_send404(server, r);
  56.         _httpd_writeAccessLog(server, r);
  57.         return;
  58.     }
  59.     /* 获取关键路径内容描述符 */
  60.     entry = _httpd_findContentEntry(r, dir, entryName);
  61.     if (entry == NULL)
  62.     {
  63.         _httpd_send404(server, r);
  64.         _httpd_writeAccessLog(server, r);
  65.         return;
  66.     }
  67.     if (entry->preload)
  68.     {
  69.         if ((entry->preload)(server) < 0)
  70.         {
  71.             _httpd_writeAccessLog(server, r);
  72.             return;
  73.         }
  74.     }
  75.     switch(entry->type)
  76.     {
  77.         case HTTP_C_FUNCT:
  78.         case HTTP_C_WILDCARD:
  79.             /* 如果是被认证服务器重定向到网关的用户,此处的关键路径为"/wifidog/auth",并执行回调函数 */
  80.             (entry->function)(server, r);
  81.             break;

  82.         case HTTP_STATIC:
  83.             _httpd_sendStatic(server, r, entry->data);
  84.             break;

  85.         case HTTP_FILE:
  86.             _httpd_sendFile(server, r, entry->path);
  87.             break;

  88.         case HTTP_WILDCARD:
  89.             if (_httpd_sendDirectoryEntry(server, r, entry,
  90.                         entryName)<0)
  91.             {
  92.                 _httpd_send404(server, r);
  93.             }
  94.             break;
  95.     }
  96.     _httpd_writeAccessLog(server, r);
  97. }

代码片段1.3

此段代码表示wifidog是如何通过http 404回调函数实现客户端重定向了,实际上就是在404回调函数中封装了一个307状态的http报头,http的307状态在http协议中就是用于重定向的,封装完成后通过已经与客户端连接的socket返回给客户端。步骤流程为

  • 判断本机是否处于离线状态
  • 判断认证服务器是否在线
  • 封装http 307报文
  • 发送于目标客户端

  1. void
  2. http_callback_404(httpd *webserver, request *r)
  3. {
  4.     char tmp_url[MAX_BUF],
  5.             *url;
  6.     s_config *config = config_get_config();
  7.     t_auth_serv *auth_server = get_auth_server();

  8.     memset(tmp_url, 0, sizeof(tmp_url));

  9.         snprintf(tmp_url, (sizeof(tmp_url) - 1), "http://%s%s%s%s",
  10.                         r->request.host,
  11.                         r->request.path,
  12.                         r->request.query[0] ? "?" : "",
  13.                         r->request.query);
  14.     url = httpdUrlEncode(tmp_url);

  15.     if (!is_online()) {
  16.         /* 本机处于离线状态,此函数调用结果由认证服务器检测线程设置 */
  17.         char * buf;
  18.         safe_asprintf(&buf,
  19.             "

    We apologize, but it seems that the internet connection that powers this hotspot is temporarily unavailable.


    "

  20.             "

    If at all possible, please notify the owners of this hotspot that the internet connection is out of service.


    "

  21.             "

    The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.


    "

  22.             "

    In a while please click here to try your request again.


    "
    , tmp_url);

  23.                 send_http_page(r, "Uh oh! Internet access unavailable!", buf);
  24.         free(buf);
  25.         debug(LOG_INFO, "Sent %s an apology since I am not online - no point sending them to auth server", r->clientAddr);
  26.     }
  27.     else if (!is_auth_online()) {
  28.         /* 认证服务器处于离线状态 */
  29.         char * buf;
  30.         safe_asprintf(&buf,
  31.             "

    We apologize, but it seems that we are currently unable to re-direct you to the login screen.


    "

  32.             "

    The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.


    "

  33.             "

    In a couple of minutes please click here to try your request again.


    ", tmp_url);

  34.                 send_http_page(r, "Uh oh! Login screen unavailable!", buf);
  35.         free(buf);
  36.         debug(LOG_INFO, "Sent %s an apology since auth server not online - no point sending them to auth server", r->clientAddr);
  37.     }
  38.     else {
  39.         /* 本机与认证服务器都在线,返回重定向包于客户端 */
  40.         char *urlFragment;
  41.         safe_asprintf(&urlFragment, "%sgw_address=%s&gw_port=%d&gw_id=%s&url=%s",
  42.             auth_server->authserv_login_script_path_fragment,
  43.             config->gw_address,
  44.             config->gw_port,
  45.             config->gw_id,
  46.             url);
  47.         debug(LOG_INFO, "Captured %s requesting [%s] and re-directing them to login page", r->clientAddr, url);
  48.         http_send_redirect_to_auth(r, urlFragment, "Redirect to login page"); /* 实际上此函数中通过socket返回一个307状态的http报头给客户端,里面包含有认证服务器地址 */
  49.         free(urlFragment);
  50.     }
  51.     free(url);
  52. }


代码片段1.4

此段表明当客户端已经在认证服务器确认登陆,认证服务器将客户端重新重定向回网关,并在重定向包中包含关键路径"/wifidog/auth"和token,认证服务器所执行的操作。


  1. void
  2. http_callback_auth(httpd *webserver, request *r)
  3. {
  4.     t_client *client;
  5.     httpVar * token;
  6.     char *mac;
  7.     /* 判断http报文是否包含登出logout */
  8.     httpVar *logout = httpdGetVariableByName(r, "logout");
  9.     if ((token = httpdGetVariableByName(r, "token"))) {
  10.         /* 获取http报文中的token */
  11.         if (!(mac = arp_get(r->clientAddr))) {
  12.             /* 获取客户端mac地址失败 */
  13.             debug(LOG_ERR, "Failed to retrieve MAC address for ip %s", r->clientAddr);
  14.             send_http_page(r, "WiFiDog Error", "Failed to retrieve your MAC address");
  15.         } else {
  16.             LOCK_CLIENT_LIST();
  17.             /* 判断客户端是否存在于列表中 */
  18.             if ((client = client_list_find(r->clientAddr, mac)) == NULL) {
  19.                 debug(LOG_DEBUG, "New client for %s", r->clientAddr);
  20.                 /* 将此客户端添加到客户端列表 */
  21.                 client_list_append(r->clientAddr, mac, token->value);
  22.             } else if (logout) {
  23.                 /* http报文为登出 */
  24.                 t_authresponse authresponse;
  25.                 s_config *config = config_get_config();
  26.                 unsigned long long incoming = client->counters.incoming;
  27.                 unsigned long long outgoing = client->counters.outgoing;
  28.                 char *ip = safe_strdup(client->ip);
  29.                 char *urlFragment = NULL;
  30.                 t_auth_serv *auth_server = get_auth_server();
  31.                 /* 修改iptables禁止客户端访问外网 */
  32.                 fw_deny(client->ip, client->mac, client->fw_connection_state);
  33.                 /* 从客户端列表中删除此客户端 */
  34.                 client_list_delete(client);
  35.                 debug(LOG_DEBUG, "Got logout from %s", client->ip);
  36.                 
  37.                 if (config->auth_servers != NULL) {
  38.                     UNLOCK_CLIENT_LIST();
  39.                     /* 发送登出认证包给认证服务器 */
  40.                     auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token->value,
  41.                                         incoming, outgoing);
  42.                     LOCK_CLIENT_LIST();
  43.                     
  44.                     /* 将客户端重定向到认证服务器 */
  45.                     debug(LOG_INFO, "Got manual logout from client ip %s, mac %s, token %s"
  46.                     "- redirecting them to logout message", client->ip, client->mac, client->token);
  47.                     safe_asprintf(&urlFragment, "%smessage=%s",
  48.                         auth_server->authserv_msg_script_path_fragment,
  49.                         GATEWAY_MESSAGE_ACCOUNT_LOGGED_OUT
  50.                     );
  51.                     http_send_redirect_to_auth(r, urlFragment, "Redirect to logout message");
  52.                     free(urlFragment);
  53.                 }
  54.                 free(ip);
  55.              }
  56.              else {
  57.                 debug(LOG_DEBUG, "Client for %s is already in the client list", client->ip);
  58.             }
  59.             UNLOCK_CLIENT_LIST();
  60.             if (!logout) {
  61.                 /* 通过认证服务器认证此客户端token */
  62.                 authenticate_client(r);
  63.             }
  64.             free(mac);
  65.         }
  66.     } else {
  67.         send_http_page(r, "WiFiDog error", "Invalid token");
  68.     }
  69. }

  70. /* 此函数用于提交token到认证服务器进行认证 */
  71. void
  72. authenticate_client(request *r)
  73. {
  74.     t_client *client;
  75.     t_authresponse auth_response;
  76.     char *mac,
  77.         *token;
  78.     char *urlFragment = NULL;
  79.     s_config *config = NULL;
  80.     t_auth_serv *auth_server = NULL;

  81.     LOCK_CLIENT_LIST();

  82.     client = client_list_find_by_ip(r->clientAddr);
  83.     /* 判断此客户端是否在列表中 */
  84.     if (client == NULL) {
  85.         debug(LOG_ERR, "Could not find client for %s", r->clientAddr);
  86.         UNLOCK_CLIENT_LIST();
  87.         return;
  88.     }
  89.     
  90.     mac = safe_strdup(client->mac);
  91.     token = safe_strdup(client->token);
  92.     
  93.     UNLOCK_CLIENT_LIST();
  94.     
  95.     /* 提交token、客户端ip、客户端mac至认证服务器 */
  96.     auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0);
  97.     
  98.     LOCK_CLIENT_LIST();
  99.     
  100.     /*再次判断客户端是否存在于列表中,保险起见,因为有可能在于认证服务器认证过程中,客户端检测线程把此客户端下线 */
  101.     client = client_list_find(r->clientAddr, mac);
  102.     
  103.     if (client == NULL) {
  104.         debug(LOG_ERR, "Could not find client node for %s (%s)", r->clientAddr, mac);
  105.         UNLOCK_CLIENT_LIST();
  106.         free(token);
  107.         free(mac);
  108.         return;
  109.     }
  110.     
  111.     free(token);
  112.     free(mac);

  113.     config = config_get_config();
  114.     auth_server = get_auth_server();

  115.         /* 判断认证服务器认证结果 */
  116.     switch(auth_response.authcode) {

  117.     case AUTH_ERROR:
  118.         /* 认证错误 */
  119.         debug(LOG_ERR, "Got %d from central server authenticating token %s from %s at %s", auth_response, client->token, client->ip, client->mac);
  120.         send_http_page(r, "Error!", "Error: We did not get a valid answer from the central server");
  121.         break;

  122.     case AUTH_DENIED:
  123.         /* 认证服务器拒绝此客户端 */
  124.         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);
  125.         safe_asprintf(&urlFragment, "%smessage=%s",
  126.             auth_server->authserv_msg_script_path_fragment,
  127.             GATEWAY_MESSAGE_DENIED
  128.         );
  129.         http_send_redirect_to_auth(r, urlFragment, "Redirect to denied message");
  130.         free(urlFragment);
  131.         break;

  132.     case AUTH_VALIDATION:
  133.         /* 认证服务器处于等待此客户端电子邮件确认回执状态 */
  134.         debug(LOG_INFO, "Got VALIDATION from central server authenticating token %s from %s at %s"
  135.                 "- adding to firewall and redirecting them to activate message", client->token,
  136.                 client->ip, client->mac);
  137.         client->fw_connection_state = FW_MARK_PROBATION;
  138.         fw_allow(client->ip, client->mac, FW_MARK_PROBATION);
  139.         safe_asprintf(&urlFragment, "%smessage=%s",
  140.             auth_server->authserv_msg_script_path_fragment,
  141.             GATEWAY_MESSAGE_ACTIVATE_ACCOUNT
  142.         );
  143.         http_send_redirect_to_auth(r, urlFragment, "Redirect to activate message");
  144.         free(urlFragment);
  145.         break;

  146.     case AUTH_ALLOWED:
  147.         /* 认证通过 */
  148.         debug(LOG_INFO, "Got ALLOWED from central server authenticating token %s from %s at %s - "
  149.                 "adding to firewall and redirecting them to portal", client->token, client->ip, client->mac);
  150.         client->fw_connection_state = FW_MARK_KNOWN;
  151.         fw_allow(client->ip, client->mac, FW_MARK_KNOWN);
  152.         served_this_session++;
  153.         safe_asprintf(&urlFragment, "%sgw_id=%s",
  154.             auth_server->authserv_portal_script_path_fragment,
  155.             config->gw_id
  156.         );
  157.         http_send_redirect_to_auth(r, urlFragment, "Redirect to portal");
  158.         free(urlFragment);
  159.         break;

  160.     case AUTH_VALIDATION_FAILED:
  161.          /* 电子邮件确认回执超时 */
  162.         debug(LOG_INFO, "Got VALIDATION_FAILED from central server authenticating token %s from %s at %s "
  163.                 "- redirecting them to failed_validation message", client->token, client->ip, client->mac);
  164.         safe_asprintf(&urlFragment, "%smessage=%s",
  165.             auth_server->authserv_msg_script_path_fragment,
  166.             GATEWAY_MESSAGE_ACCOUNT_VALIDATION_FAILED
  167.         );
  168.         http_send_redirect_to_auth(r, urlFragment, "Redirect to failed validation message");
  169.         free(urlFragment);
  170.         break;

  171.     default:
  172.         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);
  173.         send_http_page(r, "Internal Error", "We can not validate your request at this time");
  174.         break;

  175.     }

  176.     UNLOCK_CLIENT_LIST();
  177.     return;
  178. }


阅读(1050) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~