网络流量在线分析系统的设计与实现

网络流量在线分析系统的设计与实现
网络流量在线分析系统的设计与实现

综合实训报告

题目:网络流量在线分析系统的设计与实现

信息学院计算机科学系

目录

一、实训目的 (3)

二、实训内容 (3)

三、主要设备及环境 (3)

四、设计与步骤 (4)

五、过程与调试 (22)

六、整理与小结 (23)

七、参考文献 (24)

八、附录 (25)

一、实训目的

设计并实现一个网络流量的分析系统。该系统具有以下功能:(1)实时抓取网络数据。(2)网络协议分析与显示。(3)将网络数据包聚合成数据流,以源IP、目的IP、源端口、目的端口及协议等五元组的形式存储。(4)计算并显示固定时间间隔内网络连接(双向流)的统计量(如上行与下行的数据包数目,上行与下行的数据量大小等)。在这些统计数据的基础上分析不同网络应用的流量特征。

二、实训内容

(1)能够实时抓取网络中的数据包。并实时显示在程序界面上。用户可自定义过滤条件以抓取所需要的数据包。

(2)分析各个网络协议格式,能够显示各协议字段的实际意义。例如,能够通过该程序反映TCP三次握手的实现过程。

(3)采用Hash链表的形式将网络数据以连接(双向流)的形式存储。

(4)计算并显示固定时间间隔内网络连接(双向流)的统计量(如上行与下行的数据包数目,上行与下行的数据量大小等)。例如,抓取一段时间(如30分钟)的网络流量,将该段时间以固定时长(如1分钟)为单位分成若干个时间片,计算网络连接在每一个时间片内的相关统计量。并在上述统计数据的基础上分析不同应用如WEB、DNS、在线视频等服务的流量特征。注意,可根据实际的流量分析需要自己定义相关的统计量。

三、主要设备及环境

硬件设备:

(1)台式计算机或笔记本计算机(含网络适配器)

软件设备:

(2)Windows操作系统

(3)网络数据包捕获函数包,Windows平台为winpcap

(4)编程语言选用C/C++。

(5)编程环境为codeblocks

四、设计与步骤

(1)设计代码检索机器所连接的所有网络适配器,并在屏幕中显示适配器的名称和详细信息,用户可以输入适配器编号选择指定的适配器用来捕获包,如果没有找到适配器,提示用户检查WinPcap是否安装,代码与结果显示

如下:

/* set the source */

if (pcap_createsrcstr(source, PCAP_SRC_IFLOCAL, NULL, NULL, NULL, errbuf) == -1) {

printf("%s\n", errbuf);

exit(-1);

}

printf("source: %s", source);

/* find all devices */

if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1) {

printf("%s\n", errbuf);

exit(-1);

}

/* choose one devices */

d = alldevs;

while (d != NULL) {

printf("%s, %s\n", d->name, d->description);

d = d->next;

}

printf("choose a device[number between 1 to 4]:");

scanf("%d", &i);

d = alldevs;

while (--i)

d = d->next;

printf("\n----------------------------------------------\n");

printf("selected device: %s\n", d->name);

实验结果显示如下:

(2)选择指定适配器后,调用ifprint();函数计算本机的IP地址、掩码、广

播地址、目标地址等信息,并用声明static char b;用来记录本机IP地

址,为接下来查找Hash表判断流量包的流向做准备:

void ifprint(pcap_if_t *d)

{

pcap_addr_t *a;

/* 名称 */

//printf("%s\n",d->name);

/* 描述 */

if (d->description)

printf("\tDescription: %s\n",d->description);

/* 回环地址 */

printf("\tLoopback: %s\n",(d->flags &

PCAP_IF_LOOPBACK)?"yes":"no");

/* IP 地址 */

for(a=d->addresses;a;a=a->next)

{

printf("\tAddress Family: #%d\n",a->addr->sa_family);

switch(a->addr->sa_family)

{

case AF_INET:

printf("\tAddress Family Name: AF_INET\n");

if (a->addr)

/* Y- IP 地址 */

{

printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));

b = iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr);

}

