2015年4月

nodogsplash认证之ndsctl工具

ndsctl是通过unix socket与nodogsplash之间通过socket来实现进程之间的通信。ndsctl的主要作用如下:
nodogsplash_1.png

其中上面的的参数中,具有操作功能的参数,主要是用来对特定的MAC和IP地址进行操作,操作的结果就是通过iptables建立不同的数据包过滤机制来达到对用户的访问控制。如下:

~ # ./ndsctl deauth 192.168.197.11
Client 192.168.197.11 deauthenticated.

nodogsplash_2.png

配置文件:
其配置文件为:nodogsplash.conf,其中有些很重的参数:目前我使用的有:

RedirectURL http://192.168.197.1/www/index.html  此配置参数说明在进行认证通过之后将要跳转的页面。
GatewayPort 9999 用来进行监听http清楚的socket端口
PasswordAuthentication yes Password 123 表示在进行认证的时候需要输入密码,密码为:123
UsernameAuthentication yes Username root 表示在进行认证的时候需要输入用户名,用户名为:root。
在代码中函数http_nodogsplash_check_userpass(request *r, t_auth_target *authtarget) 来完成对输入的用户名和密码

的验证工作,验证与否是与上面的参数的配置相关的,在代码中有如下的判断:

  if(!config->passwordauth && !config->usernameauth) {
    /* Not configured to use username/password check; can't fail. */
    return 1;
  }

当在配置文件中没有配置的时候,不进行验证。
同时还有一个比较重要的函数:

/* Allocate and return a pointer to a string that is the redirect URL. Caller must free.
 */
char* http_nodogsplash_make_redir(char* redirhost, char* redirpath) {
  s_config *config;
  char* redir;
  config = config_get_config();
  if(config->redirectURL) 
  {
  safe_asprintf(&redir,"%s?to=%s%s",config->redirectURL,redirhost,redirpath);
  debug(LOG_DEBUG,"Redirect request http://%s%s, substituting %s",redirhost,redirpath,config->redirectURL);
    printf("Redirect address:%s\n",redir);
//redir = safe_strdup(config->redirectURL);
  }
  else
  {
    /* We just assume protocol http; after all we caught the client by
       redirecting port 80 tcp packets
    */
    safe_asprintf(&redir,"http://%s%s",redirhost,redirpath);
debug(LOG_DEBUG,"http_nodogsp lash_make_redir:no define redirectURL in config, redir= %s ",redir);
  }
  return redir;
}

上面的代码仔细的看一下。

测试
当用户在PC2的浏览器中访问:http://192.168.100.244的时候,nodogsplash中的调试信息如下:
nodogsplash_3.png

上面的日志信息中,首先可以看到:nodogsplash收到了一个来自192.168.197.11的连接请求,同时从调试信息中可以看到192.168.197.11访问的request是:192.168.100.244。之后从/proc/net/arp中取出192.168.197.11对于的MAC地址。将其对于的ip地址和MAC添加到用户访问列表中来实现对此用户进行监控的目的。
上面serving splash page /etc/nodogsplash/htdocs/splash.html to 192.168.197.11的意思当访问192.168.100.244的时候将显示splash.html 。使用的图标为:/images/wifidog.png。
执行的效果如下;
nodogsplash_4.png

当点击上面的图标时,会自动跳转到:http://192.168.100.244。当IP:192.168.197.11第一次访问外网的时候,会让用户进行确认,确认之后在默认的时候之内,如果用户再次访问外网服务时,不会再次出现此确认窗口。
当执行下面的命令的时候:

~ # ./ndsctl deauth 192.168.197.11
Client 192.168.197.11 deauthenticated.

nodogsplash中的调试信息如下:
nodogsplash_5.png

从上面的信息可以看出,当执行ndsctl相关的命令时,ndsctl会与nodogsplash建立一个socket连接。当nodogsplash收到ndsctl的请求之后创建一个thread来处理此请求,上面出现了俩条iptables命令,
第一条:删除mangle表中的ndsOUT规则,就不再对满足192.168.197.11的数据包进行进行MARK操作。
第二条:不再accept ,192.168.197.11的数据包。]

本文章由 http://www.wifidog.pro/2015/04/22/nodogsplash%E8%AE%A4%E8%AF%81ndsctl.html 整理编辑,转载请注明出处

