【智能路由器】设备流量、网速统计及上下线提醒(基于netfilter编程)

【智能路由器】系列文章连接
http://blog.csdn.net/u012819339/article/category/5803489


模块目的

本文用户流量统计是统计路由器子网下每台设备的流量,下图展现了该模块具体是要实现怎样的功能
【智能路由器】设备流量、网速统计及上下线提醒(基于netfilter编程)_第1张图片


内核模块

依然是在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;
}

在内核创建了一个线程用于统计网速,维护设备链表。
代码每秒统计一次设备网速,同时剔除一些已经下线的设备,防止输出缓冲区过满(删除整个链表然后重新建表)。

将数据映射到/proc虚拟文件系统

关键代码如下:

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

你可能感兴趣的:(智能路由器,智能路由器)