wifidog源碼分析 - 客戶端檢測(cè)線程

引言

當(dāng)wifidog啟動(dòng)時(shí)挨务,會(huì)啟動(dòng)一個(gè)線程(thread_client_timeout_check)維護(hù)客戶端列表疗垛,具體就是wifidog必須定時(shí)檢測(cè)客戶端列表中的每個(gè)客戶端是否在線梗肝,而wifidog是通過兩種方式進(jìn)行檢測(cè)客戶端在線情況之斯,一種是定時(shí)通過iptables獲取客戶端出入總流量更新客戶端時(shí)間潘悼,通過最近更新時(shí)間進(jìn)行判斷(有新的出入流量則更新客戶端時(shí)間齐唆,之后使用最新客戶端時(shí)間與當(dāng)前時(shí)間判斷)嗤栓,一種是查詢認(rèn)證服務(wù)器,通過認(rèn)證服務(wù)器的返回信息進(jìn)行判斷(將客戶端IP和狀態(tài)請(qǐng)求發(fā)送給認(rèn)證服務(wù)器,認(rèn)證服務(wù)器會(huì)返回客戶端是否在線,這種情況是用于客戶端是在認(rèn)證服務(wù)器上正常登出)。

thread_client_timeout_check

此線程執(zhí)行函數(shù)用于維護(hù)客戶端列表何陆,此線程是一個(gè)while (1)循環(huán)咒钟,每隔一個(gè)配置文件中的checkinterval時(shí)間間隔執(zhí)行一次fw_sync_with_authserver函數(shù)跪者,核心代碼處于fw_sync_with_authserver函數(shù)中,我們先具體代碼,

void

thread_client_timeout_check(const void *arg)

{

pthread_cond_t? ? ? ? cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_t? ? ? ? cond_mutex = PTHREAD_MUTEX_INITIALIZER;

struct? ? timespec? ? timeout;

while (1) {

/* 設(shè)置超時(shí)時(shí)間 */

timeout.tv_sec = time(NULL) + config_get_config()->checkinterval;

timeout.tv_nsec = 0;

/* 使用pthread_cond_timedwait必須先上鎖 */

pthread_mutex_lock(&cond_mutex);

/* 等待超時(shí) */

pthread_cond_timedwait(&cond, &cond_mutex, &timeout);

/* 解鎖 */

pthread_mutex_unlock(&cond_mutex);

debug(LOG_DEBUG, "Running fw_counter()");

/* 執(zhí)行核心代碼 */

fw_sync_with_authserver();

}

}

fw_sync_with_authserver

此函數(shù)是此線程的核心函數(shù),維護(hù)客戶端列表就在此中钮呀,其首先會(huì)遍歷客戶端列表,通過iptables獲取每個(gè)客戶端列表的出入流量昨凡,之后根據(jù)出口流量(入口流量不做判斷爽醋,詳見 代碼片段1.3)更新客戶端最近更新時(shí)間(last_updated),之后使用每個(gè)客戶端最近更新時(shí)間與當(dāng)前時(shí)間比較便脊,如果超過超時(shí)間隔則判斷為下線蚂四,而如果未超時(shí),則還會(huì)從認(rèn)證服務(wù)器中獲取此客戶端狀態(tài)哪痰,確定其是否在線遂赠。具體流程如下

更新客戶端出入口流量,根據(jù)出口流量更新每個(gè)客戶端的最近更新時(shí)間

客戶端超時(shí)則從客戶端列表中移除并通過iptables禁止其訪問網(wǎng)絡(luò)妒御,并告知認(rèn)證服務(wù)器此客戶端下線

客戶端未超時(shí)則從認(rèn)證服務(wù)器獲取此客戶端信息解愤,判斷其是否通過認(rèn)證服務(wù)器下線

代碼片段1.2:

void

fw_sync_with_authserver(void)

