分类 wifidog安装 下的文章

针对OPENWRT中WIFIDOG不稳定的优化和改进

最近在做 wifidog 的广告功能,发现 wifidog 有很多不稳定的地方,急待改进和优化。于是自习研究了 wifidog 的工作流程,结合自己的需求,简化了认证过程,流程图如下:
1.jpg

正常 wifidog 认证流程10步以上,而6次是与服务器端的直接通信。其中重复向服务器发送数据的一项可以完全省略,这样精简了认证流程可以减少4次网络传输,使每个上网用户只需要一次请求即可认证上网。认证流程优化至6步且4步是在本地通信。
注意此种方案中token的产生要在wifidog和服务器端协调一致。其实就是通过算法代替了网络认证问题。

本文章由 http://www.wifidog.pro/2015/02/04/wifidog%E6%94%B9%E8%BF%9B.html 整理编辑,转载请注明出处

WiFidog编译成库

1.在package/ utils下创建wifidog_lib目录。在wifidog_lib目录下创建一个文件夹src和一个Makefile文件。Makefile文件编写内容如下:

include $(TOPDIR)/rules.mk

PKG_NAME:=wifidog_lib
PKG_VERSION:=20130917

PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

define Package/wifidog_lib
  SECTION:=utils
  CATEGORY:=Utilities
  DEPENDS:=+iptables-mod-extra +iptables-mod-ipopt +iptables-mod-nat-extra +libpthread
  TITLE:=A wireless captive portal solution
endef

define Package/wifidog_lib/description
      The Wifidog project is a complete and embeddable captive
      portal solution for wireless community groups or individuals
      who wish to open a free Hotspot while still preventing abuse
      of their Internet connection.
endef

define Build/Prepare
      mkdir -p $(PKG_BUILD_DIR)
      $(CP) ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Configure
endef

define Build/Compile
      $(MAKE) -C $(PKG_BUILD_DIR) \
           CC="$(TARGET_CC)" \
           CFLAGS="$(TARGET_CFLAGS) -Wall" \
           LDFLAGS="$(TARGET_LDFLAGS)"
endef

define Package/wifidog_lib/conffiles
/etc/wifidog.conf
endef

define Package/wifidog_lib/install
      $(INSTALL_DIR) $(1)/usr/bin
      $(INSTALL_BIN) $(PKG_BUILD_DIR)/test_wifidog $(1)/usr/bin/
      $(INSTALL_DIR) $(1)/usr/lib
      $(CP) $(PKG_BUILD_DIR)/libwifidog.so* $(1)/usr/lib/
      $(INSTALL_DIR) $(1)/etc
      $(INSTALL_DATA) $(PKG_BUILD_DIR)/wifidog.conf $(1)/etc/
endef

$(eval $(call BuildPackage,wifidog_lib))

2.解压wifidog包进入src目录

Tar xzvf wifidog-20130917-440445db60b0c3aff528ea703a828b0567293387.tar.gz –C src

3.在src下创建Makefile文件,内容如下:

LIB_VERMAJOR = 0
LIB_VERMINOR = 1
LIB_FILENAME = libhttpd.so

LIBWIFIDOG_FILENAME = libwifidog.so

OBJEXT = o

CFLAGS += -Os -pipe -mno-branch-likely -mips32r2 -mtune=34kc -fno-caller-saves -fhonour-copts -Wno-error=unused-but-set-variable -msoft-float

LIB_CFLAGS  = $(CFLAGS) -shared -fPIC -DPIC
LIB_LDFLAGS = $(LDFLAGS) -Wl,-soname,$(LIB_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)
LIBWIFIDOG_LDFLAGS = $(LDFLAGS) -Wl,-soname,$(LIBWIFIDOG_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)

