【智能路由器】设备流量、网速统计及上下线提醒(基于netfilter编程)
模块目的
本文用户流量统计是统计路由器子网下每台设备的流量,下图展现了该模块具体是要实现怎样的功能
内核模块
依然是在netfilter的框架上进行数据捕获,分别监控每台子网设备流量信息。
原理:在netfilter的pre_routing统计上传流量和上行速度以及在post_routing节点统计下载流量和下行速度,通过/proc文件系统提供给上层应用程序利用。
流量拦截及统计
关键代码如下
/
目的:统计每个MAC的上传流量
/
staticunsignedintflow_hook_out(
unsignedinthooknum,
structsk_buffskb,
conststructnet_devicein,
conststructnet_deviceout,
int(okfn)(structsk_buff))
{
structethhdreth;
structiphdrip;
uint32_tip1,ip2;
if(!skb)
returnNF_ACCEPT;
if(skb->protocol!=htons(0x0800))
returnNF_ACCEPT;
eth=eth_hdr(skb);
if(!eth)
{
printk("ethiserror!\n");
returnNF_ACCEPT;
}
ip=ip_hdr(skb);
if(!ip)
returnNF_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
{
returnNF_ACCEPT;
}
if(ip->saddr==local_ip.IP)//local
returnNF_ACCEPT;
ip1=ip1&0xff000000;
if(ntohl(ip1)==255)//broadcast
returnNF_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);
}
returnNF_ACCEPT;
}
structnf_hook_opsflow_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()该函数用于向链表中添加数据
设备网速计算及打印
关键代码如下:
intmac_func(voiddata)
{
uint32_tlen=0;
uint32_tlenRx=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);//clearofflinemac
flow_format_and_clear(Mac_flow_tableTX,flow_buf);
}
else
{
free_maclist(Mac_flow_tableTX);//clearallmac,theremaybealotofinvalidmac!
Mac_flow_tableTX=create_maclist();//anewlist
}
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);
}
return1;
}
intk_threadinit(void)
{
info_kthread=kthread_run(mac_func,NULL,"flow");
if(NULL==info_kthread)
return-1;
return1;
}
intk_threadstop(void)
{
kthread_stop(info_kthread);
info_kthread=NULL;
return1;
}
在内核创建了一个线程用于统计网速,维护设备链表。
代码每秒统计一次设备网速,同时剔除一些已经下线的设备,防止输出缓冲区过满(删除整个链表然后重新建表)。
将数据映射到/proc虚拟文件系统
关键代码如下:
staticintflow_read(charpage,charstart,off_toff,intcount,inteof,voiddata)
{
intlen;
mutex_lock(&visit_mutex_tx);
len=strlen(flow_buf);
if(off>len)
{
mutex_unlock(&visit_mutex_tx);
eof=1;
return0;
}
if(count>len-off)
{
count=len-off;
eof=1;
}
memcpy(page,flow_buf,count);
mutex_unlock(&visit_mutex_tx);
start=page+off;
//eof=1;
//returnoff+count;
returncount;
}
staticintflow_readRx(charpage,charstart,off_toff,intcount,inteof,voiddata)
{
intlen;
//printk("flow_readRx\n");
mutex_lock(&visit_mutex_rx);
len=strlen(flow_bufRx);
if(off>len)
{
mutex_unlock(&visit_mutex_rx);
eof=1;
return0;
}
if(count>len-off)
{
count=len-off;
eof=1;
}
memcpy(page,flow_bufRx,count);
mutex_unlock(&visit_mutex_rx);
start=page+off;
//eof=1;
//returnoff+count;
returncount;
}
intinit_flowproc_moudle(void)
{
intret=0;
flow_root=proc_mkdir("flow_m",NULL);
if(flow_root==NULL)
{
printk("createdirflow_rootfail\n");
return-1;
}
//Tx
proc_entry=create_proc_entry("flowwatchTx",0444,flow_root);
if(proc_entry==NULL)
{
printk("fortune:couldn''tcreateprocentry\n");
ret=-2;
returnret;
}
proc_entry->read_proc=flow_read;
//Rx
proc_entryRx=create_proc_entry("flowwatchRx",0444,flow_root);
if(proc_entryRx==NULL)
{
printk("fortune:couldn''tcreateprocentry\n");
ret=-3;
returnret;
}
proc_entryRx->read_proc=flow_readRx;
returnret;
}
voidexit_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文件,对这两个文件分别进行读取可以得到设备流量信息。
应用层模块
应用层模块对设备上传及下载流量进行了整合,同时具备设备上下线提醒功能。
主要代码如下:
typedefstd::listF_list;
F_listFlow_table;
/
name:cat_user_flow
description:
cat/proc/flow_m/flowwatchTx
cat/proc/flow_m/flowwatchRx
/
intcat_user_flow(HASH_TABLEpMACtable)
{
Flow_Infotemflow;
charup_buf[80];
chardown_buf[80];
FILEup_flow;
FILEdown_flow;
charflag=0;
time_tcurrenttime;
up_flow=fopen("flowwatchTx","r");
down_flow=fopen("flowwatchRx","r");
if(up_flow==NULL)
{
std::cout<<"openfilefail!"< return-1;
}
if(down_flow==NULL)
{
std::cout<<"openfilefail!"< return-1;
}
//readfirstlineanddropout
if(NULL==fgets(up_buf,sizeof(up_buf),up_flow))
{
//errororendoffile
std::cout<<"readfilefailed!"< fclose(up_flow);
fclose(down_flow);
return-2;
}
if(NULL==fgets(down_buf,sizeof(down_buf),down_flow))
{
//errororendoffile
std::cout<<"readfilefailed!"< fclose(up_flow);
fclose(down_flow);
return-2;
}
currenttime=time((time_t)NULL);
std::cout<<"currenttime:"<
for(;;)//Tx
{
memset(up_buf,0,sizeof(up_buf));
if(NULL==fgets(up_buf,sizeof(up_buf),up_flow))//errororendoffile
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;
//updatelist
for(F_list::iteratorf_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);//addadditionalanullcharacter
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)//newonlinedevice
{
charbuf[12];
NODEMACdataNode=NULL;
ElemTypeval=0;
memset(buf,0,sizeof(buf));
memcpy(buf,temflow.mac,8);
//getlocaltime
temflow.s_time=time((time_t)NULL);
temflow.NewOnlineD=1;//onlineaction
//std::cout<<"-------"< val=(ElemType)(MAC_str_to10)(buf);
MACdataNode=find_data_in_hash(pMACtable,val);
if(MACdataNode==NULL)
{
std::cout<<"findnodata!\n"< //unfound!
memcpy(temflow.dev_name,"unknown",strlen("unknown"));
memcpy(temflow.dev_type,"unknown",strlen("unknown"));
}
else//findsuccess
{
memcpy(temflow.dev_name,((Code_to_Str_t)MACdataNode->data)->remark,strlen(((Code_to_Str_t)MACdataNode->data)->remark)+1);
memcpy(temfwww.shanxiwang.netlow.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_tableisempty"< fclose(up_flow);
fclose(down_flow);
charbuf[4];
memset(buf,0,sizeof(buf));
write_info(buf,dev_info_file,"w+");
return-3;
}
while(1)//combineTxwithRx
{
memset(down_buf,0,sizeof(down_buf));
if(NULL==fgets(down_buf,sizeof(down_buf),down_flow))//errororendoffile
break;
memset(&temflow,0,sizeof(temflow));
sscanf(down_buf,"%s%s%s",temflow.ip,temflow.download,temflow.totaldown);
for(F_list::iteratorf_list_iter=Flow_table.begin();f_list_iter!=Flow_table.end();f_list_iter++)//compareip
{
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;//thisipinsertok!
}//ifnotfind,ignoreit!
}
}
for(F_list::iteratorf_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::iteratorf_list_iter=Flow_table.begin();f_list_iter!=Flow_table.end();f_list_iter++)
{
std::cout<<""< std::cout<<(f_list_iter).mac<<""<<(f_list_iter).ip<<""<<(f_list_iter).totalup< }
/
return0;
}
/
name:
description:noticeothersomeinformation
/
intwrite_dev_info(pid_tpid)
{
charbuf[256];
memset(buf,0,sizeof(buf));
write_info(buf,dev_info_file,"w+");//cleardev_info_file
for(F_list::iteratorf_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))//offlinecheckandnotice
{
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("\nofflinenotice:%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);
}
//settheflagforwaittingdelete;
(f_list_iter).wait_erase=1;
}
elseif((f_list_iter).NewOnlineD==1)//onlinecheckandnotice
{
(f_list_iter).NewOnlineD=0;//clearflag
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("\nonlinenotice:%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::iteratorf_list_iter=Flow_table.begin();f_list_iter!=Flow_table.end();f_list_iter++)
{
if((f_list_iter).wait_erase==1)
{
printf("clearofflinedev!\n");
Flow_table.erase(f_list_iter);
gotodeletedev;
}
}
return0;
}
cat_user_flow()函数代码首先读取/proc/flow_m/flowwatchTx和/proc/flow_m/flowwatchRx数据,然后进行根据链表判断是否是新设备,接着通过哈希表查询该设备所属设备厂商(根据已有的MAC厂商列表),然后整合设备上传和下载流量。
write_dev_info()函数将数据写入缓存文件,同时给其他进程发送设备上下线通知的消息,并将上下线的设备数据写入对应缓存。
总体结构如此。
|
|