nodogsplash认证portal 第三方工具nodogsplash 使用分析

0:测试场景
nodogsplash.png

上面是测试使用的环境,其中网关设备是双网卡设备,其中eth0用作外网接口,br0用作内网接口。PC1和PC2分别为外网和内网设备。PC1上面建立web服务器。Home Web Server。最终实现效果是,当PC2上面的主机访问:192.168.100.244的时候,nodogsplash首先将此web页面redirect到nodogsplash内置的页面当中,当用户点击图标之后nodogsplash有会转向最初的访问页面192.168.100.244.。实现了对用户的访问控制。
在网关设备上面的IP地址分配如下:
nodogsplash-2.png

由于此模型中使用是三层模式,即内网和外网之间的通信使用3层协议,必须对在内网访问外网的时候进行SNAT的转化。在网关设备上面配置如下iptables 命令:

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

echo 1 >/proc/sys/net/ipv4/ip_forward
上面的命令是内网访问外网的时候对eth0接口上面的数据包做SNAT。ping包的的SNAT格式如下:
nodogsplash-3.png

1: nodogsplash编译
在编译前更改一下代码,在测试的时候发现,不能正确的获取IP地址对于的MAC地址。重写arp_get()函数。
nodogsplash-4.png

root@ocalhost /wlan/portal/nodogsplash-0.9_beta9.9]$分别执行:

export echo=echo

./configure CC=mips-linux---prefix=/wlan/portal/buildroot

make

make install

之后会将编译后的结果安装到/wlan/portal/buildroot目录下,
nodogsplash-5.png

2:nodogsplash运行

在运行nodogsplash之后,系统会创建一下四个线程:

Gateway.c (z:\wlan\portal\nodogsplash-0.9_beta9.9\src):      result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL);
Gateway.c (z:\wlan\portal\nodogsplash-0.9_beta9.9\src):      result = pthread_create(&tid, NULL, (void *)thread_ndsctl, (void *)safe_strdup(config->ndsctl_sock));
Gateway.c (z:\wlan\portal\nodogsplash-0.9_beta9.9\src):      result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params);
Ndsctl_thread.c (z:\wlan\portal\nodogsplash-0.9_beta9.9\src):      result = pthread_create(&tid, NULL, &thread_ndsctl_handler, (void *)fd);

由于在低版本的线程库中,把一个thread当做进程process来处理,故在中断可以看到如下信息:

/tmp # ps
PID   USER     TIME   COMMAND
    1 root       0:03 init
    2 root       0:00 [kthreadd]
    3 root       0:00 [ksoftirqd/0]
    4 root       0:01 [events/0]
    5 root       0:00 [khelper]
    6 root       0:00 [async/mgr]
    7 root       0:00 [kblockd/0]
    8 root       0:00 [pdflush]
    9 root       0:00 [pdflush]
   10 root       0:00 [kswapd0]
   11 root       0:00 [crypto/0]
   32 root       0:00 [mtdblockd]
   44 root       0:01 [jffs2_gcd_mtd3]
  266 root       0:00 /usr/sbin/telnetd
  285 root       0:00 -sh
  314 root       0:10 ./nodogsplash -c nodogsplash.conf -d 7 -f
  475 root       0:00 ./nodogsplash -c nodogsplash.conf -d 7 -f
  476 root       2:00 ./nodogsplash -c nodogsplash.conf -d 7 -f
  479 root       0:00 ./nodogsplash -c nodogsplash.conf -d 7 -f
 2565 root       0:00 -sh
 2704 root       0:00 -sh
 2710 root       0:00 -sh
 2787 root       0:00 -sh
 3478 root       0:00 ps

上面四个线程对于四个进程。
当nodogsplash 运行之后,会在br0上面建立socket server端。
nodogsplash-6.png

同时设定 web root的目录:

/etc/nodogsplash/htdocs # ls
images         infoskel.html  splash.html。 

3:nodogsplash创建的iptable规则
nodogsplash-7.png

当以./nodogsplash -c nodogsplash.conf -d 7 -f运行时,打印很详细的调试信息。
首先看一下几个宏的定义说明:

/*@{*/ 
/**Iptable chain names used by nodogsplash */
#define CHAIN_TO_INTERNET"ndsNET"
#define CHAIN_TO_ROUTER"ndsRTR"
#define CHAIN_OUTGOING"ndsOUT"
#define CHAIN_INCOMING  "ndsINC"
#define CHAIN_AUTHENTICATED     "ndsAUT"
#define CHAIN_PREAUTHENTICATED  "ndsPRE"
#define CHAIN_BLOCKED    "ndsBLK"
#define CHAIN_ALLOWED    "ndsALW"
#define CHAIN_TRUSTED    "ndsTRU"
/*@}*/ 
/** Used by fw_iptables.c to mark packets. Unmarked packets are considered 'preauthenticated' */
typedef enum _t_fw_marks {
  FW_MARK_PREAUTHENTICATED = 0x000,  /**< @brief Actually not used as a packet mark */ 
  FW_MARK_AUTHENTICATED = 0x100,  /**< @brief The client is authenticated */ 
  FW_MARK_BLOCKED = 0x200, /**< @brief The client is blocked */
  FW_MARK_TRUSTED = 0x400,  /**< @brief The client is trusted */
  FW_MARK_MASK = 0x700 /**< @brief Mask to use with FW_MARK's */
} t_fw_marks;

从调试信息中可以取出以下iptables配置命令:

iptables -t mangle -F ndsTRU
iptables -t mangle -F ndsBLK
iptables -t mangle -F ndsALW
iptables -t mangle -F ndsOUT
iptables -t mangle -F ndsINC
iptables -t mangle -X ndsTRU
iptables -t mangle -X ndsBLK
iptables -t mangle -X ndsALW
iptables -t mangle -X ndsOUT
iptables -t mangle -X ndsINC
iptables -t nat -F ndsOUT
iptables -t nat -X ndsOUT
iptables -t filter -F ndsRTR
iptables -t filter -F ndsNET
iptables -t filter -F ndsAUT
iptables -t filter -X ndsRTR
iptables -t filter -X ndsNET 
iptables -t filter -X ndsAUT

上面的iptables命令主要是删除table:mangle,nat,filter表中的chain

iptables -t mangle -N ndsTRU
iptables -t mangle -N ndsTRU
iptables -t mangle -N ndsINC
iptables -t mangle -N ndsOUT
iptables -t mangle -I PREROUTING 1 -i br0 -s 0.0.0.0/0 -j ndsOUT
iptables -t mangle -I PREROUTING 2 -i br0 -s 0.0.0.0/0 -j ndsBLK
iptables -t mangle -I PREROUTING 3 -i br0 -s 0.0.0.0/0 -j ndsTRU
iptables -t mangle -I POSTROUTING 1 -o br0 -d 0.0.0.0/0 -j ndsINC
上面的iptables命令是在mangle表之下,新建了四个chain:ndsTRU,ndsTRU,ndsINC,ndsOUT。

之后在PREROUTING的chain中添加三个规则,POSTROUTING的chain下面创建一条规则。分别执行四个不同的目标。
在此处的目标是:chain。这个chain就是前面通过-N命令创建的chain。

/tmp # iptables -t mangle --list-rules 
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N ndsBLK
-N ndsINC
-N ndsOUT
-N ndsTRU
-A PREROUTING -i br0 -j ndsOUT
-A PREROUTING -i br0 -j ndsBLK
-A PREROUTING -i br0 -j ndsTRU
-A POSTROUTING -o br0 -j ndsINC
-A ndsINC -d 192.168.197.11/32 -j ACCEPT
-A ndsOUT -s 192.168.197.11/32 -m mac --mac-source F0:4D:A2:7D:E2:75 -j MARK --set-xmark 0x100/0xffffffff

上面显示的是rules.

iptables -t nat -N ndsOUT
iptables -t nat -I PREROUTING -i br0 -s 0.0.0.0/0 -j ndsOUT
iptables -t nat -A ndsOUT -m mark --mark 0x400 -j ACCEPT
iptables -t nat -A ndsOUT -m mark --mark 0x100 -j ACCEPT
iptables -t nat -A ndsOUT -d 0.0.0.0/0 -p tcp --dport 53 -j ACCEPT
iptables -t nat -A ndsOUT -d 0.0.0.0/0 -p udp --dport 53 -j ACCEPT //执行error
iptables -t nat -A ndsOUT -p tcp --dport 80 -j DNAT --to-destination 192.168.197.1:9999
iptables -t nat -A ndsOUT -j ACCEPT

