一、题目及要求(一)题目:IP数据包流量统计(二)要求:编制程序,监控网络,捕获一段时间内网络上的IP数据包,按IP 数据包的源地址统计出该源地址在该时间段内发出的IP包的个数,将其写入日志文件中或用图形表示出来(建议用图形表示出统计结果)。
用命令行运行:IPSta time logfile;其中IPSta是程序名,time是设定的统计时间间隔,logfil表示统计结果写入的日志文件名(若用图形表示统计结果则可以不选这个参数)。
或在此基础上用图形化界面实现上述功能。
二、系统概要设计(一)流量统计主要功能模块图图中取得网络适配器列表主要是得到网卡的相关信息,即网卡的个数、连接情况等,选择要监听的网络适配器就是要用户选择网卡,编译并设置过滤器是为了只捕获网络数据流的某些数据,将网络适配器设置为统计模式就是接受所有经过网卡的数据包,包括不是发给本机的数据包,接下来的任务就是开始主循环调用回调函数来显示网络流量了。
(二)程序流程图图中获取网卡列表是为了得到网卡的相关信息,以便于用户进行选择,选取Ethermet网卡是用户所选择的网卡类型,编译设置过滤器是为了编译并设置过滤器是为了只捕获网络数据流的某些数据,打开网卡既将网卡设置为混杂(统计)模式是为了接受所有经过网卡的数据包,包括不是发给本机的数据包,开始主循环以是否超时为判断条件,循环体内主要有捕获IP数据包、将IP包的源地址加入链表、条件判断,循环结束后输出链表内容,程序至此结束。
三、系统详细设计1. 取得网络适配器列表//取得网络适配器列表步骤中,alldevs是pcap_if_it指针,指向链表头,errbuf 是char类型数组,存储错误信息[3]。
pcap_findalldevs(&alldevs,errbuf);cout<<”网络适配器列表:”<<’\n’;for(d=alldevs;d;d=d->next){cout<<++i<<":"<<d->name;if (d->description)cout<<""<<d->description;elsecout<<"No description available!"<<'\n';}2. 指定要监听的网络适配器并打开cout<<”输入要监听的网络适配器号:”<<I;cin>>inum;for(d=alldevs,i=0;i<inum-1;d=d->next,i++);fp=pcap_open_live(d->name,65536,1,1000,errbuf);3. 编译并设置过滤器//编译过滤器,fp指向打开的网络适配器,fcode为编译完成后的过滤器存储地址,“tcp“给出了过滤条件,下一个参数表示是否被优化(0为false,1为true),最后一个参数给出了子网掩码。
pcap_compile(fp,&fcode,”tcp”,1,netmask)pcap_setfilter(fp,&fcode)4.设置网络适配器为统计模式if(pcap_setmode(fp,MODE_STAT)<0){cout<<”\n设置网络适配器模式错误!\n”;pcap_close(fp);}5.开始主循环,调用回调函数显示网络流量统计信息//pcap_loop由Winpcap库定义,对每个采集来的数据包都用ProcessPacket 函数进行处理,fp指向打开的网络适配器[4]pcap_loop(fp,0,dispatcher_handler,(PUCHAR)&st_ts);pcap_close(fp);6.回调函数的实现//对于捕获到的每一个数据包应用此回调函数voiddispatcher_handler(u_char *state,conststructpcap_pkthdr *header,constu_char *pkt_data){structtimeval *old_ts=(structtimeval *)state;u_int delay;LARGE_INTEGER Bps,Pps;struct tm *ltime;chartimestr[16];time_tlocal_tv_sec;//计算距上一个数据包的时间延迟,以ms为单位//这个值是从与一个数据包相关的时间戳中截获的delay=(header->_sec-old_ts->tv_sec)*1000000-old_ts->tv_usec+header->ts.t v_usec;//获得每秒的比特数Bps.QuadPart=(((*(LONGLONG*)(pkt_data+8))*8*1000000)/(delay));//获得每秒的数据包数Pps.QuadPart=(((*(LONGLONG*)(pkt_data))*1000000)/(delay));//将时间戳转变位可读的标准格式ltime=localtime(&header->_sec);strftime(timestr,sizeoftimestr,"%H:%M:%S",ltime);//Print timestampprintf("%s",timestr);//Print the samplesprintf("BPS=%I64u",Bps.QuadPart);printf("PPS=%I64u\n",Pps.QuadPart);//store current timestampold_ts->tv_sec=header->_sec;old_ts->tv_usec=header->_usec;运行结果:四、课程设计总结这次的课程设计也使我意识到了理论与实践相结合的重要作用,学习到知识应该应用到实践中。
在此次的课程设计过程中,熟悉IP包格式和加深对IP协议的理解,告诉我们要不断地学习网络方面的知识,精益求精,我们应不断地提高自己的水平,在每次的编程中能够有所领悟,让自己在程序的编译和应用上可以有更大一步的提高。
更好的学习计算机网络和其他方便的有关知识,做到精益求精。
指导教师评语:成绩:指导教师:年月日附录一:程序代码// NodeList.h: interface for the CNodeList class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_NODELIST_H__9781C411_82DF_47F7_A449_3054B18A550E_ _INCLUDED_)#defineAFX_NODELIST_H__9781C411_82DF_47F7_A449_3054B18A550E__INCLUDE D_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include<iostream>#include<fstream>#include<iomanip>#include<winsock2.h>#include "IPNode.h"using namespace std;// 结点链表类CNodeListclassCNodeList{private:CIPNode * pHead; // 链表头CIPNode * pTail; // 链表尾public:// Default constructorCNodeList();// Default destructorvirtual ~CNodeList();// 把新捕获的IP数据包加入链表voidaddNode(unsigned long, unsigned long, unsigned char);// 输出链表ostream& print(ostream&);};#endif// !defined(AFX_NODELIST_H__9781C411_82DF_47F7_A449_3054B18A550E__I NCLUDED_)// IPNode.h: interface for the CIPNode class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_IPNODE_H__1366A568_424A_4BDF_8E76_9AF5BA10D449__ INCLUDED_)#defineAFX_IPNODE_H__1366A568_424A_4BDF_8E76_9AF5BA10D449__INCLUDED _#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000// 结点类CIPNodeclassCIPNode{private:unsigned long m_dwSourIPAddr; // 源IP地址unsigned long m_dwDestIPAddr; // 目的IP地址unsigned char m_chProtocol; // IP包的协议类型unsigned long m_dwCouter; // 数据包的数量public:CIPNode * pNext; // 指向下一类IP结点CIPNode();virtual ~CIPNode();// 构造函数CIPNode(unsigned long, unsigned long, unsigned char);// 增加数据包的数量voidaddCount();// 取得数据包数量unsigned long getCount();// 取得源IP地址unsigned long getSourIPAddr();// 取得目的IP地址unsigned long getDestIPAddr();// 取得协议类型unsigned char getProtocol();// 取得协议名称(TCP,UDP,ICMP...)char * getProtocol_String();};#endif// !defined(AFX_IPNODE_H__1366A568_424A_4BDF_8E76_9AF5BA10D449__I NCLUDED_)#include<iostream>#include<fstream>#include<winsock2.h>#include<ws2tcpip.h>#include<stdio.h>#include<stdlib.h>#include<math.h>#include<time.h>using namespace std;#include "IPNode.h"#include "NodeList.h"#pragma comment(lib, "Ws2_32.lib")// 定义IP头部typedefstructIPHeader{unsigned char Version_HeaderLength; // 版本(4位)+首部长度(4位) unsigned char TypeOfService; // 服务类型unsigned short TotalLength; // 总长度unsigned short Identification; // 标识unsigned short Flags_FragmentOffset; // 标志(3位)+分片偏移(13位) unsigned char TimeToLive; // 生存时间unsigned char Protocal; // 协议unsigned short HeaderChecksum; // 首部校验和unsigned long SourceAddress; // 源IP地址unsigned long DestAddress; // 目的IP地址}IPHEADER;#include "IPMonitor.h"#define BURRER_SIZE 65535void main(intargc,char * argv[]){// 判断输入的命令行格式是否正确if (argc != 2){cout<< "请按以下格式输入命令行: IPMonitorduration_time"<<endl<< " 其中duration_time为监控时间, 单位为秒"<<endl;return;}WSADATA wsData;// 初始化Winsock DLLif (WSAStartup(MAKEWORD(2,2),&wsData) != 0){cout<< "WSAstartup failed!" <<endl;return;}// 创建Raw SocketSOCKET sock;if ( (sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED))== INV ALID_SOCKET ){cout<< "Create socket failed!" <<endl;return;}// 设置IP头操作选项,表示用户可以亲自对IP头进行处理BOOL bFlag = TRUE;if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&bFlag, sizeof(bFlag)) == SOCKET_ERROR){cout<< "Setsockopt failed!" <<endl;return;// 获取本地主机名charpHostName[128];if (gethostname(pHostName, 100) == SOCKET_ERROR){cout<< "Gethostname failed!" <<endl;return;}// 通过本地主机名获取本地IP地址hostent * pHostIP;if((pHostIP = gethostbyname(pHostName)) == NULL){cout<<"Gethostbyname failed!"<<endl;return;}// 填充sockaddr_in结构sockaddr_inaddr_in;addr_in.sin_addr = *(in_addr *)pHostIP->h_addr_list[0]; // 设定IP地址addr_in.sin_family = AF_INET; // 设定地址类型addr_in.sin_port = htons(8000); // 设定端口// 把原始套接字绑定到本机地址上if(bind(sock,(PSOCKADDR)&addr_in,sizeof(addr_in)) == SOCKET_ERROR) {cout<< "Bind failed!" <<endl;return;}// 把网卡设置为混杂模式,以便接收所有的IP包#define IO_RCV ALL _WSAIOW(IOC_VENDOR,1)unsigned long pBufferLen[10];unsigned long dwBufferInLen = 1;unsigned long dwBytesReturned = 0;if ((WSAIoctl(sock, IO_RCV ALL, &dwBufferInLen, sizeof(dwBufferInLen), &pBufferLen,sizeof(pBufferLen), &dwBytesReturned, NULL, NULL)) == SOCKET_ERROR){cout<<"Ioctlsocket failed!"<<endl;return;// 把socket设置为非阻塞模式unsigned long dwTemp = 1;ioctlsocket(sock, FIONBIO, &dwTemp);// 设置接收缓冲区charpBuffer[BURRER_SIZE];// 定义存放IP数据包的链表CNodeListIpList;double dwDuration = atof(argv[1]); // 输入参数为捕获时间time_t beg;time_t end;time(&beg); // 获得当前系统时间// 输出本地IP地址cout<<endl;cout<< "本机IP:"<<inet_ntoa(*(in_addr *)&(addr_in.sin_addr.S_un.S_addr)) <<endl<<endl;cout<< "开始捕获..." <<endl<<endl;while (1){time(&end); // 获得当前系统时间//如果捕获时间到,就结束捕获if (end-beg >= dwDuration){break;}// 捕获经过网卡的IP数据包intnPacketSize = recv(sock,pBuffer,BURRER_SIZE,0);if (nPacketSize> 0){IPHEADER * pIpHdr;// 通过指针把缓冲区中的内容强制转换为IPHEADER数据结构pIpHdr = (IPHEADER *)pBuffer;// 判断IP包的源IP地址或目的IP地址是否为本地主机的IP地址if (pIpHdr->SourceAddress == addr_in.sin_addr.S_un.S_addr|| pIpHdr->DestAddress == addr_in.sin_addr.S_un.S_addr){// 如果源IP地址或目的IP地址是本机IP,则将该IP数据包加IpList.addNode(pIpHdr->SourceAddress, pIpHdr->DestAddress, pIpHdr->Protocal);}}}// 输出统计结果cout<< "IP数据包统计结果: (" <<dwDuration<< " 秒)"<<endl<<endl;IpList.print(cout);cout<<endl;return;}。