分类 wifidog分析 下的文章

wifidog配置分析

AuthServer {
    Hostname                  (Mandatory; Default: NONE)
    SSLAvailable              (Optional; Default: no; Possible values: yes, no)
    SSLPort                   (Optional; Default: 443)
    HTTPPort                  (Optional; Default: 80)
    Path                      (Optional; Default: /wifidog/ Note:  The path must be both prefixed and suffixed by /.  Use a single / for server root.)
    LoginScriptPathFragment   (Optional; Default: login/? Note:  This is the script the user will be sent to for login.)
    PortalScriptPathFragment  (Optional; Default: portal/? Note:  This is the script the user will be sent to after a successfull login.)
    MsgScriptPathFragment     (Optional; Default: gw_message.php? Note:  This is the script the user will be sent to upon error to read a readable message.)
    PingScriptPathFragment    (Optional; Default: ping/? Note:  This is the script the user will be sent to upon error to read a readable message.)
    AuthScriptPathFragment    (Optional; Default: auth/? Note:  This is the script the user will be sent to upon error to read a readable message.)
}

# Listen on this port
GatewayPort 2060

# Parameter: CheckInterval
# Default: 60
# Optional
#
# How many seconds should we wait between timeout checks.  This is also
# how often the gateway will ping the auth server and how often it will
# update the traffic counters on the auth server.  Setting this too low
# wastes bandwidth, setting this too high will cause the gateway to take
# a long time to switch to it's backup auth server(s).
CheckInterval 60

# Parameter: ClientTimeout
# Default: 5
# Optional
#
# Set this to the desired of number of CheckInterval of inactivity before a client is logged out
# The timeout will be INTERVAL * TIMEOUT
ClientTimeout 5

wifidog流程参照http://dev.wifidog.org/wiki/doc/developer/FlowDiagram描述,这里介绍下配置内容。

AuthServer是Portal服务器的配置项;GatewayPort是Wifidog监听的地址,默认是2060,一般保持默认即可;CheckInterval是心跳时长,单位是秒,什么是心跳呢,客户端认证成功之后,如果有网络访问动作,Wifidog getway就会每隔一段时间访问Portal服务器的一个脚本,用于认证计费,当然,如果客户使用超时或超流量,也可以通过心跳强制客户端下线。ClientTimeout是用户一次认证成功后的网络访问时长,超过这个时间需要重新认证,这个时长并非由ClientTimeout单独决定,取决于INTERVAL * TIMEOUT。详细的配置信息可以访问:http://dev.wifidog.org/browser/trunk/wifidog/wifidog.conf