if (a->netmask)

/* Y- 掩码 */

printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));

if (a->broadaddr)

/* Y- 广播地址 */

printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));

if (a->dstaddr)

/* Y - 目标地址 */

printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));

break;

default:

/* 未知 */

printf("\tAddress Family Name: Unknown\n");

break;

}

}

printf("\n");

}

/* 来自 tcptracert, 把数字IP地址转换为点格式 */

#define IPTOSBUFFERS 12

char *iptos(u_long in)

{

static char output[IPTOSBUFFERS][3*4+3+1];

static short which;

u_char *p;

p = (u_char *)∈

which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);

sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);

return output[which];

}

结果显示如下:

(3)接收到用户输入的适配器编号,打开指定适配器:

/* open one device */

cap_ins_des = pcap_open(d->name, 65536,

PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf);

if (cap_ins_des == NULL) {

printf("%s\n", errbuf);

pcap_freealldevs(alldevs);

exit(-1);

}

(4)打开指定文件存储捕获的数据包:

/* open a file to dump data */

dumpfp = pcap_dump_open(cap_ins_des, "traffic1");

if( dumpfp == NULL) {

printf("Error on opening output file\n");

exit(-1);

}

(5)在main()函数开始做一个声明,方便用户自由选择过滤规则,声明如下:int switchnum;

char t1[] = "ip";//ip过滤规则

char t2[] = "ip and tcp";//tcp过滤规则

char t3[] = "ip and udp";//udp过滤规则

char t4[] = "";//mac帧过滤

char packet_filter[100]; // the filter

设置过滤规则时使用swich()语句判断用户输入的编号,是对应的编号与

对应的过滤规则相一致:

/* open a file to dump data */

dumpfp = pcap_dump_open(cap_ins_des, "traffic1");

if( dumpfp == NULL) {

printf("Error on opening output file\n");

exit(-1);

}

/* get the netmask, used at compiling the filter */

if (d->addresses != NULL)

netmask = ((struct sockaddr_in

*)(d->addresses->netmask))->sin_addr.S_un.S_addr; /*@#$%^&*!*/ else

netmask = 0xffffff; /* 255.25.255.0 */

// netmask = 0;

/*选择过滤规则*/

printf("\n----------------------------------------------\n"); printf("%d:%s\n",1, "IP协议");

printf("%d:%s\n",2, "IP和TCP协议");

printf("%d:%s\n",3, "IP和UDP协议");

printf("%d:%s\n",4, "MAC帧");

printf("请选择要获取的协议类型):");

scanf("%d", &switchnum);

switch (switchnum){

case 1:

strcpy(packet_filter,t1);

break;

case 2:

strcpy(packet_filter,t2);

break;

case 3:

strcpy(packet_filter,t3);

break;

case 4:

strcpy(packet_filter,t4);

break;

default:printf("error\n");

}

/* compile the filter */

if (pcap_compile(cap_ins_des, &fcode, packet_filter, 1, netmask) < 0) {

printf("Error\n");

pcap_freealldevs(alldevs);

exit(-1);

}

结果显示如下:

(6)用户可以根据提示设置抓包的时间长短,该功能的实现依靠创建一个线程:pthread_t ptClock;

argument args;

args.handle = cap_ins_des;

int argv_time = atoi(argv[1]);

int timeLen;

printf("\n设置抓包时长:");

scanf("%d",&timeLen);

printf("设置抓包时长为 %d s",timeLen);

args.timeLen = (argv_time > 0) ? argv_time : timeLen;

//int argv_time = 2;

//args.timeLen = argv_time;

//printf("抓取时长:%d s\n", args.timeLen);

if(pthread_create(&ptClock, NULL, thread_clock, &args))

{

printf("pthread_create(): Error!\n");

return -1;

}

void *thread_clock(void *argv)