第一条iptables命令新建一个自定义chain,这个chain在后面用来作为PREROUTING的chain的target目标来过滤数据包。其中上面的倒数第二条很重要,将目的port为80的TCP数据包的目的地址及更改为:192.168.197.1:9999.其中192.168.197.1是br0的IP地址,也即是网关的IP地址。nodogsplash在此Ip和port上面监听http的请求。


iptables -t filter -N ndsNET
iptables -t filter -N ndsRTR
iptables -t filter -N ndsAUT

新建filter表中三条自定义chain。

iptables -t filter -I INPUT -i br0 -s 0.0.0.0/0 -j ndsRTR
iptables -t filter -A ndsRTR -m mark --mark 0x200 -j DROP
iptables -t filter -A ndsRTR -m state --state INVALID -j DROP
iptables -t filter -A ndsRTR -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A ndsRTR -p tcp --tcp-flags SYN SYN --tcp-option \! 2 -j  DROP
iptables -t filter -A ndsRTR -m mark --mark 0x400 -j ACCEPT
iptables -t filter -A ndsRTR -p tcp --dport 9999 -j ACCEPT
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p udp --dport 53 -j ACCEPT //error
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p udp --dport 67 -j ACCEPT //执行error
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p tcp --dport 22 -j ACCEPT
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p tcp --dport 80 -j ACCEPT
iptables -t filter -A ndsRTR -d 0.0.0.0/0 -p tcp --dport 443 -j ACCEPT
iptables -t filter -A ndsRTR -j REJECT --reject-with icmp-port-unreachable

此处对经过接口br0数据包的过滤处理,


iptables -t filter -I FORWARD -i br0 -s 0.0.0.0/0 -j ndsNET
iptables -t filter -A ndsNET -m mark --mark 0x200 -j DROP
iptables -t filter -A ndsNET -m state --state INVALID -j DROP
iptables -t filter -A ndsNET -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
iptables -t filter -A ndsNET -m mark --mark 0x400 -j ACCEPT
-------------------------------------------------------------

iptables -t filter -A ndsNET -m mark --mark 0x100 -j ndsAUT
iptables -t filter -A ndsAUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A ndsAUT -d 192.168.0.0/16 -j REJECT
iptables -t filter -A ndsAUT -d 10.0.0.0/8 -j REJECT
iptables -t filter -A ndsAUT -d 0.0.0.0/0 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A ndsAUT -d 0.0.0.0/0 -p udp --dport 53 -j ACCEPT //执行error
iptables -t filter -A ndsAUT -d 0.0.0.0/0 -p tcp --dport 80 -j ACCEPT
iptables -t filter -A ndsAUT -d 0.0.0.0/0 -p tcp --dport 443 -j ACCEPT
iptables -t filter -A ndsAUT -d 0.0.0.0/0 -p tcp --dport 22 -j ACCEPT
iptables -t filter -A ndsAUT -j REJECT --reject-with icmp-port-unreachable

iptables -t filter -A ndsNET -d 0.0.0.0/0 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A ndsNET -d 0.0.0.0/0 -p udp --dport 53 -j ACCEPT //执行error
iptables -t filter -A ndsNET -j REJECT --reject-with icmp-port-unreachable

iptables -t mangle -A ndsOUT -s 192.168.197.11 -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x100
iptables -t mangle -A ndsINC -d 192.168.197.11 -j ACCEPT
iptables -t mangle -D ndsOUT -s 192.168.197.11 -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x100
iptables -t mangle -D ndsINC -d 192.168.197.11 -j ACCEPT

下面俩个iptables 规则的建立是,可以通过ndsctl命令中:block/unblock MAC来实现。

iptables -t mangle -A ndsBLK -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x200
iptables -t mangle -D ndsBLK -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x200

下面俩个iptables 规则的建立是,可以通过ndsctl命令中:trust/untrust MAC来实现。

iptables -t mangle -A ndsTRU -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x400
iptables -t mangle -D ndsTRU -m mac --mac-source f0:4d:a2:7d:e2:75 -j MARK --set-mark 0x400

本文章由 http://www.wifidog.pro/2015/04/22/nodogsplash%E8%AE%A4%E8%AF%81%E4%BD%BF%E7%94%A8%E5%88%86%E6%9E%90.html 整理编辑,转载请注明出处

Tomato开发视频教程4-Tomato 系统常用命令