重点讨论Portal服务器的配置项,Hostname是Portal服务器的ip或者是域名,SSLAvailable和SSLPort是SSL加密配置,如果你的Portal服务器有配置HTTPS加密,则需要配置这两项;Path是指你的脚本路径(举例,http://a.com/to/,则a.com是域名,/to/是路径),注意路径必须以“/”开头和结尾,如果是根路径,则填一个“/”即可;接下来的5个配置指明你的脚本名,这说明了我们需要写五个脚本,我会详细说明。(以下文中涉及的“第几步”均是指Wifidog认证过程的步骤)

LoginScriptPathFragment配置项配置的是登陆脚本,它通过GET方式接受传入参数gw_address、gw_port、gw_id、mac和url,gw_address是AP Getway的ip地址;gw_port是Wifidog监听的端口,即上面介绍的wifidog.conf中的GatewayPort配置;gw_id是AP Getway的id,配置文件wifidog.conf中可以配置,默认值是default,这个值的作用是当存在多个AP是,服务器或管理员可以根据不同的id确定用户的接入点;mac是客户计算机的网卡物理地址,注意不是AP网关的mac,这个mac是用来识别客户计算机的;url是客户初始访问的Url,这些Querystring都是AP Getway向客户端发出重定向请求自动生成的。这个脚本同时需要提供登陆页面,如果登陆成功,需要向客户;端返回302重定向,重定向到:http://gw_address:gw_port/wifidog/auth?token=[token];即实现第7步,其中[token]是你自己自动生成的token字符串,随机生成一个字符串即可,但是长度最好长些,安全性更高,另外,token需要根据不同用户保存,最好保存于数据库中,之后的AP Getway询问token有效性(第9步)还需要用到。这里最好使用cookie或session,使之后的登陆成功页面可以判断用户已经成功,阻止未登录成功的人访问认证成功页面。

PortalScriptPathFragment配置项配置的是登陆成功后服务器展示的脚本(第11步),它通过GET方式接受1个传入参数,gw_id,这个脚本比较简单,告知用户登陆成功即可,当然,最好重定向到用户之前想要方位的url,即第1步用户输入的URL。
MsgScriptPathFragment配置项配置的是错误信息展示脚本,它通过GET方式接受一个传入参数message,这个脚本也很简单,展示message的内容即可,目的是当认证过程出现错误,AP Getway会重定向到这个脚本,URL中含有错误的信息。

PingScriptPathFragment配置项配置的是心跳脚本,这个脚本它通过GET方式接受5个传入参数,gw_id,sys.uptime,sys.memfree,sys.load,wifidog.uptime,其中,sys.uptime指的是AP Getway的启动时间,sys.memfree指的是AP Getway的空闲内存,sys.load指的是AP Getway的CPU负载,wifidog.uptime指的是wifidog的启动时间,这个脚本每隔一段时间(Wifidog.conf里配置的CheckInterval),Wifidog会自动访问,但是其目的不是用户验证,而是帮助管理员管理AP节点,了解AP节点的负载情况,适时增加节点等,Wifidog访问这个脚本时,需要这个脚本返回Pong,如果你没有统计AP节点负载数据的需求,可以丢弃这些数据,直接回应Pong,注意,这个回应只包含“Pong”字符串,无需包含其他html标签。

AuthScriptPathFragment是用户认证脚本,实现的是第10步的功能,这个脚本它通过GET方式接受7个传入参数:stage、ip、mac、token、incoming、outcoming和gw_id。其中stage的值是login,ip是客户端的ip,注意不是AP Getwap的ip;mac是客户端的网卡物理地址,token就是你在认证脚本生成并返回给客户端的;incoming和outcoming用于流量控制,默认值为0;gw_id同上。如何识别用户登录成功,通过mac和token吧,LoginScriptPathFragment登陆脚本在用户登陆成功后需要记录用户的mac和token,然后在此处验证,如果匹配,回复Auth: 1,否则,回复Auth: 0。另外,这个脚本也是心跳脚本,每隔一段时间Wifidog会自动访问,如果用户使用时间超过限制或流量超过额度,服务器可以及时回应Auth: 0结束用户的访问。另外需要注意的是,回应同样无需包含html标签,另外,在Auth后的冒号和0/1之间,有一个空格,缺少这个空格也会导致出错。
在配置Wifidog的配置文件wifidog.conf是,配置脚本的配置项都必须以“?”结尾,否则以GET方式传递的QueryString会因Url缺少问号访问错误的脚本。

本文章由 http://www.wifidog.pro/2015/04/03/wifidog%E9%85%8D%E7%BD%AE%E5%88%86%E6%9E%90-1.html 整理编辑,转载请注明出处

wifidog源码wifidog分析用户连接

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

此段代码是当有新用户(未认证的用户)连接时创建的线程,其主要功能为

获取用户浏览器发送过来的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回调函数中新用户被重定向到认证服务器) */
        _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/04/02/wifidog%E6%BA%90%E7%A0%81wifidog%E5%88%86%E6%9E%90.html 整理编辑,转载请注明出处

wifidog源码 - 初始化阶段

Wifidog是一个linux下开源的认证网关软件,它主要用于配合认证服务器实现无线路由器的认证放行功能。

wifidog是一个后台的服务程序,可以通过wdctrl命令对wifidog主程序进行控制。

本文解释wifidog在启动阶段所做的初始化主要工作(代码片段1.1)

初始化配置(先将配置结构体初始化为默认值,在读取配置文件修改配置结构体)
初始化已连接客户端列表(如果是通过wdctrl重启wifidog,将会读取之前wifidog的已连接客户端列表 代码片段1.2 代码片段1.3)
如无特殊情况,分离进程,建立守护进程 (代码片段1.1)
添加多个http请求回调函数(包括404错误回调函数) (见之后章节)
摧毁删除现有的iptables路由表规则 (见之后章节)
建立新的iptables路由表规则 (见之后章节)
启动多个功能线程 (见之后章节)
循环等待客户端连接 (见之后章节)

