【智能路由器】系列文章连接
http://blog.csdn.net/u012819339/article/category/5803489
本文用户流量统计是统计路由器子网下每台设备的流量,下图展现了该模块具体是要实现怎样的功能
依然是在netfilter的框架上进行数据捕获,分别监控每台子网设备流量信息。
原理:在netfilter的pre_routing统计上传流量和上行速度以及在post_routing节点统计下载流量和下行速度,通过/proc文件系统提供给上层应用程序利用。
关键代码如下
/*
目的:统计每个MAC的上传流量
*/
static unsigned int flow_hook_out(
unsigned int hooknum,
struct sk_buff * skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn) (struct sk_buff *))
{
struct ethhdr *eth;
struct iphdr *ip;
uint32_t ip1, ip2;
if (!skb)
return NF_ACCEPT;
if(skb->protocol != htons(0x0800))
return NF_ACCEPT;
eth = eth_hdr(skb);
if(!eth)
{
printk("eth is error!\n");
return NF_ACCEPT;
}
ip = ip_hdr(skb);
if(!ip)
return NF_ACCEPT;
if(!strnicmp(in->name, "br0", strlen("br0")))//流出流量 ,在pre_routing统计,统计源mac,ip
{
ip1 = (ip->saddr)&0x00ffffff;
ip2 = (local_ip.IP)&0x00ffffff;
if(ip1 != ip2) //subnet
{
return NF_ACCEPT;
}
if(ip->saddr == local_ip.IP) //local
return NF_ACCEPT;
ip1 = ip1&0xff000000;
if(ntohl(ip1)==255) //broadcast
return NF_ACCEPT;
//mutex_lock(&visit_mutex_tx);
add_maclist_data(Mac_flow_tableTX, eth->h_source, ip->saddr, ntohs(ip->tot_len));
//mutex_unlock(&visit_mutex_tx);
}
return NF_ACCEPT;
}
struct nf_hook_ops flow_ops_out = { //外出流量
.list = {NULL,NULL},
.hook = flow_hook_out,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING, //必须在NAT转发前统计
.priority = NF_IP_PRI_FIRST+1
//.hooknum = NF_INET_POST_ROUTING,
//.priority = NF_IP_PRI_LAST-1
};
代码给出了上传流量的统计方法,下载流量统计代码如法炮制,不再贴出。代码思路步骤:
1.代码首先剔除arp数据(提取ip包)
2.过滤局域网外其他干扰设备数据、本地数据、广播数据
3.添加到设备链表,这里做了一个单向链表用于存储设备mac、ip、流量、网速等信息。add_maclist_data()该函数用于向链表中添加数据
关键代码如下:
int mac_func(void *data)
{
uint32_t len = 0;
uint32_t lenRx = 0;
while(!kthread_should_stop())
{
//ssleep(10);
msleep(500);
mutex_lock(&visit_mutex_tx);
len = strlen(flow_buf) + 40;
if(len < 1800) //上传流量
{
memset(flow_buf, 0, len);
//memset(flow_buf, 0, sizeof(flow_buf));
delete_macnode_by_ct(Mac_flow_tableTX); //clear offline mac
flow_format_and_clear(Mac_flow_tableTX, flow_buf);
}
else
{
free_maclist(Mac_flow_tableTX); //clear all mac,there may be a lot of invalid mac!
Mac_flow_tableTX = create_maclist(); //a new list
}
mutex_unlock(&visit_mutex_tx);
msleep(500);
mutex_lock(&visit_mutex_rx);
lenRx = strlen(flow_bufRx) + 40;
if(lenRx < 1800) //下发流量,只有ip
{
memset(flow_bufRx, 0, lenRx);
//memset(flow_bufRx, 0, sizeof(flow_bufRx));
delete_ipnode_by_ct(ip_flow_tableRx);
ipflow_format_and_clear(ip_flow_tableRx, flow_bufRx);
}
else
{
free_iplist(ip_flow_tableRx);
ip_flow_tableRx = create_iplist();
}
mutex_unlock(&visit_mutex_rx);
}
return 1;
}
int k_threadinit(void)
{
info_kthread = kthread_run(mac_func, NULL, "flow");
if(NULL == info_kthread)
return -1;
return 1;
}
int k_threadstop(void)
{
kthread_stop(info_kthread);
info_kthread = NULL;
return 1;
}
在内核创建了一个线程用于统计网速,维护设备链表。
代码每秒统计一次设备网速,同时剔除一些已经下线的设备,防止输出缓冲区过满(删除整个链表然后重新建表)。
关键代码如下:
static int flow_read(char *page,char **start,off_t off, int count ,int *eof,void *data)
{
int len;
mutex_lock(&visit_mutex_tx);
len = strlen(flow_buf);
if(off > len)
{
mutex_unlock(&visit_mutex_tx);
*eof = 1;
return 0;
}
if(count > len-off)
{
count = len - off;
*eof = 1;
}
memcpy(page, flow_buf, count);
mutex_unlock(&visit_mutex_tx);
*start = page + off;
//*eof = 1;
//return off + count;
return count;
}
static int flow_readRx(char *page,char **start,off_t off, int count ,int *eof,void *data)
{
int len;
//printk("flow_readRx\n");
mutex_lock(&visit_mutex_rx);
len = strlen(flow_bufRx);
if(off > len)
{
mutex_unlock(&visit_mutex_rx);
*eof = 1;
return 0;
}
if(count > len-off)
{
count = len - off;
*eof = 1;
}
memcpy(page, flow_bufRx, count);
mutex_unlock(&visit_mutex_rx);
*start = page + off;
//*eof = 1;
//return off + count;
return count;
}
int init_flowproc_moudle(void)
{
int ret = 0;
flow_root = proc_mkdir("flow_m", NULL);
if(flow_root == NULL)
{
printk("create dir flow_root fail\n");
return -1;
}
//Tx
proc_entry = create_proc_entry("flowwatchTx", 0444, flow_root);
if(proc_entry==NULL)
{
printk("fortune :couldn't create proc entry\n");
ret = -2;
return ret;
}
proc_entry->read_proc = flow_read;
//Rx
proc_entryRx = create_proc_entry("flowwatchRx", 0444, flow_root);
if(proc_entryRx==NULL)
{
printk("fortune :couldn't create proc entry\n");
ret = -3;
return ret;
}
proc_entryRx->read_proc = flow_readRx;
return ret;
}
void exit_flowproc_moudle(void)
{
remove_proc_entry("flowwatchTx", flow_root); //删除文件
remove_proc_entry("flowwatchRx", flow_root);
remove_proc_entry("flow_m", NULL); //删除目录
}
代码在/proc下建立/flow_m文件夹,同时在/proc/flow_m下建立flowwatchTx和flowwatchRx文件,对这两个文件分别进行读取可以得到设备流量信息。
应用层模块对设备上传及下载流量进行了整合,同时具备设备上下线提醒功能。
主要代码如下:
typedef std::list F_list;
F_list Flow_table;
/*
name:cat_user_flow
description:
cat /proc/flow_m/flowwatchTx
cat /proc/flow_m/flowwatchRx
*/
int cat_user_flow(HASH_TABLE *pMACtable)
{
Flow_Info temflow;
char up_buf[80];
char down_buf[80];
FILE *up_flow;
FILE *down_flow;
char flag=0;
time_t currenttime;
up_flow = fopen("flowwatchTx", "r");
down_flow = fopen("flowwatchRx", "r");
if(up_flow==NULL)
{
std::cout<<"open file fail!"<<std::endl;
return -1;
}
if(down_flow==NULL)
{
std::cout<<"open file fail!"<<std::endl;
return -1;
}
//read first line and drop out
if(NULL == fgets(up_buf, sizeof(up_buf), up_flow))
{
//error or end of file
std::cout<<"read file failed!"<<std::endl;
fclose(up_flow);
fclose(down_flow);
return -2;
}
if(NULL == fgets(down_buf, sizeof(down_buf), down_flow))
{
//error or end of file
std::cout<<"read file failed!"<<std::endl;
fclose(up_flow);
fclose(down_flow);
return -2;
}
currenttime = time((time_t *)NULL);
std::cout<<"currenttime:"<std::endl;
for(;;) //Tx
{
memset(up_buf, 0, sizeof(up_buf));
if(NULL == fgets(up_buf, sizeof(up_buf), up_flow)) //error or end of file
break;
memset(&temflow, 0, sizeof(temflow));
sscanf(up_buf, "%s %s %s %s", temflow.mac, temflow.ip, temflow.upload, temflow.totalup);
if(strncmp(temflow.ip, "0.0.0.0", strlen("0.0.0.0")) == 0)
continue;
//update list
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)
{
if( 0 == strncmp((*f_list_iter).mac, temflow.mac, strlen(temflow.mac)) )
{
memcpy((*f_list_iter).ip, temflow.ip, strlen(temflow.ip)+1); //add additional a null character
memcpy((*f_list_iter).upload, temflow.upload, strlen(temflow.upload)+1);
memcpy((*f_list_iter).totalup, temflow.totalup, strlen(temflow.totalup)+1);
(*f_list_iter).loop=0;
(*f_list_iter).alive = currenttime - (*f_list_iter).s_time;
flag=1;
break;
}
}
if(!flag) //new online device
{
char buf[12];
NODE *MACdataNode = NULL;
ElemType val = 0;
memset(buf, 0, sizeof(buf));
memcpy(buf, temflow.mac, 8);
//get local time
temflow.s_time = time((time_t *)NULL);
temflow.NewOnlineD = 1; //online action
//std::cout<<"*-*-*-*-*-*-*-*"<<std::endl;
val = (ElemType)(MAC_str_to10)(buf);
MACdataNode = find_data_in_hash(pMACtable, val);
if(MACdataNode == NULL)
{
std::cout<<"find no data!\n"<<std::endl;
//unfound!
memcpy(temflow.dev_name,"unknown", strlen("unknown"));
memcpy(temflow.dev_type,"unknown", strlen("unknown"));
}
else //find success
{
memcpy(temflow.dev_name, ((Code_to_Str_t *)MACdataNode->data)->remark, strlen(((Code_to_Str_t *)MACdataNode->data)->remark)+1);
memcpy(temflow.dev_type, ((Code_to_Str_t *)MACdataNode->data)->type, strlen(((Code_to_Str_t *)MACdataNode->data)->type)+1);
}
Flow_table.push_back(temflow);
}
flag = 0;
}
if(Flow_table.empty())
{
std::cout<<"Flow_table is empty"<<std::endl;
fclose(up_flow);
fclose(down_flow);
char buf[4];
memset(buf, 0, sizeof(buf));
write_info(buf, dev_info_file, "w+");
return -3;
}
while(1) //combine Tx with Rx
{
memset(down_buf, 0, sizeof(down_buf));
if(NULL == fgets(down_buf, sizeof(down_buf), down_flow)) //error or end of file
break;
memset(&temflow, 0, sizeof(temflow));
sscanf(down_buf, "%s %s %s", temflow.ip, temflow.download, temflow.totaldown);
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)//compare ip
{
if( 0 == strncmp((*f_list_iter).ip, temflow.ip, strlen(temflow.ip)) )
{
memcpy((*f_list_iter).download, temflow.download, strlen(temflow.download)+1);
memcpy((*f_list_iter).totaldown, temflow.totaldown, strlen(temflow.totaldown)+1);
(*f_list_iter).looprx = 0;
break; //this ip insert ok!
} //if not find , ignore it!
}
}
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)
{
(*f_list_iter).loop = (*f_list_iter).loop + 1;
(*f_list_iter).looprx = (*f_list_iter).looprx + 1;
}
fclose(up_flow);
fclose(down_flow);
/*
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)
{
std::cout<<"****************"<<std::endl;
std::cout<<(*f_list_iter).mac<<" "<<(*f_list_iter).ip<<" "<<(*f_list_iter).totalup<<std::endl;
}
*/
return 0;
}
/*
name:
description: notice other some information
*/
int write_dev_info(pid_t pid)
{
char buf[256];
memset(buf, 0, sizeof(buf));
write_info(buf, dev_info_file, "w+"); //clear dev_info_file
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)
{
memset(buf, 0, sizeof(buf));
sprintf(buf, "%d,%s,%s,%s,%s,%s,%s,%s,%s,%ld,%d\n", Flow_table.size(),
(*f_list_iter).mac,
(*f_list_iter).ip,
(*f_list_iter).upload,
(*f_list_iter).download,
(*f_list_iter).totalup,
(*f_list_iter).totaldown,
(*f_list_iter).dev_name,
(*f_list_iter).dev_type,
(*f_list_iter).alive,
(*f_list_iter).loop
);
printf("-------\n%s\n", buf);
write_info(buf, dev_info_file, "a+");
if(((*f_list_iter).loop > 5) && ((*f_list_iter).looprx > 5)) //offline check and notice
{
memset(buf, 0, sizeof(buf));
sprintf(buf, "%s,%s,%s\n", (*f_list_iter).mac, (*f_list_iter).dev_type, (*f_list_iter).dev_name);
printf("*******\n offline notice: %s,%s,%s\n", (*f_list_iter).mac, (*f_list_iter).dev_type, (*f_list_iter).dev_name);
if(!write_info(buf, offline_notice_file, "a+"))
{
//kill(pid, SIGUSR1);
}
//set the flag for waitting delete;
(*f_list_iter).wait_erase = 1;
}
else if((*f_list_iter).NewOnlineD==1) //online check and notice
{
(*f_list_iter).NewOnlineD = 0; //clear flag
memset(buf, 0, sizeof(buf));
sprintf(buf, "%s,%s,%s\n", (*f_list_iter).mac, (*f_list_iter).dev_type, (*f_list_iter).dev_name);
printf("*******\n online notice: %s,%s,%s\n", (*f_list_iter).mac, (*f_list_iter).dev_type, (*f_list_iter).dev_name);
if(!write_info(buf, online_notice_file, "a+"))
{
//kill(pid, SIGUSR2);
}
}
}
//clear_offline_dev
//Flow_table.erase(f_list_iter);
deletedev:
for(F_list::iterator f_list_iter=Flow_table.begin(); f_list_iter != Flow_table.end(); f_list_iter++)
{
if((*f_list_iter).wait_erase == 1)
{
printf("clear offline dev!\n");
Flow_table.erase(f_list_iter);
goto deletedev;
}
}
return 0;
}
cat_user_flow()函数代码首先读取/proc/flow_m/flowwatchTx和/proc/flow_m/flowwatchRx数据,然后进行根据链表判断是否是新设备,接着通过哈希表查询该设备所属设备厂商(根据已有的MAC厂商列表),然后整合设备上传和下载流量。
write_dev_info()函数将数据写入缓存文件,同时给其他进程发送设备上下线通知的消息,并将上下线的设备数据写入对应缓存。
总体结构如此。
好啦,本文到此结束,作者arvik,【智能路由器】系列文章见
http://blog.csdn.net/u012819339/article/category/5803489