用C++实现网络编程---抓取网络数据包的实现方法

From: http://blog.csdn.net/zjl_1026_2001/article/details/2191311

做过网管或协议分析的人一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包。抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议、IP、UDP、TCP、甚至各种应用层协议),敏感数据的捕捉等。下面我们就来看看在windows下如何实现数据包的捕获。 

下面先对网络嗅探器的原理做简单介绍。

嗅探器设计原理

  嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。 

具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构:

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

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

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

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

16位 16位
源端口目的端口
顺序号
确认号
TCP头长(保留)7位URGACK PSHRSTSYNFIN 窗口大小
校验和 紧急指针
可选项(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、UDP、TCP、甚至各种应用层协议),敏感数据的捕捉等。下面我们就来看看在windows下如何实现数据包的捕获。

WINSOCK本身就提供了抓取流经网卡的所有数据包的函数,虽然只能在IP协议层上捕捉,但只要您的工作没有涉及到数据链路层的话,这也就足够用了。抓取数据包的编程方法基本和编写其它网络应用程序一样,只需多一个步骤,即将SOCKET设置为接收所有数据的模式,这是用WSAIoctl来实现的。

编程实现主要有以下几个步骤:
    1. 初始化WINSOCK库;
    2. 创建SOCKET句柄;
    3. 绑定SOCKET句柄到一个本地地址;
    4. 设置该SOCKET为接收所有数据的模式;
    5. 接收数据包;
    6. 关闭SOCKET句柄,清理WINSOCK库;

 (1)初始化winsock库

Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口。Winsock在常见的Windows平台上有两个主要的版本,即Winsock1和Winsock2。编写与Winsock1兼容的程序你需要引用头文件WINSOCK.H,如果编写使用Winsock2的程序,则需要引用WINSOCK2.H。此外还有一个MSWSOCK.H头文件,它是专门用来支持在Windows平台上高性能网络程序扩展功能的。使用WINSOCK.H头文件时,同时需要库文件WSOCK32.LIB,使用WINSOCK2.H时,则需要WS2_32.LIB,如果使用MSWSOCK.H中的扩展API,则需要MSWSOCK.LIB。正确引用了头文件,并链接了对应的库文件,你就构建起编写WINSOCK网络程序的环境了。

每个Winsock程序必须使用WSAStartup载入合适的Winsock动态链接库,如果载入失败,WSAStartup将返回SOCKET_ERROR,这个错误就是WSANOTINITIALISED,WSAStartup的定义如下:

int WSAStartup(

    WORD wVersionRequested,

    LPWSADATA lpWSAData

);

wVersionRequested指定了你想载入的Winsock版本,其高字节指定了次版本号,而低字节指定了主版本号。你可以使用宏MAKEWORD(x, y)来指定版本号,这里x代表主版本,而y代表次版本。lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链接库的信息。

当你使用完Winsock接口后,要调用下面的函数对其占用的资源进行释放:

    int WSACleanup(void);

    如果调用该函数失败也没有什么问题,因为操作系统为自动将其释放,对应于每一个WSAStartup调用都应该有一个WSACleanup调用.

错误处理
    
   Winsock函数调用失败大多会返回 SOCKET_ERROR(实际上就是-1),你可以调用WSAGetLastError得到错误的详细信息:

    int WSAGetLastError (void);

    对该函数的调用将返回一个错误码,其码值在WINSOCK.H或WINSOCK2.H(根据其版本)中已经定义,这些预定义值都以WSAE开头.同时你还可以使用WSASetLastError来自定义错误码值.

下面是我的winsock初始化例子:

WORD wVersion;

 WSADATA wsadata;

 int err;

 wVersion = MAKEWORD(2,2);

 // WSAStartup() initiates the winsock,if successful,the function returns zero

 err = ::WSAStartup(wVersion,&wsadata);

 if(err!=0)

 {

    printf("Couldn't initiate the winsock!/n");
 }