int main(int argc, char **argv) {

    s_config *config = config_get_config(); //就是返回全局变量config结构体的地址
    config_init(); //初始化全局变量config结构体为默认值

    parse_commandline(argc, argv); //根据传入参数执行操作(如果参数有-x则会设置restart_orig_pid为已运行的wifidog的pid)

    /* Initialize the config */
    config_read(config->configfile); //根据配置文件设置全局变量config结构体
    config_validate(); //判断GatewayInterface和AuthServer是否为空,空则无效退出程序。

    /* Initializes the linked list of connected clients */
    client_list_init(); //将已连接客户端链表置空。

    /* Init the signals to catch chld/quit/etc */
    init_signals(); //初始化一些信号

    if (restart_orig_pid) { //用于restart,如果有已运行的wifidog,先会kill它
        /*
         * We were restarted and our parent is waiting for us to talk to it over the socket
         */
        get_clients_from_parent(); //从已运行的wifidog中获取客户端列表,详见 代码片段1.2

        /*
         * At this point the parent will start destroying itself and the firewall. Let it finish it's job before we continue
         */

        while (kill(restart_orig_pid, 0) != -1) { //kill已运行的wifidog
            debug(LOG_INFO, "Waiting for parent PID %d to die before continuing loading", restart_orig_pid);
            sleep(1);
        }

        debug(LOG_INFO, "Parent PID %d seems to be dead. Continuing loading.");
    }

    if (config->daemon) { //创建为守护进程,config->daemon默认值为-1

        debug(LOG_INFO, "Forking into background");

        switch(safe_fork()) {
            case 0: /* child */
                setsid(); //创建新会话,脱离此终端,实现守护进程
                append_x_restartargv();
                main_loop(); //进入主循环(核心代码在此)。
                break;

            default: /* parent */
                exit(0);
                break;
        }
    }
    else {
        append_x_restartargv();
        main_loop();
    }

    return(0); /* never reached */
}

代码片段1.2(获取已启动的wifidog的客户端列表):

此段代表描述了新启动的wifidog如何从已启动的wifidog程序中获取已连接的客户端列表。发送端见 代码片段1.3

void get_clients_from_parent(void) {
    int sock;
    struct sockaddr_un sa_un;
    s_config * config = NULL;
    char linebuffer[MAX_BUF];
    int len = 0;
    char *running1 = NULL;
    char *running2 = NULL;
    char *token1 = NULL;
    char *token2 = NULL;
    char onechar;
    char *command = NULL;
    char *key = NULL;
    char *value = NULL;
    t_client * client = NULL;
    t_client * lastclient = NULL;

    config = config_get_config();

    debug(LOG_INFO, "Connecting to parent to download clients");

    /* 连接socket */
    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    memset(&sa_un, 0, sizeof(sa_un));
    sa_un.sun_family = AF_UNIX;
    strncpy(sa_un.sun_path, config->internal_sock, (sizeof(sa_un.sun_path) - 1)); //config->internal_sock的值为"/tmp/wifidog.sock"

    /* 连接已启动的wifidog */
    if (connect(sock, (struct sockaddr *)&sa_un, strlen(sa_un.sun_path) + sizeof(sa_un.sun_family))) {
        debug(LOG_ERR, "Failed to connect to parent (%s) - client list not downloaded", strerror(errno));
        return;
    }

    debug(LOG_INFO, "Connected to parent. Downloading clients");

    LOCK_CLIENT_LIST();

    command = NULL;
    memset(linebuffer, 0, sizeof(linebuffer));
    len = 0;
    client = NULL;
    /* 接收数据,逐个字符接收 */
    /* 数据包格式为 CLIENT|ip=%s|mac=%s|token=%s|fw_connection_state=%u|fd=%d|counters_incoming=%llu|counters_outgoing=%llu|counters_last_updated=%lu\n */
    while (read(sock, &onechar, 1) == 1) {
        if (onechar == '\n') {
            /* 如果接收到末尾('\n'),则转为'\0' */
            onechar = '\0';
        }
        linebuffer[len++] = onechar;

        if (!onechar) {
            /* 以下将数据转化为t_client结构体添加到客户端列表 */
            debug(LOG_DEBUG, "Received from parent: [%s]", linebuffer);
            running1 = linebuffer;
            while ((token1 = strsep(&running1, "|")) != NULL) {
                if (!command) {
                    /* The first token is the command */
                    command = token1;
                }
                else {
                /* Token1 has something like "foo=bar" */
                    running2 = token1;
                    key = value = NULL;
                    while ((token2 = strsep(&running2, "=")) != NULL) {
                        if (!key) {
                            key = token2;
                        }
                        else if (!value) {
                            value = token2;
                        }
                    }
                }

                if (strcmp(command, "CLIENT") == 0) {
                    /* This line has info about a client in the client list */
                    if (!client) {
                        /* Create a new client struct */
                        client = safe_malloc(sizeof(t_client));
                        memset(client, 0, sizeof(t_client));
                    }
                }

                if (key && value) {
                    if (strcmp(command, "CLIENT") == 0) {
                        /* Assign the key into the appropriate slot in the connection structure */
                        if (strcmp(key, "ip") == 0) {
                            client->ip = safe_strdup(value);
                        }
                        else if (strcmp(key, "mac") == 0) {
                            client->mac = safe_strdup(value);
                        }
                        else if (strcmp(key, "token") == 0) {
                            client->token = safe_strdup(value);
                        }
                        else if (strcmp(key, "fw_connection_state") == 0) {
                            client->fw_connection_state = atoi(value);
                        }
                        else if (strcmp(key, "fd") == 0) {
                            client->fd = atoi(value);
                        }
                        else if (strcmp(key, "counters_incoming") == 0) {
                            client->counters.incoming_history = atoll(value);
                            client->counters.incoming = client->counters.incoming_history;
                        }
                        else if (strcmp(key, "counters_outgoing") == 0) {
                            client->counters.outgoing_history = atoll(value);
                            client->counters.outgoing = client->counters.outgoing_history;
                        }
                        else if (strcmp(key, "counters_last_updated") == 0) {
                            client->counters.last_updated = atol(value);
                        }
                        else {
                            debug(LOG_NOTICE, "I don't know how to inherit key [%s] value [%s] from parent", key, value);
                        }
                    }
                }
            }

            /* End of parsing this command */
            if (client) {
                /* Add this client to the client list */
                if (!firstclient) {
                    firstclient = client;
                    lastclient = firstclient;
                }
                else {
                    lastclient->next = client;
                    lastclient = client;
                }
            }

            /* Clean up */
            command = NULL;
            memset(linebuffer, 0, sizeof(linebuffer));
            len = 0;
            client = NULL;
        }
    }

    UNLOCK_CLIENT_LIST();
    debug(LOG_INFO, "Client list downloaded successfully from parent");

    close(sock);
}

