nodogsplash分析

git出nodog的源码:
https://github.com/nodogsplash/nodogsplash
查看nodog的wiki

Nodogsplash offers a simple way to provide restricted access to an internet connection. It is derived from the codebase of the Wifi Guard Dog project. Nodogsplash is released under the GNU General Public License.

nodogsplash提供了一个简单的本地化的portal网络连接,项目源自于wifidog,可以说是wifidog代码的功能简化版。

wiki中有用的信息还有:
When the splash page is served, the following variables in the page are replaced by their values:

$gatewayname The value of GatewayName as set in nodogsplash.conf.
$authtarget A URL which encodes a unique token and the URL of the user's original web request. If nodogsplash receives a request at this URL, it completes the authentication process for the client and replies to the request with a "302 Found" to the encoded originally requested URL. (Alternatively, you can use a GET-method HTML form to send this information to the nodogsplash server; see below.) As a simple example:

<a href="$authtarget">Enter</a>

$imagesdir The directory in nodogsplash's web hierarchy where images to be displayed in the splash page must be located.

$tok,$redir,$authaction, and $denyaction are also available and can be useful if you want to write the splash page to use a GET-method HTML form instead of using $authtarget as the value of an href attribute to communicate with the nodogsplash server. As a simple example:
<form method='GET' action='$authaction'>
  <input type='hidden' name='tok' value='$tok'>
  <input type='hidden' name='redir' value='$redir'>
  <input type='submit' value='Click Here to Enter'>
</form>
$clientip, $clientmac and $gatewaymac The respective addresses of the client or gateway. This might be usefull in cases where the data needs to be forwarded to some other place by the plash page itself.

$nclients and $maxclients User stats. Usefull when you need to display something like "n of m users online" on the splash site.

$uptime The time Nodogsplash is running.

To change the appearance of informational and error pages which may occasionally be served by nodogsplash, edit the infoskel file:

/etc/nodogsplash/htdocs/infoskel.html

In this file, variables $gatewayname, $version, $title, and $content will be replaced by their values. $title is a summary of the information or kind of error; $content is the content of the information or error message.

nodogsplash中可以在splash.html中加入动态变量,以$为标志的变量,可以实现callback。
另外还以增加用户名和密码的登录认证,可以在html页面加入

 <form method='GET' action='$authaction'>
  <input type='hidden' name='tok' value='$tok'>
  <input type='hidden' name='redir' value='$redir'>
  username: <input type='text' name='nodoguser' value='' size='12' maxlength='12'>
  <br>
  password: <input type='password' name='nodogpass' value='' size='12' maxlength='10'>
  <br>
  <input type='submit' value='Enter'>
  </form>

加入了 tok redir,nodoguser,nodogpass变量。

下面分析nodog源码,找到main函数,/src/gateway.c中:


#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include <time.h>

/* for strerror() */
#include <string.h>

/* for wait() */
#include <sys/wait.h>

/* for unix socket communication*/
#include <sys/socket.h>
#include <sys/un.h>

#include "common.h"
#include "httpd.h"
#include "safe.h"
#include "debug.h"
#include "conf.h"
#include "gateway.h"
#include "firewall.h"
#include "commandline.h"
#include "auth.h"
#include "http.h"
#include "client_list.h"
#include "ndsctl_thread.h"
#include "httpd_thread.h"
#include "util.h"


/** XXX Ugly hack
 * We need to remember the thread IDs of threads that simulate wait with pthread_cond_timedwait
 * so we can explicitly kill them in the termination handler
 */
static pthread_t tid_client_check = 0;

/* The internal web server */
httpd * webserver = NULL;

/* Time when nodogsplash started  */
time_t started_time = 0;


/* Avoid race condition of folloing variables */
pthread_mutex_t httpd_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Total number of httpd request handling threads started */
int created_httpd_threads;
/* Number of current httpd request handling threads */
int current_httpd_threads;


