前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >腾讯云上 Winpcap 网络编程四之主机通信

腾讯云上 Winpcap 网络编程四之主机通信

原创
作者头像
崔庆才
修改2017-06-19 19:10:39
4.7K0
修改2017-06-19 19:10:39
举报
文章被收录于专栏:进击的Coder进击的Coder

学习资源

我的腾讯云主机预装了 Windows Server ,截图如下:

[image]
[image]

两台主机通信实战

在上一篇我给大家介绍了《腾讯云上Winpcap网络编程三之ARP协议获得MAC地址表》

接下来我们让用户输入要发送的 IP 地址和要发送的数据

代码语言:txt
复制
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

代码语言:txt
复制
	char TcpData[20];   //发送内容

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

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

代码语言:txt
复制
//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 函数中对各种结构体的数据进行初始化赋值,并计算校验和。

代码语言:txt
复制
//结构体初始化为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!\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");
		}

校验和方法如下:

代码语言:txt
复制
//获得校验和的方法
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 函数前当然就不用声明啦。

另外需要声明的变量有

代码语言:txt
复制
		struct EthernetHeader ethernet;    //以太网帧头
    struct IpHeader ip;                            //IP头
    struct TcpHeader tcp;                      //TCP头
    struct PsdTcpHeader ptcp;             //TCP伪首部

代码语言:txt
复制
unsigned char SendBuffer[200]; ? ? ? //发送队列

接下来的运行结果:

代码语言:txt
复制
获取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!

截图如下:

[image]
[image]

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

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

代码语言:txt
复制
#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 *)&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];
}

运行截图如下

[image]
[image]
[image]
[image]

Thank You

如有问题,欢迎留言~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 学习资源
  • 两台主机通信实战
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com