ARP协议浅析(3):付诸实践
上一章:ARP协议浅析(2):协议分析
下一章:ARP协议浅析(4):回顾
这次研究本意是编写一个用于局域网检测IP地址与MAC对应关系的Application,以获知局域网里IP地址的分配情况,在局域网的IP冲突中找到可用的IP地址。
设计与构思
当确定Winpcap库可以满足操纵ARP后就大胆的展开程序了,首先是对界面进行简单的构思,其中必须把界面和功能函数的关系分离好。
程序架构
表1 程序基本结构表
模块 |
说明 |
UI Class |
对话框类class CIPFluxDlg : public CDialog显示信息,接受用户命令 |
CWinPcap Class |
自定义的WinPcap封装类,包括了所有功能函数 |
WinPcap Library |
实现ARP分析功能底层库 |
主要的调用关系是从上到下,不过WinPcap中使用了回调机制,CWinPcap类的Sniffer函数也使用一个曲折的获得CIPFluxDlg指针的特殊技巧,当然这并非唯一的途径,也许读者会有更好的方法。
界面构思
界面采用对话框结构,包括
本地信息,包括IP地址,MAC编码;
IP地址搜索范围,当然起始IP地址、终止IP地址,都在内部计算过了,而且可以编辑;
搜索按钮;
搜索结果列表,显示搜索到IP-MAC信息。
点击搜索按钮后,后台线程开始搜索并把结果按先后顺序显示在列表中。程序界面如:图1 程序界面所示。
图1 程序界面
该程序定位为简易的搜索工具,所以界面很简洁,主要的工作还是在于IP搜索功能的实现。
监听模式
一个监听者线程,多个发送者线程。搜索时针对每个IP地址查询创建独立的线程,发完ARP包就完事,其余的工作就让监听者线程完成。通常我们只需要一个监听者线程就可以,这个线程建立后不用退出,直到程序退出。
WinPcap的封装
WinPcap这个库非常专业,有很多网络方面的工具,特别是Sniffer类型工具,如:SnifferPro,EffectHttpSniffer。它是一个跨平台的库,功能接口比较多,IPFlux用不了那么多,如果直接在对话框类中直接调用的话,会使得代码交错,不便于阅读和修改,所以最好进行封装,这样可以与界面无关,如果以后在别的应用用到的话,将会非常方便。
通过分析,决定划分一些具体的子功能来实现的:
1 网卡的枚举,设置和缓冲区分配;
2 填充和发送一个ARP包;
3 监听线程(Sniffer);
4 解析ARP包;
5 获得本机IP MAC地址;
6 发送线程和接受线程的同步与协调;
这些子功能主要使用WinPcap库函数,笔者封装了一个CWinPcap类。以下分别对子功能的代码作详细说明,其它请参考所附源代码。关于封装类的函数请看表2 CWinPcap的主要函数表。
表2 CWinPcap的主要函数表
函数 |
描述 |
int CWinPcap::OpenPcap() |
打开Pcap库 |
void CWinPcap::ClosePcap() |
关闭Pcap库 |
void CWinPcap::GetData(LPPACKET lp) |
分析ARP包 |
void CWinPcap::GetNetInfo(sockaddr_in &sin, CString &strMAC, BYTE mac[]) |
获取本机信息 |
int CWinPcap::Sender(ULONG ipCur, ULONG ipMine, BYTE mac[]) |
发送者函数 |
int CWinPcap::Sniff() |
监听者函数 |
1 网卡的枚举,设置和缓冲区分配
// success return 0
int CWinPcap::OpenPcap()
{
WCHAR buf[1024];
ULONG bufsize;
int res, i = 0;
memset ((void*)adapterlist, 0, sizeof(adapterlist));
res = PacketGetAdapterNames ((char*)buf, &bufsize); // 枚举网卡
if (res == 0)
{
return -1;
}
WCHAR *p1, *p2;
p1 = p2 = buf;
while ((*p1 != '/0') || (*(p1 - 1) != '/0'))
{
if (*p1 == '/0')
{
memcpy (adapterlist[i], p2, 2 * (p1 - p2));
p2 = p1 + 1;
i++;
}
p1++;
}
m_iAdapterNum = i;
m_iSelAdapter = i - 1;
// 打开最后一个网卡,PC机一般是一个网卡
m_lpAdapter = PacketOpenAdapter( ((char *)adapterlist + m_iSelAdapter * 1024) );
if (m_lpAdapter == NULL || (m_lpAdapter->hFile == INVALID_HANDLE_VALUE))
{
return -1;
}
// 分配sender包空间
m_lpPacketSender = PacketAllocatePacket();
if (m_lpPacketSender == NULL)
return -1;
return 0;
}
2 如何填充和发送一个ARP包
2.1 包的分组格式定义
//////////////////////////////////////////////////////////////////////////
// arp.h 定义以太网arp帧结构,封装了winpcap开发包的重要函数
#include
#include
#include <stdio.h>
#include
#include "afx.h"
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"packet")
#pragma once
#pragma pack(push,1)
// Ether(DLC) header
typedef struct ethdr
{
unsigned char eh_dst[6]; // 以太网目的MAC地址
unsigned char eh_src[6]; // 以太网源MAC地址
unsigned short eh_type; // 帧类型
}ETHDR,*PETHDR;
// ARP分组格式
typedef struct arphdr
{
unsigned short arp_hdr; // 硬件类型
unsigned short arp_pro; // 协议类型
unsigned char arp_hln; // 硬件地址长度
unsigned char arp_pln; // 协议地址长度
unsigned short arp_opt; // ARP/RARP
unsigned char arp_sha[6]; // 发送者地址
unsigned long arp_spa; // 发送者IP地址
unsigned char arp_tha[6]; // 接受者地址
unsigned long arp_tpa; // 接受者IP地址
}ARPHDR,*PARPHDR;
#pragma pack(push)
#define ETH_IP 0x0800
#define ETH_ARP 0x0806
#define ARP_REQUEST 0x0001
#define ARP_REPLY 0x0002
#define ARP_HARDWARE 0x0001
#define max_num_adapter 10
class CWinPcap
{
public:
/* 分析arp包内容*/
void GetData(LPPACKET lp);
/* 发送arp请求包*/
int Sender(ULONG ipCur, ULONG ipMine, BYTE mac[]);
/* 监听网卡收到的包*/
int Sniff();
/* 获得网卡信息,IP地址和MAC地址*/
void GetNetInfo(sockaddr_in &sin, CString &strMAC, BYTE mac[]);
CWinPcap()
{
OpenPcap();
};
~CWinPcap()
{
ClosePcap();
};
/* 打开网卡并初始化*/
int OpenPcap();
/* 关闭网卡*/
void ClosePcap();
private:
char adapterlist[max_num_adapter][1024];
/* 网卡*/
LPADAPTER m_lpAdapter;
/* 帧缓冲*/
LPPACKET m_lpPacketReceiver,
m_lpPacketSender;
int m_iAdapterNum;
int m_iSelAdapter;
npf_if_addr m_netinfo;
};
2.2 填充和发送一个ARP请求包
/********************************************************************
功能 供发送线程调用,发送一个arp请求包
参数
ipCur 目标IP地址
ipMine 本机IP地址
mac[] 网卡MAC地址
********************************************************************/
int CWinPcap::Sender(ULONG ipCur, ULONG ipMine, BYTE mac[])
{
char pBufSend[1024];
ETHDR eth;
ARPHDR arp;
// Fill the ARP request packets.
int i;
for (i = 0; i < 6; i++)
{
eth.eh_dst[i] = 0xff;
arp.arp_tha[i] = 0x00;
}
// {填充DLC头和ARP头
eth.eh_type = htons(ETH_ARP);
memcpy(eth.eh_src, mac, 6);
arp.arp_hdr = htons(ARP_HARDWARE);
arp.arp_pro = htons(ETH_IP);
arp.arp_hln = 6;
arp.arp_pln = 4;
arp.arp_opt = htons(ARP_REQUEST);
memcpy(arp.arp_sha, mac, 6);
arp.arp_spa = htonl(ipMine);
arp.arp_tpa = htonl(ipCur);
// }
memset(pBufSend, 0, sizeof(pBufSend));
memcpy(pBufSend, ð, sizeof(eth));
// 装配完整arp包
memcpy(pBufSend + sizeof(eth), &arp, sizeof(arp));
PacketInitPacket(m_lpPacketSender, pBufSend, sizeof(eth) + sizeof(arp));
if(PacketSendPacket(m_lpAdapter, m_lpPacketSender,TRUE)==FALSE)
{
return -1;
}
return 0;
}
3 监听线程(Sniffer)
线程函数体实际执行的函数:
int CWinPcap::Sniff()
{
static CIPFluxDlg *pdlg = (CIPFluxDlg *)AfxGetMainWnd();
char recvbuf[1024*250];
DWORD res = 0;
// {初始化网卡,设置为混合模式NDIS_PACKET_TYPE_PROMISCUOUS
if(PacketSetHwFilter(m_lpAdapter, NDIS_PACKET_TYPE_PROMISCUOUS)==FALSE)
{
//printf("Warning: Unable to set the adapter to promiscuous mode/n");
}
if(PacketSetBuff(m_lpAdapter, 500*1024)==FALSE)
{
//printf("PacketSetBuff Error: %d/n",GetLastError());
return -1;
}
if(PacketSetReadTimeout(m_lpAdapter, 1)==FALSE)
{
//printf("Warning: Unable to set the timeout/n");
}
if((m_lpPacketReceiver=PacketAllocatePacket())==FALSE)
{
//printf("PacketAllocatePacket receive Error: %d/n",GetLastError());
return -1;
}
PacketInitPacket(m_lpPacketReceiver, (char *)recvbuf, sizeof(recvbuf));
// }
// {接受包
do
{
if(PacketReceivePacket(m_lpAdapter, m_lpPacketReceiver, TRUE) == FALSE)
{
if(GetLastError() == 6)
return 0;
return -1;
}
GetData (m_lpPacketReceiver); // 解析包的内容
if (pdlg->m_bStop == TRUE)
break;
}while (1);
// }
ResetEvent(pdlg->m_hEvent); // 辅助函数
return 0;
}
4 解析ARP响应包
/********************************************************************
功能 从接受到的包中提取和解析出ARP包个字段
参数
lp 网卡接受到的包的缓冲区指针
********************************************************************/
void CWinPcap::GetData(LPPACKET lp)
{
ULONG ulOffset = 0, ulBytesReceived;
char *buf = NULL;
char *pChar, *pBase;
struct bpf_hdr *phdr = NULL;
struct sockaddr_in sin;
ETHDR *pEther;
ARPHDR *pArp;
//IPHDR *pIphdr;
CString strIP, strMAC;
static CIPFluxDlg *pdlg = (CIPFluxDlg *)AfxGetMainWnd();
buf = (char*)lp->Buffer;
ulBytesReceived = lp->ulBytesReceived;
while (ulOffset < ulBytesReceived)
{
phdr = (struct bpf_hdr *)(buf + ulOffset);
ulOffset += phdr->bh_hdrlen;
pChar = (char *)(buf + ulOffset);
pBase = pChar;
ulOffset = Packet_WORDALIGN(ulOffset + phdr->bh_caplen);
pEther = (PETHDR)pChar;
pArp = (PARPHDR)(pChar + sizeof(ETHDR));
// receive ARP reply packets which contain IP address and relative MAC address
// 受到arp响应包,包含IP地址和相关的MAC地址
if (pEther->eh_type == htons(ETH_ARP) && pArp->arp_opt == htons(ARP_REPLY))
{
sin.sin_addr.s_addr = pArp->arp_spa;
strIP.Format("%-16s", inet_ntoa(sin.sin_addr));
CString str;
str.Format("%02X", pEther->eh_src[0]);
strMAC = str;
for (int i = 1; i < 6; i++)
{
str.Format ("-%02X", pEther->eh_src[i]);
strMAC += str;
}
pdlg->ShowSearch(strIP, strMAC);
SetEvent(pdlg->m_hEvent);
}
}
}
5 获得本机IP地址和MAC地址
工程中没有采取Winpcap函数来获得MAC地址,而是采用非常有用的iphelp库函数。
DWORD WINAPI GetAdaptersInfo(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen);
/************************************************************************
功能 获得本机网卡的IP地址,物理地址,涉及到
参数
sin IP地址
strMAC MAC地址16进制表达
mac[] MAC地址字节表达
************************************************************************/
void CWinPcap::GetNetInfo(sockaddr_in &sin, CString &strMAC, BYTE mac[])
{
PIP_ADAPTER_INFO pAdapterInfo = NULL;
char ch;
long sizeinfo;
ULONG size = 0;
int res = 0;
sizeinfo = sizeof(m_netinfo);
if (m_lpAdapter)
{
// 先获得ip地址
res = PacketGetNetInfoEx (adapterlist[m_iSelAdapter], &m_netinfo, &sizeinfo);
if (res)
{
sin = *(struct sockaddr_in *)&m_netinfo.IPAddress;
}
else
{
strMAC = "FF-FF-FF-FF-FF-FF";
}
// 试图获得AdapterInfo,size返回需要的缓冲区的大小
res = GetAdaptersInfo ((PIP_ADAPTER_INFO)&ch, &size);
if (res == ERROR_BUFFER_OVERFLOW)
{
pAdapterInfo = (PIP_ADAPTER_INFO)malloc (sizeof(IP_ADAPTER_INFO));
res = GetAdaptersInfo(pAdapterInfo, &size);
if (res == 0)
{
CString str;
BYTE *pch = pAdapterInfo->Address; // 导出mac地址16进制表达
memcpy (mac, pch, 6);
for (int i = 0; i < 6; i++)
{
if (i)
strMAC += "-";
str.Format("%02X", *(pch + i));
strMAC += str;
}
}
free (pAdapterInfo);
}
}
}
6 发送线程和接受线程的协调
只是简单的多个发送线程,一个监听线程,没有涉及同步问题。
void CIPFluxDlg::OnBtnStart()
{
int nTotal = 0;
ULONG ipFirst, ipLast;
ULONG ulStartIP = 0;
m_bStop = FALSE;
static BOOL bInit = FALSE;
m_IPFirst.GetAddress(ipFirst);
m_IPLast.GetAddress(ipLast);
nTotal = ipLast - ipFirst + 1;
m_lstIP.DeleteAllItems();
GetDlgItem (IDC_BTN_START)->EnableWindow (FALSE);
// 生成一次监听线程
if (bInit == FALSE)
{
m_hSniffer = AfxBeginThread(_tFuncReceiver, (void*)this);
bInit = TRUE;
}
Sleep (300);
m_IPFirst.GetAddress(ulStartIP);
for (int i = 0; i < nTotal; i++)
{
m_ipCur = ulStartIP + i;
m_hSender = AfxBeginThread(_tFuncSender, (void*)this);
// 等待上一个线程生成
WaitForSingleObject (m_hSender, INFINITE);
}
m_bStop = TRUE;
GetDlgItem (IDC_BTN_START)->EnableWindow (TRUE);
}
上一章:ARP协议浅析(2):协议分析
下一章:ARP协议浅析(4):回顾