【注意】使用WORD 、WSADATA 和WSAStartup等时必须包含其头文件,加入语句:

#include "winsock2.h"

#include "windows.h"

我在初始化winsock库时遇到了一个问题,调用WSAStartUp(),编译后出现unresolved external symbol _WSAStartup@8,开始觉得很奇怪,因为头文件之类的都包含了怎么会出错呢?后来上网查了下才知道,需要包含一个动态链接库WS2_32.LIB,方法有两种:

第一种:

在菜单   project ->settings -> link  -> object/library   modules   下面输入ws2_32.lib  然后确定即可

 第二种:

在头文件中加入语句#pragma   comment(   lib,   "ws2_32.lib"   )  来显式加载。 即:

#include  <winsock2.h>
#pragma comment(lib, "WS2_32")

(2)创建socket句柄

winsock库初始化成功后就可以创建socket句柄了,使用函数socket即可。socket函数原型为:
  int socket(int domain, int type, int protocol);
  domain指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。

创建了socket句柄后,要将该句柄与本地IP绑定后才能使用。在进行绑定之前,要先获得本地机器的相关信息,包括主机名,主机IP地址等。最后进行绑定。我的代码如下:

SOCKET ServerSock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);

  char mname[128];

  struct hostent* pHostent;

  sockaddr_in myaddr;

  //Get the hostname of the local machine

  if( -1 == gethostname(mname, sizeof(mname)))

  {

   closesocket(ServerSock);

   printf("%d",WSAGetLastError());

   exit(-1);

  }

  else

  {

//Get the IP adress according the hostname and save it in pHostent   

   pHostent=gethostbyname((char*)mname);

   //填充sockaddr_in结构

   myaddr.sin_addr = *(in_addr *)pHostent->h_addr_list[0];

   myaddr.sin_family = AF_INET;

   myaddr.sin_port = htons(8888);//对于IP层可随意填

   //bind函数创建的套接字句柄绑定到本地地址

   if(SOCKET_ERROR==bind(ServerSock,(struct sockaddr *)&myaddr,sizeof(myaddr)))

   {

    closesocket(ServerSock);

    cout<<WSAGetLastError<<endl;

    exit(-1);

   }

接下来的工作就是把该socket设为接收所有数据的模式。

//设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
   u_long sioarg = 1;

   DWORD dwValue=0;

   if( SOCKET_ERROR == WSAIoctl( ServerSock, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,0,&dwValue,NULL,NULL ) )

   {

    closesocket(ServerSock);

    cout << WSAGetLastError();

    exit(-1);

   }

  接收网络数据的工作了。

【注意】这里一定要保证gethostname、gethostbyname、bind、ioctlsocket等函数都能够被正确执行,我在开始时就因为几个参数设置不对而导致bind和ioctlsocket执行错误,费了半天周折才搞定。

以下是我的完整代码:

WORD wVersion;

 WSADATA wsadata;

 int err;

 wVersion = MAKEWORD(2,2);

 // WSAStartup() initiates the winsock,if successful,the function returns zero

 err = ::WSAStartup(wVersion,&wsadata);

 if(err!=0)

 {

  printf("Couldn't initiate the winsock!/n");

 }

 else 

 {

  // create a socket

  SOCKET ServerSock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);

  char mname[128];

  struct hostent* pHostent;

  sockaddr_in myaddr;

  //Get the hostname of the local machine

  if( -1 == gethostname(mname, sizeof(mname)))

  {

   closesocket(ServerSock);

   printf("%d",WSAGetLastError());

   exit(-1);

  }

  else

  {

    //Get the IP adress according the hostname and save it in pHostent   

   pHostent=gethostbyname((char*)mname);

   //填充sockaddr_in结构

   myaddr.sin_addr = *(in_addr *)pHostent->h_addr_list[0];

   myaddr.sin_family = AF_INET;

   myaddr.sin_port = htons(8888);//对于IP层可随意填

   //bind函数创建的套接字句柄绑定到本地地址

   if(SOCKET_ERROR==bind(ServerSock,(struct sockaddr *)&myaddr,sizeof(myaddr)))

   {

    closesocket(ServerSock);

    cout<<WSAGetLastError<<endl;

    printf("..............................Error……");

    getchar();

    exit(-1);

   }


   //设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包

   u_long sioarg = 1;

   DWORD dwValue=0;

    if( SOCKET_ERROR == WSAIoctl( ServerSock, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,0,&dwValue,NULL,NULL ) )


   {

    closesocket(ServerSock);

    cout << WSAGetLastError();

    exit(-1);
   }

     //获取分析数据报文

   char buf[65535];

   int len = 0;

   listen(ServerSock,5);

   do

   {

    len = recv( ServerSock, buf, sizeof(buf),0);

    if( len > 0 )

    {

     //报文处理

    }
   }while( len > 0 ); 

  }       
}

 ::WSACleanup();


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/405368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二分图----最大匹配,最小点覆盖,最大点独立集