代码片段1.3(已启动的wifidog发送客户端列表到新启动的wifidog):

//thread_wdctl_handler(void *arg)函数是wifidog启动后自动创建的控制线程,主要用于与wdctrl进行socket通信,根据wdctrl命令执行不同的操作。这里我们着重讲解的是wdctrl发送restart后wifidog的执行逻辑。
static void *
thread_wdctl_handler(void *arg)
{
    int fd,
        done,
        i;
    char request[MAX_BUF];
    ssize_t read_bytes,
        len;

    debug(LOG_DEBUG, "Entering thread_wdctl_handler....");

    fd = (int)arg;

    debug(LOG_DEBUG, "Read bytes and stuff from %d", fd);

    /* 初始化变量 */
    read_bytes = 0;
    done = 0;
    memset(request, 0, sizeof(request));

    /* 读取命令 */
    while (!done && read_bytes < (sizeof(request) - 1)) {
        len = read(fd, request + read_bytes,
                sizeof(request) - read_bytes); //读取wdctrl发送的命令

        /* 判断命令正确性 */
        for (i = read_bytes; i < (read_bytes + len); i++) {
            if (request[i] == '\r' || request[i] == '\n') {
                request[i] = '\0';
                done = 1;
            }
        }

        /* Increment position */
        read_bytes += len;
    }

        //判断命令
    if (strncmp(request, "status", 6) == 0) {
        wdctl_status(fd);
    } else if (strncmp(request, "stop", 4) == 0) {
        wdctl_stop(fd);
    } else if (strncmp(request, "reset", 5) == 0) {
        wdctl_reset(fd, (request + 6));
    } else if (strncmp(request, "restart", 7) == 0) {
        wdctl_restart(fd); //执行wdctl_restart(int afd)函数
    }

    if (!done) {
        debug(LOG_ERR, "Invalid wdctl request.");
                //关闭套接字
        shutdown(fd, 2);
        close(fd);
        pthread_exit(NULL);
    }

    debug(LOG_DEBUG, "Request received: [%s]", request);

        //关闭套接字
    shutdown(fd, 2);
    close(fd);
    debug(LOG_DEBUG, "Exiting thread_wdctl_handler....");

    return NULL;
}