{

t_authresponse? authresponse;

char? ? ? ? ? ? *token, *ip, *mac;

t_client? ? ? ? *p1, *p2;

unsigned long long? ? ? ? incoming, outgoing;

s_config *config = config_get_config();

/* 根據(jù)iptables流量更新最近更新時(shí)間镇饺,具體代碼見 代碼片段1.3 */

if (-1 == iptables_fw_counters_update()) {

debug(LOG_ERR, "Could not get counters from firewall!");

return;

}

LOCK_CLIENT_LIST();

/* 遍歷客戶端列表 */

for (p1 = p2 = client_get_first_client(); NULL != p1; p1 = p2) {

p2 = p1->next;

ip = safe_strdup(p1->ip);

token = safe_strdup(p1->token);

mac = safe_strdup(p1->mac);

outgoing = p1->counters.outgoing;

incoming = p1->counters.incoming;

UNLOCK_CLIENT_LIST();

/* ping一下此客戶端乎莉,不清楚作用 */

icmp_ping(ip);

/* 將客戶端的出入流量上傳至認(rèn)證服務(wù)器,此時(shí)如果此客戶端在認(rèn)證服務(wù)器上下線會(huì)返回告知wifidog */

if (config->auth_servers != NULL) {

auth_server_request(&authresponse, REQUEST_TYPE_COUNTERS, ip, mac, token, incoming, outgoing);

}

LOCK_CLIENT_LIST();

/* 從客戶端列表獲取IP,MAC對(duì)應(yīng)客戶端 */

if (!(p1 = client_list_find(ip, mac))) {

debug(LOG_ERR, "Node %s was freed while being re-validated!", ip);

} else {

time_t? ? current_time=time(NULL);

debug(LOG_INFO, "Checking client %s for timeout:? Last updated %ld (%ld seconds ago), timeout delay %ld seconds, current time %ld, ",

p1->ip, p1->counters.last_updated, current_time-p1->counters.last_updated, config->checkinterval * config->clienttimeout, current_time);

/* 判斷是否超時(shí)奸笤,(最近更新時(shí)間 + 超時(shí)時(shí)間 <= 當(dāng)前時(shí)間) 表明以超過超時(shí)時(shí)間惋啃,下線 */

if (p1->counters.last_updated +

(config->checkinterval * config->clienttimeout)

<= current_time) {

debug(LOG_INFO, "%s - Inactive for more than %ld seconds, removing client and denying in firewall",

p1->ip, config->checkinterval * config->clienttimeout);

/* 修改iptables禁止此客戶端訪問外網(wǎng) */

fw_deny(p1->ip, p1->mac, p1->fw_connection_state);

/* 從客戶端列表中刪除此客戶端 */

client_list_delete(p1);

/* 通知認(rèn)證服務(wù)器此客戶端下線 */

if (config->auth_servers != NULL) {

UNLOCK_CLIENT_LIST();

auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token, 0, 0);

LOCK_CLIENT_LIST();

}

} else {

/* 未超時(shí)處理 */

if (config->auth_servers != NULL) {

/* 判斷認(rèn)證服務(wù)器返回信息 */

switch (authresponse.authcode) {

/* 認(rèn)證服務(wù)器禁止其訪問網(wǎng)絡(luò)(下線或遭拒絕) */

case AUTH_DENIED:

debug(LOG_NOTICE, "%s - Denied. Removing client and firewall rules", p1->ip);

fw_deny(p1->ip, p1->mac, p1->fw_connection_state);

client_list_delete(p1);

break;

case AUTH_VALIDATION_FAILED:

debug(LOG_NOTICE, "%s - Validation timeout, now denied. Removing client and firewall rules", p1->ip);

fw_deny(p1->ip, p1->mac, p1->fw_connection_state);

client_list_delete(p1);

break;

/* 認(rèn)證服務(wù)器允許其訪問網(wǎng)絡(luò)(在線) */

case AUTH_ALLOWED:

if (p1->fw_connection_state != FW_MARK_KNOWN) {

debug(LOG_INFO, "%s - Access has changed to allowed, refreshing firewall and clearing counters", p1->ip);

if (p1->fw_connection_state != FW_MARK_PROBATION) {

p1->counters.incoming = p1->counters.outgoing = 0;

}

else {

debug(LOG_INFO, "%s - Skipped clearing counters after all, the user was previously in validation", p1->ip);

}

p1->fw_connection_state = FW_MARK_KNOWN;

fw_allow(p1->ip, p1->mac, p1->fw_connection_state);

}

break;

case AUTH_VALIDATION:

debug(LOG_INFO, "%s - User in validation period", p1->ip);

break;

case AUTH_ERROR:

debug(LOG_WARNING, "Error communicating with auth server - leaving %s as-is for now", p1->ip);

break;

default:

debug(LOG_ERR, "I do not know about authentication code %d", authresponse.authcode);

break;

}

}

}

}

free(token);

free(ip);

free(mac);

}

UNLOCK_CLIENT_LIST();

}

代碼片段1.3:

int

iptables_fw_counters_update(void)

{

FILE *output;

char *script,

ip[16],

rc;

unsigned long long int counter;

t_client *p1;

struct in_addr tempaddr;

/* 通過iptables獲取其出口流量 */

safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_OUTGOING);

iptables_insert_gateway_id(&script);

output = popen(script, "r");

free(script);

if (!output) {

debug(LOG_ERR, "popen(): %s", strerror(errno));

return -1;

}

/* iptables返回信息處理 */

while (('\n' != fgetc(output)) && !feof(output))

;

while (('\n' != fgetc(output)) && !feof(output))

;

