大家好,本次我们需要完成的任务是:
完成两台主机之间的数据通信(数据链路层)
- 仿真ARP协议获得网段内主机的MAC表
- 使用帧完成两台主机的通信(Hello! I’m …)
P.S. 对Winpcap编程的基础知识有一定了解的就不用再去费工夫学习咯。我也是一点一点学习的,在此提供给大家一个学习文档,Winpcap中文文档
P.P.S. 另外....CSDN略坑爹....我的代码它可能自动转码...我都为此改了好多次了...代码有显示问题与我联系...邮箱 1016903103@qq.com ...以后转自己个人空间...
好了话不多说,我们步入正题...
首先我们要理解ARP是干嘛的,ARP主要作用就是通过IP地址来获取MAC地址。那么怎样获取呢?本机向局域网内主机发送ARP包,ARP包内包含了目的IP,源IP,目的MAC,源MAC,其中目的MAC地址为广播地址,FF-FF-FF-FF-FF-FF,即向局域网内所有主机发送一个ARP请求,那么其他主机收到这个请求之后则会向请求来源返回一个数据包。在这个返回的数据包中包含了自身的MAC地址。那么本机收到这些返回的数据包进行解析之后便会得到局域网内所有主机的MAC地址了..
编程开始:
新建一个C++项目,配好环境,引入Winpcap相关的库,这些不再赘述。
头文件引入
- #define HAVE_REMOTE
- #define WPCAP
- #include <stdio.h>
- #include <stdlib.h>
- #include <pcap.h>
- char *ip_addr; //IP地址
- char *ip_netmask; //子网掩码
- unsigned char *ip_mac; //本机MAC地址
- ip_addr = (char *) malloc(sizeof(char) * 16); //申请内存存放IP地址
- if (ip_addr == NULL)
- {
- printf("申请内存存放IP地址失败!\n");
- return -1;
- }
- ip_netmask = (char *) malloc(sizeof(char) * 16); //申请内存存放NETMASK地址
- if (ip_netmask == NULL)
- {
- printf("申请内存存放NETMASK地址失败!\n");
- return -1;
- }
- ip_mac = (unsigned char *) malloc(sizeof(unsigned char) * 6); //申请内存存放MAC地址
- if (ip_mac == NULL)
- {
- printf("申请内存存放MAC地址失败!\n");
- return -1;
- }
- //获取本地适配器列表
- if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
- //结果为-1代表出现获取适配器列表失败
- fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
- //exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
- exit(1);
- }
- for(d = alldevs;d !=NULL;d = d->next){
- printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
- if(d->description){
- //打印适配器的描述信息
- printf("description:%s\n",d->description);
- }else{
- //适配器不存在描述信息
- printf("description:%s","no description\n");
- }
- //打印本地环回地址
- printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
- /**
- pcap_addr * next 指向下一个地址的指针
- sockaddr * addr IP地址
- sockaddr * netmask 子网掩码
- sockaddr * broadaddr 广播地址
- sockaddr * dstaddr 目的地址
- */
- pcap_addr_t *a; //网络适配器的地址用来存储变量
- for(a = d->addresses;a;a = a->next){
- //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型
- switch (a->addr->sa_family)
- {
- case AF_INET: //代表IPV4类型地址
- printf("Address Family Name:AF_INET\n");
- if(a->addr){
- //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
- printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
- }
- if (a->netmask){
- printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
- }
- if (a->broadaddr){
- printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
- }
- if (a->dstaddr){
- printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
- }
- break;
- case AF_INET6: //代表IPV6类型地址
- printf("Address Family Name:AF_INET6\n");
- printf("this is an IPV6 address\n");
- break;
- default:
- break;
- }
- }
- }
- //i为0代表上述循环未进入,即没有找到适配器,可能的原因为Winpcap没有安装导致未扫描到
- if(i == 0){
- printf("interface not found,please check winpcap installation");
- }
- int num;
- printf("Enter the interface number(1-%d):",i);
- //让用户选择选择哪个适配器进行抓包
- scanf_s("%d",&num);
- printf("\n");
- //用户输入的数字超出合理范围
- if(num<1||num>i){
- printf("number out of range\n");
- pcap_freealldevs(alldevs);
- return -1;
- }
- //跳转到选中的适配器
- 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;
- }
- pcap_if_t * alldevs; //所有网络适配器
- pcap_if_t *d; //选中的网络适配器
- char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256
- pcap_t *adhandle; //捕捉实例,是pcap_open返回的对象
- int i = 0; //适配器计数变量
- char *iptos(u_long in); //u_long即为 unsigned long
- /* 将数字类型的IP地址转换成字符串类型的 */
- #define IPTOSBUFFERS 12
- char *iptos(u_long in)
- {
- static char output[IPTOSBUFFERS][3*4+3+1];
- static short which;
- u_char *p;
- p = (u_char *)∈
- which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
- sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
- return output[which];
- }
Go ON...
接下来我们首先要用ifget方法获取自身的IP和子网掩码
函数声明:
- void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask);
- //获取IP和子网掩码赋值为ip_addr和ip_netmask
- void ifget(pcap_if_t *d, char *ip_addr, char *ip_netmask) {
- pcap_addr_t *a;
- //遍历所有的地址,a代表一个pcap_addr
- for (a = d->addresses; a; a = a->next) {
- switch (a->addr->sa_family) {
- case AF_INET: //sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式。通常用的都是AF_INET。代表IPV4
- if (a->addr) {
- char *ipstr;
- //将地址转化为字符串
- ipstr = iptos(((struct sockaddr_in *) a->addr)->sin_addr.s_addr); //*ip_addr
- printf("ipstr:%s\n",ipstr);
- memcpy(ip_addr, ipstr, 16);
- }
- if (a->netmask) {
- char *netmaskstr;
- netmaskstr = iptos(((struct sockaddr_in *) a->netmask)->sin_addr.s_addr);
- printf("netmask:%s\n",netmaskstr);
- memcpy(ip_netmask, netmaskstr, 16);
- }
- case AF_INET6:
- break;
- }
- }
- }
- ifget(d, ip_addr, ip_netmask); //获取所选网卡的基本信息--掩码--IP地址
这个ARP请求的源IP地址就随便指定了,就相当于你构造了一个外来的ARP请求,本机捕获到了请求,然后发送回应给对方的数据包也被本机捕获到了并解析出来了。解析了自己发出去的数据包而已。
那么我们就声明一个函数并实现:
- int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac);
- // 获取自己主机的MAC地址
- int GetSelfMac(pcap_t *adhandle, const char *ip_addr, unsigned char *ip_mac) {
- unsigned char sendbuf[42]; //arp包结构大小
- int i = -1;
- int res;
- EthernetHeader eh; //以太网帧头
- Arpheader ah; //ARP帧头
- struct pcap_pkthdr * pkt_header;
- const u_char * pkt_data;
- //将已开辟内存空间 eh.dest_mac_add 的首 6个字节的值设为值 0xff。
- memset(eh.DestMAC, 0xff, 6); //目的地址为全为广播地址
- memset(eh.SourMAC, 0x0f, 6);
- memset(ah.DestMacAdd, 0x0f, 6);
- memset(ah.SourceMacAdd, 0x00, 6);
- //htons将一个无符号短整型的主机数值转换为网络字节顺序
- eh.EthType = htons(ETH_ARP);
- ah.HardwareType= htons(ARP_HARDWARE);
- ah.ProtocolType = htons(ETH_IP);
- ah.HardwareAddLen = 6;
- ah.ProtocolAddLen = 4;
- ah.SourceIpAdd = inet_addr("100.100.100.100"); //随便设的请求方ip
- ah.OperationField = htons(ARP_REQUEST);
- ah.DestIpAdd = inet_addr(ip_addr);
- memset(sendbuf, 0, sizeof(sendbuf));
- memcpy(sendbuf, &eh, sizeof(eh));
- memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
- printf("%s",sendbuf);
- if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
- printf("\nPacketSend succeed\n");
- } else {
- printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
- return 0;
- }
- //从interface或离线记录文件获取一个报文
- //pcap_next_ex(pcap_t* p,struct pcap_pkthdr** pkt_header,const u_char** pkt_data)
- while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
- if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)
- && *(unsigned short*) (pkt_data + 20) == htons(ARP_REPLY)
- && *(unsigned long*) (pkt_data + 38)
- == inet_addr("100.100.100.100")) {
- for (i = 0; i < 6; i++) {
- ip_mac[i] = *(unsigned char *) (pkt_data + 22 + i);
- }
- printf("获取自己主机的MAC地址成功!\n");
- break;
- }
- }
- if (i == 6) {
- return 1;
- } else {
- return 0;
- }
- }
- #define ETH_ARP 0x0806 //以太网帧类型表示后面数据的类型,对于ARP请求或应答来说,该字段的值为x0806
- #define ARP_HARDWARE 1 //硬件类型字段值为表示以太网地址
- #define ETH_IP 0x0800 //协议类型字段表示要映射的协议地址类型值为x0800表示IP地址
- #define ARP_REQUEST 1 //ARP请求
- #define ARP_REPLY 2 //ARP应答
- #define HOSTNUM 255 //主机数量
另外发送ARP请求少不了帧头和ARP头的结构,我们需要声明出来,另外我们构建发送包需要再声明两个结构体sparam和gparam
- //帧头部结构体,共14字节
- struct EthernetHeader
- {
- u_char DestMAC[6]; //目的MAC地址 6字节
- u_char SourMAC[6]; //源MAC地址 6字节
- u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
- };
- //28字节ARP帧结构
- struct Arpheader {
- unsigned short HardwareType; //硬件类型
- unsigned short ProtocolType; //协议类型
- unsigned char HardwareAddLen; //硬件地址长度
- unsigned char ProtocolAddLen; //协议地址长度
- unsigned short OperationField; //操作字段
- unsigned char SourceMacAdd[6]; //源mac地址
- unsigned long SourceIpAdd; //源ip地址
- unsigned char DestMacAdd[6]; //目的mac地址
- unsigned long DestIpAdd; //目的ip地址
- };
- //arp包结构
- struct ArpPacket {
- EthernetHeader ed;
- Arpheader ah;
- };
- struct sparam {
- pcap_t *adhandle;
- char *ip;
- unsigned char *mac;
- char *netmask;
- };
- struct gparam {
- pcap_t *adhandle;
- };
- struct sparam sp;
- struct gparam gp;
可能出现的BUG:
只显示ARP发送成功,没有接受到并解析打印。可能的原因是帧构造有问题,字节没有对齐,有偏差,像#define一样
写入如下代码:
- #pragma pack(1) //按一个字节内存对齐
go ON..
获取到了自身的MAC地址之后,就可以在本机上构建ARP广播请求,向局域网内的所有主机发送ARP请求,得到回应之后解析回应的数据包并进行解析,得到对方的MAC地址。在这里我们需要开启两个线程,一个用来发送一个用来接收。好,我们继续..
先声明两个线程
- HANDLE sendthread; //发送ARP包线程
- HANDLE recvthread; //接受ARP包线程
- sp.adhandle = adhandle;
- sp.ip = ip_addr;
- sp.mac = ip_mac;
- sp.netmask = ip_netmask;
- gp.adhandle = adhandle;
- sendthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) SendArpPacket,
- &sp, 0, NULL);
- recvthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) GetLivePC, &gp,
- 0, NULL);
- printf("\nlistening on 网卡%d ...\n", i);
发送数据包,发送数据包先对结构体数据进行赋值,就像getSelfMac方法一样,然后声明了一个buffer用来存储每一个字节内容。
利用memset方法对buffer进行赋值。再利用一个for循环对255个主机进行发送,指定他们的IP地址。另外定义了一个flag,当发送成功之后将flag设置为1
- /* 向局域网内所有可能的IP地址发送ARP请求包线程 */
- DWORD WINAPI SendArpPacket(LPVOID lpParameter) //(pcap_t *adhandle,char *ip,unsigned char *mac,char *netmask)
- {
- sparam *spara = (sparam *) lpParameter;
- pcap_t *adhandle = spara->adhandle;
- char *ip = spara->ip;
- unsigned char *mac = spara->mac;
- char *netmask = spara->netmask;
- printf("ip_mac:%02x-%02x-%02x-%02x-%02x-%02x\n", mac[0], mac[1], mac[2],
- mac[3], mac[4], mac[5]);
- printf("自身的IP地址为:%s\n", ip);
- printf("地址掩码NETMASK为:%s\n", netmask);
- printf("\n");
- unsigned char sendbuf[42]; //arp包结构大小
- EthernetHeader eh;
- Arpheader ah;
- //赋值MAC地址
- memset(eh.DestMAC, 0xff, 6); //目的地址为全为广播地址
- memcpy(eh.SourMAC, mac, 6);
- memcpy(ah.SourceMacAdd, mac, 6);
- memset(ah.DestMacAdd, 0x00, 6);
- eh.EthType = htons(ETH_ARP);
- ah.HardwareType = htons(ARP_HARDWARE);
- ah.ProtocolType = htons(ETH_IP);
- ah.HardwareAddLen = 6;
- ah.ProtocolAddLen = 4;
- ah.SourceIpAdd = inet_addr(ip); //请求方的IP地址为自身的IP地址
- ah.OperationField = htons(ARP_REQUEST);
- //向局域网内广播发送arp包
- unsigned long myip = inet_addr(ip);
- unsigned long mynetmask = inet_addr(netmask);
- unsigned long hisip = htonl((myip & mynetmask));
- //向255个主机发送
- for (int i = 0; i < HOSTNUM; i++) {
- ah.DestIpAdd = htonl(hisip + i);
- //构造一个ARP请求
- memset(sendbuf, 0, sizeof(sendbuf));
- memcpy(sendbuf, &eh, sizeof(eh));
- memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
- //如果发送成功
- if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
- //printf("\nPacketSend succeed\n");
- } else {
- printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
- }
- Sleep(50);
- }
- Sleep(1000);
- flag = TRUE;
- return 0;
- }
然后是接收数据包并打印MAC地址:
- /* 分析截留的数据包获取活动的主机IP地址 */
- DWORD WINAPI GetLivePC(LPVOID lpParameter) //(pcap_t *adhandle)
- {
- gparam *gpara = (gparam *) lpParameter;
- pcap_t *adhandle = gpara->adhandle;
- int res;
- unsigned char Mac[6];
- struct pcap_pkthdr * pkt_header;
- const u_char * pkt_data;
- while (true) {
- if (flag) {
- printf("获取MAC地址完毕,请输入你要发送对方的IP地址:\n");
- break;
- }
- if ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
- if (*(unsigned short *) (pkt_data + 12) == htons(ETH_ARP)) {
- ArpPacket *recv = (ArpPacket *) pkt_data;
- if (*(unsigned short *) (pkt_data + 20) == htons(ARP_REPLY)) {
- printf("-------------------------------------------\n");
- printf("IP地址:%d.%d.%d.%d MAC地址:",
- recv->ah.SourceIpAdd & 255,
- recv->ah.SourceIpAdd >> 8 & 255,
- recv->ah.SourceIpAdd >> 16 & 255,
- recv->ah.SourceIpAdd >> 24 & 255);
- for (int i = 0; i < 6; i++) {
- Mac[i] = *(unsigned char *) (pkt_data + 22 + i);
- printf("%02x", Mac[i]);
- }
- printf("\n");
- }
- }
- }
- Sleep(10);
- }
- return 0;
- }
- --------------------------------------------------
- number:1
- name:rpcap://\Device\NPF_{5AC72F8D-019C-4003-B51B-
- description:Network adapter 'Microsoft' on local h
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET6
- this is an IPV6 address
- --------------------------------------------------
- number:2
- name:rpcap://\Device\NPF_{C17EB3F6-1E86-40E5-8790-
- description:Network adapter 'Microsoft' on local h
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET
- Address:192.168.95.1
- Netmask: 255.255.255.0
- Broadcast Address: 255.255.255.255
- --------------------------------------------------
- number:3
- name:rpcap://\Device\NPF_{33E23A2F-F791-409B-8452-
- description:Network adapter 'Qualcomm Atheros Ar81
- oller' on local host
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET
- Address:121.250.216.237
- Netmask: 255.255.255.0
- Broadcast Address: 255.255.255.255
- --------------------------------------------------
- number:4
- name:rpcap://\Device\NPF_{DCCF036F-A9A8-4225-B980-
- description:Network adapter 'Microsoft' on local h
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET6
- this is an IPV6 address
- --------------------------------------------------
- number:5
- name:rpcap://\Device\NPF_{D62A0060-F424-46FC-83A5-
- description:Network adapter 'Microsoft' on local h
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET
- Address:192.168.191.1
- Netmask: 255.255.255.0
- Broadcast Address: 255.255.255.255
- --------------------------------------------------
- number:6
- name:rpcap://\Device\NPF_{B5224A53-8450-4537-AB3B-
- description:Network adapter 'Microsoft' on local h
- Loopback: no
- Address Family Name:AF_INET6
- this is an IPV6 address
- Address Family Name:AF_INET
- Address:192.168.191.2
- Netmask: 255.255.255.0
- Broadcast Address: 255.255.255.255
- Enter the interface number(1-6):3
- ipstr:121.250.216.237
- netmask:255.255.255.0
-
- PacketSend succeed
- 获取自己主机的MAC地址成功!
- listening on 网卡2 ...
- ip_mac:dc-0e-a1-ec-53-c3
- 自身的IP地址为:121.250.216.237
- 地址掩码NETMASK为:255.255.255.0
- 请按任意键继续. . . ------------------------------
- IP地址:121.250.216.1 MAC地址:000fe28e6100
- -------------------------------------------
- IP地址:121.250.216.3 MAC地址:089e012d20d5
- -------------------------------------------
- IP地址:121.250.216.5 MAC地址:5404a6af5f2d
- -------------------------------------------
- IP地址:121.250.216.6 MAC地址:28d244248d81
- -------------------------------------------
- IP地址:121.250.216.7 MAC地址:80fa5b0283f3
- -------------------------------------------
- IP地址:121.250.216.8 MAC地址:14dae9005b9e
- -------------------------------------------
- IP地址:121.250.216.9 MAC地址:b82a72bf8bce
- -------------------------------------------
- IP地址:121.250.216.12 MAC地址:84c9b2fefeed
- -------------------------------------------
- IP地址:121.250.216.15 MAC地址:28d2440b4b1b
- -------------------------------------------
- IP地址:121.250.216.16 MAC地址:bcee7b969beb
- -------------------------------------------
- ........此处省略一万字....
- u_int ip1,ip2,ip3,ip4;
- scanf_s("%d.%d.%d.%d",&ip1,&ip2,&ip3,&ip4);
- printf("请输入你要发送的内容:\n");
- getchar();
- gets_s(TcpData);
- printf("要发送的内容:%s\n",TcpData);
- char TcpData[20]; //发送内容
最后构成一个完整的帧,那么另外声明的结构体如下,前面代码声明过的帧头部结构体就去掉了。
- //IP地址格式
- struct IpAddress
- {
- u_char byte1;
- u_char byte2;
- u_char byte3;
- u_char byte4;
- };
- //帧头部结构体,共14字节
- struct EthernetHeader
- {
- u_char DestMAC[6]; //目的MAC地址 6字节
- u_char SourMAC[6]; //源MAC地址 6字节
- u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
- };
- //IP头部结构体,共20字节
- struct IpHeader
- {
- unsigned char Version_HLen; //版本信息4位 ,头长度4位 1字节
- unsigned char TOS; //服务类型 1字节
- short Length; //数据包长度 2字节
- short Ident; //数据包标识 2字节
- short Flags_Offset; //标志3位,片偏移13位 2字节
- unsigned char TTL; //存活时间 1字节
- unsigned char Protocol; //协议类型 1字节
- short Checksum; //首部校验和 2字节
- IpAddress SourceAddr; //源IP地址 4字节
- IpAddress DestinationAddr; //目的IP地址 4字节
- };
- //TCP头部结构体,共20字节
- struct TcpHeader
- {
- unsigned short SrcPort; //源端口号 2字节
- unsigned short DstPort; //目的端口号 2字节
- unsigned int SequenceNum; //序号 4字节
- unsigned int Acknowledgment; //确认号 4字节
- unsigned char HdrLen; //首部长度4位,保留位6位 共10位
- unsigned char Flags; //标志位6位
- unsigned short AdvertisedWindow; //窗口大小16位 2字节
- unsigned short Checksum; //校验和16位 2字节
- unsigned short UrgPtr; //紧急指针16位 2字节
- };
- //TCP伪首部结构体 12字节
- struct PsdTcpHeader
- {
- IpAddress SourceAddr; //源IP地址 4字节
- IpAddress DestinationAddr; //目的IP地址 4字节
- char Zero; //填充位 1字节
- char Protcol; //协议号 1字节
- unsigned short TcpLen; //TCP包长度 2字节
- };
- //结构体初始化为0序列
- memset(ðernet, 0, sizeof(ethernet));
- BYTE destmac[8];
- //目的MAC地址,此处没有对帧的MAC地址进行赋值,因为网卡设置的混杂模式,可以接受经过该网卡的所有帧。当然最好的方法是赋值为ARP刚才获取到的MAC地址,当然不赋值也可以捕捉到并解析,在此处仅做下说明。
- destmac[0] = 0x00;
- destmac[1] = 0x11;
- destmac[2] = 0x22;
- destmac[3] = 0x33;
- destmac[4] = 0x44;
- destmac[5] = 0x55;
- //赋值目的MAC地址
- memcpy(ethernet.DestMAC, destmac, 6);
- BYTE hostmac[8];
- //源MAC地址
- hostmac[0] = 0x00;
- hostmac[1] = 0x1a;
- hostmac[2] = 0x4d;
- hostmac[3] = 0x70;
- hostmac[4] = 0xa3;
- hostmac[5] = 0x89;
- //赋值源MAC地址
- memcpy(ethernet.SourMAC, hostmac, 6);
- //上层协议类型,0x0800代表IP协议
- ethernet.EthType = htons(0x0800);
- //赋值SendBuffer
- memcpy(&SendBuffer, ðernet, sizeof(struct EthernetHeader));
- //赋值IP头部信息
- ip.Version_HLen = 0x45;
- ip.TOS = 0;
- ip.Length = htons(sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
- ip.Ident = htons(1);
- ip.Flags_Offset = 0;
- ip.TTL = 128;
- ip.Protocol = 6;
- ip.Checksum = 0;
- //源IP地址
- ip.SourceAddr.byte1 = 127;
- ip.SourceAddr.byte2 = 0;
- ip.SourceAddr.byte3 = 0;
- ip.SourceAddr.byte4 = 1;
- //目的IP地址
- ip.DestinationAddr.byte1 = ip1;
- ip.DestinationAddr.byte2 = ip2;
- ip.DestinationAddr.byte3 = ip3;
- ip.DestinationAddr.byte4 = ip4;
- //赋值SendBuffer
- memcpy(&SendBuffer[sizeof(struct EthernetHeader)], &ip, 20);
- //赋值TCP头部内容
- tcp.DstPort = htons(102);
- tcp.SrcPort = htons(1000);
- tcp.SequenceNum = htonl(11);
- tcp.Acknowledgment = 0;
- tcp.HdrLen = 0x50;
- tcp.Flags = 0x18;
- tcp.AdvertisedWindow = htons(512);
- tcp.UrgPtr = 0;
- tcp.Checksum = 0;
- //赋值SendBuffer
- memcpy(&SendBuffer[sizeof(struct EthernetHeader) + 20], &tcp, 20);
- //赋值伪首部
- ptcp.SourceAddr = ip.SourceAddr;
- ptcp.DestinationAddr = ip.DestinationAddr;
- ptcp.Zero = 0;
- ptcp.Protcol = 6;
- ptcp.TcpLen = htons(sizeof(struct TcpHeader) + strlen(TcpData));
- //声明临时存储变量,用来计算校验和
- char TempBuffer[65535];
- memcpy(TempBuffer, &ptcp, sizeof(struct PsdTcpHeader));
- memcpy(TempBuffer + sizeof(struct PsdTcpHeader), &tcp, sizeof(struct TcpHeader));
- memcpy(TempBuffer + sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
- //计算TCP的校验和
- tcp.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct PsdTcpHeader) + sizeof(struct TcpHeader) + strlen(TcpData));
- //重新把SendBuffer赋值,因为此时校验和已经改变,赋值新的
- memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader), &tcp, sizeof(struct TcpHeader));
- memcpy(SendBuffer + sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader), TcpData, strlen(TcpData));
- //初始化TempBuffer为0序列,存储变量来计算IP校验和
- memset(TempBuffer, 0, sizeof(TempBuffer));
- memcpy(TempBuffer, &ip, sizeof(struct IpHeader));
- //计算IP校验和
- ip.Checksum = checksum((USHORT*)(TempBuffer), sizeof(struct IpHeader));
- //重新把SendBuffer赋值,IP校验和已经改变
- memcpy(SendBuffer + sizeof(struct EthernetHeader), &ip, sizeof(struct IpHeader));
- //发送序列的长度
- int size = sizeof(struct EthernetHeader) + sizeof(struct IpHeader) + sizeof(struct TcpHeader) + strlen(TcpData);
- int result = pcap_sendpacket(adhandle, SendBuffer,size);
- if (result != 0)
- {
- printf("Send Error!\n");
- }
- else
- {
- printf("Send TCP Packet.\n");
- printf("Dstination Port:%d\n", ntohs(tcp.DstPort));
- printf("Source Port:%d\n", ntohs(tcp.SrcPort));
- printf("Sequence:%d\n", ntohl(tcp.SequenceNum));
- printf("Acknowledgment:%d\n", ntohl(tcp.Acknowledgment));
- printf("Header Length:%d*4\n", tcp.HdrLen >> 4);
- printf("Flags:0x%0x\n", tcp.Flags);
- printf("AdvertiseWindow:%d\n", ntohs(tcp.AdvertisedWindow));
- printf("UrgPtr:%d\n", ntohs(tcp.UrgPtr));
- printf("Checksum:%u\n", ntohs(tcp.Checksum));
- printf("Send Successfully!\n");
- }
- //获得校验和的方法
- unsigned short checksum(unsigned short *data, int length)
- {
- unsigned long temp = 0;
- while (length > 1)
- {
- temp += *data++;
- length -= sizeof(unsigned short);
- }
- if (length)
- {
- temp += *(unsigned short*)data;
- }
- temp = (temp >> 16) + (temp &0xffff);
- temp += (temp >> 16);
- return (unsigned short)(~temp);
- }
另外需要声明的变量有
- struct EthernetHeader ethernet; //以太网帧头
- struct IpHeader ip; //IP头
- struct TcpHeader tcp; //TCP头
- struct PsdTcpHeader ptcp; //TCP伪首部
- unsigned char SendBuffer[200]; //发送队列
- 获取MAC地址完毕,请输
- 121.250.216.112
- 请输入你要发送的内容
- what is tcp
- 要发送的内容:what i
- Send TCP Packet.
- Dstination Port:102
- Source Port:1000
- Sequence:11
- Acknowledgment:0
- Header Length:5*4
- Flags:0x18
- AdvertiseWindow:512
- UrgPtr:0
- Checksum:17149
- Send Successfully!
截图如下:
好啦,发送帧到此就告一段落啦!如果有疑问请留言。
帧的接收很简单,直接贴源码如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pcap.h>
- char *iptos(u_long in); //u_long即为 unsigned long
- void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
- //struct tm *ltime; //和时间处理有关的变量
- struct IpAddress
- {
- u_char byte1;
- u_char byte2;
- u_char byte3;
- u_char byte4;
- };
- //帧头部结构体,共14字节
- struct EthernetHeader
- {
- u_char DestMAC[6]; //目的MAC地址 6字节
- u_char SourMAC[6]; //源MAC地址 6字节
- u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
- };
- //IP头部结构体,共20字节
- struct IpHeader
- {
- unsigned char Version_HLen; //版本信息4位 ,头长度4位 1字节
- unsigned char TOS; //服务类型 1字节
- short Length; //数据包长度 2字节
- short Ident; //数据包标识 2字节
- short Flags_Offset; //标志3位,片偏移13位 2字节
- unsigned char TTL; //存活时间 1字节
- unsigned char Protocol; //协议类型 1字节
- short Checksum; //首部校验和 2字节
- IpAddress SourceAddr; //源IP地址 4字节
- IpAddress DestinationAddr; //目的IP地址 4字节
- };
- //TCP头部结构体,共20字节
- struct TcpHeader
- {
- unsigned short SrcPort; //源端口号 2字节
- unsigned short DstPort; //目的端口号 2字节
- unsigned int SequenceNum; //序号 4字节
- unsigned int Acknowledgment; //确认号 4字节
- unsigned char HdrLen; //首部长度4位,保留位6位 共10位
- unsigned char Flags; //标志位6位
- unsigned short AdvertisedWindow; //窗口大小16位 2字节
- unsigned short Checksum; //校验和16位 2字节
- unsigned short UrgPtr; //紧急指针16位 2字节
- };
- //TCP伪首部结构体 12字节
- struct PsdTcpHeader
- {
- unsigned long SourceAddr; //源IP地址 4字节
- unsigned long DestinationAddr; //目的IP地址 4字节
- char Zero; //填充位 1字节
- char Protcol; //协议号 1字节
- unsigned short TcpLen; //TCP包长度 2字节
- };
- int main(){
- EthernetHeader *ethernet; //以太网帧头
- IpHeader *ip; //IP头
- TcpHeader *tcp; //TCP头
- PsdTcpHeader *ptcp; //TCP伪首部
- pcap_if_t * alldevs; //所有网络适配器
- pcap_if_t *d; //选中的网络适配器
- char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256
- char source[PCAP_ERRBUF_SIZE];
- pcap_t *adhandle; //捕捉实例,是pcap_open返回的对象
- int i = 0; //适配器计数变量
- struct pcap_pkthdr *header; //接收到的数据包的头部
- const u_char *pkt_data; //接收到的数据包的内容
- int res; //表示是否接收到了数据包
- u_int netmask; //过滤时用的子网掩码
- char packet_filter[] = "tcp"; //过滤字符
- struct bpf_program fcode; //pcap_compile所调用的结构体
- u_int ip_len; //ip地址有效长度
- u_short sport,dport; //主机字节序列
- u_char packet[100]; //发送数据包目的地址
- pcap_dumper_t *dumpfile; //堆文件
- //time_t local_tv_sec; //和时间处理有关的变量
- //char timestr[16]; //和时间处理有关的变量
- //获取本地适配器列表
- if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){
- //结果为-1代表出现获取适配器列表失败
- fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf);
- //exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统
- exit(1);
- }
- //打印设备列表信息
- for(d = alldevs;d !=NULL;d = d->next){
- printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name);
- if(d->description){
- //打印适配器的描述信息
- printf("description:%s\n",d->description);
- }else{
- //适配器不存在描述信息
- printf("description:%s","no description\n");
- }
- //打印本地环回地址
- printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
- pcap_addr_t *a; //网络适配器的地址用来存储变量
- for(a = d->addresses;a;a = a->next){
- //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型
- switch (a->addr->sa_family)
- {
- case AF_INET: //代表IPV4类型地址
- printf("Address Family Name:AF_INET\n");
- if(a->addr){
- //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型
- printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
- }
- if (a->netmask){
- printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
- }
- if (a->broadaddr){
- printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
- }
- if (a->dstaddr){
- printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
- }
- break;
- case AF_INET6: //代表IPV6类型地址
- printf("Address Family Name:AF_INET6\n");
- printf("this is an IPV6 address\n");
- break;
- default:
- break;
- }
- }
- }
- //i为0代表上述循环未进入,即没有找到适配器,可能的原因为Winpcap没有安装导致未扫描到
- if(i == 0){
- printf("interface not found,please check winpcap installation");
- }
- int num;
- printf("Enter the interface number(1-%d):",i);
- //让用户选择选择哪个适配器进行抓包
- scanf_s("%d",&num);
- printf("\n");
- //用户输入的数字超出合理范围
- if(num<1||num>i){
- printf("number out of range\n");
- pcap_freealldevs(alldevs);
- return -1;
- }
- //跳转到选中的适配器
- 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;
- }
- //打印输出,正在监听中
- printf("\nlistening on %s...\n", d->description);
- //所在网络不是以太网,此处只取这种情况
- if(pcap_datalink(adhandle) != DLT_EN10MB)
- {
- fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
- //释放列表
- pcap_freealldevs(alldevs);
- return -1;
- }
- //先获得地址的子网掩码
- if(d->addresses != NULL)
- //获得接口第一个地址的掩码
- netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
- else
- // 如果接口没有地址,那么我们假设一个C类的掩码
- netmask=0xffffff;
- //pcap_compile()的原理是将高层的布尔过滤表
- //达式编译成能够被过滤引擎所解释的低层的字节码
- if(pcap_compile(adhandle, //适配器处理对象
- &fcode,
- packet_filter, //过滤ip和UDP
- 1, //优化标志
- netmask //子网掩码
- )<0)
- {
- //过滤出现问题
- fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
- // 释放设备列表
- pcap_freealldevs(alldevs);
- return -1;
- }
- //设置过滤器
- if (pcap_setfilter(adhandle, &fcode)<0)
- {
- fprintf(stderr,"\nError setting the filter.\n");
- //释放设备列表
- pcap_freealldevs(alldevs);
- return -1;
- }
- //利用pcap_next_ex来接受数据包
- while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0)
- {
- if(res ==0){
- //返回值为0代表接受数据包超时,重新循环继续接收
- continue;
- }else{
- //运行到此处代表接受到正常从数据包
- //header为帧的头部
- printf("%.6ld len:%d ", header->ts.tv_usec, header->len);
- // 获得IP数据包头部的位置
- ip = (IpHeader *) (pkt_data +14); //14为以太网帧头部长度
- //获得TCP头部的位置
- ip_len = (ip->Version_HLen & 0xf) *4;
- printf("ip_length:%d ",ip_len);
- tcp = (TcpHeader *)((u_char *)ip+ip_len);
- char * data;
- data = (char *)((u_char *)tcp+20);
- //将网络字节序列转换成主机字节序列
- sport = ntohs( tcp->SrcPort );
- dport = ntohs( tcp->DstPort );
- printf("srcport:%d desport:%d\n",sport,dport);
- 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("%s\n",data);
- }
- }
- //释放网络适配器列表
- pcap_freealldevs(alldevs);
- /**
- int pcap_loop ( pcap_t * p,
- int cnt,
- pcap_handler callback,
- u_char * user
- );
- typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
- const u_char *);
- */
- //开始捕获信息,当捕获到数据包时,会自动调用这个函数
- //pcap_loop(adhandle,0,packet_handler,NULL);
- int inum;
- scanf_s("%d", &inum);
- return 0;
- }
- /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */
- /**
- pcap_loop()函数是基于回调的原理来进行数据捕获的,如技术文档所说,这是一种精妙的方法,并且在某些场合下,
- 它是一种很好的选择。但是在处理回调有时候会并不实用,它会增加程序的复杂度,特别是在多线程的C++程序中
- */
- /*
- void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
- {
- struct tm *ltime = NULL;
- char timestr[16];
- time_t local_tv_sec;
- // 将时间戳转换成可识别的格式
- local_tv_sec = header->ts.tv_sec;
- localtime_s(ltime,&local_tv_sec);
- strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
- printf("%s,%.6ld len:%d\n", timestr, header->ts.tv_usec, header->len);
- }
- */
- /* 将数字类型的IP地址转换成字符串类型的 */
- #define IPTOSBUFFERS 12
- char *iptos(u_long in)
- {
- static char output[IPTOSBUFFERS][3*4+3+1];
- static short which;
- u_char *p;
- p = (u_char *)∈
- which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
- sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
- return output[which];
- }
运行截图如下
Thank You
如有问题,欢迎留言~