• wifidog 源码初分析(4)-转


    在上一篇《wifidog 源码处分析(3)》的流程结束后,接入设备的浏览器重定向至 路由器 上 wifidog 的 http 服务(端口 2060) /wifidog/auth 上(且携带了 认证服务器 为此接入设备分配的 token),本篇就是从 wifidog 接收到 /wifidog/auth 的访问后的 校验流程。

    -

    根据《wifidog 源码初分析(2)》中描述的,在 wifidog 启动 http 服务前,注册了一个针对访问路径 /wifidog/auth 的回调,如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片

    1. httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about);  
    2. httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status);  
    3. // 注册了针对 /wifidog/auth 的访问回调 http_callback_auth
    4. httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth); 

    这样对于 接入设备(or 客户端) 重定向过来的 /wifidog/auth 就进入了 http_callback_auth 函数中,如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片

    1. http_callback_auth(httpd *webserver, request *r)  
    2. {  
    3.     t_client    *client;  
    4.     httpVar * token;  
    5. char    *mac;  
    6. // 1, 获取条件参数中的 logout 值
    7.     httpVar *logout = httpdGetVariableByName(r, "logout");  
    8. // 2, 获取条件参数中的 token 值
    9. if ((token = httpdGetVariableByName(r, "token"))) {  
    10. /* They supplied variable "token" */
    11. // 3, 可以看到, 这里要求必须能够通过 ARP 协议获取到 接入设备 的 MAC 地址
    12. if (!(mac = arp_get(r->clientAddr))) {  
    13. /* We could not get their MAC address */
    14.             debug(LOG_ERR, "Failed to retrieve MAC address for ip %s", r->clientAddr);  
    15.             send_http_page(r, "WiFiDog Error", "Failed to retrieve your MAC address");  
    16.         } else {  
    17. /* We have their MAC address */
    18.             LOCK_CLIENT_LIST();  
    19. // 4, 检查该客户端(接入设备)是否已经在 wifidog 维护的接入客户端列表中
    20. if ((client = client_list_find(r->clientAddr, mac)) == NULL) {  
    21.                 debug(LOG_DEBUG, "New client for %s", r->clientAddr);  
    22.                 client_list_append(r->clientAddr, mac, token->value);  
    23.             } else if (logout) {  
    24. // 5, 退出处理
    25.                 t_authresponse  authresponse;  
    26.                 s_config *config = config_get_config();  
    27.                 unsigned long long incoming = client->counters.incoming;  
    28.                 unsigned long long outgoing = client->counters.outgoing;  
    29. char *ip = safe_strdup(client->ip);  
    30. char *urlFragment = NULL;  
    31.                 t_auth_serv *auth_server = get_auth_server();  
    32.                 fw_deny(client->ip, client->mac, client->fw_connection_state);  
    33.                 client_list_delete(client);  
    34.                 debug(LOG_DEBUG, "Got logout from %s", client->ip);  
    35. /* Advertise the logout if we have an auth server */
    36. if (config->auth_servers != NULL) {  
    37.                     UNLOCK_CLIENT_LIST();  
    38.                     auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token->value,  
    39.                                         incoming, outgoing);  
    40.                     LOCK_CLIENT_LIST();  
    41. /* Re-direct them to auth server */
    42.                     debug(LOG_INFO, "Got manual logout from client ip %s, mac %s, token %s"
    43. "- redirecting them to logout message", client->ip, client->mac, client->token);  
    44.                     safe_asprintf(&urlFragment, "%smessage=%s",  
    45.                         auth_server->authserv_msg_script_path_fragment,  
    46.                         GATEWAY_MESSAGE_ACCOUNT_LOGGED_OUT  
    47.                     );  
    48.                     http_send_redirect_to_auth(r, urlFragment, "Redirect to logout message");  
    49.                     free(urlFragment);  
    50.                 }  
    51.                 free(ip);  
    52.             }  
    53. else {  
    54. // 6, 已经登录校验通过
    55.                 debug(LOG_DEBUG, "Client for %s is already in the client list", client->ip);  
    56.             }  
    57.             UNLOCK_CLIENT_LIST();  
    58. if (!logout) {  
    59. // 7, 到 auth server 上进一步校验 token
    60.                 authenticate_client(r);  
    61.             }  
    62.             free(mac);  
    63.         }  
    64.     } else {  
    65. /* They did not supply variable "token" */
    66. // 8, 未携带 token, 直接拒绝
    67.         send_http_page(r, "WiFiDog error", "Invalid token");  
    68.     }  

    在该函数中主要处理了 客户端退出,非法校验,以及 客户端校验等流程,下面分别描述注释中的各个步骤:

    -

    1,对于客户端退出,则会携带 logout 参数信息,并走到第 5 步(当然,如果连 token 参数都没有的话,会直接走到第 8 步,也就是拒绝);

    2,按照正常的认证流程,会携带由认证服务器分配的 token 参数;

    3,正如注释说明的,这里要求必须能够通过 ARP 协议获取到 接入设备 的 MAC 地址;(其实通过查看 arg_get 的实现,可以看到是直接解析 /proc/net/arp 文件 -- ARP cache -- 来获取对应客户端 IP 地址的 MAC 信息的),类似如下:

    [steven@sasd ~]$ more /proc/net/arp

    IP address       HW type     Flags       HW address            Mask     Device

    192.168.1.203    0x1         0x2         18:03:73:d5:1b:a2     *        eth0

    192.168.1.1      0x1         0x2         00:21:27:63:c0:ce     *        eth0

    [steven@sasd ~]$

    4,在能够获取到该客户端的 MAC 地址后,根据客户端的 IP 和 MAC 地址检查该客户端是否已经在 wifidog 维护的接入设备(or客户端)列表中,如果不在,则追加到此列表中(关于此列表的数据结构在后面再详细描述);

    5,如果该客户端已经存在,且本次访问是要求 logout 退出的,则进入此退出处理的流程,该流程主要包括几个步骤:关闭该客户端 ip/mac 的出口(outgoing)规则 --> 从客户端列表中删除该客户端记录 --> 通知 认证服务器 该客户端退出(且携带该客户端的token, 上下行流量等信息) --> 返回重定向至 认证服务器 的 #define DEFAULT_AUTHSERVMSGPATHFRAGMENT "gw_message.php?" 访问路径(携带一个已退出的 message);

    6,如果该客户端已经登录校验过,且本次访问非 logout 退出,则直接跳转到第 7 步;

    7,这一步就是 token 校验的过程,具体实现在 authenticate_client 函数中:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片

    1. authenticate_client(request *r)  
    2. {  
    3.     t_client    *client;  
    4.     t_authresponse  auth_response;  
    5. char    *mac,  
    6.         *token;  
    7. char *urlFragment = NULL;  
    8.     s_config    *config = NULL;  
    9.     t_auth_serv *auth_server = NULL;  
    10.     LOCK_CLIENT_LIST();  
    11. // 根据 IP 地址获取 客户端的 MAC 地址以及本次会话分配的 token
    12. // 主要用于 token 校验过程
    13.     client = client_list_find_by_ip(r->clientAddr);  
    14. if (client == NULL) {  
    15.         debug(LOG_ERR, "authenticate_client(): Could not find client for %s", r->clientAddr);  
    16.         UNLOCK_CLIENT_LIST();  
    17. return;  
    18.     }  
    19.     mac = safe_strdup(client->mac);  
    20.     token = safe_strdup(client->token);  
    21.     UNLOCK_CLIENT_LIST();  
    22. /*
    23.      * At this point we've released the lock while we do an HTTP request since it could
    24.      * take multiple seconds to do and the gateway would effectively be frozen if we
    25.      * kept the lock.
    26.      */
    27. // 通过 "login" 到 认证服务器 上进行客户端的 token 校验
    28.     auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0);  
    29.     LOCK_CLIENT_LIST();  
    30. /* can't trust the client to still exist after n seconds have passed */
    31. // 这里主要防止在到 认证服务器 上进行 token 校验的过程中
    32. // 该客户端已经退出的情形, 此时就不需要再进行处理
    33.     client = client_list_find(r->clientAddr, mac);  
    34. if (client == NULL) {  
    35.         debug(LOG_ERR, "authenticate_client(): Could not find client node for %s (%s)", r->clientAddr, mac);  
    36.         UNLOCK_CLIENT_LIST();  
    37.         free(token);  
    38.         free(mac);  
    39. return;  
    40.     }  
    41.     free(token);  
    42.     free(mac);  
    43. /* Prepare some variables we'll need below */
    44.     config = config_get_config();  
    45.     auth_server = get_auth_server();  
    46. // 根据返回的校验结果做不同的处理
    47. switch(auth_response.authcode) {  
    48. case AUTH_ERROR:  
    49. case AUTH_DENIED:  
    50. case AUTH_VALIDATION:  
    51. case AUTH_VALIDATION_FAILED:  
    52.         ... ...  
    53. break;  
    54. case AUTH_ALLOWED:  
    55. /* Logged in successfully as a regular account */
    56.         debug(LOG_INFO, "Got ALLOWED from central server authenticating token %s from %s at %s - "
    57. "adding to firewall and redirecting them to portal", client->token, client->ip, client->mac);  
    58.         client->fw_connection_state = FW_MARK_KNOWN;  
    59.         fw_allow(client->ip, client->mac, FW_MARK_KNOWN);  
    60.         served_this_session++;  
    61.         safe_asprintf(&urlFragment, "%sgw_id=%s",  
    62.             auth_server->authserv_portal_script_path_fragment,  
    63.             config->gw_id  
    64.         );  
    65.         http_send_redirect_to_auth(r, urlFragment, "Redirect to portal");  
    66.         free(urlFragment);  
    67. break;  
    68.     }  
    69.     UNLOCK_CLIENT_LIST();  
    70. return;  

    这里主要是两大步骤:

    -

    1,通过调用 auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0); 让 认证服务器 对该客户端的 token 进行校验;

    2,根据 认证服务器 返回的 token 校验结果进行不同的处理(主要是对该客户端的防火墙过滤规则进行不同的设置),这里主要以 AUTH_ALLOWED 校验结果进行分析,这里主要是两个动作:

    2.1,通过 fw_allow 函数调用对此客户端"放行";

    2.2,返回重定向至 认证服务器的 portal 路径访问的响应;

    -

    这里就简要分析一下 fw_allow 函数的实现,查看fw_allow的实现可以看到真正设置allow客户端通过防火墙的动作是在iptables_fw_access中实现的,如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片

    1. /** Set if a specific client has access through the firewall */
    2. // 针对上面的流程,这里的输入参数
    3. // type 为 FW_ACCESS_ALLOW,tag 为 FW_MARK_KNOWN
    4. int iptables_fw_access(fw_access_t type, const char *ip, const char *mac, int tag)  
    5. {  
    6. int rc;  
    7.     fw_quiet = 0;  
    8. switch(type) {  
    9. case FW_ACCESS_ALLOW:  
    10.             iptables_do_command("-t mangle -A " TABLE_WIFIDOG_OUTGOING " -s %s -m mac --mac-source %s -j MARK --set-mark %d", ip, mac, tag);  
    11.             rc = iptables_do_command("-t mangle -A " TABLE_WIFIDOG_INCOMING " -d %s -j ACCEPT", ip);  
    12. break;  
    13. case FW_ACCESS_DENY:  
    14.             iptables_do_command("-t mangle -D " TABLE_WIFIDOG_OUTGOING " -s %s -m mac --mac-source %s -j MARK --set-mark %d", ip, mac, tag);  
    15.             rc = iptables_do_command("-t mangle -D " TABLE_WIFIDOG_INCOMING " -d %s -j ACCEPT", ip);  
    16. break;  
    17. default:  
    18.             rc = -1;  
    19. break;  
    20.     }  
    21. return rc;  

    同样的,我们这里主要分析一下ALLOW时的iptables的防火墙设置规则,对执行的两个iptables命令展开来就是下面两个步骤:

    -

    1) 在mangle表中追加WiFiDog_$ID$_Outgoing外出过滤链,该链的规则如下几条:

       a) IP 地址为该客户端的IP地址;

       b) MAC地址为该客户端的MAC地址;

       c) 设置MARK为FW_MARK_KNOWN;

    -

    iptables –t mangle –AWiFiDog_$ID$_Outgoing  -s 客户端IP地址 -m mac --mac-source 客户端MAC地址 -j MARK --set-markFW_MARK_KNOWN

    -

    2)在mangle表中追加一条[接受所有目的地址为此客户端IP地址的] WifiDog_$ID$_Incoming输入过滤链;

    -

    iptables -t mangle -AWiFiDog_$ID$_Incoming -d 客户端IP地址 -j ACCEPT

    -

    最后,看一下 wifidog 返回的重定向请求到 认证服务器 的请求报文 以及 认证服务器 返回给 客户端的(重定向到原始访问 baidu.com 的)响应报文:

    http://s3.51cto.com/wyfs02/M02/23/4B/wKioL1M1huOBxAg1AAQPxuinLeU316.jpg

  • 相关阅读:
    饿了么P7级前端工程师进入大厂的面试经验
    前端程序员面试的坑,简历写上这一条信息会被虐死!
    这次来分享前端的九条bug吧
    移动端开发必会出现的问题和解决方案
    创建一个dynamics 365 CRM online plugin (八)
    创建一个dynamics 365 CRM online plugin (七)
    创建一个dynamics 365 CRM online plugin (六)
    创建一个dynamics 365 CRM online plugin (五)
    使用User Primary Email作为GUID的问题
    怎样Debug Dynamics 365 CRM Plugin
  • 原文地址:https://www.cnblogs.com/xmphoenix/p/3806838.html
Copyright © 2020-2023  润新知