本期视频讲解了Tomato中常见命令的用法,观看视频可以让大家在命令行使用方面有质的飞跃。

终端相关命令
Tab键按一次 Tab键按两次 clear  Ctrl+c Ctrl+d

文件管理命令
tar gzip unzip cp cd rm mv echo cat dd ln ls pwd strings grep which


编辑器命令
vi


系统相关命令
uname dmesg insmod modprobe lsmod rmmod mount umount ps top kill killall logger chmod chown sync reboot


网络相关命令
ifconfig iptables ping traceroute route wget ip nslookup brctl telnet ssh scp


Tomato特有命令
nvram mtd-erase mtd-write

土豆视频地址,在条件允许的情况下,请大家选择超清模式。

优酷视频地址,在条件允许的情况下,请大家选择超清模式或1080P。

百度网盘,视频教程原文件下载地址,无损视频适合收藏 http://pan.baidu.com/s/1dD5ylFF

大家如有路由器定制开发相关(Tomato,DD-WRT,OpenWRT,软路由)的项目欢迎大家来电或者QQ联系(非诚勿扰)
联系方式 180-135-82125(手机和QQ) 陆工

本文章由 http://www.wifidog.pro/2015/04/21/tomato%E5%BC%80%E5%8F%91%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B-tomato%E7%B3%BB%E7%BB%9F%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(4) -----连接socket的处理与超时处理

前面讲了lighttpd是怎样使用fdevent系统的,以及监听socket的处理过程。这一篇我们来看一看lighttpd是怎样处理连接socket的。
首先,我们来看看lighttpd是怎样建立和客户端的连接的。前面在讲监听socket的处理过程中其实已经讲解了连接的建立过程。lighttpd监测监听socket的IO事件,如果有可读事件发生,那么表示有新的连接请求,然后调用network.c/network_server_handle_fdevent()来处理连接请求。network_server_handle_fdevent()函数调用connections.c/connection_accept() 接受客户端的请求,建立连接。在建立连接的同时,就得到了连接socket的fd,也就是accept函数的返回值。
建立连接之后,这个连接对应的状态机的状态被设置为CON_STATE_REQUEST_START,就是开始读取客户端发过来的request信息。在从connection_accept函数跳回network_server_handle_fdevent()函数的for循环中后,程序紧接着就调用了一次connection_state_machine()函数,这个函数是根据当前连接的状态机的状态设置状态机的下一个状态,CON_STATE_REQUEST_START的下一个状态是CON_STATE_READ,这个状态表示连接正在读取客户端发送的数据。当连接的状态机被设置成CON_STATE_READ后,在connection_state_machine()函数的最后,有这样一个switch语句:

switch (con->state)
    {
    case CON_STATE_READ_POST:
    case CON_STATE_READ:
    case CON_STATE_CLOSE:
        fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
        break;
    case CON_STATE_WRITE:
        /*
         * request write-fdevent only if we really need it
         * - if we have data to write
         * - if the socket is not writable yet
         */
        if (!chunkqueue_is_empty(con->write_queue) && (con->is_writable == 0) &&(con->traffic_limit_reached == 0))
        {
            fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
        }
        else
        {
            fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
        }
        break;
    default:
        fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
        break;
    }

  上面这个switch语句将状态处在CON_STATE_READ_POST,CON_STATE_READ和CON_STATE_CLOSE的连接对应的连接socket fd加入到fdevent系统中,并监听可读事件。将处CON_STATE_WRITE状态且有数据要写的连接对应的socket fd加入到fdevent系统中,并监听可写事件。其他状态的连接则把对应的fd从fdevent系统中删除,因为这些连接不会有IO事件发生。
  这样,连接socket fd就被加入到了fdevent系统中。下面就是等待IO事件的发生。程序在前面已经提到过,如下:

if ((n = fdevent_poll(srv->ev, 1000)) > 0)
        {
            int revents;
            int fd_ndx;
            fd_ndx = -1;
            do
            {
                fdevent_handler handler;
                void *context;
                handler_t r;
                fd_ndx = fdevent_event_next_fdndx(srv->ev, fd_ndx);
                revents = fdevent_event_get_revent(srv->ev, fd_ndx);
                fd = fdevent_event_get_fd(srv->ev, fd_ndx);
                handler = fdevent_get_handler(srv->ev, fd);
                context = fdevent_get_context(srv->ev, fd);
                switch (r = (*handler) (srv, context, revents))
                {
                case HANDLER_FINISHED:
                case HANDLER_GO_ON:
                case HANDLER_WAIT_FOR_EVENT:
                case HANDLER_WAIT_FOR_FD:
                    break;
                case HANDLER_ERROR:
                    /*
                     * should never happen
                     */
                    SEGFAULT();
                    break;
                default:
                    log_error_write(srv, __FILE__, __LINE__, "d", r);
                    break;
                }
            }while (--n > 0);
        } 

  这段程序在前面已经讲解过。对于fdevent系统,它不关心自己处理的fd是连接fd还是监听fd,它所做的就是对于发生了这个fd所希望的IO事件以后,调用这个fd对应的处理函数处理IO事件。连接fd对应的处理函数是connections.c/connection_handle_fdevent()函数。函数的代码如下:

handler_t connection_handle_fdevent(void *s, void *context,int revents)
{
    server *srv = (server *) s;
    connection *con = context;
    //把这个连接加到作业队列中。
     joblist_append(srv, con);
    if (revents & FDEVENT_IN)
    {
        con->is_readable = 1;
    }
    if (revents & FDEVENT_OUT)
    {
        con->is_writable = 1;
        /*
         * we don't need the event twice
         */
    }
    if (revents & ~(FDEVENT_IN | FDEVENT_OUT))
    {
        /*
         * looks like an error 即可读又可写,可能是一个错误。
         */
        /*
         * FIXME: revents = 0x19 still means that we should read from the queue
         */
        if (revents & FDEVENT_HUP)
        {
            if (con->state == CON_STATE_CLOSE)
            {
                con->close_timeout_ts = 0;
            }
            else
            {
                /*
                 * sigio reports the wrong event here there was no HUP at all
                 */
                connection_set_state(srv, con, CON_STATE_ERROR);
            }
        }
        else if (revents & FDEVENT_ERR)
        {
            connection_set_state(srv, con, CON_STATE_ERROR);
        }
        else
        {
            log_error_write(srv, __FILE__, __LINE__, "sd","connection closed: poll() -> ???", revents);
        }
    }
    if (con->state == CON_STATE_READ|| con->state == CON_STATE_READ_POST)
    {
        connection_handle_read_state(srv, con);
 //继续读取数据,直到数据读取完毕
     }
 // 数据的写回并没有放给状态机去处理。
     if (con->state == CON_STATE_WRITE&& !chunkqueue_is_empty(con->write_queue) && con->is_writable)
    {
        if (-1 == connection_handle_write(srv, con))
        {
            connection_set_state(srv, con, CON_STATE_ERROR);
            log_error_write(srv, __FILE__, __LINE__, "ds", con->fd,"handle write failed.");
        }
        else if (con->state == CON_STATE_WRITE)
        {
            //写数据出错,记录当前时间,用来判断连接超时。
             con->write_request_ts = srv->cur_ts;
        }
    }
    if (con->state == CON_STATE_CLOSE)
    {
        /*
         * flush the read buffers 清空缓冲区中的数据。
         */
        int b;
        //获取缓冲区中数据的字节数
         if (ioctl(con->fd, FIONREAD, &b))
        {
            log_error_write(srv, __FILE__, __LINE__, "ss","ioctl() failed", strerror(errno));
        }
        if (b > 0)
        {
            char buf[1024];
            log_error_write(srv, __FILE__, __LINE__, "sdd","CLOSE-read()", con->fd, b);
            //将缓冲区中的数据读取后并丢弃,此时连接已经关闭,数据是无用数据。
             read(con->fd, buf, sizeof(buf));
        }
        else
        {
            /*
             * nothing to read 缓冲区中没有数据。复位连接关闭超时计时。
             */
            con->close_timeout_ts = 0;
        }
    }
    return HANDLER_FINISHED;
}

  可以看到,connection_handle_fdevent()函数根据当前连接fd所发生的IO事件,对connection结构体中的标记变量赋值,如is_writable,is_readable等,并做一些时间的记录。这些事件所对应的真正的IO处理则交给状态机处理。状态机根据这些标记变量进行相应的动作处理。
  这样,对于fdevent系统对于一次连接fd的IO事件就处理结束了。当然,真正的处理工作是由状态机来完成。下面的图简要的描述了fdevent系统对连接fd和监听fd的处理:
1.jpg

  下面我们来看一看连接超时的处理。连接超时有三种:读数据超时,写数据超时和关闭超时。处理超时的代码在server.c中的main函数woker进程开始部分:

/**
         * alarm函数发出的信号,表示一秒钟已经过去了。
         */
        if (handle_sig_alarm)
        {
            /*
             * a new second  新的一秒开始了。。。
             */
#ifdef USE_ALARM
            /*
             * reset notification 重置
             */
            handle_sig_alarm = 0;
 #endif
            /*
             * get current time  当前时间。精确到一秒
             */
            min_ts = time(NULL);
            /**
             * 这里判断和服务器记录的当前时间是否相同。
             * 相同,则表示服务器还在这一秒中,继续处理请求等。
             * 如果不相同,则进入了一个新的周期(当然周期是一秒)。这就要做一些触发和检查以及清理的动作。
             * 如插件的触发连接的超时清理状态缓存等。
             * 其中,最主要的工作是检查连接的超时。
             */
            if (min_ts != srv->cur_ts)
            {
                int cs = 0;
                connections *conns = srv->conns;
                handler_t r;
                switch (r = plugins_call_handle_trigger(srv))
                {
                    case HANDLER_GO_ON:
                        break;
                    case HANDLER_ERROR:
                        log_error_write(srv, __FILE__, __LINE__, "s","one of the triggers failed");
                        break;
                    default:
                        log_error_write(srv, __FILE__, __LINE__, "d", r);
                        break;
                }
                /*
                 * trigger waitpid
                 */
                srv->cur_ts = min_ts;
                /*
                 * cleanup stat-cache 清理状态缓存。每秒钟清理一次。
                 */
                stat_cache_trigger_cleanup(srv);
                /**
                 * check all connections for timeouts
                 */
                for (ndx = 0; ndx < conns->used; ndx++)
                {
                    int changed = 0;
                    connection *con;
                    int t_diff;



                    con = conns->ptr[ndx];

                    //连接的状态是在读
                     if (con->state == CON_STATE_READ|| con->state == CON_STATE_READ_POST)
                    {
                        if (con->request_count == 1) //连接处理一个请求
                         {
                            if (srv->cur_ts - con->read_idle_ts >con->conf.max_read_idle)
                            {
                                /*
                                 * time - out
                                 */
                                connection_set_state(srv, con, CON_STATE_ERROR);
                                changed = 1;
                            }
                        }  //这个连接同时处理多个请求
                         else
                        {
                            if (srv->cur_ts - con->read_idle_ts> con->conf.max_keep_alive_idle)
                            {
                                /*
                                 * time - out
                                 */
                                connection_set_state(srv, con, CON_STATE_ERROR);
                                changed = 1;
                            }
                        }
                    }
                    //连接的状态是写
                     if ((con->state == CON_STATE_WRITE)&& (con->write_request_ts != 0))
                    {
                        if (srv->cur_ts - con->write_request_ts> con->conf.max_write_idle)
                        {
                            /*
                             * time - out
                             */
#if 1
                            log_error_write(srv, __FILE__, __LINE__,"sbsosds", "NOTE: a request for",
                                            con->request.uri, "timed outafter writing", con->bytes_written, "bytes. We waited",
                                        (int) con->conf. max_write_idle,
                                            "seconds. If this a problemincrease server.max-write-idle");
#endif
                            connection_set_state(srv, con, CON_STATE_ERROR);
                            changed = 1;
                        }
                    }

                    /*
                     * we don't like div by zero 防止除0。。。
                     */
                    if (0 ==(t_diff = srv->cur_ts - con->connection_start))
                            t_diff = 1;

                    /**
                     * 下面的if语句不是用来判断连接是否超时。
                     * lighttpd对每个连接设置了一个kbytes_per_second,这个变量设定每个连接在一秒钟内多能传输的最大数据量。
                     * 如果传送的数据大于这个值,那么这个连接将停止传输数据,被追加到作业队列中等待下一次处理。
                     * 作者这样做估计是为了平衡各个连接之间的数据传输。
                     */
                    if (con->traffic_limit_reached && (con->conf.kbytes_per_second == 0|| ((con->bytes_written / t_diff)< con->conf.kbytes_per_second * 1024)))
                    {
                        /*
                         * enable connection again
                         */
                        con->traffic_limit_reached = 0;
                        changed = 1;
                    }

                    if (changed)
                    {
                        connection_state_machine(srv, con);
                    }
                    con->bytes_written_cur_second = 0;
                    *(con->conf.global_bytes_per_second_cnt_ptr) = 0;
                }//end of for( ndx = 0; ...
                if (cs == 1)
                    fprintf(stderr, "\n");
            }//end of if (min_ts != srv->cur_ts)...
        }//end of if (handle_sig_alarm)...

