Winpcap网络编程八之Winpcap实战,ARP协议获得MAC表及主机通信

大家好,本次我们需要完成的任务是:

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

  • 仿真ARP协议获得网段内主机的MAC表
  • 使用帧完成两台主机的通信(Hello! I’m …)        
声明:本博客不针对伸手党,求代码?求源码?求完整程序?得了,既然你对自己的学习这么不负责,那我也没必要写出来了,本文章的目的是为大家的Winpcap编程带来一定的借鉴,希望对大家的课程设计有一定的帮助。总之,我相信,大家看了前几篇 Winpcap 编程基础知识,再加上这篇文章的讲解,一步一步做下来,相信你能成功的。

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

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


首先我们要理解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地址失败!\n");
		return -1;
	}
	ip_netmask = (char *) malloc(sizeof(char) * 16); //申请内存存放NETMASK地址
	if (ip_netmask == NULL)
	{
		printf("申请内存存放NETMASK地址失败!\n");
		return -1;
	}
	ip_mac = (unsigned char *) malloc(sizeof(unsigned char) * 6); //申请内存存放MAC地址
	if (ip_mac == NULL)
	{
		printf("申请内存存放MAC地址失败!\n");
		return -1;
	}
接下来就是烂大街的程序,获取适配器列表并选中相应的适配器,注释已经在代码中了,如果还有不明白的请参照前几次的讲解。

//获取本地适配器列表
	if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
		//结果为-1代表出现获取适配器列表失败
		fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
		//exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
		exit(1);
	}
	

	for(d = alldevs;d !=NULL;d = d->next){
		printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
		if(d->description){
			//打印适配器的描述信息
			printf("description:%s\n",d->description);
		}else{
			//适配器不存在描述信息
			printf("description:%s","no description\n");
		}
		//打印本地环回地址
		 printf("\tLoopback: %s\n",(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\n");
					 if(a->addr){
						 //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
						 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
					 }
					if (a->netmask){
						 printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
					}
					if (a->broadaddr){
						   printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
					 }
					 if (a->dstaddr){
						   printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
					 }
        			 break;
				 case AF_INET6: //代表IPV6类型地址
					 printf("Address Family Name:AF_INET6\n");
					 printf("this is an IPV6 address\n");
					 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("\n");

	//用户输入的数字超出合理范围
	if(num<1||num>i){
		printf("number out of range\n");
		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,"\nUnable to open the adapter. %s is not supported by WinPcap\n", 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 *)∈
    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\n",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\n",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("\nPacketSend succeed\n");
	} else {
		printf("PacketSendPacket in getmine Error: %d\n", 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地址成功!\n");
			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("\nlistening on 网卡%d ...\n", 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\n", mac[0], mac[1], mac[2],
			mac[3], mac[4], mac[5]);
	printf("自身的IP地址为:%s\n", ip);
	printf("地址掩码NETMASK为:%s\n", netmask);
	printf("\n");
	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("\nPacketSend succeed\n");
		} else {
			printf("PacketSendPacket in getmine Error: %d\n", 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地址:\n");
			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("-------------------------------------------\n");
					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("\n");
				}
			}
		}
		Sleep(10);
	}
	return 0;
}
以上暂告一段落,通过整合以上代码,我们可以得到如下运行结果:

--------------------------------------------------
number:1
name:rpcap://\Device\NPF_{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://\Device\NPF_{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://\Device\NPF_{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://\Device\NPF_{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://\Device\NPF_{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://\Device\NPF_{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("请输入你要发送的内容:\n");
		getchar();
		gets_s(TcpData);
		printf("要发送的内容:%s\n",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;                        //源端口号  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
{
	IpAddress SourceAddr;                     //源IP地址  4字节
	IpAddress DestinationAddr;             //目的IP地址 4字节
    char Zero;                                                    //填充位  1字节
    char Protcol;                                               //协议号  1字节
    unsigned short TcpLen;                           //TCP包长度 2字节
};
继续main函数中对各种结构体的数据进行初始化赋值,并计算校验和。

//结构体初始化为0序列
		memset(ðernet, 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.byte1 = 0;
		ip.SourceAddr.byte1 = 0;
		ip.SourceAddr.byte1 = 1;
		//目的IP地址
		ip.DestinationAddr.byte1 = ip1;
		ip.DestinationAddr.byte2 = ip2;
		ip.DestinationAddr.byte2 = ip3;
		ip.DestinationAddr.byte2 = 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!\n");
		} 
		else
		{
			printf("Send TCP Packet.\n");
			printf("Dstination Port:%d\n", ntohs(tcp.DstPort));
			printf("Source Port:%d\n", ntohs(tcp.SrcPort));
			printf("Sequence:%d\n", ntohl(tcp.SequenceNum));
			printf("Acknowledgment:%d\n", ntohl(tcp.Acknowledgment));
			printf("Header Length:%d*4\n", tcp.HdrLen >> 4);
			printf("Flags:0x%0x\n", tcp.Flags);
			printf("AdvertiseWindow:%d\n", ntohs(tcp.AdvertisedWindow));
			printf("UrgPtr:%d\n", ntohs(tcp.UrgPtr));
			printf("Checksum:%u\n", ntohs(tcp.Checksum));
			printf("Send Successfully!\n");
		}
校验和方法如下:

//获得校验和的方法
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伪首部
<span style="white-space:pre">	</span>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!

截图如下:

Winpcap网络编程八之Winpcap实战,ARP协议获得MAC表及主机通信

好啦,发送帧到此就告一段落啦!如果有疑问请留言。

帧的接收很简单,直接贴源码如下:

#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:\n",errbuf);
		//exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
		exit(1);
	}
	//打印设备列表信息
	for(d = alldevs;d !=NULL;d = d->next){
		printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
		if(d->description){
			//打印适配器的描述信息
			printf("description:%s\n",d->description);
		}else{
			//适配器不存在描述信息
			printf("description:%s","no description\n");
		}
		//打印本地环回地址
		printf("\tLoopback: %s\n",(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\n");
					 if(a->addr){
						 //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
						 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
					 }
					if (a->netmask){
						 printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
					}
					if (a->broadaddr){
						   printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
					 }
					 if (a->dstaddr){
						   printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
					 }
        			 break;
				 case AF_INET6: //代表IPV6类型地址
					 printf("Address Family Name:AF_INET6\n");
					 printf("this is an IPV6 address\n");
					 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("\n");

	//用户输入的数字超出合理范围
	if(num<1||num>i){
		printf("number out of range\n");
		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,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
        // 释放设备列表 
        pcap_freealldevs(alldevs);
        return -1;
	}
	

	//打印输出,正在监听中
	printf("\nlistening on %s...\n", d->description);

	//所在网络不是以太网,此处只取这种情况
	if(pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
        //释放列表
        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,"\nUnable to compile the packet filter. Check the syntax.\n");
        // 释放设备列表
        pcap_freealldevs(alldevs);
        return -1;
	}

	//设置过滤器
    if (pcap_setfilter(adhandle, &fcode)<0)
    {
        fprintf(stderr,"\nError setting the filter.\n");
        //释放设备列表
        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\n",sport,dport);
			
			printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
					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\n",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\n", 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 *)∈
    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];
}
运行截图

Winpcap网络编程八之Winpcap实战,ARP协议获得MAC表及主机通信

Winpcap网络编程八之Winpcap实战,ARP协议获得MAC表及主机通信

Thank You

如有问题,欢迎留言~





Winpcap网络编程八之Winpcap实战,ARP协议获得MAC表及主机通信

上一篇:怎么在popupwindow上监听手势


下一篇:详解Windows不重启使环境变量修改生效(经典)