簡介
目前openwrt系統中流量統計做的最好的應該是“石像鬼”固件了,用以做流量統計的工具也有很多如:tomato,luci-app-statistics等。
本文想給大家介紹一種基於iptables規則的流量統計方法。該方法的基本原理是利用iptables自帶的對規則鏈的流量統計功能,通過制定不同的規則並掛在不同的表和鏈上來實現對特定流量統計。
本方法的好處如下:
靈活統計多種流量。
對系統性能幾乎不影響。
流量統計準確。
便於擴張。
iptables簡介
iptables表說明
mangle 表 #主要作用是更具規則修改數據包的標誌,以便其他規則或應用程序對其進行 處理。做好不要在該表中做DROP處理。
-->PREROUTING #在執行路由決策前的數據經過該鏈。
-->INPUT #本機接收的數據包經過該鏈。
-->FORWARD #需要轉發出去的數據包經過該鏈。
-->OUTPUT #本機發出的數據包經過該鏈。
-->POSTROUTING #在執行完路由決策後即將發送出去的數據包經過該鏈。
nat 表 #顧名思義該表主要做網絡地址裝換的。如:SNAT DNAT REDIRECT。該表不能 對數據包執行丟棄動作。
-->PREROUTING #在執行路由決策前的數據經過該鏈,可在該鏈上做REDIRECT
-->POSTROUTING #在執行完路由決策後即將發送出去的數據包經過該鏈。SNAT
-->OUTPUT #本機發出的數據包經過該鏈。
filter 表 #在該表上主要做數據包的過濾。
-->INPUT #到本機數據包的的過濾。
-->FORWARD #轉發數據包的過濾。
-->OUTPUT #本機發出的數據包的過濾。
iptables數據流向說明
目的地爲本機的數據流向圖如下:
本機發出的數據流向圖
本機轉發的數據流向圖
特別說明:如果已經建立連接的TCP數據是不會經過nat表的PREROUTING鏈的。
統計規則鏈的掛載說明
根據以上說明,對於統計規則鏈最好放在filter表中,且同時爲了提高統計的精確性,需要改造filter表的上的所有ACCEPT的規則到同一自定義鏈上。如所有INPUT鏈上的ACCEPT動作都重定向到input_accept中,OUTPUT鏈上的ACCEPT動作都重定向到output_accept鏈上,同理FORWARD鏈上的所有ACCEPT規則都重定向到forward_accept鏈上。
然後分別在自定義的*_accept鏈上增加對各個客戶端的統計。其中input_accept可以統計所有客戶端到本機的上行流量。 output_accept可以統計所有客戶端到本機的下行流量。 forward_accept可以統計每個客戶端經本機轉發的上外網的上行與下行流量。
代碼實現說明
對於實現該功能主要有幾個重點需要說明:
1、如何發現接入的設備和斷開的設備,從而爲每個設備添加其對應的統計規則。
2、如何高效的獲取iptables規則鏈上的統計數據。
3、對於及時數度的計算
發現設備接入和斷開說明
我的實現是每隔一秒去讀取/proc/net/arp文件,通過與現有的接入鏈表對比,從而找到新增的斷開的設備,從而對應的添加和刪除其統計規則。
這樣實現的好處有:1、不會影響數據流向。2、能準確的發現設備的接入和斷開事件。3、對系統性能影響不大。
實現代碼
static void handle_client_item(const char *ip, const char *mac, const char *device) { struct client_info *client = NULL; if (strcmp(device, LAN_IFNAME)) { debug(MSG_DEBUG, "unsupport client from device:%s", device); return ; } client = find_client_by_ipmac(ip, mac); if (client == NULL) { debug(MSG_DEBUG, "add new client ip:%s mac:%s device:%s", ip, mac, device); client = append_new_client(ip, mac); } assert(client); client->client_exist = true; } static void handle_arp_line(char *line) { int count = 0; char *delim = " ", *p = NULL; char *ip = NULL, *mac = NULL, *device = NULL; p = strtok(line, delim); do { switch (count) { case 0: ip = p; break; case 3: mac = p; break; case 5: device = p; break; } count++; } while ((p = strtok(NULL, delim)) != NULL); if (check_ip_valid(ip) == 0) { debug(MSG_DEBUG, "get ip:[%s] from arp error!", ip); return; } if (check_mac_valid(mac) == 0) { debug(MSG_DEBUG, "get mac:[%s] from arp error!", mac); return; } if (device == NULL || strlen(device) <= 0) { debug(MSG_DEBUG, "get device name error!"); return; } handle_client_item(ip, mac, device); } static void do_find_new_client() { FILE *fp = NULL; char line[1024] = {0}; fp = fopen("/proc/net/arp", "r"); if (fp == NULL) { return ; } update_client_no_exist(); while (fgets(line, sizeof(line), fp)) { if (strlen(line) <= 0) { continue; } if (strstr(line, "IP") || strstr(line, "HW")) { continue; } // delete the '\n' line[strlen(line)-1] = '\0'; handle_arp_line(line); } fclose(fp); do_network_stats(); delete_unexist_client(); }
IPTABLES規則的高效統計說明
爲了實現高效的對iptables規則鏈上的流量統計,程序可以基於libiptc庫來實現。實例代碼如下:
void do_network_stats() { struct iptc_handle *handle = NULL; const struct ipt_entry *entry = NULL; handle = iptc_init("filter"); if (!handle) { debug(MSG_ERROR, "iptc_init err msg:%s", iptc_strerror(errno)); return ; } entry = iptc_first_rule("input_accept", handle); while (entry) { ip = (char *)inet_ntoa(entry->ip.src); debug(MSG_INFO, "stats ip:%s pcnt:%llu bcnt:%llu", ip, entry->counters.pcnt, entry->counters.bcnt); update_flow(ip, entry->counters.pcnt, entry->counters.bcnt); iptc_zero_counter("input_accept", count++, handle); //統計完成後清空規則鏈上的數據,用以實現增量統計,從而解決iptables規則被刪除後重新加載導致的流量統計不正確的問題。 entry = iptc_next_rule(entry, handle); } iptc_commit(handle); //用以應用上面清空規則鏈上的統計信息的代碼。 iptc_free(handle); }
對於統計設備的及時速度的說明
當把一段事件的流量除以統計間隔,就得到對應流量的速度了。(可以一秒統計一次流量的增量,它就是1秒的平均速度了。)
後續說明
如果各位想要了解更多的實現細節可以在評論在探討。