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 


    
  • 相关阅读:
    解决配置Windows Update失败问题
    Rabbit MQ 消息确认和持久化机制
    Spring Boot事务管理(上)
    Spring Boot事务管理(中)
    Spring Boot事务管理(下)
    接口和抽象类的区别
    MySQL索引优化
    Spring 注解之@RestController与@Controller的区别
    Spring Boot核心注解@SpringBootApplication
    Java抽象类与接口的区别
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4060226.html
Copyright © 2011-2022 走看看