while (output && !(feof(output))) {

rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s %*s", &counter, ip);

//rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %15[0-9.] %*s %*s %*s %*s %*s 0x%*u", &counter, ip);

if (2 == rc && EOF != rc) {

if (!inet_aton(ip, &tempaddr)) {

debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);

continue;

}

debug(LOG_DEBUG, "Read outgoing traffic for %s: Bytes=%llu", ip, counter);

LOCK_CLIENT_LIST();

/* 通過ip獲取客戶端信息結(jié)構(gòu) */

if ((p1 = client_list_find_by_ip(ip))) {

/* (上一次出口總流量(outgoing) + wifidog啟動(dòng)時(shí)的出口總流量(outgoing_history) < iptables返回的出口總流量) 表示此客戶端有新的出口流量 */

if ((p1->counters.outgoing - p1->counters.outgoing_history) < counter) {

/* 更新上一次出口總流量(outgoing)為wifidog啟動(dòng)時(shí)的出口總流量(outgoing_history) + iptables返回總流量(counter) */

p1->counters.outgoing = p1->counters.outgoing_history + counter;

/* 更新最近更新時(shí)間為當(dāng)前時(shí)間 */

p1->counters.last_updated = time(NULL);

debug(LOG_DEBUG, "%s - Updated counter.outgoing to %llu bytes.? Updated last_updated to %d", ip, counter, p1->counters.last_updated);

}

} else {

debug(LOG_ERR, "Could not find %s in client list", ip);

}

UNLOCK_CLIENT_LIST();

}

}

pclose(output);

/* 通過iptables獲取其入口流量,入口流量不做更新最近更新時(shí)間參考监右,只用于更新后上傳至認(rèn)證服務(wù)器边灭,其原理同上,后面的代碼不做詳細(xì)分析 */

safe_asprintf(&script, "%s %s", "iptables", "-v -n -x -t mangle -L " TABLE_WIFIDOG_INCOMING);

iptables_insert_gateway_id(&script);

output = popen(script, "r");

free(script);

if (!output) {

debug(LOG_ERR, "popen(): %s", strerror(errno));

return -1;

}

while (('\n' != fgetc(output)) && !feof(output))

;

while (('\n' != fgetc(output)) && !feof(output))

;

while (output && !(feof(output))) {

rc = fscanf(output, "%*s %llu %*s %*s %*s %*s %*s %*s %15[0-9.]", &counter, ip);

if (2 == rc && EOF != rc) {

if (!inet_aton(ip, &tempaddr)) {

debug(LOG_WARNING, "I was supposed to read an IP address but instead got [%s] - ignoring it", ip);

continue;

}

debug(LOG_DEBUG, "Read incoming traffic for %s: Bytes=%llu", ip, counter);

LOCK_CLIENT_LIST();

if ((p1 = client_list_find_by_ip(ip))) {

if ((p1->counters.incoming - p1->counters.incoming_history) < counter) {

p1->counters.incoming = p1->counters.incoming_history + counter;

debug(LOG_DEBUG, "%s - Updated counter.incoming to %llu bytes", ip, counter);

}

} else {

debug(LOG_ERR, "Could not find %s in client list", ip);

}

UNLOCK_CLIENT_LIST();

}

}

pclose(output);

return 1;

}

本文由http://www.wifidog.pro/2015/02/02/wifidog%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A3%80%E6%B5%8B.html整理編輯健盒,轉(zhuǎn)載請(qǐng)注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绒瘦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扣癣,更是在濱河造成了極大的恐慌惰帽,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件父虑,死亡現(xiàn)場(chǎng)離奇詭異该酗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門呜魄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悔叽,“玉大人,你說我怎么就攤上這事爵嗅〗颗欤” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵操骡,是天一觀的道長(zhǎng)九火。 經(jīng)常有香客問我,道長(zhǎng)册招,這世上最難降的妖魔是什么岔激? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮是掰,結(jié)果婚禮上虑鼎,老公的妹妹穿的比我還像新娘。我一直安慰自己键痛,他們只是感情好炫彩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著絮短,像睡著了一般江兢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丁频,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天杉允,我揣著相機(jī)與錄音,去河邊找鬼席里。 笑死叔磷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奖磁。 我是一名探鬼主播改基,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咖为!你這毒婦竟也來了秕狰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤躁染,失蹤者是張志新(化名)和其女友劉穎鸣哀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褐啡,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诺舔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片低飒。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡许昨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褥赊,到底是詐尸還是另有隱情糕档,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布拌喉,位于F島的核電站速那,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏尿背。R本人自食惡果不足惜端仰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望田藐。 院中可真熱鬧荔烧,春花似錦、人聲如沸汽久。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)景醇。三九已至臀稚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間三痰,已是汗流浹背吧寺。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酒觅,地道東北人撮执。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓微峰,卻偏偏與公主長(zhǎng)得像舷丹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜓肆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容