正文
程序启动过程
初始化运行参数--config_init()
读取配置文件--config_read(config->configfile)
检查关键参数是否存在--config_validate()--网关接口--认证服务器IP
初始化用户链表--client_list_init()
注册信号--由wdctl线程发送信号,重启等待--init_signals()
进入主函数运行--main_loop()
初始化启动时间--started_time = time(NULL)
根据接口获得网关的IP地址,并保存到运行参数中--config->gw_address = get_iface_ip(config->gw_interface)
根据接口获得网关的MAC,并保存到运行参数中config->gw_id = get_iface_mac(config->gw_interface)
初始化web服务器,创建socket,用来监听新用户上线, webserver = httpdCreate(config->gw_address, config->gw_port)
调用httpdAddCContent(),创建一个链表,链表的每个节点包含一个用户与认证服务器交互的每个步骤的标识与对应的处理函数,主要为http_callback_auth()函数,这是用户上线的认证函
数,由thread_httpd线程调用
重置,删除现有防火墙(iptables)规则--fw_destroy()
初始化防火墙规则,重定向所有流量至本机端口,去往服务器的流量允许,并添加一些自定义的链--fw_init()
启动用户超时、失效检测线程--thread_client_timeout_check
启动交互、控制线程,控制程序重启等---thread_wdctl
启动与认证服务器的保活线程--thread_ping
之后程序进入while(1)循环监听新用户上线,监听到用户上线后启动--thread_httpd线程处理,程序继续回去监听
新用户上线过程
第一步
主进程调用httpdGetConnection()函数监听用户上线--阻塞在这里,监听到用户后函数会将用户的IP等信息一并打包交给thread_httpd线程进行处理。
thread_httpd线程中httpdProcessRequest()为处理用户上线的主函数。
函数会读取用户socket中的数据,检查读取到的内容,如果读取的内容当中没有之前在httpdCContent链表中添加的关键字----_httpd_findContentDir()为空(这个关键字是用户与服务器交互后被服务器重定向回来后所携带的标识),说明这个用户还没有与服务器进行过交互,那么就将这个用户重定向至认证服务器--_httpd_send404() -> 执行这个allback函数--http_callback_404()
http_callback_404()是httpdCContent链表的节点中存储的函数指针--(server->handle404->function)(server,r)作用是根据用户URL中分析到的关键字来匹配不同的节点,从而执行不同的函数。
重定向的参数:调用http_callback_404()中的http_send_redirect_to_auth(),设置服务器IP、端口、网址、--sprintf(&url,"%s://%s:%d%s%s",protocol,auth_server->authserv_hostname,port,auth_server->authserv_path,urlFragment)
到这里新用户接待的任务已完成,剩下的就是等待用户与认证服务器交互完之后被重定向回来,而主程序已经回去继续监听了。
第二步
用户与服务器交互之后被重定向回来,被主程序监听到,于是继续将这个socket拿来处理,而程序不知道这个是已经与服务器交互过的用户,还是会进行与上面同样的数据检查。这一次发现在数据中检查到了关键的标识,也就是httpdContent链表中添加的内容(被服务器重定向回来携带的是auth、token字串),于是执行对应节点的函数(通过函数指针),程序会执行http_callback_auth()函数。
执行http_callback_auth()函数后,检查用户URL中携带的token--httpdGetVariableByName(r, "token"),这是认证服务器给用户分配的标识符,认证成功的用户URL中才会携带,检查到这个关键字说明用户合法,然后添加这个用户到用户链表--client_list_append(r->clientAddr, mac, token->value),但此时流量还没有放行。添加用户时会取出用户的MAC,这是从内核/proc/net/arp文件根据IP查找到的。
第三步
添加完用户之后,程序会执行authenticate_client(request *r),拿着这个用户的信息去认证服务器比较,根据服务器返回的代码执行相应的动作--switch(auth_response.authcode)AUTH_ALLOWED或者AUTH_DENIED。验证失败时会再次将用户重定向至服务器,验证通过时会放行用户流量--fw_allow(client->ip, client->mac, FW_MARK_PROBATION),也就是在iptables的mangle表中添加一条以这个用户的mac为源的记录,对他的流量进行打标,后面的策略在检查到这个标识后就会放行。操作语句--iptables_do_command("-t mangle -A "TABLE_WIFIDOG_OUTGOING " -s %s -m mac --mac-source %s -j MARK --set-mark%d",ip,mac,tag),至此新用户上线工作全部完成。
用户定期检查
wifidog在之前已经启动了thread_client_timeout_check--用户检查线程,这个线程会定期检查用户链表中的所有用户,检查他们的上线时间是否已经超时、认证码是否仍然有效,以决定是否将用户踢出。这个线程是一个while(1)循环,它会从之前的配置参数)中读取设置的超时时间---config_get_config()->checkinterval--以秒为单位,来确定用户检查线程工作的周期(执行fw_sync_with_authserver()函数的周期)--pthread_cond_timedwait(&cond,&cond_mutex, &timeout)。
这个线程中用fw_sync_with_authserver()函数来完成用户检查的主要任务。
先调用iptables_fw_counters_update()更新本地链表中用户的进、出字节数(流量),这是从iptables输出重定向得来的--safe_asprintf(&script,"%s %s", "iptables", "-v -n -x -t mangle -L "TABLE_WIFIDOG_OUTGOING)。
之后将每个用户的ip、mac、token、流量等传给auth_server_request()函数,由它发送给服务器,并返回用户当前的有效状态。
程序先检查用户是否已经超时,如果已经超时那么即使从服务器返回的状态有效也要踢出,并通知认证服务器删除用户。如果用户没有超时,则根据返回的有效位进行处理--switch (authresponse.authcode)--继续或者踢出。
iptables操作
程序启动后对iptables进程初始化。
调用fw_destroy()->iptables_fw_destroy()函数,在mangle、nat、filter表的PREROUTING链、POSTROUTING链、FORWARD链中查找是否有我们要自定义的规则名,有的话就删除,以免冲突。确保不会冲突之后就开始初始化--fw_init()。
fw_init()检查参数中的本地接口、端口号,检查通过后建立wifidog自定义的规则。
建立自定义规则名,然后将这些规则名作为-j target插入到系统默认链中,让它来引用。
mangle表
建立以下规则
iptables -t mangle -N TABLE_WIFIDOG_TRUSTED
iptables -t mangle -NTABLE_WIFIDOG_OUTGOING
iptables -t mangle -NTABLE_WIFIDOG_INCOMING
使用PREROUTING链引用建立的规则,
iptables -t mangle -I PREROUTING 1 -iconfig->gw_interface -j TABLE_WIFIDOG_OUTGOING
所有数据进入网关后第一个执行的规则,从这个PREROUTING链开始。
这个命令在作用:从config->gw_interface接口(网关的内网接口)进入的数据都去跳转到这个自定义链里(TABLE_WIFIDOG_OUTGOING)去匹配里面的规则。
链中的规则命令如下:
iptables -t mangle -ATABLE_WIFIDOG_OUTGOING -s %s -m mac --mac-source %s -j MARK --set-mark %d
这些规则的作用:如果查找到有对应的mac,就对这个mac的流量进行标记--- mark %d,之后filter表中检查到这个标记就会放行这个流量。
所以用户认证通过后会在这个自定义链中插入一条记录。
iptables -t mangle -I POSTROUTING 1 -oconfig->gw_interface -j TABLE_WIFIDOG_INCOMING
--去往这个接口的流量都去这个规则->TABLE_WIFIDOG_INCOMING(这个接口是网关的内网接口,即从外网回来的流量、用户下载的数据等等),没有太多限制
iptables -t mangle -A TABLE_WIFIDOG_TRUSTED-m mac --mac-source p->mac -j MARK --set-mark %d
--最后插入p列表中的mac,相当于白名单。
nat表
建立了一下自定义规则
iptables -t nat -N TABLE_WIFIDOG_OUTGOING
iptables -t nat -NTABLE_WIFIDOG_WIFI_TO_ROUTER
iptables -t nat -NTABLE_WIFIDOG_WIFI_TO_INTERNET
iptables -t nat -N TABLE_WIFIDOG_GLOBAL
iptables -t nat -N TABLE_WIFIDOG_UNKNOWN
iptables -t nat -NTABLE_WIFIDOG_AUTHSERVERS
有流量到来后开始过滤:
iptables -t nat -A PREROUTING -iconfig->gw_address -j TABLE_WIFIDOG_OUTGOING
--从内网来的流量去这个自定义链进行匹配->所有内网流量被定向到这个链开始一级一级过滤
iptables -t nat -A TABLE_WIFIDOG_OUTGOING-d config->gw_address -j TABLE_WIFIDOG_WIFI_TO_ROUTER
--去往网关的流量都去这个链进行匹配->被放行
iptables -t nat -ATABLE_WIFIDOG_WIFI_TO_ROUTER -j ACCEPT
--流量都被接收--这个规则被前面引用,去往网关的流量
iptables -t nat -A TABLE_WIFIDOG_OUTGOING-j TABLE_WIFIDOG_WIFI_TO_INTERNET
--剩下的都不是去往网关的流量,跳转到这个链进行匹配
iptables -t nat -ATABLE_WIFIDOG_WIFI_TO_INTERNET -m mark --mark 0x%u -j ACCEPT
--被标记为0x%u的流量都被放行,这里的流量都已经经过了mangle表的PREROUTING,所以合法用户都被标记过。
iptables -t nat -ATABLE_WIFIDOG_WIFI_TO_INTERNET -j TABLE_WIFIDOG_UNKNOWN
--没被标记的跳转这个链
iptables -t nat -A TABLE_WIFIDOG_UNKNOWN -ptcp --dport 80 -j REDIRECT --to-ports gw_port
--这个链的动作时重定向,剩下的目标为80端口的流量被重定向到本机的gw_port,wifodog在这里监听
filter表
建立以下自定义规则
iptables -t filter -NTABLE_WIFIDOG_WIFI_TO_INTERNET
iptables -t filter -NTABLE_WIFIDOG_AUTHSERVERS
iptables -t filter -N TABLE_WIFIDOG_LOCKED
iptables -t filter -N TABLE_WIFIDOG_GLOBAL
iptables -t filter -NTABLE_WIFIDOG_VALIDATE
iptables -t filter -N TABLE_WIFIDOG_KNOWN
iptables -t filter -N TABLE_WIFIDOG_UNKNOWN
主要是检查流量中的mark,决定放行还是拒绝。
iptables -t filter -I FORWARD -iconfig->gw_interface -j TABLE_WIFIDOG_WIFI_TO_INTERNET
--从内网接口来的所有流量去往这个链进行匹配
iptables -t filter -ATABLE_WIFIDOG_WIFI_TO_INTERNET -m state --state INVALID -j DROP
--首先丢弃所有状态无效的流量
iptables -t filter -A TABLE_WIFIDOG_WIFI_TO_INTERNET-o ext_interface -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
--去往ext_interface接口且tcp状态为SYN,RST(发起连接,连接重置)的流量去往这个链--返回TCPMSS
iptables -t filter -ATABLE_WIFIDOG_WIFI_TO_INTERNET -j TABLE_WIFIDOG_AUTHSERVERS
--其他流量去往这个链->认证服务器
iptables -t filter -ATABLE_WIFIDOG_AUTHSERVERS -d auth_server->last_ip -j ACCEPT
iptables -t nat -ATABLE_WIFIDOG_AUTHSERVERS -d auth_server->last_ip -j ACCEPT
--目的地为认证服务器的流量被放行--其他流量继续往下匹配
iptables -t filter -ATABLE_WIFIDOG_WIFI_TO_INTERNET -m mark --mark 0x%u -j TABLE_WIFIDOG_LOCKED
--被标记为FW_MARK_LOCKED的流量跳转到这个链匹配--没匹配到的往下继续
iptables -t filter -ATABLE_WIFIDOG_WIFI_TO_INTERNET -j TABLE_WIFIDOG_GLOBAL
iptables -t filter -ATABLE_WIFIDOG_WIFI_TO_INTERNET -m mark --mark 0x%u -j TABLE_WIFIDOG_VALIDATE
iptables -t filter -A TABLE_WIFIDOG_UNKNOWN-j REJECT --reject-with icmp-port-unreachable
--所有流量到这里还没有符合的条件,被这链则统一REJECT