wifidog_OBJECTS = src/conf.$(OBJEXT) src/commandline.$(OBJEXT)\
      src/debug.$(OBJEXT) src/fw_iptables.$(OBJEXT) src/firewall.$(OBJEXT) \
      src/gateway.$(OBJEXT) src/centralserver.$(OBJEXT) src/http.$(OBJEXT) \
      src/auth.$(OBJEXT) src/client_list.$(OBJEXT) src/util.$(OBJEXT) \
      src/wdctl_thread.$(OBJEXT) src/ping_thread.$(OBJEXT) src/safe.$(OBJEXT) \
      src/httpd_thread.$(OBJEXT) src/wifidogapi.$(OBJEXT)

wdctl_OBJECTS = src/wdctl.$(OBJEXT)

LIB_OBJ = libhttpd/protocol.o libhttpd/api.o libhttpd/version.o libhttpd/ip_acl.o

DEFS = -DHAVE_CONFIG_H
DEFAULT_INCLUDES = -I.-I..
sysconfdir = /etc
AM_CPPFLAGS += \
      -I./libhttpd/ \
      -DSYSCONFDIR='"$(sysconfdir)"'

COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
      $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
      --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
      $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
CCLD = $(CC)
LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
      $(LDFLAGS) -o $@

wifidog_LDADD = libhttpd/$(LIB_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)

LIBS = -lnsl -lpthread


all: Makefile   libhttpd   libwifidog test_wifidog

test_wifidog: src/test_wifidog.o libhttpd libwifidog
      @rm -f test_wifidog
      $(LINK) src/test_wifidog.o $(LIBWIFIDOG_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)  $(LIBS)

wdctl: $(wdctl_OBJECTS) libhttpd
      @rm -f wdctl
      $(LINK) $(wdctl_OBJECTS) $(wdctl_LDADD) $(LIBS)
wifidog_test: $(wifidog_OBJECTS) libhttpd
      @rm -f wifidog_test
      $(LINK) $(wifidog_OBJECTS) $(wifidog_LDADD) $(LIBS)          

libhttpd:$(LIB_OBJ) ./libhttpd/httpd_priv.h ./libhttpd/httpd.h
      $(CC) $(LIB_CFLAGS) $(LIB_LDFLAGS) $(LIB_OBJ) $(LIBS) \
           -o libhttpd/$(LIB_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)

libwifidog:$(wifidog_OBJECTS) $(wdctl_OBJECTS) $(LIB_OBJ) ./libhttpd/httpd_priv.h ./libhttpd/httpd.h
      $(CC) $(LIB_CFLAGS) $(LIBWIFIDOG_LDFLAGS) $(LIB_OBJ) $(wifidog_OBJECTS) $(wdctl_OBJECTS) $(LIBS) \
           -o $(LIBWIFIDOG_FILENAME).$(LIB_VERMAJOR).$(LIB_VERMINOR)

.c.o:
    $(CC) $(DEFS) $(AM_CPPFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CFLAGS) -MD -fPIC -DPIC -c -o $@ $<


clean:
      rm -f  $(LIB_FILENAME)*
  1. 修改src下对应的源代码,同时把scripts/init.d/wifidog脚本提供的功能通过lib库提供,这样 lib库需要提供int wifidogstart(),int wifidogstop(),int wifidogreload(),int wifidogrestart()。make package/wifidog_lib/compile V=s进行编译生成libwifidog.so.0.1和测试程序test_wifidog

本文章由 http://www.wifidog.pro/2015/02/03/wifidog%E7%BC%96%E8%AF%91-1.html 整理编辑,转载请注明出处

wifidog认证流程(图文版)

学习使用wifidog一段时间了,觉得这玩意真的不错,虽然有些代码写的不够严谨,运行效率不够高,但是少量人数情况下实现portal是很好的方案。
下面是我摘自一个博客的内容和apfree写的文档中的一部分发上来的,希望能对研究wifidog的新人给予帮助!

