arp数据包格式:
主要文件有:
datastruct.h 存储数据包格式的数据结构
transfunc.h 发送arp的封装函数声明
transfunc.cpp 定义
Winpcap_arp.cpp main函数
datastruct.h
#ifndef HEADERSTRUCT_H #define HEADERSTRUCT_H // ip地址 typedef struct ip_address { unsigned char byte1; unsigned char byte2; unsigned char byte3; unsigned char byte4; }ip_address; // ipv4首部 typedef struct ip_header { unsigned char ver_ihl; // 版本和首部长度各占4位,长度是4位数据乘以4,所以首部长度最长是60字节 unsigned char tos; // 区分服务 unsigned short tlen; // 总长度 unsigned short identification; // 标识 unsigned short flags_fo; // 标志 和 片偏移 unsigned char ttl; // 生存时间 unsigned char proto; // 协议 unsigned short crc; // 首部检验和 ip_address saddr; // 源地址 ip_address daddr; // 目的地址 unsigned int op_pad; // 可选字段 + 填充 }ip_header; // udp首部 typedef struct udp_header { unsigned short sport; unsigned short dport; unsigned short len; unsigned short crc; }udp_header; // arp数据包中的帧头 typedef struct ether_header { // 注意:类型字段需要转为网络字节序 char etherdaddr[6]; // 以太网目的地址 char ethersaddr[6]; // 以太网源地址 unsigned short etherflametype; // 以太网帧类型,0x0806是arp,0x0800是IP }ether_header; // // arp 头部 #pragma pack(push, 1) // c struct 编译层面字节对齐,编译器做,根据数据类型进行对齐,通过pack设置 typedef struct arp_header { // 注意:类型字段 和 操作码均需要转为网络字节序 unsigned short hardtype; // 硬件类型,如以太网为1 unsigned short prototype; // 协议类型,如ip 是0x0800 unsigned char hardaddrlen; // 硬件地址长度(6) unsigned char protoaddrlen; //协议地址长度,ip为4 unsigned short operate; // 操作字段,1为arp请求,2为arp应答 char sendetheraddr[6]; // 发送端以太网地址 unsigned long sendipaddr; // 发送端ip地址 char destetheraddr[6]; // 接收端以太网地址 unsigned long destipaddr; // 接收端ip地址 }arp_header/*__attribute__((aligned(1))) or __attribute__((pack)) 都是设置1字节对齐*/; #pragma pack(pop) // // 值得注意的是,MSVN编译器编译结果中,两个struct并非按声明顺序在内存中存放, // 而且,两个struct地址并不连续 typedef struct arp_packet { struct ether_header etherheader; struct arp_header arpheader; }arp_packet; #endif // HEADERSTRUCT_H
transfunc.cpp
#include "stdafx.h" #include "transfunc.h" void arp_handler(unsigned char*param, // 对应pcap_loop / pcap_dispatch 的参数 const struct pcap_pkthdr *header, //winpcap 生成的一个头 const unsigned char *pkt_data) // 数据包 { // 代解决:解析响应得arp 数据包 struct arp_packet arp; memcpy(&arp.etherheader, pkt_data, 14); memcpy(&arp.arpheader, pkt_data + 14, 28); //struct ip_address *ipaddr = (struct ip_address *)(static_cast<void *>(&(arp->arpheader.sendipaddr))); unsigned char mac[6]; for (int index = 0; index < 6; ++index) { mac[index] = arp.etherheader.ethersaddr[index]; } unsigned long sendip = arp.arpheader.sendipaddr; struct ip_address *ipaddr = static_cast<struct ip_address *>((void *)&sendip); // 注意,这里会看到输出mac 为全0,因为我在BroadArp函数里广播了一个arp包, // 曾发送一个源mac为全零的arp,估计现在收到了 // 后来正确了,那是arp系统自动发的包(不是我触发的),我抓取到了 if (arp.etherheader.etherflametype == htons(0x0806)) printf("ip: %d.%d.%d.%d -> MAC:%.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n", ipaddr->byte1, ipaddr->byte2, ipaddr->byte3, ipaddr->byte4, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } // 问:为什么需要网络字节序? void BroadcastArp(pcap_t *handle, pcap_if_t *dev) { struct arp_packet arp; char mac[6]; if (dev->addresses->addr->sa_family == AF_INET6) dev->addresses = dev->addresses->next; if ( GetSelfMac(handle, inet_ntoa(((struct sockaddr_in *)(dev->addresses->addr))->sin_addr), mac) < 0) { fprintf(stderr, "GetSelfMac error\n"); exit(-1); } bpf_u_int32 broadip = 0; // inet_addr 得到的正数解析出的地址顺序相反,就是说传输中的Ip是按照反序(8bit为一段)存储的 // 如192.168.1.107,则在整数的存储方式为 (((107*256 + 1)*256)+168)*256 + 192 broadip = ((struct sockaddr_in *)(dev->addresses->broadaddr))->sin_addr.S_un.S_addr; // printf("1.%u\n", broadip); //初始化以太网头部 memset(arp.etherheader.etherdaddr, 0xff, 6); // 广播MAC地址 strncpy(arp.etherheader.ethersaddr, mac, 6); arp.etherheader.etherflametype = htons(0x0806); // 0x8060是arp //初始化arp头, 请求时,arp_header里的mac地址可以是任意值,不受影响 arp.arpheader.hardtype = htons(1); arp.arpheader.prototype = htons(0x0800); // 0x0800是ip arp.arpheader.hardaddrlen = 6; arp.arpheader.protoaddrlen = 4; arp.arpheader.operate = htons(1); // 1是arp请求,2是arp应答 strncpy(arp.arpheader.sendetheraddr, arp.etherheader.ethersaddr, 6); // 任意值 memset(arp.arpheader.destetheraddr, 0xff, 6); arp.arpheader.sendipaddr = ((struct sockaddr_in *)(dev->addresses->addr))->sin_addr.S_un.S_addr; // 这里broadip 是255.255.255.255(全网广播IP来的),用wireshark发现提示,谁是255.255.255.255 ,请告诉上面设置的sendipaddr arp.arpheader.destipaddr = broadip; // 发送arp数据包 unsigned char *buf = (unsigned char *)malloc(42); memset(buf, 0, 42); memcpy(buf, &(arp.etherheader), sizeof(arp.etherheader)); memcpy(buf + sizeof(arp.etherheader), &(arp.arpheader), sizeof(arp.arpheader)); // fatal bad memory block!!! if (pcap_sendpacket(handle, buf, 42) < 0) { fprintf(stderr, "pacap_sendpacket error\n"); exit(-1); } free(buf); } int GetSelfMac(pcap_t *adhandle, const char *ip_addr, char *ip_mac) { // unsigned char sendbuf[42]; //arp包结构大小 int i = -1; int res; ether_header eh; //以太网帧头 arp_header ah; //ARP帧头 struct pcap_pkthdr * pkt_header; const u_char * pkt_data; //将已开辟内存空间 eh.dest_mac_add 的首 6个字节的值设为值 0xff。 memset(eh.etherdaddr, 0xff, 6); //目的地址为全为广播地址 // 以以太网源地址为0发送arp, 接收端网卡接收到无端的arp,就发送一个包含自己mac地址的arp // 到”无端“的ip对应的主机, memset(eh.ethersaddr, 0x00, 6); // 当有源mac地址则是正式的arp请求 // arpheader里的以太网地址没用 memset(ah.destetheraddr, 0xff, 6); memset(ah.sendetheraddr, 0x00, 6); //htons将一个无符号短整型的主机数值转换为网络字节顺序 eh.etherflametype = htons(0x0806); ah.hardtype = htons(0x0001); ah.prototype = htons(0x0800); ah.hardaddrlen = 6; ah.protoaddrlen = 4; ah.sendipaddr = inet_addr(ip_addr); //随便设的请求方ip ah.operate = htons(0x0001); // 如果是192.168.223.255, 则是提高这个地址是谁的,因为这是个广播地址 // 是否什么错误?(vmnet8 的测试),xxx.1则是gratuitous(无端的)请求,来自xxx.1 ah.destipaddr = inet_addr(ip_addr); printf("sizeof(eh) = %d \t sizeof(ah) = %d\n", sizeof(eh), sizeof(ah)); memset(sendbuf, sizeof(sendbuf), 0); 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) int count = 0; while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) { if (*(unsigned short *)(pkt_data + 12) == htons(0x0806) && *(unsigned short*)(pkt_data + 20) == htons(0x0002) && *(unsigned long*)(pkt_data + 28) == inet_addr(ip_addr)) { for (i = 0; i < 6; i++) { ip_mac[i] = *(unsigned char *)(pkt_data + 22 + i); } // 为什么会输出很多ffff printf("MAC:%2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\t", ip_mac[0], ip_mac[1], ip_mac[2], ip_mac[3], ip_mac[4], ip_mac[5]); printf("Get mac success !\n"); break; } } if (i == 6) { return 1; } else { return -1; } } // 网上有这个方法,只是我调用时总是打开适配器的函数出错 int GetMacAddress(char* source, char* mac_buf) { LPADAPTER lpAdapter; PPACKET_OID_DATA OidData; BOOLEAN status; lpAdapter = PacketOpenAdapter(source); if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE)) { printf("error : %d\n", GetLastError()); return -1; } OidData = (PPACKET_OID_DATA)malloc(6 + sizeof(PACKET_OID_DATA)); if (OidData == NULL) { return 0; } OidData->Oid = 0x01010102;//OID_802_3_CURRENT_ADDRESS; OidData->Length = 6; ZeroMemory(OidData->Data, 6); status = PacketRequest(lpAdapter, FALSE, OidData); if (!status) { return -1; } memcpy((void *)mac_buf, (void *)OidData->Data, 6); printf("The MAC address of the adapter is %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", (PCHAR)(OidData->Data)[0], (PCHAR)(OidData->Data)[1], (PCHAR)(OidData->Data)[2], (PCHAR)(OidData->Data)[3], (PCHAR)(OidData->Data)[4], (PCHAR)(OidData->Data)[5]); free(OidData); PacketCloseAdapter((LPADAPTER)source); return 1; } char *ip6toa(struct sockaddr *sockaddr, char *address, int addrlen) { socklen_t sockaddrlen; #ifdef WIN32 sockaddrlen = sizeof(struct sockaddr_in6); #else sockaddrlen = sizeof(struct sockaddr_storage); #endif if (getnameinfo(sockaddr, sockaddrlen, address, addrlen, NULL, 0, NI_NUMERICHOST) != 0) address[0] = '\0'; return address; }
winpcap_arp.cpp
// Winpcap_arp.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" // 为remote***.h 头文件 #ifndef HAVE_REMOTE #define HAVE_REMOTE #endif #include "pcap.h" #include "remote-ext.h" #include <winsock.h> #include <winsock2.h> #include <ws2tcpip.h> #include "datastruct.h" #include "transfunc.h" #pragma comment(lib, "wpcap.lib") #pragma comment(lib, "Packet.lib") #pragma comment(lib, "Ws2_32.lib") int _tmain(int argc, _TCHAR* argv[]) { // 设备类型 /* * struct pcap_if{ * struct pcap_if *next; * char *name; * char *description; * struct pcap_addr *addresses; 设备地址结构 * bpf_u_int32 flags; * }; */ pcap_if_t *alldevs = NULL, *onedev = NULL; /* * struct pcap_addr{ * struct pcap_addr *next; * struct sockaddr *addr; * ... netmask, broadaddr, dstaddr; * }; */ pcap_addr *devsaddr = NULL; char ip6str[128]; char error[PCAP_ERRBUF_SIZE]; char source[PCAP_BUF_SIZE] = "rpcap://"; int index; // 获取所有网卡设备 if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, error) < 0) { fprintf(stderr, "pcap_findalldevs error.\n"); exit(-1); } if (alldevs == NULL) { printf("No device found\n"); return 0; } // 遍历所有获取到的网卡设备 for (onedev = alldevs, index = 1; onedev != NULL; onedev = onedev->next, ++index) { printf("(%d)%s", index, onedev->name); if (onedev->description) printf("description: %s\n", onedev->description); else printf("No description available\n"); if (onedev->flags == PCAP_IF_LOOPBACK) printf("Lookback device.\n"); devsaddr = onedev->addresses; for (; devsaddr; devsaddr = devsaddr->next) { switch (devsaddr->addr->sa_family) { // 1.待解决:解析本地地址等等 2.获取本机适配器mac 地址 case AF_INET: printf("\tAddress family name : AF_INET\n"); if (devsaddr->addr) printf("\tAddress : %s\n", inet_ntoa( ((struct sockaddr_in *)(devsaddr->addr))->sin_addr)); if (devsaddr->netmask) printf("\tAddress netmask : %s\n", inet_ntoa( ((struct sockaddr_in *)(devsaddr->netmask))->sin_addr)); if (devsaddr->broadaddr) printf("\tAddress broadcast : %s\n", inet_ntoa( ((struct sockaddr_in *)(devsaddr->broadaddr))->sin_addr)); if (devsaddr->dstaddr) printf("\tAddress destination : %s\n", inet_ntoa( ((struct sockaddr_in *)(devsaddr->dstaddr))->sin_addr)); break; case AF_INET6: printf("\tAddress family name : AF_INET6\n"); if (devsaddr->addr) { // memset(ip6str, 0, sizeof(ip6str)); // ip6toa(devsaddr->addr, ip6str, sizeof(ip6str)); // printf("\tAddress : %s\n", ip6str); } break; default: printf("\tAddress family no found\n"); break; } } printf(("-----------------------------------sperator-------------------------------------\n")); } // 选择监控的设备 reenter: printf("Enter which interface[1,%d] you want to scrap:", index - 1); int which, iindex; scanf("%d", &which); if (which < 1 || which > index - 1) { printf("enter error\n"); goto reenter; } for (onedev = alldevs, iindex = 1; iindex <= index - 1; ++iindex, onedev = onedev->next) { if (iindex == which) { break; } } if (iindex == index) { fprintf(stderr, "Find device error\n"); exit(-1); } pcap_t *capHandle = NULL; // 打开设备 if ((capHandle = pcap_open_live(onedev->name, 65536, // 最大数据包长度 1, // PCAP_OPENFLAG_PROMISCUOUS,1为混杂模式 1000, // 超时时间,单位毫秒。注意: // pcap_loop 不会因为超时而返回,直到当cnt(pcap_loop第二个参数) // 个数据包被捕获后才返回,pcap_dispatch则因超时会返回。 NULL // error buf )) == NULL) { fprintf(stderr, "pcap_open error\n"); pcap_freealldevs(alldevs); exit(-1); } // 返回链路层的类型,如以太网/wifi 。。等等不同的帧格式对应的类型 if (pcap_datalink(capHandle) != DLT_EN10MB) { fprintf(stderr, "pcap_datalink error\n"); pcap_freealldevs(alldevs); exit(-1); } // 问题:整形数据怎么和ip,掩码等等转换? struct bpf_program fcode; char packet_filter[] = "arp"; // or / and / not / src 192.168.1.x / 等等布尔表达式树 bpf_u_int32 mask; //掩码 if (onedev->addresses->addr->sa_family == AF_INET6) onedev->addresses = onedev->addresses->next; // 网络字节序是大端,一般电脑是小端字节序(针对long 和 short 等控制字段要注意字节序) if (onedev) mask = ((struct sockaddr_in *)(onedev->addresses->netmask))->sin_addr.S_un.S_addr; else mask = 0xffffff; // 将packet_filter 字符串表达式转换成过滤结构 // int pcap_compile(pcap_t *p, struct bpf_program *fp,char *str, int optimize, bpf_u_int32 netmask) if (pcap_compile(capHandle, &fcode, packet_filter, 1, mask) < 0) { fprintf(stderr, "pcap_compile error\n"); pcap_freealldevs(alldevs); exit(-1); } // 设置过滤 if (pcap_setfilter(capHandle, &fcode) < 0) { fprintf(stderr, "pcap_setfilter error\n"); pcap_freealldevs(alldevs); exit(-1); } // 广播arp数据包 BroadcastArp(capHandle, onedev); printf("\nListening on %s ...\n", onedev->description); // pcap_breakloop , 设置标志,强制使pcap_loop , pcap_dispatch 返回,不继续循环 // pcap_loop 不会因为超时而返回,直到当cnt个数据包被捕获后才返回,pcap_dispatch则因超时会返回。 // 第二个参数为-1表示无限捕获 pcap_loop(capHandle, -1, arp_handler, NULL); // pcap_next_ex 可用易于并发的读取帧 //释放资源 pcap_freealldevs(alldevs); return 0; }