wifidog源码分析 - 用户连接过程

引言

  之前的文章已经描述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

  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     /* 设置启动时间 */

 11     if (!started_time) {

 12         debug(LOG_INFO, "Setting started_time");

 13         started_time = time(NULL);

 14     }

 15     else if (started_time < MINIMUM_STARTED_TIME) {

 16         debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time");

 17         started_time = time(NULL);

 18     }

 19 

 20     /* 获取网关IP,失败退出程序 */

 21     if (!config->gw_address) {

 22         debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface);

 23         if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) {

 24             debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface);

 25             exit(1);

 26         }

 27         debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_address);

 28     }

 29 

 30     /* 获取网关ID,失败退出程序 */

 31     if (!config->gw_id) {

 32         debug(LOG_DEBUG, "Finding MAC address of %s", config->gw_interface);

 33         if ((config->gw_id = get_iface_mac(config->gw_interface)) == NULL) {

 34             debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface);

 35             exit(1);

 36         }

 37         debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_id);

 38     }

 39 

 40     /* 初始化监听网关2060端口的socket */

 41     debug(LOG_NOTICE, "Creating web server on %s:%d", config->gw_address, config->gw_port);

 42     

 43     if ((webserver = httpdCreate(config->gw_address, config->gw_port)) == NULL) {

 44         debug(LOG_ERR, "Could not create web server: %s", strerror(errno));

 45         exit(1);

 46     }

 47 

 48     debug(LOG_DEBUG, "Assigning callbacks to web server");

 49     /* 设置关键路径及其回调函数,在代码片段1.2中会使用到 */

 50     httpdAddCContent(webserver, "/", "wifidog", 0, NULL, http_callback_wifidog);

 51     httpdAddCContent(webserver, "/wifidog", "", 0, NULL, http_callback_wifidog);

 52     httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about);

 53     httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status);

 54     httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth);

 55     /* 设置404错误回调函数,在里面实现了重定向至认证服务器 */

 56     httpdAddC404Content(webserver, http_callback_404);

 57 

 58     /* 清除iptables规则 */

 59     fw_destroy();

 60     /* 重新设置iptables规则 */

 61     if (!fw_init()) {

 62         debug(LOG_ERR, "FATAL: Failed to initialize firewall");

 63         exit(1);

 64     }

 65 

 66     /* 客户端检测线程 */

 67     result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL);

 68     if (result != 0) {

 69         debug(LOG_ERR, "FATAL: Failed to create a new thread (fw_counter) - exiting");

 70         termination_handler(0);

 71     }

 72     pthread_detach(tid_fw_counter);

 73 

 74     /* wdctrl交互线程 */

 75     result = pthread_create(&tid, NULL, (void *)thread_wdctl, (void *)safe_strdup(config->wdctl_sock));

 76     if (result != 0) {

 77         debug(LOG_ERR, "FATAL: Failed to create a new thread (wdctl) - exiting");

 78         termination_handler(0);

 79     }

 80     pthread_detach(tid);

 81     

 82     /* 认证服务器心跳检测线程 */

 83     result = pthread_create(&tid_ping, NULL, (void *)thread_ping, NULL);

 84     if (result != 0) {

 85         debug(LOG_ERR, "FATAL: Failed to create a new thread (ping) - exiting");

 86         termination_handler(0);

 87     }

 88     pthread_detach(tid_ping);

 89     

 90     debug(LOG_NOTICE, "Waiting for connections");

 91     while(1) {

 92         /* 监听2060端口等待用户http请求 */

 93         r = httpdGetConnection(webserver, NULL);

 94 

 95         /* 错误处理 */

 96         if (webserver->lastError == -1) {

 97             /* Interrupted system call */

 98             continue; /* restart loop */

 99         }

100         else if (webserver->lastError < -1) {

101             debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError);

102             termination_handler(0);

103         }

104         else if (r != NULL) {

105             /* 用户http请求接收成功 */

106             debug(LOG_INFO, "Received connection from %s, spawning worker thread", r->clientAddr);

107             params = safe_malloc(2 * sizeof(void *));

108             *params = webserver;

109             *(params + 1) = r;

110 

111             /* 开启http请求处理线程 */

112             result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);

113             if (result != 0) {

114                 debug(LOG_ERR, "FATAL: Failed to create a new thread (httpd) - exiting");

115                 termination_handler(0);

116             }

117             pthread_detach(tid);

118         }

119         else {

120             ;

121         }

122     }

123 

124     /* never reached */

125 }        

 

