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("请输入你要转发数据的网卡代号:\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,能够持有这个指针来释放设备列表,出现错误时释放资源并退出。
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\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最大值小于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!\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);
//赋值SendBuffer
memcpy(&SendBuffer, &send_ethernet, sizeof(struct EthernetHeader));
以上仅仅是赋值了帧头,至于IP头,TCP头,数据的赋值就參照0基础功能的来赋值吧,不要忘了校验和的检验。好,大体上就是这样,接受来数据包并转发出去的原理就是这样。
三、接收
不用多改,就是0基础功能中的接收,在此写一写小小的优化措施,防止接收到过多的数据帧而造成不断乱蹦,导致你看不到接收的东西。
在打印的时候加一个过滤就好了。部分代码例如以下:
在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表及主机通信
大家好,本次我们须要完毕的任务是: 完毕两台主机之间的数据通信(数据链路层) 仿真ARP协议获得网段内主机的MAC表 使用帧完毕两台主机的通信(Hello! I'm -) 声明:本文章的目的是为大家的 ...
- c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码
原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP 入门级客户端与服务端交互代码 网 ...
- C#网络编程技术微软Socket实战项目演练(三)
一.课程介绍 本次分享课程属于<C#高级编程实战技能开发宝典课程系列>中的第三部分,阿笨后续会计划将实际项目中的一些比较实用的关于C#高级编程的技巧分享出来给大家进行学习,不断的收集.整理 ...
- Python网络编程02 /基于TCP、UDP协议的socket简单的通信、字符串转bytes类型
Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...
- linu SSH 不在同一网段的两台机器如何通过ssh通信,SSH限制特定网段登陆的方法
1. linu SSH 不在同一网段的两台机器如何通过ssh通信 https://blog.csdn.net/lhf19891003/article/details/39895763 https:// ...
- cocos2dx 网络编程(CCHttpRequest和CURL两个方式)
转自:http://blog.csdn.net/sg619262284/article/details/20144087 在使用之前需要设置一些参数:参考:http://blog.csdn.net/w ...
- 初识Java网络编程
事实上网络编程简单的理解就是两台计算机相互通讯数据而已,对于程序员而言,去掌握一种编程接口并使用一种编程模型相对就会显得简单的多了,Java SDK提供一些相对简单的Api来完成这些工作.Socket ...
- Java Socket网络编程的经典例子(转)
事实上网络编程简单的理解就是两台计算机相互通讯数据而已,对于程序员而言,去掌握一种编程接口并使用一种编程模型相对就会显得简单的多了,Java SDK提供一些相对简单的Api来完成这些工作.Socket ...
- Java网络编程简明教程
Java网络编程简明教程 网络编程 计算机网络相关概念 计算机网络是两台或更多的计算机组成的网络,同一网络内的任意两台计算机可以直接通信,所有计算机必须遵循同一种网络协议. 互联网 互联网是连接计算 ...
随机推荐
- windows phone 墓碑化(9)
原文:windows phone 墓碑化(9) 几个术语的理解: 名称 说明 ; App ap = (App)Application.Current; // 构造函数 ...
- php-GD库函数(三)
<?php //imagefilledellipse — 画一椭圆并填充 /*bool imagefilledellipse ( resource $image , int $cx , int ...
- hbase列表排序
hbase都是依照字典序进行排序的,也就是降序,在页面的表现就是最早的数据(rowkey最小的)排在前面. 眼下的解决方式是:给主键添加一个外键关联表.外键的生成规则是 400000000000-主键 ...
- 王立平--include在Android应用
它包括一个布局和布局 1.在layout确定activity_other.xml布局 2.代码中的包括例如以下: <LinearLayout xmlns:android="http:/ ...
- 【ALearning】第四章 Android Layout组件布局(一)
在本章中,我们将Android学习组件布局.在前面的章节,我们也开始使用LinearLayout布局.然后我们在布局文件更加具体的学习和理解,会. Android的界面是有布局和组件协同完毕的,布局好 ...
- RedGate 工具SQLTEST 1.0.15.1
原文:RedGate 工具SQLTEST 1.0.15.1 RedGate 工具SQLTEST 1.0.15.1 SQL TEST1.0.15.1的破解可以参考这篇文章:http://www.cnbl ...
- 解决 - java.lang.OutOfMemoryError: unable to create new native thread
工作中碰到过这个问题好几次了,觉得有必要总结一下,所以有了这篇文章,这篇文章分为三个部分:认识问题.分析问题.解决问题. 一.认识问题: 首先我们通过下面这个 测试程序 来认识这个问题: 运行的环境 ...
- 编译Android源代码与内核总结
这些天花了些时间自己下载了android源代码来编译,当中走了一些弯路导致耗了些时间,如今又一次梳理总结下,让有同样想法的人自己编译的时候能少走些弯路,官方指导文档在http://source.and ...
- 在WPF中使用PlaneProjection模拟动态3D效果
原文:在WPF中使用PlaneProjection模拟动态3D效果 虽然在WPF中也集成了3D呈现的功能,在简单的3D应用中,有时候并不需要真实光影的3D场景.毕竟使用3D引擎会消耗很多资源,有时候使 ...
- FusionCharts简明教程(一)---建立FusionCharts图形
由于该项目需要的报告需要做的事情,选择FusionCharts作为一种工具. 由于该报告没有任何接触,网上有没有更具体fusionCharts课程,所以我们决定做一个彻底的研究FusionCharts ...