ARP协议浅析(3):付诸实践

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 程序界面

ARP协议浅析(3):付诸实践_第1张图片

该程序定位为简易的搜索工具,所以界面很简洁,主要的工作还是在于IP搜索功能的实现。

监听模式

一个监听者线程,多个发送者线程。搜索时针对每个IP地址查询创建独立的线程,发完ARP包就完事,其余的工作就让监听者线程完成。通常我们只需要一个监听者线程就可以,这个线程建立后不用退出,直到程序退出。

WinPcap的封装

WinPcap这个库非常专业,有很多网络方面的工具,特别是Sniffer类型工具,如:SnifferProEffectHttpSniffer。它是一个跨平台的库,功能接口比较多,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";

              }

              // 试图获得AdapterInfosize返回需要的缓冲区的大小

              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):回顾

你可能感兴趣的:(Library&Domain)