用户连接启动线程(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 

 28 

 29 

 30 /* 被thread_httpd调用 */

 31 void httpdProcessRequest(httpd *server, request *r)

 32 {

 33     char    dirName[HTTP_MAX_URL],

 34         entryName[HTTP_MAX_URL],

 35         *cp;

 36     httpDir    *dir;

 37     httpContent *entry;

 38 

 39     r->response.responseLength = 0;

 40     strncpy(dirName, httpdRequestPath(r), HTTP_MAX_URL);

 41     dirName[HTTP_MAX_URL-1]=0;

 42     cp = rindex(dirName, '/');

 43     if (cp == NULL)

 44     {

 45         printf("Invalid request path '%s'\n",dirName);

 46         return;

 47     }

 48     strncpy(entryName, cp + 1, HTTP_MAX_URL);

 49     entryName[HTTP_MAX_URL-1]=0;

 50     if (cp != dirName)

 51         *cp = 0;

 52     else

 53         *(cp+1) = 0;

 54 

 55      /* 获取http报文中的关键路径,在main_loop中已经设置 */

 56     dir = _httpd_findContentDir(server, dirName, HTTP_FALSE);

 57     if (dir == NULL)

 58     {

 59         /* http报文中未包含关键路径,执行404回调函数(在404回调函数中新用户被重定向到认证服务器),见代码片段1.3 */

 60         _httpd_send404(server, r);

 61         _httpd_writeAccessLog(server, r);

 62         return;

 63     }

 64     /* 获取关键路径内容描述符 */

 65     entry = _httpd_findContentEntry(r, dir, entryName);

 66     if (entry == NULL)

 67     {

 68         _httpd_send404(server, r);

 69         _httpd_writeAccessLog(server, r);

 70         return;

 71     }

 72     if (entry->preload)

 73     {

 74         if ((entry->preload)(server) < 0)

 75         {

 76             _httpd_writeAccessLog(server, r);

 77             return;

 78         }

 79     }

 80     switch(entry->type)

 81     {

 82         case HTTP_C_FUNCT:

 83         case HTTP_C_WILDCARD:

 84             /* 如果是被认证服务器重定向到网关的用户,此处的关键路径为"/wifidog/auth",并执行回调函数 */

 85             (entry->function)(server, r);

 86             break;

 87 

 88         case HTTP_STATIC:

 89             _httpd_sendStatic(server, r, entry->data);

 90             break;

 91 

 92         case HTTP_FILE:

 93             _httpd_sendFile(server, r, entry->path);

 94             break;

 95 

 96         case HTTP_WILDCARD:

 97             if (_httpd_sendDirectoryEntry(server, r, entry,

 98                         entryName)<0)

 99             {

100                 _httpd_send404(server, r);

101             }

102             break;

103     }

104     _httpd_writeAccessLog(server, r);

105 }

 

代码片段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 

 9     memset(tmp_url, 0, sizeof(tmp_url));

10 

11         snprintf(tmp_url, (sizeof(tmp_url) - 1), "http://%s%s%s%s",

12                         r->request.host,

13                         r->request.path,

14                         r->request.query[0] ? "?" : "",

15                         r->request.query);

16     url = httpdUrlEncode(tmp_url);

17 

18     if (!is_online()) {

19         /* 本机处于离线状态,此函数调用结果由认证服务器检测线程设置 */

20         char * buf;

21         safe_asprintf(&buf, 

22             "<p>We apologize, but it seems that the internet connection that powers this hotspot is temporarily unavailable.</p>"

23             "<p>If at all possible, please notify the owners of this hotspot that the internet connection is out of service.</p>"

24             "<p>The maintainers of this network are aware of this disruption.  We hope that this situation will be resolved soon.</p>"

25             "<p>In a while please <a href='%s'>click here</a> to try your request again.</p>", tmp_url);

26 

27                 send_http_page(r, "Uh oh! Internet access unavailable!", buf);

28         free(buf);

29         debug(LOG_INFO, "Sent %s an apology since I am not online - no point sending them to auth server", r->clientAddr);

30     }

31     else if (!is_auth_online()) {

32         /* 认证服务器处于离线状态 */

33         char * buf;

34         safe_asprintf(&buf, 

35             "<p>We apologize, but it seems that we are currently unable to re-direct you to the login screen.</p>"

36             "<p>The maintainers of this network are aware of this disruption.  We hope that this situation will be resolved soon.</p>"

37             "<p>In a couple of minutes please <a href='%s'>click here</a> to try your request again.</p>", tmp_url);

38 

39                 send_http_page(r, "Uh oh! Login screen unavailable!", buf);

40         free(buf);

41         debug(LOG_INFO, "Sent %s an apology since auth server not online - no point sending them to auth server", r->clientAddr);

42     }

43     else {

44         /* 本机与认证服务器都在线,返回重定向包于客户端 */

45         char *urlFragment;

46         safe_asprintf(&urlFragment, "%sgw_address=%s&gw_port=%d&gw_id=%s&url=%s",

47             auth_server->authserv_login_script_path_fragment,

48             config->gw_address,

49             config->gw_port, 

50             config->gw_id,

51             url);

52         debug(LOG_INFO, "Captured %s requesting [%s] and re-directing them to login page", r->clientAddr, url);

53         http_send_redirect_to_auth(r, urlFragment, "Redirect to login page");  /* 实际上此函数中通过socket返回一个307状态的http报头给客户端,里面包含有认证服务器地址 */

54         free(urlFragment);

55     }

56     free(url);

57 }

 

