使用套接字编程实现捕获一段时间内以本机为源地址或目的地址的IP数据包(不包括以广播形式发出的数据包),统计IP数据包的信息,列出本机与其他主机之间不同协议类型IP数据包的数量 及流量。以源地址 目的地址 协议类型 数据包数量 流量的格式输出统计信息。
1、IP数据报格式:
(1)版本:表示所使用的IP协议的版本,4表示IPv4,6表示IPv6
(2)报头长度:以4B为单位指定了IP数据包报头的长度。
(3)服务类型:指示了路由器应该如何处理该数据报。
(4)总长度:以字节为单位具体说明包括报头在内的整个IP数据报的长度,占16位,所以IP数据报最长可达65535B
(5)标识:用来唯一标识主机发送的每一份数据报,通常每发送一份报文它的值会加1
(6)标志:标志字段占3位,第一位保留并总设为0;第二位是禁止分片标志DF,该位为0,说明数据报可以被分片;第三位是分片标志MF,只有再DF为0才有效,用以标识此报文是否是这系列分片的最后一个,为0 标识接收到的是最后一个分片。
(7)片偏移:给出了每一个分片在完整IP数据报中的相对位置。片偏移以8B为偏移单位,因此除最后一片外,分片长度应是8B的整数倍
(8)生存时间:设置数据报可以经过的最多路由器数。
(9)协议类型:指出此IP数据报的最高层协议类型。常用的有1表示ICMP,2表示IGMP,6表示TCP,8表示EGP,17表示UDP,41表示IPv6,89表示OSPF
(10)头部校验和:IP数据报头部校验和采用网际校验和算法:发送端先把校验和字段置0,然后将头部划分为长度为16位的比特序列,对头部中每个16位进行二进制反码求和,结果存在检验和字段中。
(11)源IP地址:发送数据报的源主机IP地址
(12)目的IP地址:接收数据报的目的主机的IP地址
(13)选项域:长度范围为0~40B,主要用于支持纠错、测量及安全等措施
(14)填充域:当IP报头长度不是4B的整数倍时,必须利用填充域“添0”来加以补充。
2、程序流程图
3、部分函数注释
(1) int WSAStart(WORD wVersionRequested,LPWSADATA lpWSAData)
wVersionRequested:指明程序使用的Socket版本,其中高位字节指明副版本,低位字节指明主版本。
lpWSAData:返回请求的Socket的版本信息。
返回值:成功返回0,失败返回WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
(2)SOCKET sock;
sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,0);
AF_INET:指明通信发生的域为Internet协议簇
SOCK_RAW:指明套接字的类型为原始套接字。套接字分为流式套接字、数据报套接字、流式套接字。
IPPROTO_IP:指定套接字所用的特定协议为IP协议
返回值:成功返回套接字描述符,失败返回INVALID_SOCKET
(3)int bind(int sockfd,struct sockaddr* my_addr,int addrlen)
sockfd:套接字描述符
my_addr:特定的网络地址
addrlen:my_addr的长度
返回值:正确返回0,失败返回SOCKET_ERROR
(4)int recv(int fockfd,char* buff,int len,int flag)
fockfd:套接字描述符
buff:指向接收缓冲区
len:缓冲区的长度
flag:指明调用的方式,一般置0
返回值:成功返回接收的数据长度,失败返回-1
(5)setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int))
设置SockRaw这个套接字的ip选项中的IP_HDRINCL
4、3中表示套接字地址的数据结构
(1)sockaddr结构:针对各种通信域的套接字,存储他们的地址信息
struct sockaddr{
unsigned short sa_family;//地址家族
char sa_data;//协议地址
}
(2)sockaddr_in结构:专门针对Internet通信域,存储套接字相关的网络地址信息
struct sockaddr_in{
short int sin_family;
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//IP地址
unsigned char size_zero[8];//全为0
}
(3)in_addr结构:专门用来存储IP地址
struct in_addr{
unsigned long s_addr;
}
(4)以上3种数据结构的一般用法:
首先,定义一个sockaddr_in的结构实例,并将它清零
例:struct sockaddr_in myaddr;
memset(&myad,0,sizeof(struct sockaddr_in));
然后,为这个结构赋值:
例:myaddr.sin_family=AF_INET;
myaddr.sin_port=htons(8080);
myaddr.sin_addr.s_addr=htonl(INADDR_ANY)
最后,在函数调用时将这个股结构强制转换为sockaddr类型
例:accept(listenfd,(sockaddr*)&myad,&addrlen);
5、完整代码及注释:
节点类CIPNode:
IPNode.h文件:
#pragma once
class CIPNode
{
private:
unsigned long m_dwSourceIPAddr; //源IP地址
unsigned long m_dwDestIPAddr; //目的IP地址
unsigned char m_chProtocol; //IP包协议类型
unsigned long m_dwCounter; //数据包的数量
unsigned short m_dwFlow; //数据包的流量
public:
CIPNode * pNext;
public:
CIPNode(void);
~CIPNode(void);
CIPNode(unsigned long dwSourceIP,unsigned long dwDestIP,unsigned char chPro,unsigned short dwFlow);
//增加数据包数量
void addCount();
//增加数据包流量
void addFlow(unsigned short dwFlow);
//取得数据包数量
unsigned long getCount();
//取得数据包流量
unsigned short getFlow();
//取得源IP地址
unsigned long getSourceIPAddr();
//取得目的IP地址
unsigned long getDestIPAddr();
//取得协议类型
unsigned char getProtocol();
// 取得协议名称
char * getProtocol_String();
};
/******************************************************************************************************************************************************************************/
IPNode.cpp文件:
#include "stdafx.h"
#include "IPNode.h"
CIPNode::CIPNode(unsigned long dwSourceIP,unsigned long dwDestIP,unsigned char chPro,unsigned short dwFlow)
{
m_dwSourceIPAddr=dwSourceIP;
m_dwDestIPAddr=dwDestIP;
m_chProtocol=chPro;
m_dwCounter=1;
m_dwFlow=dwFlow;
}
CIPNode::CIPNode()
{
}
CIPNode::~CIPNode(void)
{
}
void CIPNode::addCount()
{
m_dwCounter++; //增加数据包数量
}
void CIPNode::addFlow(unsigned short dwFlow)
{
m_dwFlow+=dwFlow;
}
//取得数据包数量
unsigned long CIPNode::getCount()
{
return m_dwCounter;
}
unsigned short CIPNode::getFlow()
{
return m_dwFlow;
}
//取得源IP地址
unsigned long CIPNode::getSourceIPAddr()
{
return m_dwSourceIPAddr;
}
//取得目的IP地址
unsigned long CIPNode::getDestIPAddr()
{
return m_dwDestIPAddr;
}
//取得协议类型
unsigned char CIPNode::getProtocol()
{
return m_chProtocol;
}
// 取得协议名称
char * CIPNode::getProtocol_String()
{
switch(m_chProtocol)
{
case 1:
return "ICMP";
break;
case 2:
return "IGMP";
break;
case 4:
return "IP in IP";
break;
case 6:
return "TCP";
break;
case 8:
return "EGP";
break;
case 17:
return "UDP";
break;
case 41:
return "IPv6";
break;
case 46:
return "RSVP";
break;
case 89:
return "OSPF";
break;
default:
return "UNKNOWN";
}
}
/***********************************************************************************************************************************************************************/
节点链表类:CNodeList
NodeLIst.h文件
#pragma once
#include "IPNode.h"
class CNodeList
{
public:
CNodeList(void);
~CNodeList(void);
void addNode(unsigned long dwSourIP,unsigned long dwDestIP,unsigned char chPro,unsigned short dwFlow);
void print();
private:
CIPNode * pHead; //链表头
CIPNode * pTail; //链表尾
};
/***********************************************************************************************************************************************************************************/
NodeList.cpp文件:
#include "stdafx.h"
#include "NodeList.h"
#include "winsock2.h"
#include
using namespace std;
CNodeList::CNodeList(void)
{
pHead=pTail=NULL;
}
CNodeList::~CNodeList(void)
{
if(pHead!=NULL)
{
CIPNode * pTemp=pHead;
pHead=pHead->pNext;
delete pTemp;
}
}
void CNodeList::addNode(unsigned long dwSourceIP,unsigned long dwDestIP,unsigned char chpro,unsigned short dwFlow)
{
if(pHead==NULL) //链表为空
{
pTail=new CIPNode(dwSourceIP,dwDestIP,chpro,dwFlow);
pHead=pTail;
pTail->pNext=NULL;
}
else
{
CIPNode *pTemp;
for(pTemp=pHead;pTemp;pTemp=pTemp->pNext)
{
//如果链表中已存在该类型的IP包,则数据包个数加一
if(pTemp->getSourceIPAddr()==dwSourceIP&&
pTemp->getDestIPAddr()==dwDestIP&&pTemp->getProtocol()==chpro)
{
pTemp->addCount();
pTemp->addFlow(dwFlow);
break;
}
}
//如果链表中不存在该类型的IP包,则创建新的节点加入链表
if(pTemp==NULL)
{
pTail->pNext=new CIPNode(dwSourceIP,dwDestIP,chpro,dwFlow);
pTail=pTail->pNext;
pTail->pNext=NULL;
}
}
}
void CNodeList::print(){
CIPNode * pTemp;
if(pHead == NULL)
{
cout << "没有捕获到IP数据包!" << endl;
}
else
{
cout << "源地址 " << '\t' << "目的地址" << '\t' << "协议类型 " << "数据包数量" <<"数据流量"<
{
unsigned long dwSourTemp = pTemp->getSourceIPAddr();
unsigned long dwDestTemp = pTemp->getDestIPAddr();
cout << inet_ntoa(*(in_addr *)&(dwSourTemp)) << '\t';
cout << inet_ntoa(*(in_addr *)&(dwDestTemp)) << '\t';
cout << pTemp->getProtocol_String()<<'\t'<< pTemp->getCount()<<"\t" <
}
}
}
/**********************************************************************************************************************************************************************************************/
ConsoleApplication_IP.h文件:
#include
#include
#include
#include "IPNode.h"
#include "NodeList.h"
#pragma comment(lib, "Ws2_32.lib")
// 定义IP头部
typedef struct IPHeader
{
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;
/********************************************************************************************************************************************************************************************/
主函数所在文件:
// ConsoleApplication_IP.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "winsock2.h"
#include
#include "ConsoleAPPlication_IP.h"
#include
using namespace std;
#define IO_RCVALL _WSAIOW(IOC_VENDOR,1)
#define BURRER_SIZE 65535
int _tmain(int argc, _TCHAR* argv[])
{
//初始化Winsock DLL
WSADATA wsData;
if(WSAStartup(MAKEWORD(2,2),&wsData)!=0)
{
cout<<"Winsock DLL初始化失败!"<
}
//创建socket
SOCKET sock;
sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,0);
if(sock==INVALID_SOCKET)
{
cout<<"socket创建失败!"<
}
// 设置IP头操作选项,表示用户可以亲自对IP头进行处理
BOOL bFlag = TRUE;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&bFlag, sizeof(bFlag)) == SOCKET_ERROR)
{
cout << "Setsockopt 失败!" << endl;
return 3;
}
//绑定套接字
//获得主机网络地址
char LocalName[256];
gethostname(LocalName,256);
HOSTENT * pHost;
pHost=gethostbyname(LocalName);
//填充sockaddr_in结构
sockaddr_in addr_in;
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(8000);
addr_in.sin_addr=*(in_addr *)pHost->h_addr_list[0];
bind(sock,(sockaddr *)&addr_in,sizeof(addr_in));
//将网卡设置为混杂模式,以便接收所有所有的IP数据包
DWORD dwBufferLen[10];
DWORD dwBufferInLen=1;
DWORD dwBytesReturned=0;
WSAIoctl(sock,IO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferLen,
sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
// 把socket设置为非阻塞模式
DWORD dwTemp = 1;
ioctlsocket(sock, FIONBIO, &dwTemp);
// 设置接收缓冲区
char pBuffer[BURRER_SIZE];
CNodeList IpList;
double dwDuration = 10; // 捕获时间
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数据包
int nPacketSize = 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,pIpHdr->TotalLength);
}
}
}
// 输出统计结果
cout << "IP数据包统计结果: (" << dwDuration << " 秒)"<< endl << endl;
IpList.print();
cout << endl;
system("PAUSE");
return 0;
}