{

pcap_t *handle = ((argument*)argv)->handle;

int timeLen = ((argument*)argv)->timeLen; // set time

// printf("%d",timeLen);

Sleep(timeLen*1000);

pcap_breakloop(handle);

}

结果显示如下:

(7)抓包时调用函数pcap_loop()函数调用cb_getPacket()函数,实现在

线程内的抓包,Sleep函数一旦结束,通过pcap_breakloop()退出抓包:pcap_loop(cap_ins_des, -1, cb_getPacket, (u_char*)dumpfp);

void cb_getPacket(u_char *dumpfile, const struct pcap_pkthdr *pkthdr, const u_char *packet)

{

// ip_header *seg_ip = (ip_header*)(package + ETHER_LEN);

pcap_dump(dumpfile, pkthdr, packet);

ethernet_protocol_packet_callback(dumpfile,pkthdr,packet);

}

(8)设置完成抓包时长后,系统开始进行抓包,一旦抓包结束,调用

pcap_close()关闭会话并释放适配器列表:

pcap_close(cap_ins_des);

pcap_freealldevs(allAdapters);//释放适配器列表

(9)捕获结束后将捕获的数据包存入traffic1.data文件中,再将文件打开进行分析,打开文件之前使用pcap_createsrcstr函数指明文件位置为本机

文件,文件名为“traffic1.data”,在调用pcap_open()打开捕获文件:pcap_t *fp;//文件指针

//pcap_createsrcstr指明打开文件的地方:本地文件

if (pcap_createsrcstr(source,/*源字符串*/

PCAP_SRC_FILE,/*本机文件*/

NULL,/*远程主机*/

NULL,/*远程主机端口*/

"traffic1",/*文件名*/

errbuf/*错误缓冲区*/) != 0)