一. 用户上线

  1. 用户访问网络,通过iptables将未认证的用户dnat到wifidog进程,wifidog通过307报文将用户重定向到认证服务器
  2. 用户打开认证服务器登录页面,输入用户名密码,发送认证请求
  3. 认证成功的话服务器会发送302报文,携带token信息重定向到wifidog页面。认证失败的话会返回失败页面
  4. 用户携带token信息向wifidog发起认证请求,wifidog再向认证服务器发起请求,认证成功后授权,并将用户重定向到成功页面
    1.jpg

二. 保活和下线

  1. wifidog会定时向认证服务器发送保活消息
  2. 当用户主动请求下线后,wifidog此时并没有下线
  3. 当wifidog再次发起保活请求时,认证服务器会告诉它用户已下线,此时wifidog会将用户下线
    2.jpg

认证流程描述
i.Wifidog运行之后建立一系列的防火墙规则,主要规则起到如下作用:1.阻断所有内网到外网的访问。2.开通内网到外网的dns访问。3.开通内网到认证服务器以及域名白名单的访问。4.对内网到外网80端口的访问转向到wifidog自己的http服务(2060端口)
ii. 手机、pc连接上来后,app或者系统(安卓、ios会自己连接到各自的服务器上来验证网络的连通性)会发起对外网的访问请求,dns请求会被放过,然后对应的80端口的访问会被指向2060的http服务,其他的请求都会被拦截
iii. Wifidog的http接到web请求后,基本上都会被指向404页面,404页面会给客户端一个重定向返回(302),要求客户端重定向访问认证服务器的login页面,附加参数gw_id、gw_address、gw_port、url
iv. 手机、pc客户端加载、显示认证服务器的login页面,用户根据页面内容做相关的认证操作(qq登录、微博登录、用户名密码登录、手机短信登录等多种登录方式),原则只有一个认证不成功就仍然让用户停留在认证服务器继续认证操作,认证成功给客户端一个302重定向返回,根据login接口提交上来的参数gw_address、gw_port跳转套wifidogweb服务的/wifidog/auth页面上,附带token和url参数
v.Wifidog的web服务收到手机、pc客户端的/wifidog/auth请求后,会主动对认证服务器的auth接口发起一个验证请求,附带参数ip、mac、token、stage=login
vi. 认证服务器的auth接口收到wifidog的请求,要根据内部逻辑返回是否允许通过的应答Auth: 0拒绝Auth: 1 允许
vii. Wifidog接收到验证结果后,如果拒绝访问,就会返回302给客户端,重定向到认证服务器的gw_message接口,附带message=denied参数,客户端的上网访问仍然会回到第二步骤;如果允许访问,则改动防火墙规则,开通改客户端的上网(至此客户端已经能够正常上网),然后返回302重点向给客户端,重定向到认证服务器的portal接口,附带参数gw_id
viii. 认证服务器的的portal接口根据业务流成显示广告业或者做其他的跳转
ix. 整个认证流程完成

本文章由 http://www.wifidog.pro/2015/02/03/wifidog%E6%B5%81%E7%A8%8B-3.html 整理编辑,转载请注明出处

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

用户连接启动线程(void thread_httpd(void * args))
代码片段1.3分析

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

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

代码片段1.3:

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, 
            "<p>We apologize, but it seems that the internet connection that powers this hotspot is temporarily unavailable.</p>"
            "<p>If at all possible, please notify the owners of this hotspot that the internet connection is out of service.</p>"
            "<p>The maintainers of this network are aware of this disruption.  We hope that this situation will be resolved soon.</p>"
            "<p>In a while please <a href='%s'>click here</a> to try your request again.</p>", 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, 
            "<p>We apologize, but it seems that we are currently unable to re-direct you to the login screen.</p>"
            "<p>The maintainers of this network are aware of this disruption.  We hope that this situation will be resolved soon.</p>"
            "<p>In a couple of minutes please <a href='%s'>click here</a> to try your request again.</p>", 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,认证服务器所执行的操作。
代码片段1.4

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;
}

本文章由 http://www.wifidog.pro/2015/02/03/wifidog%E7%94%A8%E6%88%B7%E8%BF%9E%E6%8E%A5_2.html 整理编辑,转载请注明出处