这次研究本意是编写一个用于局域网检测IP地址与MAC对应关系的Application,以获知局域网里IP地址的分配情况。
按照功能来规划模块,通过分析发现以下一些独立的功能是必须实现的:
1 网卡的枚举,设置和缓冲区分配;
2 填充和发送一个
ARP包;
3 监听线程
(Sniffer);
4 解析
ARP包;
5 获得本机
IP MAC地址;
6 发送线程和接受线程的同步与协调;
至于如何显示IP和MAC信息,比较简单请参考源码。
把每个功能封装在一个函数里面。为了更方便的调用winpcap库函数,笔者封装了一个CWinPcap类。代码的几个关键之处说明一下,其它请参考所附源代码。
// success return 0
intCWinPcap::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;
}
//////////////////////////////////////////////////////////////////////////
// arp.h 定义以太网arp帧结构,封装了winpcap开发包的重要函数
#include <packet32.h>
#include <ntddndis.h>
#include <stdio.h>
#include <conio.h>
#include "afx.h"
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"packet")
#pragma once
#pragma pack(push,1)
// Ether(DLC) header
typedefstructethdr
{
unsignedchar eh_dst[6]; // 以太网目的MAC地址
unsignedchar eh_src[6]; // 以太网源MAC地址
unsignedshort eh_type; // 帧类型
}ETHDR,*PETHDR;
// ARP分组格式
typedefstructarphdr
{
unsignedshort arp_hdr; // 硬件类型
unsignedshort arp_pro; // 协议类型
unsignedchar arp_hln; // 硬件地址长度
unsignedchar arp_pln; // 协议地址长度
unsignedshort arp_opt; // ARP/RARP
unsignedchar arp_sha[6]; // 发送者地址
unsignedlong arp_spa; // 发送者IP地址
unsignedchar arp_tha[6]; // 接受者地址
unsignedlong 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
classCWinPcap
{
public:
/* 分析arp包内容*/
voidGetData(LPPACKET lp);
/* 发送arp请求包*/
int Sender(ULONG ipCur, ULONG ipMine, BYTE mac[]);
/* 监听网卡收到的包*/
int Sniff();
/* 获得网卡信息,IP地址和MAC地址*/
voidGetNetInfo(sockaddr_in &sin, CString &strMAC, BYTE mac[]);
CWinPcap()
{
OpenPcap();
};
~CWinPcap()
{
ClosePcap();
};
/* 打开网卡并初始化*/
int OpenPcap();
/* 关闭网卡*/
voidClosePcap();
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;
};
/********************************************************************
功能 供发送线程调用,发送一个arp请求包
参数
ipCur 目标IP地址
ipMine 本机IP地址
mac[] 网卡MAC地址
********************************************************************/
intCWinPcap::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;
}
线程函数体实际执行的函数:
intCWinPcap::Sniff()
{
staticCIPFluxDlg *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;
}
/********************************************************************
功能 从接受到的包中提取和解析出ARP包个字段
参数
lp 网卡接受到的包的缓冲区指针
********************************************************************/
voidCWinPcap::GetData(LPPACKET lp)
{
ULONG ulOffset = 0, ulBytesReceived;
char *buf = NULL;
char *pChar, *pBase;
structbpf_hdr *phdr = NULL;
structsockaddr_in sin;
ETHDR *pEther;
ARPHDR *pArp;
//IPHDR *pIphdr;
CString strIP, strMAC;
staticCIPFluxDlg *pdlg = (CIPFluxDlg *)AfxGetMainWnd();
buf = (char*)lp->Buffer;
ulBytesReceived = lp->ulBytesReceived;
while (ulOffset < ulBytesReceived)
{
phdr = (structbpf_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);
}
}
}
工程中没有采取Winpcap函数来获得MAC地址,而是采用非常有用的iphelp库函数。
DWORD WINAPI GetAdaptersInfo(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen);
/************************************************************************
功能 获得本机网卡的IP地址,物理地址,涉及到<ntddndis.h>
参数
sin IP地址
strMAC MAC地址16进制表达
mac[] MAC地址字节表达
************************************************************************/
voidCWinPcap::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 = *(structsockaddr_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);
}
}
}
只是简单的多个发送线程,一个监听线程,没有涉及同步问题。
voidCIPFluxDlg::OnBtnStart()
{
int nTotal = 0;
ULONG ipFirst, ipLast;
ULONG ulStartIP = 0;
m_bStop = FALSE;
staticBOOL 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);
}