raw socket recv

本文介绍如何设计并实现一个简易的网络嗅探器,包括捕获数据包及解析IP与TCP头部信息的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IP数据包的总体结构:  

数据包  
IP头   TCP头(或其他信息头)   数据  

  数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下:  


16位   16位  
源端口   目的端口  
UDP长度   UDP校验和  

  而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成:  


16位   16位  
源端口   目的端口  
顺序号  
确认号  
TCP头长   (保留)7位   URG   ACK   PSH   RST   SYN   FIN   窗口大小  
校验和   紧急指针  
可选项(0或更多的32位字)  
数据(可选项)  

  对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义:  


typedef   struct   _TCP{   WORD   SrcPort;   //   源端口  
WORD   DstPort;   //   目的端口  
DWORD   SeqNum;   //   顺序号  
DWORD   AckNum;   //   确认号  
BYTE   DataOff;   //   TCP头长  
BYTE   Flags;   //   标志(URG、ACK等)  
WORD   Window;   //   窗口大小  
WORD   Chksum;   //   校验和  
WORD   UrgPtr;   //   紧急指针  
}   TCP;  
typedef   TCP   *LPTCP;  
typedef   TCP   UNALIGNED   *   ULPTCP;  

  在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下:  


16位   16位  
版本   IHL   服务类型   总长  
标识   标志   分段偏移  
生命期   协议   头校验和  
源地址  
目的地址  
选项(0或更多)  

  同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义:  


typedef   struct   _IP{  
union{   BYTE   Version;   //   版本  
BYTE   HdrLen;   //   IHL  
};  
BYTE   ServiceType;   //   服务类型  
WORD   TotalLen;   //   总长  
WORD   ID;   //   标识  
union{   WORD   Flags;   //   标志  
WORD   FragOff;   //   分段偏移  
};  
BYTE   TimeToLive;   //   生命期  
BYTE   Protocol;   //   协议  
WORD   HdrChksum;   //   头校验和  
DWORD   SrcAddr;   //   源地址  
DWORD   DstAddr;   //   目的地址  
BYTE   Options;   //   选项  
}   IP;  
typedef   IP   *   LPIP;  
typedef   IP   UNALIGNED   *   ULPIP;  


  在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。  

嗅探器的具体实现  

  根据前面的设计思路,不难写出网络嗅探器的实现代码,下面就给出一个简单的示例,该示例可以捕获到所有经过本地网卡的数据包,并可从中分析出协议、IP源地址、IP目标地址、TCP源端口号、TCP目标端口号以及数据包长度等信息。由于前面已经将程序的设计流程讲述的比较清楚了,因此这里就不在赘述了,下面就结合注释对程序的具体是实现进行讲解,同时为程序流程的清晰起见,去掉了错误检查等保护性代码。主要代码实现清单为:  

//   检查   Winsock   版本号,WSAData为WSADATA结构对象  
WSAStartup(MAKEWORD(2,   2),   &WSAData);  
//   创建原始套接字  
sock   =   socket(AF_INET,   SOCK_RAW,   IPPROTO_RAW));  
//   设置IP头操作选项,其中flag   设置为ture,亲自对IP头进行处理  
setsockopt(sock,   IPPROTO_IP,   IP_HDRINCL,   (char*)&flag,   sizeof(flag));  
//   获取本机名  
gethostname((char*)LocalName,   sizeof(LocalName)-1);  
//   获取本地   IP   地址  
pHost   =   gethostbyname((char*)LocalName));  
//   填充SOCKADDR_IN结构  
addr_in.sin_addr   =   *(in_addr   *)pHost-> h_addr_list[0];   //IP  
addr_in.sin_family   =   AF_INET;  
addr_in.sin_port   =   htons(57274);  
//   把原始套接字sock   绑定到本地网卡地址上  
bind(sock,   (PSOCKADDR)&addr_in,   sizeof(addr_in));  
//   dwValue为输入输出参数,为1时执行,0时取消  
DWORD   dwValue   =   1;  
//   设置   SOCK_RAW   为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL  
//   的定义为:   #define   SIO_RCVALL   _WSAIOW(IOC_VENDOR,1)  
ioctlsocket(sock,   SIO_RCVALL,   &dwValue);  

  前面的工作基本上都是对原始套接字进行设置,在将原始套接字设置完毕,使其能按预期目的工作时,就可以通过recv()函数从网卡接收数据了,接收到的原始数据包存放在缓存RecvBuf[]中,缓冲区长度BUFFER_SIZE定义为65535。然后就可以根据前面对IP数据段头、TCP数据段头的结构描述而对捕获的数据包进行分析:  

while   (true)  
{  
//   接收原始数据包信息  
int   ret   =   recv(sock,   RecvBuf,   BUFFER_SIZE,   0);  
if   (ret   >   0)  
{  
//   对数据包进行分析,并输出分析结果  
ip   =   *(IP*)RecvBuf;  
tcp   =   *(TCP*)(RecvBuf   +   ip.HdrLen);  
TRACE( "协议:   %s/r/n ",GetProtocolTxt(ip.Protocol));  
TRACE( "IP源地址:   %s/r/n ",inet_ntoa(*(in_addr*)&ip.SrcAddr));  
TRACE( "IP目标地址:   %s/r/n ",inet_ntoa(*(in_addr*)&ip.DstAddr));  
TRACE( "TCP源端口号:   %d/r/n ",tcp.SrcPort);  
TRACE( "TCP目标端口号:%d/r/n ",tcp.DstPort);  
TRACE( "数据包长度:   %d/r/n/r/n/r/n ",ntohs(ip.TotalLen));  
}  
}  

  其中,在进行协议分析时,使用了GetProtocolTxt()函数,该函数负责将IP包中的协议(数字标识的)转化为文字输出,该函数实现如下:  

#define   PROTOCOL_STRING_ICMP_TXT   "ICMP "  
#define   PROTOCOL_STRING_TCP_TXT   "TCP "  
#define   PROTOCOL_STRING_UDP_TXT   "UDP "  
#define   PROTOCOL_STRING_SPX_TXT   "SPX "  
#define   PROTOCOL_STRING_NCP_TXT   "NCP "  
#define   PROTOCOL_STRING_UNKNOW_TXT   "UNKNOW "  
……  
CString   CSnifferDlg::GetProtocolTxt(int   Protocol)  
{  
switch   (Protocol){  
case   IPPROTO_ICMP   :   //1   /*   control   message   protocol   */  
return   PROTOCOL_STRING_ICMP_TXT;  
case   IPPROTO_TCP   :   //6   /*   tcp   */  
return   PROTOCOL_STRING_TCP_TXT;  
case   IPPROTO_UDP   :   //17   /*   user   datagram   protocol   */  
return   PROTOCOL_STRING_UDP_TXT;  
default:  
return   PROTOCOL_STRING_UNKNOW_TXT;  
}  

  最后,为了使程序能成功编译,需要包含头文件winsock2.h和ws2tcpip.h。在本示例中将分析结果用TRACE()宏进行输出,在调试状态下运行,得到的一个分析结果如下:  

协议:   UDP  
IP源地址:   172.168.1.5  
IP目标地址:   172.168.1.255  
TCP源端口号:   16707  
TCP目标端口号:19522  
数据包长度:   78  
……  
协议:   TCP  
IP源地址:   172.168.1.17  
IP目标地址:   172.168.1.1  
TCP源端口号:   19714  
TCP目标端口号:10  
数据包长度:   200  
……  

  从分析结果可以看出,此程序完全具备了嗅探器的数据捕获以及对数据包的分析等基本功能。  


看看这篇文章吧
改下就OK了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值