{

fprintf(stderr, "\nError in create source

string:%s\n",errbuf);

return -1;

}

//打开捕获文件

if ((fp = pcap_open(source,/*设备名*/65536,/*要捕捉的数据包的部分,65535保证能捕获到不同数据链路层上的每个数据包的全部内容

*/

PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式

1000, // 读取超时时间

NULL, // 远程机器验证

errbuf // 错误缓冲池

)) == NULL)

{

fprintf(stderr, "\nCan not open the file %s.\n", source);

return -1;

}

(10)打开文件开始对数据包进行分析,通过timeval记录当前时间和上一次采

样时间,通过计算可以求出延迟时间,根据数据包的大小,进行字节转换,

求出采样时每秒的比特数以及每秒的数据包数量:

struct timeval *old_ts = (struct timeval *)argument;

u_int delay;

LARGE_INTEGER Bps,Pps;

struct tm *ltime;

char timestr[16];

time_t local_tv_sec;

//以毫秒计算上一次采样的延迟时间

//这个值通过采样到的时间戳获得

delay=(packet_header->https://www.360docs.net/doc/17636213.html,_sec - old_ts->tv_sec) * 1000000 - old_ts->tv_usec + packet_header->https://www.360docs.net/doc/17636213.html,_usec;

//获取每秒的比特数b/s

//Bps.QuadPart=(((*(LONGLONG*)(packet_content + 8)) * 8 * 1000000) / (delay));

/* ^ ^

| |

| |

| |

将字节转换成比特 -- | |

延时是以毫秒表示的 --| */

u_int m = (*(LONGLONG*)(packet_content + 8)) * 8 * 1000000;

u_int n = ((*(LONGLONG*)(packet_content)) * 1000000);

Bps.QuadPart = m/delay;

Pps.QuadPart = n/delay;

//printf("%I64u\n",m);

// printf("%I64u\n",delay);

//得到每秒的数据包数量

// Pps.QuadPart=(((*(LONGLONG*)(packet_content)) * 1000000) / (delay));

// 将时间戳转化为可识别的格式

/*local_tv_sec = packet_header->https://www.360docs.net/doc/17636213.html,_sec;

ltime=localtime(&local_tv_sec);

strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);*/

//打印时间戳

//printf("%s ", timestr);

//打印采样结果

//printf("%I64u\n",delay);

printf("\n**************************************************\n

每秒的比特数:");

printf("BPS=%I64u\n", Bps.QuadPart);

printf("每秒的数据包数量:");

printf("PPS=%I64u\n", Pps.QuadPart);

//存储当前的时间戳

old_ts->tv_sec=packet_header->https://www.360docs.net/doc/17636213.html,_sec;

old_ts->tv_usec=packet_header->https://www.360docs.net/doc/17636213.html,_usec;

结果显示如下:

(11)通过捕获的文件,对抓取的每个数据包的各层的首部进行解析,并将解析结果进行显示

①首先对以太网协议进行解析:

printf("捕获第%d个网络数据包\n",packet_number);

printf("捕获时间:");

printf("%s",ctime((const

time_t*)&packet_header->https://www.360docs.net/doc/17636213.html,_sec));

printf("数据包长度:");

printf("%d\n",packet_header->len);

printf("\n--------------以太网协议--------------\n");

ethernet_protocol=(struct

ether_header*)packet_content;//获得数据包内容

printf("以太网类型:");

ethernet_type=ntohs(ethernet_protocol->ether_type);//获得以太网类型

printf("%04x\n",ethernet_type);

switch (ethernet_type)

{

case 0x0800: printf("上层协议是IP协议\n");break;

case 0x0806: printf("上层协议是ARP协议\n");break;

case 0x8035: printf("上层协议是RARP协议\n");break;

case 0x814C: printf("上层协议是简单网络管理协议SNMP\n"); break;

case 0x8137: printf("上层协议是因特网包交换(IPX:Internet Packet Exchange)\n"); break;

case 0x86DD: printf("上层协议是IPv6协议\n"); break;

case 0x880B: printf("上层协议是点对点协议(PPP:

Point-to-Point Protocol)\n"); break;

default:break;

}

printf("MAC帧源地址:");

mac_string=ethernet_protocol->ether_shost;

printf("%02x:%02x:%02x:%02x:%02x:%02x\n",*mac_string,*(mac_stri ng+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_str ing+5));

printf("MAC帧目的地址:");

mac_string=ethernet_protocol->ether_dhost;

printf("%02x:%02x:%02x:%02x:%02x:%02x\n",*mac_string,*(mac_stri ng+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_str ing+5));

if(ethernet_type==0x0800)//继续分析IP协议

{

ip_protool_packet_callback

(argument,packet_header,packet_content + sizeof(struct

ether_header));

}

printf("----------------------------------------------\n\n");

packet_number++;

结果显示如下:

②对IP协议进行解析:

struct tcp_header *tcp_protocol;

// u_int offset;

//u_char tos;

u_int16_t checksum;

tcp_protocol = (struct tcp_header *)packet_content;

checksum = ntohs(tcp_protocol->check);

// tos = tcp_protocol->tcp_tos;

//offset = ntohs(tcp_protocol->tcp_off);

printf("\n----------------TCP协议

-----------------\n");

printf(" 源端口:%d",tcp_protocol->source_port);

printf(" 目的端口:%d\n", tcp_protocol->dest_port);

printf(" SEQ:%d\n", ntohs(tcp_protocol->seq));

printf(" ACK_SEQ:%d\n",ntohs(tcp_protocol->ack_seq));

printf(" fin标志位:%d\n",tcp_protocol->fin);

printf(" syn标志位:%d\n",tcp_protocol->syn);

printf(" rst标志位:%d\n",tcp_protocol->rst);

printf(" psh标志位:%d\n",tcp_protocol->psh);

printf(" ack标志位:%d\n",tcp_protocol->ack);

printf(" urg标志位:%d\n",tcp_protocol->urg);

printf(" ece标志位:%d\n",tcp_protocol->ece);

printf(" cwr标志位:%d\n",tcp_protocol->cwr);

printf("check:%d\n",checksum);

printf("滑动窗口:%d\n",tcp_protocol->window);

printf("紧急字段:%d\n",tcp_protocol->urg_ptr);

结果显示如下:

③ 对UDP协议进行解析

struct udp_header* udp_protocol;

//u_int header_length = 0;

u_int16_t checksum;

udp_protocol = (struct udp_header *)packet_content;

checksum = ntohs(udp_protocol->check);

/* u_int16_t source_port; //源地址端口

u_int16_t dest_port; //目的地址端口

u_int16_t len; //UDP长度

u_int16_t check; //UDP校验和

*/

printf("\n----------------UDP协议----------------\n"); printf("源端口:%d", udp_protocol->source_port);

printf(" 目的端口:%d\n", udp_protocol->dest_port); printf("用户数据包长度:%d\n", udp_protocol->len);

printf("校验和:%d\n", checksum);

结果显示如下:

④对TCP协议进行解析,其中三次握手的过程可以通过syn与ack

标志位来实现:

/* 1、syn=1

2、syn=1,ack=1

3、ack=1*/

struct tcp_header *tcp_protocol;

// u_int offset;

//u_char tos;

u_int16_t checksum;

tcp_protocol = (struct tcp_header *)packet_content;

checksum = ntohs(tcp_protocol->check);

// tos = tcp_protocol->tcp_tos;

//offset = ntohs(tcp_protocol->tcp_off);

printf("\n----------------TCP协议

-----------------\n");

printf(" 源端口:%d",tcp_protocol->source_port);

printf(" 目的端口:%d\n", tcp_protocol->dest_port);

printf(" SEQ:%d\n", ntohs(tcp_protocol->seq));

printf(" ACK_SEQ:%d\n",ntohs(tcp_protocol->ack_seq));

printf(" fin标志位:%d\n",tcp_protocol->fin);

printf(" syn标志位:%d\n",tcp_protocol->syn);

printf(" rst标志位:%d\n",tcp_protocol->rst);

printf(" psh标志位:%d\n",tcp_protocol->psh);

printf(" ack标志位:%d\n",tcp_protocol->ack);

printf(" urg标志位:%d\n",tcp_protocol->urg);

printf(" ece标志位:%d\n",tcp_protocol->ece);

printf(" cwr标志位:%d\n",tcp_protocol->cwr);

printf("check:%d\n",checksum);

printf("滑动窗口:%d\n",tcp_protocol->window);

printf("紧急字段:%d\n",tcp_protocol->urg_ptr);

结果显示如下:

(12)建立Hash表对数据包进行分流存储,结点与表声明如下,调用

InitHashTable()建立TCP和UDP的哈希表,然后调用Hash()函数利用用除留取余法获取最初的结点位置,通InsertHashTable查表将结点放入正确位置,SerchHashTable()查表返回bool值判断关键结点是否已经

在哈希表中:

typedef struct Node

{

struct in_addr ip_source_address;

struct in_addr ip_dest_address;

u_int16_t source_port;

u_int16_t dest_port;

u_int32_t sum;

struct node *next;

}HashNode;

typedef struct Table

{

HashNode *Table;

int count;

}HashTable;

void InitHashTable(HashTable* H)

{

// printf("hahahahahahahahahhahahaah");

int i;

H -> count = MAXSIZE;

H -> Table = ( HashNode* )malloc( ( H->count ) *

sizeof( HashNode ) );

for(i = 0;i < H->count; i++)

{

// H->Table[i].ip_source_address = NULLKEY;

// H->Table[i].ip_dest_address = NULLKEY;

H->Table[i].ip_source_address;

H->Table[i].ip_dest_address;

H->Table[i].source_port = 0;

H->Table[i].dest_port = 0;

H->Table[i].sum = NULLKEY;

H->Table[i].next = NULL;

}

}

int Hash(int key)

{

// printf("%d",key % MAXSIZE);

return key % MAXSIZE;

}

int InsertHashTable(HashTable *H,struct in_addr

source_address,struct in_addr dest_address,u_int16_t

s_port,u_int16_t d_port,int key)

{

int addr;

addr = Hash(key);

if(H->Table[addr].sum != key && H ->Table[addr].sum != NULLKEY)

{

HashNode *hashnode = (HashNode

*)malloc(sizeof(HashNode));

hashnode->next = H->Table[addr].next;

hashnode->ip_source_address = source_address;

hashnode->ip_dest_address = dest_address;

hashnode->sum = key;

hashnode->source_port = s_port;

hashnode->dest_port = d_port;

H->Table[addr].next = hashnode;

addr++;

return addr;

}

else if(H->Table[addr].sum = NULLKEY)

{

H->Table[addr].sum = key;

return addr;

}

}

bool SerchHashTable(HashTable *H,struct in_addr

source_address,struct in_addr dest_address,u_int16_t

s_port,u_int16_t d_port,int key)

{

int addr;

addr = Hash(key);

if(H->Table[addr].sum == key &&

(inet_ntoa(H->Table[addr].ip_source_address) == inet_ntoa(source_address)) &&

(inet_ntoa(H->Table[addr].ip_dest_address) == inet_ntoa(dest_address)) &&

(H->Table[addr].source_port == s_port) &&

(H->Table[addr].dest_port == d_port))

{

return true;

}

HashNode *p = H->Table[addr].next;

while(p != NULL)

{

if((p->sum == key) && (inet_ntoa(p->ip_source_address) == inet_ntoa(source_address)) &&

(inet_ntoa(p->ip_dest_address) ==

inet_ntoa(dest_address)) && (p->source_port == s_port)

&&(p->dest_port == d_port))

{

return true;

}

else

p->next;

}

return false;

}

(13)对基于TCP和UDP协议的两个哈希表进行初始化,通过判断协议号为6或17分别将数据包插入不同的链表中,实现数据包的分流,通过目的地址

与本机地址的对比,判断数据包的流向是下载还是上传:

HashTable Htcp;//基于TCP协议的哈希链表

InitHashTable(&Htcp);//进行初始化

HashTable Hudp;//基于UDP协议的哈希链表

InitHashTable(&Hudp);//进行初始化

while ((res = pcap_next_ex(fp, &header, &pkt_data)) >= 0) {

int tcp_s = 0;

int udp_s = 0;

struct ip_header *ip_protocol;

struct ip_header *ip_protocol1;

ip_protocol = (struct ip_header *)(pkt_data + 14);

struct tcp_header *tcp_protocol;

tcp_protocol = (struct tcp_header *)(pkt_data + 34);

struct udp_header *udp_protocol;

udp_protocol = (struct udp_header *)(pkt_data + 34);

if (ip_protocol -> ip_protocol == 17)

/*UDP协议*/

{

int sum = inet_addr(inet_ntoa(ip_protocol

->ip_souce_address));

udp_s = InsertHashTable(&Hudp, ip_protocol ->

ip_souce_address, ip_protocol -> ip_destination_address,

udp_protocol -> source_port, udp_protocol -> dest_port, sum);

//printf("%d",abs(udp_s));

printf("该数据包为UDP协议:\n");

printf("源IP地址:%s\n",

inet_ntoa(Hudp.Table[abs(udp_s)].source_addr));

printf("目的地址:%s\n",

inet_ntoa(Hudp.Table[abs(udp_s)].dest_addr));

printf("源端口:%d\n",

ntohs(Hudp.Table[abs(udp_s)].source_port));

printf("目的端口:%d\n",

ntohs(Hudp.Table[abs(udp_s)].dest_port));

printf("+++++++++++++++++++++++++++++++++++++++++++++\n");

}

else

{

if (ip_protocol -> ip_protocol == 6)

/*TCP协议*/

{

相关主题
相关文档
最新文档