本节任务
本次我们需要完成的任务是 完成两台主机通过中间主机的数据通信(网络层)
其实最主要的就是基于 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 不变。构建新帧之后发送出去。 好啦,思路大体就是这样。
实战
需要三个程序,一个是发送,一个路由,一个接收。所以一共三个程序要同时运行起来执行。 以上是我的大体思路,如有错误,还请指正。现已用代码实现完毕。 代码暂不公开,只提供部分重点代码解析:
一、发送端
其实发送端和初级功能的发送差不多 个人编写的交互流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
IP地址:121.250 .216 .221 MAC地址:3 c970e4b56d6con:127 ------------------------------------------- IP地址:121.250 .216 .227 MAC地址:089e01 b948f4con:128 ------------------------------------------- IP地址:121.250 .216 .228 MAC地址:10 bf48705aeecon:129 获取MAC地址完毕,请输入你要发送对方的IP地址: 192.168 .1 .3 请输入你要发送的内容: im cqc 要发送的内容:im cqc
具体代码不再解析,同上一篇初级功能。
二、路由端
首先要开启两个网卡,声明两个网卡对象和处理器
1 2
pcap_if_t *d,*d2; pcap_t *adhandle,*adhandle2;
一个用来接收一个用来发送,这里定义了 adhandle 是用来发送,adhandle2 是用来接收数据。 那么打开适配器就在 main 方法中,提前打开两个网卡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
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 的对应关系。 这个表可以用结构体数组来保存,比如可以这样:
1 2 3 4
struct ip_mac_list{ IpAddress ip; unsigned char mac[6]; };
那么以上便是准备工作,我们完成了两个网卡的打开,发送网卡扫描获取局域网 MAC,接下来便是最重要的监听加转发。 那么这个怎办?那就开一个新线程。 让我们声明一个新的路由线程。
1
DWORD WINAPI RouteThread(LPVOID lpParameter)
那么线程要接收进来什么参数呢? 首先必须要的是两个网卡处理器,在 main 方法中已经做好初始化的 adhandle 和 adhandle2,另外还有 alldevs,可以持有这个指针来释放设备列表,出现错误时释放资源并退出。 初级功能中声明过了 struct sparam sp; struct gparam gp; 这两个就是发送 ARP 线程和接收 ARP 线程中的两个参数,那么仿照这个功能,我们定义一个新的结构体
1 2 3 4 5
struct rparam { pcap_t *adhandle_rec; pcap_t *adhandle_send; pcap_if_t * alldevs; };
在 main 方法中把它来初始化赋值
1 2 3
rp.adhandle_send = adhandle rp.adhandle_rec = adhandle2 rp.alldevs = alldevs
当做参数传入这个线程
1 2
routethread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE ) RouteThread, &rp , 0 , NULL)
其中第四个参数就是传递了这个结构体进去。注意这个语句最好不要直接放在 main 方法中直接调用,可以在全部获取完 MAC 地址之后再开启这个线程。 那么接下来就说一下这个线程都干了些什么,只简略说一下核心部分。 首先开启了这个线程之后会一直都在执行,那么就可以加入
1
while((res = pcap_next_ex(adhandle2 ,&header ,&pkt_data ))>=0)
这样的 while 判断语句来一直监听数据包的接收,然后解析数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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 声明为一个固定长度了,为了防止越界,我先进行一个长度判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
if(strlen(data)<1500 ){ 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 ); BYTE send_hostmac[6 ]; send_hostmac[0 ] = local_mac[0 ]; 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 ]; memcpy(send_ethernet.SourMAC, send_hostmac, 6 ); send_ethernet.EthType = htons(0x0800 ); memcpy(&SendBuffer, &send_ethernet, sizeof(struct EthernetHeader));
以上只是赋值了帧头,至于 IP 头,TCP 头,数据的赋值就参照初级功能的来赋值吧,不要忘了校验和的检验。好,大体上就是这样,接受来数据包并转发出去的原理就是这样。
三、接收
不用多改,就是初级功能中的接收,在此写一写小小的优化措施,防止接收到过多的数据帧而造成不断乱蹦,导致你看不到接收的东西。 在打印的时候加一个过滤就好了。部分代码如下: 在 main 方法中提示用户输入要接收的 IP 地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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 ; } }
打印时的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
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" ); }
好,代码就先放送这么多,具体的实现只要有了思路我相信肯定不难,如有问题,欢迎与我交流。