zoukankan      html  css  js  c++  java
  • Winpcap网络编程九之Winpcap实战,ARP协议获得MAC表及主机通信

    大家好,本次我们须要完毕的任务是:

     完毕两台主机之间的数据通信(数据链路层)

    • 仿真ARP协议获得网段内主机的MAC表
    • 使用帧完毕两台主机的通信(Hello! I’m …)        
    声明:本文章的目的是为大家的Winpcap编程带来一定的借鉴,希望对大家的课程设计有一定的帮助。总之,我相信,大家看了前几篇 Winpcap 编程基础知识,再加上这篇文章的解说,一步一步做下来,相信你能成功的。

    P.S. 对Winpcap编程的基础知识有一定了解的就不用再去费工夫学习咯。我也是一点一点学习的。在此提供给大家一个学习文档。Winpcap中文文档

    P.P.S. 另外....CSDN略坑爹....我的代码它可能自己主动转码...我都为此改了好多次了...代码有显示问题与我联系...邮箱 1016903103@qq.com ...以后转自己个人空间...

    好了话不多说,我们步入正题...


    首先我们要理解ARP是干嘛的,ARP主要作用就是通过IP地址来获取MAC地址。

    那么如何获取呢?本机向局域网内主机发送ARP包,ARP包内包括了目的IP,源IP,目的MAC,源MAC。当中目的MAC地址为广播地址,FF-FF-FF-FF-FF-FF,即向局域网内全部主机发送一个ARP请求,那么其它主机收到这个请求之后则会向请求来源返回一个数据包。在这个返回的数据包中包括了自身的MAC地址。

    那么本机收到这些返回的数据包进行解析之后便会得到局域网内全部主机的MAC地址了..

    编程開始:

    新建一个C++项目,配好环境。引入Winpcap相关的库,这些不再赘述。

    头文件引入

    #define HAVE_REMOTE
    #define WPCAP
    #include <stdio.h>
    #include <stdlib.h>
    #include <pcap.h>
    在main函数中首先声明一系列变量例如以下

    	char *ip_addr;                                    //IP地址
    	char *ip_netmask;                             //子网掩码
    	unsigned char *ip_mac;          //本机MAC地址
    为这三个变量分配地址空间

    ip_addr = (char *) malloc(sizeof(char) * 16); //申请内存存放IP地址
    	if (ip_addr == NULL)
    	{
    		printf("申请内存存放IP地址失败!
    ");
    		return -1;
    	}
    	ip_netmask = (char *) malloc(sizeof(char) * 16); //申请内存存放NETMASK地址
    	if (ip_netmask == NULL)
    	{
    		printf("申请内存存放NETMASK地址失败!
    ");
    		return -1;
    	}
    	ip_mac = (unsigned char *) malloc(sizeof(unsigned char) * 6); //申请内存存放MAC地址
    	if (ip_mac == NULL)
    	{
    		printf("申请内存存放MAC地址失败!
    ");
    		return -1;
    	}
    接下来就是烂大街的程序,获取适配器列表并选中对应的适配器,凝视已经在代码中了,假设还有不明确的请參照前几次的解说。

    //获取本地适配器列表
    	if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
    		//结果为-1代表出现获取适配器列表失败
    		fprintf(stderr,"Error in pcap_findalldevs_ex:
    ",errbuf);
    		//exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
    		exit(1);
    	}
    	
    
    	for(d = alldevs;d !=NULL;d = d->next){
    		printf("-----------------------------------------------------------------
    number:%d
    name:%s
    ",++i,d->name);
    		if(d->description){
    			//打印适配器的描写叙述信息
    			printf("description:%s
    ",d->description);
    		}else{
    			//适配器不存在描写叙述信息
    			printf("description:%s","no description
    ");
    		}
    		//打印本地环回地址
    		 printf("	Loopback: %s
    ",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
    		 /**
    		 pcap_addr *  next     指向下一个地址的指针
    		 sockaddr *  addr       IP地址
    		 sockaddr *  netmask  子网掩码
    		 sockaddr *  broadaddr   广播地址
    		 sockaddr *  dstaddr        目的地址
    		 */
    		 pcap_addr_t *a;       //网络适配器的地址用来存储变量
    		 for(a = d->addresses;a;a = a->next){
    			 //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型
    			 switch (a->addr->sa_family)
    			 {
    				 case AF_INET:  //代表IPV4类型地址
    					 printf("Address Family Name:AF_INET
    ");
    					 if(a->addr){
    						 //->的优先级等同于括号,高于强制类型转换,由于addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
    						 printf("Address:%s
    ",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
    					 }
    					if (a->netmask){
    						 printf("	Netmask: %s
    ",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
    					}
    					if (a->broadaddr){
    						   printf("	Broadcast Address: %s
    ",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
    					 }
    					 if (a->dstaddr){
    						   printf("	Destination Address: %s
    ",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
    					 }
            			 break;
    				 case AF_INET6: //代表IPV6类型地址
    					 printf("Address Family Name:AF_INET6
    ");
    					 printf("this is an IPV6 address
    ");
    					 break;
    				 default:
    					 break;
    			 }
    		 }
    	}
    	//i为0代表上述循环未进入,即没有找到适配器,可能的原由于Winpcap没有安装导致未扫描到
    	if(i == 0){
    		printf("interface not found,please check winpcap installation");
    	}
    
    	int num;
    	printf("Enter the interface number(1-%d):",i);
    	//让用户选择选择哪个适配器进行抓包
    	scanf_s("%d",&num);
    	printf("
    ");
    
    	//用户输入的数字超出合理范围
    	if(num<1||num>i){
    		printf("number out of range
    ");
    		pcap_freealldevs(alldevs);
    		return -1;
    	}
    	//跳转到选中的适配器
    	for(d=alldevs, i=0; i< num-1 ; d=d->next, i++);
    
    	//执行到此处说明用户的输入是合法的
    	if((adhandle = pcap_open(d->name,		//设备名称
    														65535,       //存放数据包的内容长度
    														PCAP_OPENFLAG_PROMISCUOUS,  //混杂模式
    														1000,           //超时时间
    														NULL,          //远程验证
    														errbuf         //错误缓冲
    														)) == NULL){
            //打开适配器失败,打印错误并释放适配器列表
    		fprintf(stderr,"
    Unable to open the adapter. %s is not supported by WinPcap
    ", d->name);
            // 释放设备列表 
            pcap_freealldevs(alldevs);
            return -1;
    	}
    上述代码中须要另外声明的有:
    	pcap_if_t  * alldevs;       //全部网络适配器
    	pcap_if_t  *d;					//选中的网络适配器
    	char errbuf[PCAP_ERRBUF_SIZE];   //错误缓冲区,大小为256
    	pcap_t *adhandle;           //捕捉实例,是pcap_open返回的对象
    	int i = 0;                            //适配器计数变量
    char *iptos(u_long in);       //u_long即为 unsigned long
    /* 将数字类型的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 *)&in;
        which = (which + 1 == IPTOSBUFFERS ?

    0 : which + 1); sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return output[which]; }

    到此程序应该会编译通过,能够试着编译一下执行。

    GO ON...

    接下来我们首先要用ifget方法获取自身的IP和子网掩码

    函数声明:

    void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask);   
    //获取IP和子网掩码赋值为ip_addr和ip_netmask
    void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask) {
    	pcap_addr_t *a;
    	//遍历全部的地址,a代表一个pcap_addr
    	for (a = d->addresses; a; a = a->next) {
    		switch (a->addr->sa_family) {
    		case AF_INET:  //sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式。通经常使用的都是AF_INET。

    代表IPV4 if (a->addr) { char *ipstr; //将地址转化为字符串 ipstr = iptos(((struct sockaddr_in *) a->addr)->sin_addr.s_addr); //*ip_addr printf("ipstr:%s ",ipstr); memcpy(ip_addr, ipstr, 16); } if (a->netmask) { char *netmaskstr; netmaskstr = iptos(((struct sockaddr_in *) a->netmask)->sin_addr.s_addr); printf("netmask:%s ",netmaskstr); memcpy(ip_netmask, netmaskstr, 16); } case AF_INET6: break; } } }

    main函数继续写。例如以下调用。之前声明的ip_addr和ip_netmask就已经被赋值了

    ifget(d, ip_addr, ip_netmask); //获取所选网卡的基本信息--掩码--IP地址
    到如今我们已经获取到了本机的IP和子网掩码。下一步发送一个ARP请求来获取自身的MAC地址

    这个ARP请求的源IP地址就随便指定了。就相当于你构造了一个外来的ARP请求,本机捕获到了请求,然后发送回应给对方的数据包也被本机捕获到了并解析出来了。解析了自己发出去的数据包而已。

    那么我们就声明一个函数并实现:

    int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac);

    // 获取自己主机的MAC地址
    int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac) {
    	unsigned char sendbuf[42]; //arp包结构大小
    	int i = -1;
    	int res;
    	EthernetHeader eh; //以太网帧头
    	Arpheader ah;  //ARP帧头
    	struct pcap_pkthdr * pkt_header;
    	const u_char * pkt_data;
    	//将已开辟内存空间 eh.dest_mac_add 的首 6个字节的值设为值 0xff。
    	memset(eh.DestMAC, 0xff, 6); //目的地址为全为广播地址
    	memset(eh.SourMAC, 0x0f, 6);
    	memset(ah.DestMacAdd, 0x0f, 6);
    	memset(ah.SourceMacAdd, 0x00, 6);
    	//htons将一个无符号短整型的主机数值转换为网络字节顺序
    	eh.EthType = htons(ETH_ARP);
    	ah.HardwareType= htons(ARP_HARDWARE);
    	ah.ProtocolType = htons(ETH_IP);
    	ah.HardwareAddLen = 6;
    	ah.ProtocolAddLen = 4;
    	ah.SourceIpAdd = inet_addr("100.100.100.100"); //随便设的请求方ip
    	ah.OperationField = htons(ARP_REQUEST);
    	ah.DestIpAdd = inet_addr(ip_addr);
    	memset(sendbuf, 0, sizeof(sendbuf));
    	memcpy(sendbuf, &eh, sizeof(eh));
    	memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
    	printf("%s",sendbuf);
    	if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
    		printf("
    PacketSend succeed
    ");
    	} else {
    		printf("PacketSendPacket in getmine Error: %d
    ", GetLastError());
    		return 0;
    	}
    	//从interface或离线记录文件获取一个报文
    	//pcap_next_ex(pcap_t* p,struct pcap_pkthdr** pkt_header,const u_char** pkt_data)
    	while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
    		if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)
    				&& *(unsigned short*) (pkt_data + 20) == htons(ARP_REPLY)
    				&& *(unsigned long*) (pkt_data + 38)
    						== inet_addr("100.100.100.100")) {
    			for (i = 0; i < 6; i++) {
    				ip_mac[i] = *(unsigned char *) (pkt_data + 22 + i);
    			}
    			printf("获取自己主机的MAC地址成功!
    ");
    			break;
    		}
    	}
    	if (i == 6) {
    		return 1;
    	} else {
    		return 0;
    	}
    }
    当中我们须要定义一下常量例如以下

    #define ETH_ARP         0x0806  //以太网帧类型表示后面数据的类型,对于ARP请求或应答来说。该字段的值为x0806
    #define ARP_HARDWARE    1  //硬件类型字段值为表示以太网地址
    #define ETH_IP          0x0800  //协议类型字段表示要映射的协议地址类型值为x0800表示IP地址
    #define ARP_REQUEST     1   //ARP请求
    #define ARP_REPLY       2      //ARP应答
    #define HOSTNUM         255   //主机数量

    另外发送ARP请求少不了帧头和ARP头的结构。我们须要声明出来,另外我们构建发送包须要再声明两个结构体sparam和gparam

    //帧头部结构体。共14字节
    struct EthernetHeader
    {
        u_char DestMAC[6];    //目的MAC地址 6字节
        u_char SourMAC[6];   //源MAC地址 6字节
        u_short EthType;         //上一层协议类型。如0x0800代表上一层是IP协议,0x0806为arp  2字节
    };
    
    //28字节ARP帧结构
    struct Arpheader {
    	unsigned short HardwareType; //硬件类型
    	unsigned short ProtocolType; //协议类型
    	unsigned char HardwareAddLen; //硬件地址长度
    	unsigned char ProtocolAddLen; //协议地址长度
    	unsigned short OperationField; //操作字段
    	unsigned char SourceMacAdd[6]; //源mac地址
    	unsigned long SourceIpAdd; //源ip地址
    	unsigned char DestMacAdd[6]; //目的mac地址
    	unsigned long DestIpAdd; //目的ip地址
    };
    
    //arp包结构
    struct ArpPacket {
    	EthernetHeader ed;
    	Arpheader ah;
    };
    
    struct sparam {
    	pcap_t *adhandle;
    	char *ip;
    	unsigned char *mac;
    	char *netmask;
    };
    struct gparam {
    	pcap_t *adhandle;
    };
    
    struct sparam sp;
    struct gparam gp;
    到如今代码也是完整能够执行的,假设有问题请检查上述代码完整性和位置。

    可能出现的BUG:

    仅仅显示ARP发送成功,没有接受到并解析打印。

    可能的原因是帧构造有问题。字节没有对齐,有偏差。像#define一样

    写入例如以下代码:

    #pragma pack(1)  //按一个字节内存对齐

    GO ON..

    获取到了自身的MAC地址之后,就能够在本机上构建ARP广播请求,向局域网内的全部主机发送ARP请求,得到回应之后解析回应的数据包并进行解析,得到对方的MAC地址。在这里我们须要开启两个线程。一个用来发送一个用来接收。好,我们继续..

    先声明两个线程

    	HANDLE sendthread;      //发送ARP包线程
    	HANDLE recvthread;       //接受ARP包线程
    在main方法中继续写。对sp和gp两个ARP请求所须要的结构体进行赋值。

    赋值什么?就是你之前用ifget获取来的IP地址和子网掩码以及用getSelfMac获取来的MAC地址。

    sp.adhandle = adhandle;
    	sp.ip = ip_addr;
    	sp.mac = ip_mac;
    	sp.netmask = ip_netmask;
    	gp.adhandle = adhandle;
    接下来直接创建两个线程。一个是发送一个接受。分别调用两个方法。


    sendthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) SendArpPacket,
    			&sp, 0, NULL);
    	recvthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) GetLivePC, &gp,
    			0, NULL);
    	printf("
    listening on 网卡%d ...
    ", i);
    那么发送数据包的方法和接收解析数据包的方法如何实现呢?

    发送数据包。发送数据包先对结构体数据进行赋值。就像getSelfMac方法一样,然后声明了一个buffer用来存储每个字节内容。

    利用memset方法对buffer进行赋值。再利用一个for循环对255个主机进行发送,指定他们的IP地址。另外定义了一个flag,当发送成功之后将flag设置为1

    /* 向局域网内全部可能的IP地址发送ARP请求包线程 */
    DWORD WINAPI SendArpPacket(LPVOID lpParameter) //(pcap_t *adhandle,char *ip,unsigned char *mac,char *netmask)
    {
    	sparam *spara = (sparam *) lpParameter;
    	pcap_t *adhandle = spara->adhandle;
    	char *ip = spara->ip;
    	unsigned char *mac = spara->mac;
    	char *netmask = spara->netmask;
    	printf("ip_mac:%02x-%02x-%02x-%02x-%02x-%02x
    ", mac[0], mac[1], mac[2],
    			mac[3], mac[4], mac[5]);
    	printf("自身的IP地址为:%s
    ", ip);
    	printf("地址掩码NETMASK为:%s
    ", netmask);
    	printf("
    ");
    	unsigned char sendbuf[42]; //arp包结构大小
    	EthernetHeader eh;
    	Arpheader ah;
    	//赋值MAC地址
    	memset(eh.DestMAC, 0xff, 6);       //目的地址为全为广播地址
    	memcpy(eh.SourMAC, mac, 6);
    	memcpy(ah.SourceMacAdd, mac, 6);
    	memset(ah.DestMacAdd, 0x00, 6);
    	eh.EthType = htons(ETH_ARP);
    	ah.HardwareType = htons(ARP_HARDWARE);
    	ah.ProtocolType = htons(ETH_IP);
    	ah.HardwareAddLen = 6;
    	ah.ProtocolAddLen = 4;
    	ah.SourceIpAdd = inet_addr(ip); //请求方的IP地址为自身的IP地址
    	ah.OperationField = htons(ARP_REQUEST);
    	//向局域网内广播发送arp包
    	unsigned long myip = inet_addr(ip);
    	unsigned long mynetmask = inet_addr(netmask);
    	unsigned long hisip = htonl((myip & mynetmask));
    	//向255个主机发送
    	for (int i = 0; i < HOSTNUM; i++) {
    		ah.DestIpAdd = htonl(hisip + i);
    		//构造一个ARP请求
    		memset(sendbuf, 0, sizeof(sendbuf));
    		memcpy(sendbuf, &eh, sizeof(eh));
    		memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
    		//假设发送成功
    		if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
    			//printf("
    PacketSend succeed
    ");
    		} else {
    			printf("PacketSendPacket in getmine Error: %d
    ", GetLastError());
    		}
    		Sleep(50);
    	}
    	Sleep(1000);
    	flag = TRUE;
    	return 0;
    }
    注: 此函数和flag变量在前面别忘了声明一下...

    然后是接收数据包并打印MAC地址:

    /* 分析截留的数据包获取活动的主机IP地址 */
    DWORD WINAPI GetLivePC(LPVOID lpParameter) //(pcap_t *adhandle)
    {
    	gparam *gpara = (gparam *) lpParameter;
    	pcap_t *adhandle = gpara->adhandle;
    	int res;
    	unsigned char Mac[6];
    	struct pcap_pkthdr * pkt_header;
    	const u_char * pkt_data;
    	while (true) {
    		if (flag) {
    			printf("获取MAC地址完成,请输入你要发送对方的IP地址:
    ");
    			break;
    		}
    		if ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
    			if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)) {
    				ArpPacket *recv = (ArpPacket *) pkt_data;
    				if (*(unsigned short *) (pkt_data + 20) == htons(ARP_REPLY)) {
    					printf("-------------------------------------------
    ");
    					printf("IP地址:%d.%d.%d.%d   MAC地址:",
    						     recv->ah.SourceIpAdd & 255,
    							 recv->ah.SourceIpAdd >> 8 & 255,
    							 recv->ah.SourceIpAdd >> 16 & 255,
    							 recv->ah.SourceIpAdd >> 24 & 255);
    					for (int i = 0; i < 6; i++) {
    						Mac[i] = *(unsigned char *) (pkt_data + 22 + i);
    						printf("%02x", Mac[i]);
    					}
    					printf("
    ");
    				}
    			}
    		}
    		Sleep(10);
    	}
    	return 0;
    }
    以上暂告一段落。通过整合以上代码,我们能够得到例如以下执行结果:

    --------------------------------------------------
    number:1
    name:rpcap://DeviceNPF_{5AC72F8D-019C-4003-B51B-
    description:Network adapter 'Microsoft' on local h
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET6
    this is an IPV6 address
    --------------------------------------------------
    number:2
    name:rpcap://DeviceNPF_{C17EB3F6-1E86-40E5-8790-
    description:Network adapter 'Microsoft' on local h
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET
    Address:192.168.95.1
            Netmask: 255.255.255.0
            Broadcast Address: 255.255.255.255
    --------------------------------------------------
    number:3
    name:rpcap://DeviceNPF_{33E23A2F-F791-409B-8452-
    description:Network adapter 'Qualcomm Atheros Ar81
    oller' on local host
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET
    Address:121.250.216.237
            Netmask: 255.255.255.0
            Broadcast Address: 255.255.255.255
    --------------------------------------------------
    number:4
    name:rpcap://DeviceNPF_{DCCF036F-A9A8-4225-B980-
    description:Network adapter 'Microsoft' on local h
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET6
    this is an IPV6 address
    --------------------------------------------------
    number:5
    name:rpcap://DeviceNPF_{D62A0060-F424-46FC-83A5-
    description:Network adapter 'Microsoft' on local h
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET
    Address:192.168.191.1
            Netmask: 255.255.255.0
            Broadcast Address: 255.255.255.255
    --------------------------------------------------
    number:6
    name:rpcap://DeviceNPF_{B5224A53-8450-4537-AB3B-
    description:Network adapter 'Microsoft' on local h
            Loopback: no
    Address Family Name:AF_INET6
    this is an IPV6 address
    Address Family Name:AF_INET
    Address:192.168.191.2
            Netmask: 255.255.255.0
            Broadcast Address: 255.255.255.255
    Enter the interface number(1-6):3
    
    ipstr:121.250.216.237
    netmask:255.255.255.0
    
    PacketSend succeed
    获取自己主机的MAC地址成功!
    
    listening on 网卡2 ...
    ip_mac:dc-0e-a1-ec-53-c3
    自身的IP地址为:121.250.216.237
    地址掩码NETMASK为:255.255.255.0
    
    请按随意键继续. . . ------------------------------
    IP地址:121.250.216.1   MAC地址:000fe28e6100
    -------------------------------------------
    IP地址:121.250.216.3   MAC地址:089e012d20d5
    -------------------------------------------
    IP地址:121.250.216.5   MAC地址:5404a6af5f2d
    -------------------------------------------
    IP地址:121.250.216.6   MAC地址:28d244248d81
    -------------------------------------------
    IP地址:121.250.216.7   MAC地址:80fa5b0283f3
    -------------------------------------------
    IP地址:121.250.216.8   MAC地址:14dae9005b9e
    -------------------------------------------
    IP地址:121.250.216.9   MAC地址:b82a72bf8bce
    -------------------------------------------
    IP地址:121.250.216.12   MAC地址:84c9b2fefeed
    -------------------------------------------
    IP地址:121.250.216.15   MAC地址:28d2440b4b1b
    -------------------------------------------
    IP地址:121.250.216.16   MAC地址:bcee7b969beb
    -------------------------------------------
    ........此处省略一万字....
    接下来我们让用户输入要发送的IP地址和要发送的数据

    u_int ip1,ip2,ip3,ip4;
    		scanf_s("%d.%d.%d.%d",&ip1,&ip2,&ip3,&ip4);
    		printf("请输入你要发送的内容:
    ");
    		getchar();
    		gets_s(TcpData);
    		printf("要发送的内容:%s
    ",TcpData);
    声明一下TcpData

    	char TcpData[20];   //发送内容
    接下来就是重头戏了,须要声明各种结构体。我们发送的是TCP数据,这样。TCP的TcpData 就作为真正的内容,然后在前面加上TCP头,IP头,帧头,还有校验和要正确。

    最后构成一个完整的帧,那么另外声明的结构体例如以下,前面代码声明过的帧头部结构体就去掉了。

    //IP地址格式
    struct IpAddress
    {
        u_char byte1;
        u_char byte2;
        u_char byte3;
        u_char byte4;
    };
    
    //帧头部结构体,共14字节
    struct EthernetHeader
    {
        u_char DestMAC[6];    //目的MAC地址 6字节
        u_char SourMAC[6];   //源MAC地址 6字节
        u_short EthType;         //上一层协议类型。如0x0800代表上一层是IP协议,0x0806为arp  2字节
    };
    
    //IP头部结构体,共20字节
    struct IpHeader
    {
        unsigned char Version_HLen;   //版本号信息4位 。头长度4位 1字节
        unsigned char TOS;                    //服务类型    1字节
        short Length;                              //数据包长度 2字节
        short Ident;                                 //数据包标识  2字节
        short Flags_Offset;                    //标志3位,片偏移13位  2字节
        unsigned char TTL;                   //存活时间  1字节
        unsigned char Protocol;          //协议类型  1字节
        short Checksum;                       //首部校验和 2字节
    	IpAddress SourceAddr;       //源IP地址   4字节
    	IpAddress DestinationAddr; //目的IP地址  4字节
    };
    
    //TCP头部结构体。共20字节
    struct TcpHeader
    {
        unsigned short SrcPort;                        //源port号  2字节
        unsigned short DstPort;                        //目的port号 2字节
        unsigned int SequenceNum;               //序号  4字节
        unsigned int Acknowledgment;         //确认号  4字节
        unsigned char HdrLen;                         //首部长度4位,保留位6位 共10位
        unsigned char Flags;                              //标志位6位
        unsigned short AdvertisedWindow;  //窗体大小16位 2字节
        unsigned short Checksum;                  //校验和16位   2字节
        unsigned short UrgPtr;						  //紧急指针16位   2字节
    };
    
    //TCP伪首部结构体 12字节
    struct PsdTcpHeader
    {
    	IpAddress SourceAddr;                     //源IP地址  4字节
    	IpAddress DestinationAddr;             //目的IP地址 4字节
        char Zero;                                                    //填充位  1字节
        char Protcol;                                               //协议号  1字节
        unsigned short TcpLen;                           //TCP包长度 2字节
    };
    
    继续main函数中对各种结构体的数据进行初始化赋值,并计算校验和。

    //结构体初始化为0序列
    		memset(&ethernet, 0, sizeof(ethernet));
    		BYTE destmac[8];
    		//目的MAC地址,此处没有对帧的MAC地址进行赋值,由于网卡设置的混杂模式,能够接受经过该网卡的全部帧。当然最好的方法是赋值为ARP刚才获取到的MAC地址,当然不赋值也能够捕捉到并解析,在此处仅做下说明。
    		destmac[0] = 0x00;
    		destmac[1] = 0x11;
    		destmac[2] = 0x22;
    		destmac[3] = 0x33;
    		destmac[4] = 0x44;
    		destmac[5] = 0x55;
    		//赋值目的MAC地址
    		memcpy(ethernet.DestMAC, destmac, 6);
    		BYTE hostmac[8];
    		//源MAC地址
    		hostmac[0] = 0x00;
    		hostmac[1] = 0x1a;
    		hostmac[2] = 0x4d;
    		hostmac[3] = 0x70;
    		hostmac[4] = 0xa3;
    		hostmac[5] = 0x89;
    		//赋值源MAC地址
    		memcpy(ethernet.SourMAC, hostmac, 6);
    		//上层协议类型,0x0800代表IP协议
    		ethernet.EthType = htons(0x0800);
    		//赋值SendBuffer
    		memcpy(&SendBuffer, ðernet, sizeof(struct EthernetHeader));
    		//赋值IP头部信息
    		ip.Version_HLen = 0x45;
    		ip.TOS = 0;
    		ip.Length = htons(sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
    		ip.Ident = htons(1);
    		ip.Flags_Offset = 0;
    		ip.TTL = 128;
    		ip.Protocol = 6;
    		ip.Checksum = 0;
    		//源IP地址
    		ip.SourceAddr.byte1 = 127;
    		ip.SourceAddr.byte2 = 0;
    		ip.SourceAddr.byte3 = 0;
    		ip.SourceAddr.byte4 = 1;
    		//目的IP地址
    		ip.DestinationAddr.byte1 = ip1;
    		ip.DestinationAddr.byte2 = ip2;
    		ip.DestinationAddr.byte3 = ip3;
    		ip.DestinationAddr.byte4 = ip4;
    		//赋值SendBuffer
    		memcpy(&SendBuffer[sizeof(struct EthernetHeader)], &ip, 20);
    		//赋值TCP头部内容
    		tcp.DstPort = htons(102);
    		tcp.SrcPort = htons(1000);
    		tcp.SequenceNum = htonl(11);
    		tcp.Acknowledgment = 0;
    		tcp.HdrLen = 0x50;
    		tcp.Flags = 0x18;
    		tcp.AdvertisedWindow = htons(512);
    		tcp.UrgPtr = 0;
    		tcp.Checksum = 0;
    		//赋值SendBuffer
    		memcpy(&SendBuffer[sizeof(struct EthernetHeader) + 20], &tcp, 20);
    		//赋值伪首部
    		ptcp.SourceAddr = ip.SourceAddr;
    		ptcp.DestinationAddr = ip.DestinationAddr;
    		ptcp.Zero = 0;
    		ptcp.Protcol = 6;
    		ptcp.TcpLen = htons(sizeof(struct TcpHeader) + strlen(TcpData));
    		//声明暂时存储变量,用来计算校验和
    		char TempBuffer[65535];
    		memcpy(TempBuffer, &ptcp, sizeof(struct PsdTcpHeader));
    		memcpy(TempBuffer + sizeof(struct PsdTcpHeader), &tcp, sizeof(struct TcpHeader));
    		memcpy(TempBuffer + sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
    		//计算TCP的校验和
    		tcp.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
    		//又一次把SendBuffer赋值。由于此时校验和已经改变。赋值新的
    		memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader), &tcp, sizeof(struct TcpHeader));
    		memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
    		//初始化TempBuffer为0序列,存储变量来计算IP校验和
    		memset(TempBuffer, 0, sizeof(TempBuffer));
    		memcpy(TempBuffer, &ip, sizeof(struct IpHeader));
    		//计算IP校验和
    		ip.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct IpHeader));
    		//又一次把SendBuffer赋值,IP校验和已经改变
    		memcpy(SendBuffer + sizeof(struct EthernetHeader), &ip, sizeof(struct IpHeader));
    		//发送序列的长度
    		int size = sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData);
    		int result = pcap_sendpacket(adhandle, SendBuffer,size);
    		if (result != 0)
    		{
    			printf("Send Error!
    ");
    		} 
    		else
    		{
    			printf("Send TCP Packet.
    ");
    			printf("Dstination Port:%d
    ", ntohs(tcp.DstPort));
    			printf("Source Port:%d
    ", ntohs(tcp.SrcPort));
    			printf("Sequence:%d
    ", ntohl(tcp.SequenceNum));
    			printf("Acknowledgment:%d
    ", ntohl(tcp.Acknowledgment));
    			printf("Header Length:%d*4
    ", tcp.HdrLen >> 4);
    			printf("Flags:0x%0x
    ", tcp.Flags);
    			printf("AdvertiseWindow:%d
    ", ntohs(tcp.AdvertisedWindow));
    			printf("UrgPtr:%d
    ", ntohs(tcp.UrgPtr));
    			printf("Checksum:%u
    ", ntohs(tcp.Checksum));
    			printf("Send Successfully!
    ");
    		}
    校验和方法例如以下:

    //获得校验和的方法
    unsigned short checksum(unsigned short *data, int length)
    {
        unsigned long temp = 0;
        while (length > 1)
        {
            temp +=  *data++;
            length -= sizeof(unsigned short);
        }
        if (length)
        {
            temp += *(unsigned short*)data;
        }
        temp = (temp >> 16) + (temp &0xffff);
        temp += (temp >> 16);
        return (unsigned short)(~temp);
    }
    记得在声明一下这种方法。假设放在main函数前当然就不用声明啦。

    另外须要声明的变量有

    			struct EthernetHeader ethernet;    //以太网帧头
        struct IpHeader ip;                            //IP头
        struct TcpHeader tcp;                      //TCP头
        struct PsdTcpHeader ptcp;             //TCP伪首部
    unsigned char SendBuffer[200];       //发送队列
    
    接下来的执行结果:

    获取MAC地址完成,请输
    121.250.216.112
    请输入你要发送的内容
    what is tcp
    要发送的内容:what i
    Send TCP Packet.
    Dstination Port:102
    Source Port:1000
    Sequence:11
    Acknowledgment:0
    Header Length:5*4
    Flags:0x18
    AdvertiseWindow:512
    UrgPtr:0
    Checksum:17149
    Send Successfully!

    截图例如以下:


    好啦。发送帧到此就告一段落啦!

    假设有疑问请留言。

    帧的接收非常easy。直接贴源代码例如以下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <pcap.h>
    
    
    char *iptos(u_long in);       //u_long即为 unsigned long
    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
    //struct tm *ltime;					//和时间处理有关的变量
    
    struct IpAddress
    {
        u_char byte1;
        u_char byte2;
        u_char byte3;
        u_char byte4;
    };
    
    //帧头部结构体。共14字节
    struct EthernetHeader
    {
        u_char DestMAC[6];    //目的MAC地址 6字节
        u_char SourMAC[6];   //源MAC地址 6字节
        u_short EthType;         //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp  2字节
    };
    
    //IP头部结构体。共20字节
    struct IpHeader
    {
        unsigned char Version_HLen;   //版本号信息4位 ,头长度4位 1字节
        unsigned char TOS;                    //服务类型    1字节
        short Length;                              //数据包长度 2字节
        short Ident;                                 //数据包标识  2字节
        short Flags_Offset;                    //标志3位,片偏移13位  2字节
        unsigned char TTL;                    //存活时间  1字节
        unsigned char Protocol;           //协议类型  1字节
        short Checksum;                        //首部校验和 2字节
        IpAddress SourceAddr;           //源IP地址   4字节
        IpAddress DestinationAddr;   //目的IP地址  4字节
    };
    
    //TCP头部结构体。共20字节
    struct TcpHeader
    {
        unsigned short SrcPort;                        //源端口号  2字节
        unsigned short DstPort;                        //目的端口号 2字节
        unsigned int SequenceNum;               //序号  4字节
        unsigned int Acknowledgment;         //确认号  4字节
        unsigned char HdrLen;                         //首部长度4位,保留位6位 共10位
        unsigned char Flags;                              //标志位6位
        unsigned short AdvertisedWindow;  //窗体大小16位 2字节
        unsigned short Checksum;                  //校验和16位   2字节
        unsigned short UrgPtr;						  //紧急指针16位   2字节
    };
    
    //TCP伪首部结构体 12字节
    struct PsdTcpHeader
    {
        unsigned long SourceAddr;                     //源IP地址  4字节
        unsigned long DestinationAddr;             //目的IP地址 4字节
        char Zero;                                                    //填充位  1字节
        char Protcol;                                               //协议号  1字节
        unsigned short TcpLen;                           //TCP包长度 2字节
    };
    
    
    int main(){
    
    	EthernetHeader *ethernet;    //以太网帧头
        IpHeader *ip;                            //IP头
        TcpHeader *tcp;                      //TCP头
        PsdTcpHeader *ptcp;             //TCP伪首部
    
    	pcap_if_t  * alldevs;       //全部网络适配器
    	pcap_if_t  *d;					//选中的网络适配器
    	char errbuf[PCAP_ERRBUF_SIZE];   //错误缓冲区,大小为256
    	char source[PCAP_ERRBUF_SIZE];
    	pcap_t *adhandle;           //捕捉实例,是pcap_open返回的对象
    	int i = 0;                            //适配器计数变量
    	struct pcap_pkthdr *header;    //接收到的数据包的头部
        const u_char *pkt_data;			  //接收到的数据包的内容
    	int res;                                    //表示是否接收到了数据包
    	u_int netmask;                       //过滤时用的子网掩码
    	char packet_filter[] = "tcp";        //过滤字符
    	struct bpf_program fcode;                     //pcap_compile所调用的结构体
    
    	u_int ip_len;                                       //ip地址有效长度
    	u_short sport,dport;                        //主机字节序列
    	u_char packet[100];                       //发送数据包目的地址
    	pcap_dumper_t *dumpfile;         //堆文件
    
    	//time_t local_tv_sec;				//和时间处理有关的变量
        //char timestr[16];					//和时间处理有关的变量
    
    	
    	//获取本地适配器列表
    	if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
    		//结果为-1代表出现获取适配器列表失败
    		fprintf(stderr,"Error in pcap_findalldevs_ex:
    ",errbuf);
    		//exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
    		exit(1);
    	}
    	//打印设备列表信息
    	for(d = alldevs;d !=NULL;d = d->next){
    		printf("-----------------------------------------------------------------
    number:%d
    name:%s
    ",++i,d->name);
    		if(d->description){
    			//打印适配器的描写叙述信息
    			printf("description:%s
    ",d->description);
    		}else{
    			//适配器不存在描写叙述信息
    			printf("description:%s","no description
    ");
    		}
    		//打印本地环回地址
    		printf("	Loopback: %s
    ",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
    		
    		 pcap_addr_t *a;       //网络适配器的地址用来存储变量
    		 for(a = d->addresses;a;a = a->next){
    			 //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型
    			 switch (a->addr->sa_family)
    			 {
    				 case AF_INET:  //代表IPV4类型地址
    					 printf("Address Family Name:AF_INET
    ");
    					 if(a->addr){
    						 //->的优先级等同于括号,高于强制类型转换,由于addr为sockaddr类型。对其进行操作须转换为sockaddr_in类型
    						 printf("Address:%s
    ",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
    					 }
    					if (a->netmask){
    						 printf("	Netmask: %s
    ",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
    					}
    					if (a->broadaddr){
    						   printf("	Broadcast Address: %s
    ",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
    					 }
    					 if (a->dstaddr){
    						   printf("	Destination Address: %s
    ",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
    					 }
            			 break;
    				 case AF_INET6: //代表IPV6类型地址
    					 printf("Address Family Name:AF_INET6
    ");
    					 printf("this is an IPV6 address
    ");
    					 break;
    				 default:
    					 break;
    			 }
    		 }
    	}
    	//i为0代表上述循环未进入,即没有找到适配器,可能的原由于Winpcap没有安装导致未扫描到
    	if(i == 0){
    		printf("interface not found,please check winpcap installation");
    	}
    
    	int num;
    	printf("Enter the interface number(1-%d):",i);
    	//让用户选择选择哪个适配器进行抓包
    	scanf_s("%d",&num);
    	printf("
    ");
    
    	//用户输入的数字超出合理范围
    	if(num<1||num>i){
    		printf("number out of range
    ");
    		pcap_freealldevs(alldevs);
    		return -1;
    	}
    	//跳转到选中的适配器
    	for(d=alldevs, i=0; i< num-1 ; d=d->next, i++);
    
    	//执行到此处说明用户的输入是合法的
    	if((adhandle = pcap_open(d->name,		//设备名称
    														65535,       //存放数据包的内容长度
    														PCAP_OPENFLAG_PROMISCUOUS,  //混杂模式
    														1000,           //超时时间
    														NULL,          //远程验证
    														errbuf         //错误缓冲
    														)) == NULL){
            //打开适配器失败,打印错误并释放适配器列表
    		fprintf(stderr,"
    Unable to open the adapter. %s is not supported by WinPcap
    ", d->name);
            // 释放设备列表 
            pcap_freealldevs(alldevs);
            return -1;
    	}
    	
    
    	//打印输出,正在监听中
    	printf("
    listening on %s...
    ", d->description);
    
    	//所在网络不是以太网,此处仅仅取这样的情况
    	if(pcap_datalink(adhandle) != DLT_EN10MB)
        {
            fprintf(stderr,"
    This program works only on Ethernet networks.
    ");
            //释放列表
            pcap_freealldevs(alldevs);
            return -1;
        }
    
    	//先获得地址的子网掩码
    	if(d->addresses != NULL)
            //获得接口第一个地址的掩码 
            netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
        else
            // 如果接口没有地址,那么我们如果一个C类的掩码
            netmask=0xffffff;
    
    	//pcap_compile()的原理是将高层的布尔过滤表
    	//达式编译成可以被过滤引擎所解释的低层的字节码
    	if(pcap_compile(adhandle,	//适配器处理对象
    										&fcode,
    										packet_filter,   //过滤ip和UDP
    										1,                       //优化标志
    										netmask           //子网掩码
    										)<0)
    	{
    		//过滤出现故障
    		fprintf(stderr,"
    Unable to compile the packet filter. Check the syntax.
    ");
            // 释放设备列表
            pcap_freealldevs(alldevs);
            return -1;
    	}
    
    	//设置过滤器
        if (pcap_setfilter(adhandle, &fcode)<0)
        {
            fprintf(stderr,"
    Error setting the filter.
    ");
            //释放设备列表
            pcap_freealldevs(alldevs);
            return -1;
        }
    
    
    	//利用pcap_next_ex来接受数据包
    	while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0)
    	{
    		if(res ==0){
    			//返回值为0代表接受数据包超时。又一次循环继续接收
    			continue;
    		}else{
    			//执行到此处代表接受到正常从数据包
    			//header为帧的头部
    			printf("%.6ld len:%d ", header->ts.tv_usec, header->len);
    			// 获得IP数据包头部的位置
    			ip = (IpHeader *) (pkt_data +14);    //14为以太网帧头部长度
    			//获得TCP头部的位置
    			ip_len = (ip->Version_HLen & 0xf) *4;
    			printf("ip_length:%d ",ip_len);
    			tcp = (TcpHeader *)((u_char *)ip+ip_len);
    			char * data;
    			 data = (char *)((u_char *)tcp+20);
    			 //将网络字节序列转换成主机字节序列
    			sport = ntohs( tcp->SrcPort );
    			dport = ntohs( tcp->DstPort );
    			printf("srcport:%d desport:%d
    ",sport,dport);
    			printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d
    ",
    					ip->SourceAddr.byte1,
    					ip->SourceAddr.byte2,
    					ip->SourceAddr.byte3,
    					ip->SourceAddr.byte4,
    				    sport,
    				    ip->DestinationAddr.byte1,
    				    ip->DestinationAddr.byte2,
    				    ip->DestinationAddr.byte3,
    				    ip->DestinationAddr.byte4,
    				    dport);
    			printf("%s
    ",data);
    		}
    
    	}
    
    	
    	//释放网络适配器列表
    	pcap_freealldevs(alldevs);
    
    	/**
    	int pcap_loop  ( pcap_t *  p,  
    								  int  cnt,  
    								  pcap_handler  callback,  
    								  u_char *  user   
    								 );
         typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
                     const u_char *);
    	*/
    	//開始捕获信息,当捕获到数据包时,会自己主动调用这个函数
    	//pcap_loop(adhandle,0,packet_handler,NULL);
    
    	int inum;
    	scanf_s("%d", &inum);
    
    	return 0;
    
    }
    
    /* 每次捕获到数据包时,libpcap都会自己主动调用这个回调函数 */
    /**
    pcap_loop()函数是基于回调的原理来进行数据捕获的,如技术文档所说,这是一种精妙的方法。而且在某些场合下,
    它是一种非常好的选择。可是在处理回调有时候会并不有用。它会添加程序的复杂度。特别是在多线程的C++程序中
    */
    /*
    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
    {
        struct tm *ltime = NULL;
        char timestr[16];
        time_t local_tv_sec;
    
        // 将时间戳转换成可识别的格式
        local_tv_sec = header->ts.tv_sec;
        localtime_s(ltime,&local_tv_sec);
        strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
    
        printf("%s,%.6ld len:%d
    ", timestr, header->ts.tv_usec, header->len);
    
    }
    */
    /* 将数字类型的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 *)&in;
        which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
        sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
        return output[which];
    }
    

    执行截图例如以下




    Thank You

    如有问题,欢迎留言~





    
  • 相关阅读:
    监听器和过滤器
    jdbc电话本项目
    jdbc数据库中的增删改
    servlet实现登陆注册
    Python 数据分析基础小结
    分类模型构建
    回归模型构建
    sklearn——数据集调用及应用
    数据处理——数据集划分
    数据分析——Matplotlib图形绘制
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5232185.html
Copyright © 2011-2022 走看看