/**@internal
 * @brief Handles SIGCHLD signals to avoid zombie processes
 *
 * When a child process exits, it causes a SIGCHLD to be sent to the
 * parent process. This handler catches it and reaps the child process so it
 * can exit. Otherwise we'd get zombie processes.
 */     //以上英文解释已经很详细了,sigchld_handler在子进程结束时通识父进程回收,避免僵尸进程
void
sigchld_handler(int s)
{
    int status;
    pid_t rc;

    debug(LOG_DEBUG, "SIGCHLD handler: Trying to reap a child");

    rc = waitpid(-1, &status, WNOHANG | WUNTRACED);

    if(rc == -1) {
        if(errno == ECHILD) {
            debug(LOG_DEBUG, "SIGCHLD handler: waitpid(): No child exists now.");
        } else {
            debug(LOG_ERR, "SIGCHLD handler: Error reaping child (waitpid() returned -1): %s", strerror(errno));
        }
        return;
    }

    if(WIFEXITED(status)) {
        debug(LOG_DEBUG, "SIGCHLD handler: Process PID %d exited normally, status %d", (int)rc, WEXITSTATUS(status));
        return;
    }

    if(WIFSIGNALED(status)) {
        debug(LOG_DEBUG, "SIGCHLD handler: Process PID %d exited due to signal %d", (int)rc, WTERMSIG(status));
        return;
    }

    debug(LOG_DEBUG, "SIGCHLD handler: Process PID %d changed state, status %d not exited, ignoring", (int)rc, status);
    return;
}

/** Exits cleanly after cleaning up the firewall.
 *  Use this function anytime you need to exit after firewall initialization */
void     //该函数在调用到时,则结束整个进程,关闭软件
termination_handler(int s)
{
    static  pthread_mutex_t sigterm_mutex = PTHREAD_MUTEX_INITIALIZER;

    debug(LOG_NOTICE, "Handler for termination caught signal %d", s);

    /* Makes sure we only call fw_destroy() once. */
    if (pthread_mutex_trylock(&sigterm_mutex)) {
        debug(LOG_INFO, "Another thread already began global termination handler. I'm exiting");
        pthread_exit(NULL);
    } else {
        debug(LOG_INFO, "Cleaning up and exiting");
    }

    debug(LOG_INFO, "Flushing firewall rules...");
    fw_destroy();

    /* XXX Hack
     * Aparently pthread_cond_timedwait under openwrt prevents signals (and therefore
     * termination handler) from happening so we need to explicitly kill the threads
     * that use that
     */
    if (tid_client_check) {
        debug(LOG_INFO, "Explicitly killing the fw_counter thread");
        pthread_kill(tid_client_check, SIGKILL);
    }

    debug(LOG_NOTICE, "Exiting...");
    exit(s == 0 ? 1 : 0);
}


/** @internal
 * Registers all the signal handlers
 */
static void   //信号初始化函数,为进程提供信号处理
init_signals(void)
{
    struct sigaction sa;

    debug(LOG_DEBUG, "Setting SIGCHLD handler to sigchld_handler()");
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        debug(LOG_ERR, "sigaction(): %s", strerror(errno));
        exit(1);
    }

    /* Trap SIGPIPE */
    /* This is done so that when libhttpd does a socket operation on
     * a disconnected socket (i.e.: Broken Pipes) we catch the signal
     * and do nothing. The alternative is to exit. SIGPIPE are harmless
     * if not desirable.
     */
    debug(LOG_DEBUG, "Setting SIGPIPE  handler to SIG_IGN");
    sa.sa_handler = SIG_IGN;
    if (sigaction(SIGPIPE, &sa, NULL) == -1) {
        debug(LOG_ERR, "sigaction(): %s", strerror(errno));
        exit(1);
    }

    debug(LOG_DEBUG, "Setting SIGTERM,SIGQUIT,SIGINT  handlers to termination_handler()");
    sa.sa_handler = termination_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    /* Trap SIGTERM */
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        debug(LOG_ERR, "sigaction(): %s", strerror(errno));
        exit(1);
    }

    /* Trap SIGQUIT */
    if (sigaction(SIGQUIT, &sa, NULL) == -1) {
        debug(LOG_ERR, "sigaction(): %s", strerror(errno));
        exit(1);
    }

    /* Trap SIGINT */
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        debug(LOG_ERR, "sigaction(): %s", strerror(errno));
        exit(1);
    }
}