一.二分图 二分图又称作二部图&#xff0c;是图论中的一种特殊模型。 设G(V,E)是一个无向图&#xff0c;如果顶点V可分割为两个互不相交的子集(A,B)&#xff0c;并且图中的每条边&#xff08;i&#xff0c;j&#xff09;所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A…

[react] 怎么使用Context开发组件?

[react] 怎么使用Context开发组件&#xff1f; import React, {Component} from react// 首先创建一个 context 对象这里命名为&#xff1a;ThemeContext const ThemeContext React.createContext(light)// 创建一个祖先组件组件 内部使用Provier 这个对象创建一个组件 其中…

Linux 进程通信 -- 信号

一、概述 信号用于保持进程间的通信&#xff0c;可以备发送到一个进程或者一组进程&#xff0c;发送给进程的这个唯一信息通常是标志信号的一个数。信号可从键盘终端产生、虚拟内存中非法访问系统资源等情况下产生。信号异步发生&#xff0c;收到信号的进程可以采取某种动作或…

简单理解Socket

&#xfeff;&#xfeff;TCP/IP 要想理解socket首先得熟悉一下TCP/IP协议族&#xff0c; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;即传输控制协议/网间协议&#xff0c;定义了主机如何连入因特网及数据如何再它们之间传输的标准&…

女人必知:10个好习惯 让老公不想出轨

阅读提示&#xff1a;要知道&#xff0c;妻子这10个动作是征求了数百名老公的意见之后进行总结得出的&#xff0c;不仅效果显著&#xff0c;杀伤力强&#xff0c;最关键的是简单易行。女人必知&#xff1a;10个好习惯 让老公不想出轨  1.老公累了&#xff0c;靠在沙发上睡了&…

codeforce Gym 100500F Door Lock (二分)

根据题意略推一下&#xff0c;其实就是问你满足(a*(a1))/2 < m < ((a1)*a(a2))/2的a和m-(a*(a1))/2 -1是多少。 二分求解就行了 #include<cstdio>using namespace std; typedef long long ll;int main() {int T;scanf("%d",&T);for(int k 1; k <…

write() vs. writev()

From: http://www.cppblog.com/whoami17/archive/2009/05/10/82452.html 今天突然想比较一下 write() 和 writev() 的性能&#xff0c; 网上google了半天&#xff0c; 竟然没有发现一点有关的数据信息&#xff0c; 自己就测试了一下。 平台如下&#xff1a; CentOS 5.2 Lin…

[react] React Intl是什么原理?

[react] React Intl是什么原理&#xff1f; 实现原理和react-redux的实现原理类似&#xff0c;最外层包一个Provider&#xff0c;利用getChildContext&#xff0c;将intlConfigPropTypes存起来&#xff0c;在FormattedMessage、FormattedNumber等组件或者调用injectIntl生成的…

linux下GPRS模块ppp拨号上网