在这个If语句中,作者的本意是通过alarm信号来判断时间是否到一秒种。handle_sig_alarm就是标记是否已经过了一秒钟。在server.c的信号处理函数sigaction_handler()中可以看到:

case SIGALRM:     //超时信号
         handle_sig_alarm = 1;
         break;

当收到SIGALRM信号时,标记handle_sig_alarm为1。
下面的代码是启动计时器。两段代码都被宏包围。说明需要定义宏USE_ALARM才启动计时器。

#ifdef USE_ALARM
    struct itimerval interval;
    interval.it_interval.tv_sec = 1;
    interval.it_interval.tv_usec = 0;
    interval.it_value.tv_sec = 1;
    interval.it_value.tv_usec = 0;
#endif
#ifdef USE_ALARM
    signal(SIGALRM, signal_handler);
    if (setitimer(ITIMER_REAL, &interval, NULL))
    {
        log_error_write(srv, __FILE__, __LINE__, "s", "setting timer failed");
        return -1;
    }
    getitimer(ITIMER_REAL, &interval);
#endif

下面寻找宏USE_ALARM的定义。仍然在server.c文件中:

/*
 * IRIX doesn't like the alarm based time() optimization
 */
/*
 * #define USE_ALARM
 */

  不过,这个唯一的定义被注释掉了。。。
  那么,也就是说,作者并没有使用计时器产生SIGALRM信号来判断时间是否过了一秒。其实,上面处理连接超时的代码中,作者通过判断当前时间和服务器记录的当前时间来判断时间是否过了一秒。如果两个时间不一样,那么时间就过了一秒。不使用SIGALRM信号,可以减少很多信号处理,降低程序的复杂度。没有使用SIGALRM信号,那么handle_sig_alarm就一直是1。子进程每循环一次都要比较服务器记录的时间和当前时间。
  下面继续看超时处理。在上面的处理程序中,lighttpd通过比较read_idle_ts,write_request_ts和当前时间的差值来判断连接是否读超时或写超时。如果这两个差值分别大于max_read_idle和max_write_idle则表示超时。如果一个连接正在处理多个请求时,读超时是和max_keep_alive_idle比较。这些上限值在配置中设置。
  那么,read_idle_ts和write_request_ts又是记录的什么呢?
  对于read_idle_ts,在连接进入CON_STATE_REQUEST_START状态时,记录了当前时间。如果连接长时间没有去读取request请求,则也表示连接超时。当连接开始读数据时,read_idle_ts记录开始读数据的时间。这个不多说了。
  对于write_request_ts,在处理CON_STATE_WRITE状态时,有对其赋值的语句。在connection_handle_fdevent函数中也有。其实,都是在调用connection_handle_write函数出错并且连接处在CON_STATE_WRITE状态时,记录当前时间。
  通过这两个变量可以看出,lighttpd对读和写的超时处理是不一样的。对于读,设定了最长时间,不管读多少数据,一旦时间超了就算超时。而对于写,只有在写出错的时候才开始计算超时。如果没有出错,那么写数据花再多的时间也不算超时。这就有一个问题了,如果客户端上传的数据很多呢?这样没上传完就有可能被判断为超时。其实,lighttpd做为一个web服务器,其假设上传的数据都是有限的。在绝大多数情况下,上传数据都是很小的,也就是http头等,而下载的数据往往很多。因此,这样处理可以提高效率。如果需要上传大量数据,可以修改配置中的超时限制。(PS:这点不太确定,望高手讲解。)
  lighttpd每过一秒钟就要轮询连接,检查是否超时。如果连接很多时,这将浪费大量的时间。虽然这样很低效,但是处理简单,程序复杂度低。在真正的使用中,效率也没有想像中的那么差。
  至此,lighttpd的fdevent系统就介绍完毕了。从下一篇开始,我们将走进lighttpd的状态机。

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