/**@internal
 * Main execution loop
 */      //main_loop为nodog的执行循环,是进程的核心
static void
main_loop(void)
{
    int result;
    pthread_t   tid;
    struct timespec wait_time;
    int msec;
    s_config *config;
    request *r;
    void **params;
    int* thread_serial_num_p;

    config = config_get_config();     //读取配置文件中的配置,填充到config结构中

    /* Set the time when nodogsplash started */    //设置时间
    if (!started_time) { 
        debug(LOG_INFO, "Setting started_time");
        started_time = time(NULL);
    } else if (started_time < MINIMUM_STARTED_TIME) {
        debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time");
        started_time = time(NULL);
    }
             //获取网管IP和mac,如果没有检测到则结束
    /* If we don't have the Gateway IP address, get it. Exit on failure. */
    if (!config->gw_address) {
        debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface);
        if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) {
            debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface);
            exit(1);
        }
    }
    if ((config->gw_mac = get_iface_mac(config->gw_interface)) == NULL) {
        debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface);
        exit(1);
    }
    debug(LOG_NOTICE, "Detected gateway %s at %s (%s)", config->gw_interface, config->gw_address, config->gw_mac);

    /* Initializes the web server */   //在网关IP上建立web server进程,
    if ((webserver = httpdCreate(config->gw_address, config->gw_port, config->ip6)) == NULL) {
        debug(LOG_ERR, "Could not create web server: %s", strerror(errno));
        exit(1);
    }
    debug(LOG_NOTICE, "Created web server on %s:%d", config->gw_address, config->gw_port);

    /* Set web root for server */    //设置web server的根目录
    debug(LOG_DEBUG, "Setting web root: %s",config->webroot);
    httpdSetFileBase(webserver,config->webroot);
   //在web server加入imagesdir目录,
    /* Add images files to server: any file in config->imagesdir can be served */
    debug(LOG_DEBUG, "Setting images subdir: %s",config->imagesdir);
    httpdAddWildcardContent(webserver,config->imagesdir,NULL,config->imagesdir);
    //在web server加入pagesdir目录
    /* Add pages files to server: any file in config->pagesdir can be served */
    debug(LOG_DEBUG, "Setting pages subdir: %s",config->pagesdir);
    httpdAddWildcardContent(webserver,config->pagesdir,NULL,config->pagesdir);


    debug(LOG_DEBUG, "Registering callbacks to web server");
       //增加web server输入的网页内容
    httpdAddCContent(webserver, "/", "", 0, NULL, http_nodogsplash_callback_index);
    httpdAddCWildcardContent(webserver, config->authdir, NULL, http_nodogsplash_callback_auth);       //增加web server的授权功能
    httpdAddCWildcardContent(webserver, config->denydir, NULL, http_nodogsplash_callback_deny);       //增加web server的deny
    httpdAddC404Content(webserver, http_nodogsplash_callback_404);
                                    //增加404页面
    /* Reset the firewall (cleans it, in case we are restarting after nodogsplash crash) */

    fw_destroy();          //清空所有的iptables防火墙配置
    /* Then initialize it */
    debug(LOG_NOTICE, "Initializing firewall rules");
    if( fw_init() != 0 ) {       //初始化iptables的防火墙设置
        debug(LOG_ERR, "Error initializing firewall rules! Cleaning up");
        fw_destroy();           //初始化失败则清空
        debug(LOG_ERR, "Exiting because of error initializing firewall rules");
        exit(1);
    }

    /* Start client statistics and timeout clean-up thread */
    //启动客户服务统计和时间轮询的线程
    result = pthread_create(&tid_client_check, NULL, thread_client_timeout_check, NULL);
    if (result != 0) {
        debug(LOG_ERR, "FATAL: Failed to create thread_client_timeout_check - exiting");
        termination_handler(0);
    }
    pthread_detach(tid_client_check);

    /* Start control thread */
    //启用ndsctl的控制进程
    result = pthread_create(&tid, NULL, thread_ndsctl, (void *)safe_strdup(config->ndsctl_sock));
    if (result != 0) {
        debug(LOG_ERR, "FATAL: Failed to create thread_ndsctl - exiting");
        termination_handler(0);
    }
    pthread_detach(tid);

    /*
     * Enter the httpd request handling loop
     */
    debug(LOG_NOTICE, "Waiting for connections");
    created_httpd_threads = 0;
    current_httpd_threads = 0;
    while(1) {           //进入nodog的主循环
        r = httpdGetConnection(webserver, NULL);   //连接到web server,并返回request结构体

        /* We can't convert this to a switch because there might be
         * values that are not -1, 0 or 1. */
        if (webserver->lastError == -1) {
            /* Interrupted system call */
            continue; /* continue loop from the top */
        } else if (webserver->lastError < -1) {
            /*
             * FIXME
             * An error occurred - should we abort?
             * reboot the device ?
             */
            debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError);
            termination_handler(0);
        } else if (r != NULL) {     //如果连接web server获取的request不为空,则进入网络服务线程中
            /*
             * We got a connection
             *
             * We create another thread to handle the request,
             * possibly sleeping first if there are too many already
             */
            debug(LOG_DEBUG,"%d current httpd threads.", current_httpd_threads);
            if(config->decongest_httpd_threads && current_httpd_threads >= config->httpd_thread_threshold) {
                msec = current_httpd_threads * config->httpd_thread_delay_ms;
                wait_time.tv_sec = msec / 1000;
                wait_time.tv_nsec = (msec % 1000) * 1000000;
                debug(LOG_INFO, "Httpd thread creation delayed %ld sec %ld nanosec for congestion.",
                      wait_time.tv_sec, wait_time.tv_nsec);
                nanosleep(&wait_time,NULL);
            }
            thread_serial_num_p = (int*) malloc(sizeof(int)); /* thread_httpd() must free */
            pthread_mutex_lock(&httpd_mutex);
            *thread_serial_num_p = created_httpd_threads;
            created_httpd_threads++;
            pthread_mutex_unlock(&httpd_mutex);
            debug(LOG_INFO, "Creating httpd request thread %d for %s", *thread_serial_num_p, r->clientAddr);
            /* The void**'s are a simulation of the normal C
             * function calling sequence. */
            params = safe_malloc(3 * sizeof(void *)); /* thread_httpd() must free */
            *params = webserver;
            *(params + 1) = r;
            *(params + 2) = thread_serial_num_p;   
           //创建web server的服务线程
            result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);
            if (result != 0) {
                debug(LOG_ERR, "FATAL: pthread_create failed to create httpd request thread - exiting...");
                termination_handler(0);
            }
            pthread_detach(tid);
        } else {
            /* webserver->lastError should be 2 */
            /* XXX We failed an ACL.... No handling because
             * we don't set any... */
        }
    }

    /* never reached */
}

/** Main entry point for nodogsplash.
 * Reads the configuration file and then starts the main loop.
 */   //nodog的主函数,读取配置文件,各项初始化,然后进入main_loop中
int main(int argc, char **argv)
{
    s_config *config = config_get_config();
    config_init();

    parse_commandline(argc, argv);

    /* Initialize the config */
    debug(LOG_NOTICE,"Reading and validating configuration file %s", config->configfile);
    config_read(config->configfile);
    config_validate();

    /* Initializes the linked list of connected clients */
    client_list_init();

    /* Init the signals to catch chld/quit/etc */
    debug(LOG_NOTICE,"Initializing signal handlers");
    init_signals();

    if (config->daemon) {

        debug(LOG_NOTICE, "Starting as daemon, forking to background");

        switch(safe_fork()) {
        case 0: /* child */   //创建进程,父进程退出,子进程成为孤儿进程,被init进程收养,进入main_loop循环
            setsid();
            main_loop();
            break;

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

    return(0); /* never reached */
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值