&#xfeff;&#xfeff;交叉编译器&#xff1a;arm-linux-gcc-4.5.4 Linux内核版本&#xff1a;Linux-3.0 主机操作系统&#xff1a;Centos 6.5 开发板&#xff1a;FL2440 GPRS:SIM900A 在开发SIM900模块之前&#xff0c;开发板已经加载了linux内核以及文件系统&#xf…

Linux 使用sigaction查询或设置信号处理方式

一、概述 Linux的系统调用函数sigaction()可以用来查询或设置信号处理方式。 函数声明为&#xff1a; #include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 如果执行成功返回0&#xff0c;否则返回-1。其中&…

mysql replicate error

工作日志之-MySQL slave Replication ErrorDescription&#xff1a; mysql> start slave;ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log [rootslave ~]# tail /var/log/mysqld.log 090105 11:…

高级I/O(七)--readv和writev函数

From: http://blog.chinaunix.net/uid-26822401-id-3158225.html readv和write函数让我们在单个函数调用里从多个不连续的缓冲里读入或写出。这些操作被称为分散读&#xff08;scatter read&#xff09;和集合写&#xff08;gather write&#xff09;。 #include <sys/uio…

CABasicAnimation动画

使用CABasicAnimation动画: CALayer *znzLayer; [[CALayer alloc]init]; //创建不断该表CALayer的transform属性动画CABasicAnimation *anim [CABasicAnimation animationWithKeyPath:"transform"];CATransform3D fromValue znzLayer.transform;//设置动画开始的属…

linux pppd脚本配置

&#xfeff;&#xfeff;摘要本文主要介绍了嵌入式Linux系统下使用pppd 2.4.4来进行PPP拨号需要使用的脚本是如何配置的&#xff0c;配置项的含义&#xff0c;同时也说明了如何来配置参数&#xff0c;实现ppp拨号上网。一&#xff0e;问题提出嵌入式Linux操作系统下&#xff…

React面试题目录汇总

总29 2021年11月10日&#xff08;更12&#xff09; [react] 什么时候使用状态管理器&#xff1f; [react] render函数中return如果没有使用()会有什么问题&#xff1f; [react] componentWillUpdate可以直接修改state的值吗 [react] 什么渲染劫持&#xff1f; [react] 说…

Linux运行可执行文件

原先以为linux下运行可执行文件在文件名前加"./"是执行命令&#xff0c;今天才搞明白是指当前目录转载于:https://www.cnblogs.com/dpc525/archive/2011/04/25/2028715.html

Linux下如何定位Java进程CPU利用率过高原因

首先通过Top命令查看占用CPU较高的进程PID&#xff0c;执行Top之后按1可以查看每个核占用比例 1 top这里由于我是用的虚拟机&#xff0c;即使我的Java进程占用CPU很高也只是占的虚拟机的&#xff0c;而对整个机器的CPU来说占的并不高。这里我们找到了pid7957 然后我们在根据pi…

在用户态下使用uint64_t

#include <stdio.h> #include <stdint.h>typedef struct {unsigned short msg_type;unsigned short msg_len;//msg body len }st_msg_h;typedef struct{char audio_name[64];uint64_t time_stamp;//输入的要播放的时间戳绝对时间int dec_ch;}st_msg_start;typedef …

【iOS开发】企业版证书($299)In-House方式发布指南

一、明确几个概念 1、企业版IDP&#xff1a;即iOS Development Enterprise Program。注意是$299&#xff0f;Year那种&#xff0c;并不是$99/Year的那种。 2、In House&#xff1a;是只企业内部发布&#xff0c;仅限企业内部人员使用。 二、In-House方式特点 1、不能发布到Appl…

[react] 说说你对windowing的了解

[react] 说说你对windowing的了解 展示长列表 如果你的应用会渲染大量的列表数据&#xff0c;我们建议使用一种称为‘windowing’的技术&#xff0c;这种技术下在任何给定的时间内只会渲染一小部分数据列表&#xff0c;并可以减少列表项的重复渲染&#xff08;即再次渲染已经渲…