openwrt linux 基於iptables的流量和速度統計

  1. 簡介

    目前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數據流向說明

    目的地爲本機的數據流向圖如下:

wKioL1XjJgyymYqZAAHC1sf8_xQ821.jpg

    本機發出的數據流向圖

    wKioL1XjJmmjN3yrAAHLHd-BtS4827.jpg

    本機轉發的數據流向圖

    wKioL1XjJoTylDh_AAHIW70wHbM664.jpg

    特別說明:如果已經建立連接的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秒的平均速度了。)

後續說明

    如果各位想要了解更多的實現細節可以在評論在探討。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章