代码片段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 

 71 /* 此函数用于提交token到认证服务器进行认证 */

 72 void

 73 authenticate_client(request *r)

 74 {

 75     t_client    *client;

 76     t_authresponse    auth_response;

 77     char    *mac,

 78         *token;

 79     char *urlFragment = NULL;

 80     s_config    *config = NULL;

 81     t_auth_serv    *auth_server = NULL;

 82 

 83     LOCK_CLIENT_LIST();

 84 

 85     client = client_list_find_by_ip(r->clientAddr);

 86     /* 判断此客户端是否在列表中 */

 87     if (client == NULL) {

 88         debug(LOG_ERR, "Could not find client for %s", r->clientAddr);

 89         UNLOCK_CLIENT_LIST();

 90         return;

 91     }

 92     

 93     mac = safe_strdup(client->mac);

 94     token = safe_strdup(client->token);

 95     

 96     UNLOCK_CLIENT_LIST();

 97     

 98     /* 提交token、客户端ip、客户端mac至认证服务器 */

 99     auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0);

100     

101     LOCK_CLIENT_LIST();

102     

103     /*再次判断客户端是否存在于列表中,保险起见,因为有可能在于认证服务器认证过程中,客户端检测线程把此客户端下线 */

104     client = client_list_find(r->clientAddr, mac);

105     

106     if (client == NULL) {

107         debug(LOG_ERR, "Could not find client node for %s (%s)", r->clientAddr, mac);

108         UNLOCK_CLIENT_LIST();

109         free(token);

110         free(mac);

111         return;

112     }

113     

114     free(token);

115     free(mac);

116 

117     config = config_get_config();

118     auth_server = get_auth_server();

119 

120         /* 判断认证服务器认证结果 */

121     switch(auth_response.authcode) {

122 

123     case AUTH_ERROR:

124         /* 认证错误 */

125         debug(LOG_ERR, "Got %d from central server authenticating token %s from %s at %s", auth_response, client->token, client->ip, client->mac);

126         send_http_page(r, "Error!", "Error: We did not get a valid answer from the central server");

127         break;

128 

129     case AUTH_DENIED:

130         /* 认证服务器拒绝此客户端 */

131         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);

132         safe_asprintf(&urlFragment, "%smessage=%s",

133             auth_server->authserv_msg_script_path_fragment,

134             GATEWAY_MESSAGE_DENIED

135         );

136         http_send_redirect_to_auth(r, urlFragment, "Redirect to denied message");

137         free(urlFragment);

138         break;

139 

140     case AUTH_VALIDATION:

141         /* 认证服务器处于等待此客户端电子邮件确认回执状态 */

142         debug(LOG_INFO, "Got VALIDATION from central server authenticating token %s from %s at %s"

143                 "- adding to firewall and redirecting them to activate message", client->token,

144                 client->ip, client->mac);

145         client->fw_connection_state = FW_MARK_PROBATION;

146         fw_allow(client->ip, client->mac, FW_MARK_PROBATION);

147         safe_asprintf(&urlFragment, "%smessage=%s",

148             auth_server->authserv_msg_script_path_fragment,

149             GATEWAY_MESSAGE_ACTIVATE_ACCOUNT

150         );

151         http_send_redirect_to_auth(r, urlFragment, "Redirect to activate message");

152         free(urlFragment);

153         break;

154 

155     case AUTH_ALLOWED:

156         /* 认证通过 */

157         debug(LOG_INFO, "Got ALLOWED from central server authenticating token %s from %s at %s - "

158                 "adding to firewall and redirecting them to portal", client->token, client->ip, client->mac);

159         client->fw_connection_state = FW_MARK_KNOWN;

160         fw_allow(client->ip, client->mac, FW_MARK_KNOWN);

161         served_this_session++;

162         safe_asprintf(&urlFragment, "%sgw_id=%s",

163             auth_server->authserv_portal_script_path_fragment,

164             config->gw_id

165         );

166         http_send_redirect_to_auth(r, urlFragment, "Redirect to portal");

167         free(urlFragment);

168         break;

169 

170     case AUTH_VALIDATION_FAILED:

171          /* 电子邮件确认回执超时 */

172         debug(LOG_INFO, "Got VALIDATION_FAILED from central server authenticating token %s from %s at %s "

173                 "- redirecting them to failed_validation message", client->token, client->ip, client->mac);

174         safe_asprintf(&urlFragment, "%smessage=%s",

175             auth_server->authserv_msg_script_path_fragment,

176             GATEWAY_MESSAGE_ACCOUNT_VALIDATION_FAILED

177         );

178         http_send_redirect_to_auth(r, urlFragment, "Redirect to failed validation message");

179         free(urlFragment);

180         break;

181 

182     default:

183         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);

184         send_http_page(r, "Internal Error", "We can not validate your request at this time");

185         break;

186 

187     }

188 

189     UNLOCK_CLIENT_LIST();

190     return;

191 }

 

你可能感兴趣的:(源码分析)