Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信
来源:互联网 发布:男士护肤面膜 知乎 编辑:程序博客网 时间:2024/06/04 01:14
注:源代码等等的我不会完全公开的,此篇文章写出来为大家的网络编程或者课程设计提供一定的思路..
好,本次我们需要完成的任务是:
完成两台主机通过中间主机的数据通信(网络层)
- 增加基于IP地址的转发功能
- 增加网络层封装
其实最主要的就是基于IP地址的转发功能,网络层的封装其实我们在初级功能中就已经做好了。
首先,实验的思路是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不变。构建新帧之后发送出去。
好啦,思路大体就是这样。
需要三个程序,一个是发送,一个路由,一个接收。所以一共三个程序要同时运行起来执行。
以上是我的大体思路,如有错误,还请指正。现已用代码实现完毕。
代码暂不公开,只提供部分重点代码解析:
一、发送端
其实发送端和初级功能的发送差不多
个人编写的交互流程如下:
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
具体代码不再解析,同上一篇初级功能。
二、路由端
首先要开启两个网卡,声明两个网卡对象和处理器
pcap_if_t *d,*d2;//选中的网络适配器pcap_t *adhandle,*adhandle2; //捕捉实例,是pcap_open返回的对象,adhandle是用来发送数据,adhandle2是用来接收数据
一个用来接收一个用来发送,这里定义了adhandle是用来发送,adhandle2是用来接收数据。
那么打开适配器就在main方法中,提前打开两个网卡
int num;printf("请输入你要转发数据的网卡代号:\n");//让用户选择选择哪个适配器进行转发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,"\nUnable to open the adapter. %s is not supported by WinPcap\n", 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,"\nUnable to open the adapter. %s is not supported by WinPcap\n", 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,可以持有这个指针来释放设备列表,出现错误时释放资源并退出。
初级功能中声明过了
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\n",data);printf("ip:");printf("%d.%d.%d.%d -> %d.%d.%d.%d\n",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\n", 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\n", 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最大值小于1500if(strlen(data)<1500){//目的MACBYTE 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!\n");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\n",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);//赋值SendBuffermemcpy(&SendBuffer, &send_ethernet, sizeof(struct EthernetHeader));
以上只是赋值了帧头,至于IP头,TCP头,数据的赋值就参照初级功能的来赋值吧,不要忘了校验和的检验。好,大体上就是这样,接受来数据包并转发出去的原理就是这样。
三、接收
不用多改,就是初级功能中的接收,在此写一写小小的优化措施,防止接收到过多的数据帧而造成不断乱蹦,导致你看不到接收的东西。
在打印的时候加一个过滤就好了。部分代码如下:
在main方法中提示用户输入要接收的IP地址
printf("请输入要接收的IP地址,输入0.0.0.0代表全部接收,请输入\n");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输入不合法,请重新输入:\n");}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\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("sou_mac:%02x-%02x-%02x-%02x-%02x-%02x\n", 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\n", des_mac[0], des_mac[1], des_mac[2], des_mac[3], des_mac[4], des_mac[5]);printf("%s\n",data);printf("-----------------------------------------------------\n");}
好,代码就先放送这么多,具体的实现只要有了思路我相信肯定不难,如有问题,欢迎与我交流。
我的邮箱 1016903103@qq.com
- Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信
- Winpcap网络编程九之Winpcap实战,ARP协议获得MAC表及主机通信
- Winpcap网络编程九之Winpcap实战,ARP协议获得MAC表及主机通信
- 两台主机通过SSH通信
- Winpcap网络编程八之Winpcap学习教程,发包,发包!
- MySQL数据库两台主机同步实战
- Erlang [1] 两台主机通信实现
- 通过ftp命令测试两台主机之间的网络情况
- Winpcap网络编程三之环境配置,Eclipse配置Winpcap环境
- Winpcap网络编程五之Winpcap学习教程,获取设备列表
- Winpcap网络编程六之Winpcap学习教程,获取已安装设备的高级信息
- Winpcap网络编程七之Winpcap学习教程,抓包,抓包!
- Winpcap网络编程四之环境配置,VS配置Winpcap环境
- ssh_两台互不信任的主机如何通过一台信任的主机传输数据
- Linux基本应用之NFS---实现两台主机间的通信
- 网络抓包之WinPcap
- 不同内网的两台主机之间的通信
- 两台Linux主机通信(服务器客户端搭建)
- 第九周 项目六穷举法解决组合问题之换分币
- Arcgis for Js实现graphiclayer的空间查询
- POJ 2425
- 找二叉树中两个节点的最近的公共父节点(三种情景)
- 推荐!国外程序员整理的 C++ 资源大全(原文http://fffaraz.github.io/awesome-cpp/#standard-libraries)
- Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信
- Sicily 1029. Rabbit
- Power Network (poj 1459 网络流)
- css盒子模型及实例
- Linux笔记
- socket 使用
- 博弈
- 第九周项目三输出星号图(f)
- poj1256