//wdctl_restart(int afd)函数详解
static void
wdctl_restart(int afd)
{
    int sock,
        fd;
    char *sock_name;
    struct sockaddr_un sa_un;
    s_config * conf = NULL;
    t_client * client = NULL;
    char * tempstring = NULL;
    pid_t pid;
    ssize_t written;
    socklen_t len;

    conf = config_get_config();

    debug(LOG_NOTICE, "Will restart myself");

    /*
     * 准备内部连接socket
     */
    memset(&sa_un, 0, sizeof(sa_un));
    sock_name = conf->internal_sock; //conf->internal_sock值为"/tmp/wifidog.sock"
    debug(LOG_DEBUG, "Socket name: %s", sock_name);

    if (strlen(sock_name) > (sizeof(sa_un.sun_path) - 1)) {

        debug(LOG_ERR, "INTERNAL socket name too long");
        return;
    }

    debug(LOG_DEBUG, "Creating socket");
    sock = socket(PF_UNIX, SOCK_STREAM, 0); //建立内部socket套接字

    debug(LOG_DEBUG, "Got internal socket %d", sock);

    /* 如果sock_name文件存在,则删除*/
    unlink(sock_name);

    debug(LOG_DEBUG, "Filling sockaddr_un");
    strcpy(sa_un.sun_path, sock_name); 
    sa_un.sun_family = AF_UNIX;

    debug(LOG_DEBUG, "Binding socket (%s) (%d)", sa_un.sun_path, strlen(sock_name));


    if (bind(sock, (struct sockaddr *)&sa_un, strlen(sock_name) + sizeof(sa_un.sun_family))) {
        debug(LOG_ERR, "Could not bind internal socket: %s", strerror(errno));
        return;
    }

    if (listen(sock, 5)) {
        debug(LOG_ERR, "Could not listen on internal socket: %s", strerror(errno));
        return;
    }

    /*
     * socket建立完成,创建子进程
     */
    debug(LOG_DEBUG, "Forking in preparation for exec()...");
    pid = safe_fork();
    if (pid > 0) {
        /* 父进程 */

        /* 等待子进程连接此socket :*/
        debug(LOG_DEBUG, "Waiting for child to connect on internal socket");
        len = sizeof(sa_un);
        if ((fd = accept(sock, (struct sockaddr *)&sa_un, &len)) == -1){ //接受连接
            debug(LOG_ERR, "Accept failed on internal socket: %s", strerror(errno));
            close(sock);
            return;
        }

        close(sock);

        debug(LOG_DEBUG, "Received connection from child. Sending them all existing clients");

        /*子进程已经完成连接,发送客户端列表 */
        LOCK_CLIENT_LIST();
        client = client_get_first_client(); //获取第一个客户端
        while (client) {
            /* Send this client */
            safe_asprintf(&tempstring, "CLIENT|ip=%s|mac=%s|token=%s|fw_connection_state=%u|fd=%d|counters_incoming=%llu|counters_outgoing=%llu|counters_last_updated=%lu\n", client->ip, client->mac, client->token, client->fw_connection_state, client->fd, client->counters.incoming, client->counters.outgoing, client->counters.last_updated);
            debug(LOG_DEBUG, "Sending to child client data: %s", tempstring);
            len = 0;
            while (len != strlen(tempstring)) {
                written = write(fd, (tempstring + len), strlen(tempstring) - len); //发送给子进程
                if (written == -1) {
                    debug(LOG_ERR, "Failed to write client data to child: %s", strerror(errno));
                    free(tempstring);
                    break;
                }
                else {
                    len += written;
                }
            }
            free(tempstring);
            client = client->next;
        }
        UNLOCK_CLIENT_LIST();

        close(fd);

        debug(LOG_INFO, "Sent all existing clients to child. Committing suicide!");

        shutdown(afd, 2);
        close(afd);


        wdctl_stop(afd);
    }
    else {
        /* 子进程,先关闭资源 */
        close(wdctl_socket_server);
        close(icmp_fd);
        close(sock);
        shutdown(afd, 2);
        close(afd);
        debug(LOG_NOTICE, "Re-executing myself (%s)", restartargv[0]);

        setsid();
        execvp(restartargv[0], restartargv); //执行外部命令,这里重新启动wifidog

        debug(LOG_ERR, "I failed to re-execute myself: %s", strerror(errno));
        debug(LOG_ERR, "Exiting without cleanup");
        exit(1);
    }
}

