zoukankan      html  css  js  c++  java
  • Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信

    注:源码等等的我不会全然公开的,此篇文章写出来为大家的网络编程或者课程设计提供一定的思路..

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

    完毕两台主机通过中间主机的数据通信(网络层)

    •  添加基于IP地址的转发功能
    •  添加网络层封装

    事实上最基本的就是基于IP地址的转发功能,网络层的封装事实上我们在0基础功能中就已经做好了。

    首先,实验的思路是A通过中间主机B向C发送数据。那么B则作为一个路由器,B要监听两个网卡,一个网卡发来的数据通过还有一个网卡发出去。

    示意图例如以下:

    A--------->B1===B2------------>C

    从图上能够看出,B主机的两个网卡数据互通,A和B1则处于一个局域网内,B2和C处于还有一个局域网内。

    就比方这样,如今室友A在用有线上网,我的电脑B也在用有线上网,我们的有线处在同一局域网,我的电脑B同一时候散着一个无线网,我的手机C又连接到了这个无线上。

    那么要实现A到C的数据传送,即模拟室友A要发送数据到我的手机C,那么流程则是这种:

    室友A在有线局域网发送数据到我的网卡B1,B1将数据通过网卡B2转发到无线局域网,通过无线局域网到达我的手机C。

    A的发送要构建一个帧,目的MAC地址为B1,目的IP为C。B则要开启两个网卡,B1监听接收数据,B2网卡则要用ARP协议扫描所在无线局域网内的IP和MAC,B获取到了A发来的帧之后,解析它的IP地址和MAC地址,匹配刚才扫描得到的IP和MAC相应表,将源MAC换成B2网卡MAC,目的MAC换成C的MAC,IP不变,数据data不变。构建新帧之后发送出去。

    好啦,思路大体就是这样。

    须要三个程序,一个是发送,一个路由,一个接收。所以一共三个程序要同一时候执行起来执行。

    以上是我的大体思路,如有错误,还请指正。现已用代码实现完成。

    代码暂不公开,仅仅提供部分重点代码解析:

    一、发送端

    事实上发送端和0基础功能的发送差点儿相同

    个人编写的交互流程例如以下:

    IP地址:121.250.216.221   MAC地址:3c970e4b56d6con:127
    
    -------------------------------------------
    IP地址:121.250.216.227   MAC地址:089e01b948f4con:128
    
    -------------------------------------------
    IP地址:121.250.216.228   MAC地址:10bf48705aeecon:129
    
    获取MAC地址完成,请输入你要发送对方的IP地址:
    192.168.1.3
    请输入你要发送的内容:
    im cqc
    要发送的内容:im cqc
    

    详细代码不再解析,同上一篇0基础功能。

    二、路由端

    首先要开启两个网卡,声明两个网卡对象和处理器

    pcap_if_t  *d,*d2;					//选中的网络适配器
    pcap_t *adhandle,*adhandle2;           //捕捉实例,是pcap_open返回的对象,adhandle是用来发送数据,adhandle2是用来接收数据

    一个用来接收一个用来发送,这里定义了adhandle是用来发送,adhandle2是用来接收数据。

     那么打开适配器就在main方法中,提前打开两个网卡

    int num;
    	printf("请输入你要转发数据的网卡代号:
    ");
    	//让用户选择选择哪个适配器进行转发
    	scanf_s("%d",&num);
    
    	//跳转到选中的适配器
    	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;
    	}
    
    	int num2;
    	printf("请输入你要接收数据的网卡代号:");
    	//让用户选择用哪个网卡来收数据
    	scanf_s("%d",&num2);
    	//用户输入的数字超出合理范围
    
    
    	//跳转到选中的适配器
    	for(d2=alldevs, i=0; i< num2-1 ; d2=d2->next, i++);
    
    	//执行到此处说明用户的输入是合法的
    	if((adhandle2 = pcap_open(d2->name,		//设备名称
    													65535,       //存放数据包的内容长度
    													PCAP_OPENFLAG_PROMISCUOUS,  //混杂模式
    													1000,           //超时时间
    													NULL,          //远程验证
    													errbuf         //错误缓冲
    													)) == NULL){
        //打开适配器失败,打印错误并释放适配器列表
    	fprintf(stderr,"
    Unable to open the adapter. %s is not supported by WinPcap
    ", d2->name);

    接下来用用于发送的handle处理器来扫描它的局域网IP,获得局域网内的MAC地址,记录在一个表中,存放IP和MAC的相应关系。

    这个表能够用结构体数组来保存,比方能够这样:

    struct ip_mac_list{
    	IpAddress ip;
    	unsigned char mac[6];
    };
    ip_mac_list  list[256];                       //存储IP和MAC地址的相应表

    那么以上便是准备工作,我们完毕了两个网卡的打开,发送网卡扫描获取局域网MAC,接下来便是最重要的监听加转发。

    那么这个怎办?那就开一个新线程。

    让我们声明一个新的路由线程。

    DWORD WINAPI RouteThread(LPVOID lpParameter);

    那么线程要接收进来什么參数呢?

    首先必需要的是两个网卡处理器,在main方法中已经做好初始化的adhandle和adhandle2,另外还有alldevs,能够持有这个指针来释放设备列表,出现错误时释放资源并退出。

    0基础功能中声明过了

    struct sparam sp;
    struct gparam gp;

    这两个就是发送ARP线程和接收ARP线程中的两个參数,那么仿照这个功能,我们定义一个新的结构体

    struct rparam{
    	pcap_t *adhandle_rec;
    	pcap_t *adhandle_send;
    	pcap_if_t  * alldevs;       //全部网络适配器
    };

    在main方法中把它来初始化赋值

    rp.adhandle_send = adhandle;
    	rp.adhandle_rec = adhandle2;
    	rp.alldevs = alldevs;

    当做參数传入这个线程

    routethread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) RouteThread, &rp,
    			0, NULL);

    当中第四个參数就是传递了这个结构体进去。注意这个语句最好不要直接放在main方法中直接调用,能够在所有获取完MAC地址之后再开启这个线程。

    那么接下来就说一下这个线程都干了些什么,仅仅简略说一下核心部分。

    首先开启了这个线程之后会一直都在运行,那么就能够增加

    while((res = pcap_next_ex(adhandle2,&header,&pkt_data))>=0)

    这种while推断语句来一直监听数据包的接收,然后解析数据。

    ethernet =  (EthernetHeader *)(pkt_data);
    			for(int i=0;i<6;i++){
    				sou_mac[i] = ethernet->SourMAC[i];
    			}
    			for(int i=0;i<6;i++){
    				des_mac[i] = ethernet->DestMAC[i];
    			}
    			// 获得IP数据包头部的位置
    			ip = (IpHeader *) (pkt_data +14);    //14为以太网帧头部长度
    			//获得TCP头部的位置
    			ip_len = (ip->Version_HLen & 0xf) *4;
    			tcp = (TcpHeader *)((u_char *)ip+ip_len);
    			data = (char *)((u_char *)tcp+20);
    			printf("data:%s
    ",data);
    			printf("ip:");
    			printf("%d.%d.%d.%d -> %d.%d.%d.%d
    ",
    					ip->SourceAddr.byte1,
    					ip->SourceAddr.byte2,
    					ip->SourceAddr.byte3,
    					ip->SourceAddr.byte4,
    				    ip->DestinationAddr.byte1,
    				    ip->DestinationAddr.byte2,
    				    ip->DestinationAddr.byte3,
    				    ip->DestinationAddr.byte4);
    			 printf("sou_mac:%02x-%02x-%02x-%02x-%02x-%02x
    ", sou_mac[0], sou_mac[1], sou_mac[2],
    			    sou_mac[3], sou_mac[4], sou_mac[5]);
    			printf("des_mac:%02x-%02x-%02x-%02x-%02x-%02x
    ", des_mac[0], des_mac[1], des_mac[2],
    			    des_mac[3], des_mac[4], des_mac[5]);


    然后接下来每接收到一个数据,就进行构建新的帧转发出去,目的MAC先匹配list表,假设list没有找到,那么我让他指定了一个mac,比方广播MAC。源MAC地址则赋值网卡的MAC地址。

    注意,传统以太网中数据长度为45-1500,那么我在构建前把解析出的data作了下推断长度再构建,由于我已经把sendbuffer声明为一个固定长度了,为了防止越界,我先进行一个长度推断。

    //下面開始构建帧发送
    		//首先推断data最大值小于1500
    		if(strlen(data)<1500){
    			//目的MAC
    			BYTE send_destmac[6];
    			bool findMac = false;
    			for(int c = 0;c<con;c++){
    				if(ip->DestinationAddr.byte1 ==  list[c].ip.byte1&&
    					ip->DestinationAddr.byte2 == list[c].ip.byte2&&
    					ip->DestinationAddr.byte3 == list[c].ip.byte3&&
    					ip->DestinationAddr.byte4 == list[c].ip.byte4)
    				{
    					printf("Find its MAC!
    ");
    					findMac = true;
    					send_destmac[0] = list[c].mac[0];   
    					send_destmac[1] = list[c].mac[1];
    					send_destmac[2] = list[c].mac[2];
    					send_destmac[3] = list[c].mac[3];
    					send_destmac[4] = list[c].mac[4];
    					send_destmac[5] = list[c].mac[5];
    				}
    			}
    			if(!findMac){
    				send_destmac[0] = 0xff;   
    				send_destmac[1] = 0xff;   
    				send_destmac[2] = 0xff;   
    				send_destmac[3] = 0xff;   
    				send_destmac[4] = 0xff;   
    				send_destmac[5] = 0xff;   
    			}
    			printf("destmac:%02x-%02x-%02x-%02x-%02x-%02x
    ",
    				send_destmac[0],send_destmac[1],send_destmac[2],
    				send_destmac[3],send_destmac[4],send_destmac[5]
    				);
    			memcpy(send_ethernet.DestMAC, send_destmac, 6);
    			//源MAC地址
    			BYTE send_hostmac[6];
    			//源MAC地址
    			send_hostmac[0] = local_mac[0];     //赋值本地MAC地址
    			send_hostmac[1] = local_mac[1];
    			send_hostmac[2] = local_mac[2];
    			send_hostmac[3] = local_mac[3];
    			send_hostmac[4] = local_mac[4];
    			send_hostmac[5] = local_mac[5];
    			//赋值源MAC地址
    			memcpy(send_ethernet.SourMAC, send_hostmac, 6);
    			send_ethernet.EthType = htons(0x0800);
    			//赋值SendBuffer
    			memcpy(&SendBuffer, &send_ethernet, sizeof(struct EthernetHeader));

    以上仅仅是赋值了帧头,至于IP头,TCP头,数据的赋值就參照0基础功能的来赋值吧,不要忘了校验和的检验。好,大体上就是这样,接受来数据包并转发出去的原理就是这样。

    三、接收

    不用多改,就是0基础功能中的接收,在此写一写小小的优化措施,防止接收到过多的数据帧而造成不断乱蹦,导致你看不到接收的东西。

    在打印的时候加一个过滤就好了。部分代码例如以下:

    在main方法中提示用户输入要接收的IP地址

    printf("请输入要接收的IP地址,输入0.0.0.0代表所有接收,请输入
    ");
    	bool receiveAll = false;
    	u_int ip1,ip2,ip3,ip4;
    	bool legal = false;
    	while(!legal){
    		scanf_s("%d.%d.%d.%d",&ip1,&ip2,&ip3,&ip4);
    		if(ip1==0&&ip2==0&&ip3==0&&ip4==0){
    			receiveAll = true;
    			legal = true;
    			break;
    		}
    		if(ip1<0||ip1>255||ip2<0||ip2>255||ip3<0||ip3>255||ip4<1||ip4>254){
    			legal = false;
    			printf("对不起,IP输入不合法,请又一次输入:
    ");
    		}else{
    			legal = true;
    		}
    	}

    打印时的推断

    if(receiveAll||(ip->SourceAddr.byte1==ip1&&
    					ip->SourceAddr.byte2==ip2&&
    					ip->SourceAddr.byte3==ip3&&
    					ip->SourceAddr.byte4==ip4)){
    					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("sou_mac:%02x-%02x-%02x-%02x-%02x-%02x
    ", sou_mac[0], sou_mac[1], sou_mac[2],
    			    sou_mac[3], sou_mac[4], sou_mac[5]);
    				printf("des_mac:%02x-%02x-%02x-%02x-%02x-%02x
    ", des_mac[0], des_mac[1], des_mac[2],
    			    des_mac[3], des_mac[4], des_mac[5]);
    				printf("%s
    ",data);
    				printf("-----------------------------------------------------
    ");
    			}


     好,代码就先放送这么多,详细的实现仅仅要有了思路我相信肯定不难,如有问题,欢迎与我交流。

    我的邮箱 1016903103@qq.com 


    
  • 相关阅读:
    LeetCode Merge Two Sorted Lists 归并排序
    LeetCode Add Binary 两个二进制数相加
    LeetCode Climbing Stairs 爬楼梯
    034 Search for a Range 搜索范围
    033 Search in Rotated Sorted Array 搜索旋转排序数组
    032 Longest Valid Parentheses 最长有效括号
    031 Next Permutation 下一个排列
    030 Substring with Concatenation of All Words 与所有单词相关联的字串
    029 Divide Two Integers 两数相除
    028 Implement strStr() 实现 strStr()
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4060226.html
Copyright © 2011-2022 走看看