获取以太网中IP地址与MAC地址的对应关系(中)

标签:
杂谈 |
分类: Netwoks |
三、 程序框架和流程
1. 程序框架
本程序分为以下多个部分:
·ARP帧的结构定义,然后定义一个接口信息结构,便于对各种接口数据进行处理
程序中对ARP数据包的各种处理都要严格依照ARP帧的结构定义,这里要说明的是,ARP帧的定义不仅要包括图4中所示的各个字段,同时还要包含ARP帧的首部,故首先要定义ARP帧首部的结构,该结构中,只包括有目的地址和源地址以及长度/类型这些通过软件处理的字段。
另外,在这部分所有结构定义的前后分别要加上预处理命令#pragma pack(1)来调整各个结构进入1字节对齐方式。在这里数据包及其头部应该是连续的字节,但在内存里struct里为了让程序跑的快,减少CPU读取数据的指令周期,对结构体的存储进行了优化,在一些成员存储的内存空间之间加入填充使整个struct的成员以其中某一成员字节标准来对齐,但会使数据在内存中的存储不再连续,所以设定这个可以让所有struct内的成员全部1字节对齐,即避免了填充,可在内存里连续存储,符号数据包要求,使WinPcap捕获到的数据包在缓冲区中是连续存放的。具体实现如下:
http://s9/bmiddle/6c7c7fe3490d1259c4958&690
·设置一个缓冲区储存捕获到的数据包
首先为缓冲区数组定义一个保存数据包信息的结构,所包括的成员分别对应pcap_next_ex()的后两个参数,保存数据包的一些基本信息并指向捕获到的网络数据包。其中struct pcap_pkthdr结构是WinPcap中pcap.h定义的一个数据包首部的结构类型包含一个时间戳,一个捕获数据的端口长度,一个接收的数据包在离线下的长度。
该缓冲区依靠两个下标CaptureHeader和CaptureTail来维护,初始化两下标均指向数组第一个元素,即0下标,每次通过CaptureTail的向后移动来添加新捕获的数据包,当缓冲区满后,将CaptureTail重置为0;当需要读取缓冲区内的数据包时,通过CaptureHeader的向后移动来实现,直到移动到CaptureTail的位置处理完毕,或者移动到缓冲区尾,则将CaptureHeader重置为0。具体实现如下:
http://s6/bmiddle/6c7c7fe3490d1265450f5&690
·初始化对话框,获取设备列表,打开列表上的网络接口,获取相应句柄,启动数据包捕获工作者线程,为模拟的远端主机设置虚假的MAC地址和IP地址,进行ARP请求
在OnInitDialog()中,首先要利用pcap_findalldevs_ex()获得本机的设备列表。函数的原型如下:
int pcap_findalldevs_ex(
);
·source指定从哪里获取网络接口列表,使用PCAP_SRC_IFSTRING常数,可以获取本机的网络接口列表。
·auth在获取远程设备的网络接口列表时,如果远程设备需要认证,则需要使用该参数。本实验无需使用,设置为NULL。
·alldevs该函数成功返回后,该参数指向获取的网络接口列表的第一个元素,为一个pcap_if_t结构。
·errbuf用户定义的存放错误信息的缓冲区,长度不能小于PCAP_ERRBUF_SIZE。
·调用发生错误上函数返回-1,具体错误信息存于errbuf参数中;成功则返回0。
在获取本机设备列表之后,要将获取的各种列表信息存于之前定义的接口信息中。其中涉及到2个重要的结构类型struct pcap_if和struct pcap_addr。
在alldevs指向的网络接口链表中,每一个元素都是一个pcap_if_t结构。其定义如下:
typedef struct pcap_if pcap_if_t;
struct pcap_if {
};
struct pcap_addr {
};
·next指向链表中的下一个元素,NULL表示链表结束。
·name指向一个字符串,该字符串是WinPcap为本网络接口卡分配的名字。本实验中该名字需要传递给pcap_open()函数来打开这块网卡。
·description指向该网卡的描述字符串。
·addresses指向该网卡拥有的地址链表的第一个元素。该地址链表的每一个元素都是一个pcap_addr结构,其中包括了下一个地址元素的指针(next)、地址(addr)、网络掩码(netmask)、广播地址(broadaddr)和目的地址(dstaddr)。在TCP/IP网络中,address指向的地址链表中包含了这块网卡拥有的所有IP地址。
·flags标识该网络接口卡是不是一块回送网卡,本实验中不使用该参数。
具体实现如下:
http://s7/bmiddle/6c7c7fe3490d12775e9e6&690
获取本机网络接口卡之后,需要将其IP地址等信息存入之前定义的接口信息中。其中涉及到2个重要的结构类型struct sockaddr、struct sockaddr_in和struct in_addr,其定义如下:
struct sockaddr {
};
struct sockaddr_in {
};
struct in_addr {
#define
s_addr
#define
s_host
#define
s_net
#define
s_imp
#define s_impno S_un.S_un_b.s_b4
#define
s_lh
};
sockaddr是内核用来储存地址的结构。sa_family指向一个地址族,本实验中用来判断该地址是否为IP地址。该结构用不同的unsigned short数来表示不同的地址协议,AF_INET被定义为2,代表TCP/IP协议族。sa_data数组存储地址,本实验中不使用。
sockaddr_in是一个指向socket地址和网络类型的结构。sin_family指代协议族,在socket编程中只能是AF_INET,sin_port存储端口号(使用网络字节顺序),sin_addr存储IP地址,使用in_addr这个数据结构,sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。s_addr按照网络字节顺序存储IP地址,sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向sockaddr的结构体,并代替它,即可以使用sockaddr_in建立所需要的信息,然后进行类型的强制转换。
in_addr是一个用来储存IP地址的结构,本实验中使用该结构中定义的联合中的一个unsigned long变量s_addr来实际存储IP地址。
实现过程中,需要使用双重链表,即pcap_if和pcap_addr来处理IP地址。
具体实现如下:
http://s12/bmiddle/6c7c7fe3490d128168a9b&690
在打开本机设备列表上的网络接口时,要使用pcap_open()函数。其原型如下:
pcap_t* pcap_open(
);
·source指向需要打开的网络接口卡的名字。该名字通常可以从获取网络接口卡设备列表中得到。
·snaplen指向WinPcap获取网络数据包的最大长度。本实验中用户数据报总长度最大为65535字节,因此设置该值为65536,这样可以完整捕获链路层中的数据包。
·flags指定以何种方式打开网络接口设备并获取网络数据包。本实验中将该标志设置为PCAP_OPENFLAG_PROMISCUOUS,它通知系统以混杂模式打开网络接口设备,便于监听流经一块网络接口卡的所有数据包。
·read_timeout指定数据包捕获函数返回的时间。本实验中,无论pcap_next_ex()是否捕获到数据包,read_timeout指定的时间到时之后捕获函数一定返回。
·auth在远程设备中捕获网络数据包时使用,本实验不需要,设置为NULL。
·errbuf用户定义的存放错误信息的缓冲区。
该函数调用出错时,返回NULL,通过errbuf获取错误的详细信息。调用成功,则返回一个指向pcap_t的指针,后续函数调用时会使用。
该函数具体实现如下:
http://s11/bmiddle/6c7c7fe3490d128cdbb7a&690
在打开网络接口之后,就要启动数据包捕获工作者线程,然后准备设置一个远端主机,用来发送一个ARP请求报文并启动定时器,该报文请求本机网络接口上绑定的IP地址与MAC地址的对应关系。在组装报文过程中,源MAC地址字段和源IP地址字段需要使用虚假的MAC地址和IP地址,本实验中使用课本中的定义,即以66-66-66-66-66-66作为源MAC地址,以112.112.112.112作为源IP地址。本地主机一旦获取该ARP请求就会做出响应,向虚假主机发送ARP响应信息,虚假主机获取该响应信息之后,便可对该信息进行分析,从而获取本地主机IP地址与MAC地址的对应关系。具体实现如下:
http://s16/bmiddle/6c7c7fe3490d12962cb3f&690