本文章由 http://www.wifidog.pro/2015/04/02/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E5%88%9D%E5%A7%8B%E5%8C%96.html 整理编辑,转载请注明出处

wifidog源码wifidog分析 - wifidog原理

wifidog是一个用于配合认证服务器实现无线网页认证功能的程序,常见的情景就是使用于公共场合的无线wifi接入点,首先移动设备会连接公共wifi接入点,之后会弹出网页要求输入用户名密码,认证过后才能够连入外网。其主页是http://dev.wifidog.org/

实现原理

  其实wifidog原理很简单,主要是通过管控iptables,配合认证服务器进行客户端的放行操作。wifidog在启动后都会自动启动三个线程,分别为客户端检测线程、wdctrl交互线程、认证服务器心跳检测线程。每当新用户连接无线AP并浏览网页时,wifidog会获取新用户的此次操作,并返回一个重定向到认证服务器的http于用户,此后用户通过认证服务器认证后,再继续浏览网页时,wifidog会询问认证服务器此用户权限,若放行则修改iptables放行此用户IP。

  主要流程如下
添加关键路径对应的回调函数
删除所有iptables路由表
建立新的iptables路由表
开启客户端检测线程(用于判断客户端是否在线,是否登出)
开启wdctrl交互线程
开启认证服务器心跳检测线程
循环等待客户端连接(使用socket绑定2060端口并监听,实际上在建立新的iptables路由表规则时会将网关的80端口重定向到2060端口)

  回调函数
  回调函数主要用于根据用户http报文执行不同的操作,其原理就是分析http报文请求中有没有关键路径,若有,则执行关键路径对应的回调函数,若没有,则返回一个重定向到认证服务器的包给用户。一次典型的流程为

用户连接无线AP,访问某网站(比如http://www.baidu.com
wifidog获取到此http报文,检查是否包含关键路径,没有则返回重定向包给用户,将其重定向到认证服务器
用户认证成功,认证服务器将用户重定向到无线AP网关,并包含关键路径"/wifidog/auth"和token
wifidog接收到用户重定向后访问的报文,检测到关键路径"/wifidog/auth",然后访问认证服务器进行token认证
认证成功,wifidog修改iptables放行此用户(根据mac和ip进行放行)

  wifidog的iptables规则
  这一部分我没有仔细认真看源码,但可以推论出wifidog是怎么修改iptables的规则的,了解iptables基本原理的同学都清楚iptables实际上有两条路进行数据包处理,一条路会通过应用程序,一条路不同过应用程序,直接到POSTOUTPUT,而我认为wifidog建立的规则是

只要是访问认证服务器的http请求都直接不通过wifidog发送出去
只要是通过认证的客户端wifidog都会修改iptables让其数据直接从FORWARD到POSTOUTPUT,而不经过wifidog
其他行为都必须进过wifidog处理

  客户端检测线程
  此线程每隔60s会遍历一次客户端列表,对每一个客户端列表统计流量,如果客户端在60s间隔内没有新产生的流量则不更新客户端的最新更新时间,当当前时间减去最新更新时间大于断线要求时间时,则会将此客户端从客户端列表删除,并修改iptables规则禁止其访问外部网络,然后发送此客户端登出包于认证服务器,认证服务器根据此登出包将此客户端做登出处理。如若没有超出断线要求时间,此线程还会发送客户端状态获取包于认证服务器,认证服务器返回此客户端在认证服务器上的信息,如若信息表示此客户端已在认证服务器上登出,wifidog则会执行此客户端下线操作。

  wdctrl交互线程
  其原理是使用unix socket进行进程间通信,具体实现在之后文章中体现

  认证服务器心跳检测线程
  原理也很简单,就是每隔60s将路由的一些系统信息发送给认证服务器,认证服务器接收到会返回一个回执

  循环等待客户端连接
  这里主要会接收到两种类型的客户端连接

未认证的客户端打开网页操作,wifidog会接收到此http请求,并返回一个重定向到认证服务器的包于客户端
经过认证服务器认证成功后,认证服务器自动将客户端重定向到无线AP的操作,wifidog接收到此类http请求后会检测关键路径"/tmp/wifidog",并把http请求中携带的token与认证服务器进行认证,认证成功后则修改iptables放行客户端。

本文章由 http://www.wifidog.pro/2015/04/02/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-7.html 整理编辑,转载请注明出处