MFC下开发基于winpcap的数据包捕获和分析软件

来源:互联网 发布:办公大楼网络设计方案 编辑:程序博客网 时间:2024/04/28 09:57

http://blog.csdn.net/leotangcw/archive/2006/05.aspx

从来没写过blog,今天是我的第一次写blog。尝试一下,呵呵!随便转载,转载请注明出处http://blog.csdn.net/leotangcw/

欢迎大家和我交流QQ:17371764     Email:tangchengwen@163.com

本文适合初学MFC编程的同学,如果您对VC(MFC)编程不是太熟悉请您参看《VC++技术内幕》或其他的VC入门书籍。

代码写的很冗余,还用了很多全局的变量,程序写的太少,不好意思,嘿嘿!请大家多指教!
我今年7月就本科毕业了,最近做毕业设计,导师给了局域网数据包截取与分析的题目,算是中等难度的题目(对面一个兄弟做机器人避障,感觉交给本科生就不是很现实,没选,也有做个什么什么系统的,感觉太没意思,选个这个难度适中),写了两周现在算是完成了,把些心得发上来,希望能对有的朋友有帮助。
先是选捕获方法了,可以有winsocket  winpacp  libcap等很多捕获方式,由于我只是在windows下开发,选了winpcap+MFC的方式。
一开始是设计一个界面,主要是设置一些参数什么的,如网卡接口选择,捕获数目什么的,主要参考了Ethereal的界面设置,做了一个简单的

捕获接口,过滤器的后面是一个COMBOX,使用混杂模式等是CHECK控件,其他都是EDIT控件和SPIN控件,和STATIC放好了以后申明各个组件对应的变量。然后在类向导里面添加WM_INITDIALOG消息,在生成的函数里添加初始化代码//以下是对话框上资源的初始化
 m_spacketlimit.SetBuddy(&m_epacketlimit);
 m_spacketlimit.SetBase(10);
 m_spacketlimit.SetRange(50,32767);
    m_spacketlimit.SetPos(100);
 m_sstopnum.SetBuddy(&m_estopnum);
 m_sstopnum.SetBase(10);
 m_sstopnum.SetRange(1,32767);
    m_sstopnum.SetPos(100);
    m_sstoptime.SetBuddy(&m_estoptime);
 m_sstoptime.SetBase(10);
 m_sstoptime.SetRange(1,32767);
    m_sstoptime.SetPos(30);
 m_cinpromiscuousmode=true;
 //以上是对话框上复选框的初始化
 UpdateData(false);
 //以下是combox的初始化
 pcap_if_t *alldevs;
 pcap_if_t *d;
 struct in_addr net_ip_address;
 pcap_t *adhandle;
 char errbuf[PCAP_ERRBUF_SIZE];
 if(pcap_findalldevs(&alldevs, errbuf)==-1)
 {
  AfxMessageBox("Error in pcap_findalldevs");
  AfxMessageBox(errbuf);
 }
 for(d=alldevs; d; d=d->next)
 {
  if (d->description)
  {
      m_cinterface.AddString(d->description);
  }
  else
   AfxMessageBox("No description available");
        m_devnum++;
 }
 m_cfilter.AddString("arp");
    m_cfilter.AddString("tcp");
 m_cfilter.AddString("udp");
 m_cfilter.AddString("port 80");
 m_cfilter.AddString("ip src host 172.29.6.67");
 m_cfilter.AddString("自定义过滤规则请自己添加,空白为捕获所有包");
 m_exsn=0;
 m_eventEnd.ResetEvent();
 m_stoptimeup.ResetEvent();

然后在各个复选框的点击事件里添加类式代码,实现点击就可以允许/禁止后面的EDIT和SPIN控件的使用的目的。

UpdateData(true);
 if(m_cpacketlimit)
 {
    GetDlgItem(IDC_EDIT_PACKETLIMIT)->EnableWindow(true);
    GetDlgItem(IDC_SPIN_PACKETLIMIT)->EnableWindow(true);
 }
 else
 {
    GetDlgItem(IDC_EDIT_PACKETLIMIT)->EnableWindow(false);
    GetDlgItem(IDC_SPIN_PACKETLIMIT)->EnableWindow(false);
 }
 UpdateData(true);

在接口选择的CHANG事件里面用如下的代码实现遍历本机的各个网卡并且添加到下拉列表里面以供选择。

m_devindex=m_cinterface.GetCurSel()+1;
 pcap_if_t *alldevs;
 pcap_if_t *d;
 bpf_u_int32 net_mask;
 bpf_u_int32 net_ip;
 struct in_addr net_ip_addr;
 struct in_addr net_mask_addr;
 int m_count=1;
 char errbuf[PCAP_ERRBUF_SIZE];
 if(pcap_findalldevs(&alldevs,errbuf)==-1)
 {
  AfxMessageBox("Error in pcap_findalldevs");
  AfxMessageBox(errbuf);
 }
 else
 {
  d=alldevs;
    while(m_count<m_devindex)
    {
  d=d->next;
  m_count++;
    }
 }
    /* 获得网络接口 */
    pcap_lookupnet(d->name, &net_ip, &net_mask, errbuf);
 net_ip_addr.S_un.S_addr=net_ip;
    net_mask_addr.S_un.S_addr=net_mask;
 m_sMask=inet_ntoa(net_mask_addr);
 m_sIPAddress=inet_ntoa(net_ip_addr);
 pcap_freealldevs(alldevs);
 GetDlgItem(IDC_STATIC_NAME)->SetWindowText(m_sMask);
 GetDlgItem(IDC_STATIC_IP)->SetWindowText(m_sIPAddress);
 UpdateData(false);

主要是使用findalldevs函数得到一个网卡接口列表,你可以参考winpcap开发包,尽量不要使用lookupdev那个,我试过因为很多机器经常第一个网卡接口是一个虚拟的接口(一般看不见),如果你用lookupdev的话就会什么都得不到。在用pcap_lookupnet把网卡地址和子网掩码显示在左下角。

在浏览的按钮里添加

CFileDialog m_file_dlg(TRUE, "DAT", ".DAT", OFN_HIDEREADONLY, _T(".DAT Files|*.DAT"), this);
 m_file_dlg.m_ofn.lpstrTitle="保存数据包文件";
 if(m_file_dlg.DoModal() == IDOK )
 {
       CString pathName=m_file_dlg.GetPathName();
    m_efile_path.SetWindowText(pathName);
    UpdateData(true);
 }

打开一个文本框,返回一个文件路径以后用。

今天就写到这吧,明天继续!

一步一步尝试在MFC下开发基于winpcap的数据包捕获和分析软件(之二):

随便转载,转载请注明出处http://blog.csdn.net/leotangcw/

欢迎大家和我交流QQ:17371764     Email:tangchengwen@163.com

今天学校开运动会了,和同学在看台最后一排打扑克,呵呵,输了4块,郁闷,一开始牌很好的,后来就不行了,大四了对学校这些活动都有一种看穿一切的感觉

昨天主要做了设置页面,今天主要是做数据包的捕获了,其实数据包的捕获主要是几个winpcap函数,现在的参考书目很多都是在dos下的捕包,所以很多都用了winpcap的pcap_loop函数来捕包,再通过一个回掉函数来做包处理,但是在MFC下回掉函数一般要写到一个类里面,这样我们就得把它申明成静态得函数,但是静态函数在后期操作上面又有很多问题,但是我也想了看有没什么好方法,结果最后放弃用loop函数而用了pcap_next_ex这个函数,回头一翻winpcap开发手册,发现里面有这样得提示:在Capturing the packets without the callback那个例子里面原文如下:The example program in this lesson behaves exactly like the previous program (Opening an adapter and capturing the packets), but it uses pcap_next_ex() instead of pcap_loop().

The callback-based capture mechanism of pcap_loop() is elegant and it could be a good choice in some situations. However, handling a callback is sometimes not practical -- it often makes the program more complex especially in situations with multithreaded applications or C++ classes.

In these cases, pcap_next_ex() retrievs a packet with a direct call -- using pcap_next_ex() packets are received only when the programmer wants them.

看来我的选择还是对的,这也是winpcap开发包推荐的选择。这样在选好了捕包函数后就是界面的问题了,我创建了SDI的工程,做了一个切分窗口,就是在CMainFrame类里面重载OnCreateClient函数,添加类式如下的代码m_splitter.CreateStatic(this,2,1);
 m_splitter.CreateView(0,0,RUNTIME_CLASS(CSetupdlgView),CSize(400,400),pContext);
 m_splitter.CreateView(1,0,RUNTIME_CLASS(CViewDown),CSize(200,200),pContext);

m_splitter是一个声明的CSplitterWnd变量,我视图的上面部分是从CListView派生的,下面部分嵌入了一个从CFormView继承的Dialog程序运行后的情况如下:

我们先开始上面部分的代码编写,一开始在OnInitialUpdate()做初始化list,代码大概如下:

CListCtrl& theCtrl = GetListCtrl();
  DWORD dwStyle;
    dwStyle = theCtrl.GetExtendedStyle();//GetStyle();
    dwStyle |=LVS_EX_FULLROWSELECT;
    theCtrl.SetExtendedStyle(dwStyle);
    // Insert a column. This override is the most convenient.
    theCtrl.InsertColumn(0, _T("序号"), LVCFMT_LEFT);   //初始化列名
 theCtrl.InsertColumn(1, _T("捕获时间"), LVCFMT_LEFT);
 theCtrl.InsertColumn(2, _T("包长度"), LVCFMT_LEFT);
 theCtrl.InsertColumn(3, _T("源IP"), LVCFMT_LEFT);
 theCtrl.InsertColumn(4, _T("目的IP"), LVCFMT_LEFT);
 theCtrl.InsertColumn(5, _T("源物理地址"), LVCFMT_LEFT);
 theCtrl.InsertColumn(6, _T("目的物理地址"),LVCFMT_LEFT);
 theCtrl.InsertColumn(7, _T("使用协议"), LVCFMT_LEFT);
 theCtrl.InsertColumn(8, _T("相关信息"), LVCFMT_LEFT);
    theCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);
    theCtrl.SetColumnWidth(1, 160);
 theCtrl.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);
 theCtrl.SetColumnWidth(3, 110);
 theCtrl.SetColumnWidth(4, 110);
 theCtrl.SetColumnWidth(5,120);
 theCtrl.SetColumnWidth(6,120);
 theCtrl.SetColumnWidth(7,90);
 theCtrl.SetColumnWidth(8,150);

记得先要在PreCreateWindow(CREATESTRUCT& cs)中调整style以便后面做整行选择

cs.style |= LVS_REPORT|LVS_SINGLESEL ;

接着把从设置对话框传来得参数传如自己写的捕包函数开始捕包,大家可以看到其实我得函数写的很不是地方,写到了View里面,但是图简单,不想弄了,呵呵!

从设置页传回来的接口参数只传了选择的接口的序号(没直接传接口指针),所以只有再遍历一便接口。

还有就是为了不让程序进入失去相应状态,而且也为了提高效率,我开辟了另一个线程捕包,但是我以前没写过多线程的程序,写的很不好,参数传递全是通过全局变量传递的不过CPU占用率确实下来了捕包时<1%(基本就是0%)

而且为了以后好用,也为了捕包时不随包的增多让内存不断扩大,每次捕包都是保存在文件的,如果在设置时没有选择文件名就会默认保存在default.dat里面。这样只有到显示时才占用很多内存,捕包时内存占用一直在10M以内不会增加。

void CSetupdlgView::Cappacket(int m_di, int m_pl, int m_sn, int m_st,bool m_ipm)
{
 int m_count=1;               //申明自己用的变量
 CString m_count1;
 CString m_packetlen;
 CString m_protocol;
    //以下是网络变量
 pcap_if_t *alldevs;//指向设备的链表
 pcap_if_t *d;//临时变量,指向设备的链表
 char error_content[PCAP_ERRBUF_SIZE];
    /* 存储错误信息 */
    pcap_t *pcap_handle;
    /* winpcap句柄 */
    const u_char *packet_content;
    /* 数据包内容 */
    u_char *mac_string;
    /* 以太网地址 */
    u_int16_t ethernet_type;
    /* 以太网类型 */
    bpf_u_int32 net_mask;
    /* 掩码地址 */
    bpf_u_int32 net_ip;
    /* 网络地址 */
    char *net_interface;
    /* 网络接口 */
    struct pcap_pkthdr* protocol_header;
    /* 数据包头部信息 */
    struct ether_header *ethernet_protocol;
    /* 以太网协议变量 */
    struct bpf_program bpf_filter;
    /* BPF过滤规则 */
    char* bpf_filter_string= "";
    /* 过滤规则字符串 */
    bpf_filter_string=(LPSTR)(LPCTSTR)m_filter;
    struct ip_header* ip_protocol;//ip头
 struct tcp_header* tcp_protocol;//tcp头
 struct udp_header* udp_protocol;//UDP头
 struct icmp_header* icmp_protocol;//icmp头
 struct arp_header* arp_protocol;//arp头
 struct ether_header* ether_protocol;//以太网头
    u_int data_len;
    u_int tcp_len;
 const u_char* pkt_data;//数据
    const u_char* pkt_data_temp;//数据备份
 pcap_dumper_t *dumpfile;//open_dump指针
 if(pcap_findalldevs(&alldevs,error_content)==-1)
 {
  AfxMessageBox("Error in pcap_findalldevs");
  AfxMessageBox(error_content);
 }
 else
 {
  d=alldevs;
    while(m_count<m_di)
    {
  d=d->next;
  m_count++;
    }
 }
    /* 获得网络接口 */
    pcap_lookupnet(d->name, &net_ip, &net_mask, error_content);
    /* 获得网络地址和网络掩码 */
 if(d->addresses != NULL)
  net_mask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
 else
  net_mask=0xffffff;
    pcap_handle = pcap_open_live(d->name,65536,m_ipm,1000, error_content);
    /* 打开网路接口 */
 pcap_freealldevs(alldevs);
    if(pcap_compile(pcap_handle,&bpf_filter,bpf_filter_string,1,net_mask)<0)
 {
  AfxMessageBox("过滤规则出错!");
 }
    /* 编译过滤规则 */
    if(pcap_setfilter(pcap_handle, &bpf_filter)<0)
 {
  AfxMessageBox("过滤规则出错!将使用默认方式捕获数据包");
 }
    /* 设置过滤规则 */
    if (pcap_datalink(pcap_handle) != DLT_EN10MB)
        AfxMessageBox("不在局域网内");
 dumpfile=pcap_dump_open(pcap_handle,m_filepath);
 if(dumpfile==NULL)
 {
  AfxMessageBox("不能打开文件!将使用默认文件default.DAT进行保存");
  CFile m_file;
  if (m_file.Open("default.DAT",CFile::modeRead,NULL))
  {
   m_file.Close();
   m_file.Remove("default.DAT");
  }
  m_filepath="default.DAT";
  dumpfile=pcap_dump_open(pcap_handle,m_filepath);
 }
 m_exsn=m_sn;
 expcap_handle=pcap_handle;
    exprotocol_header=protocol_header;
 expkt_data=pkt_data;
    m_exipm=m_ipm;
 m_expl=m_pl;
 m_exstoptime=m_st;
 exdumpfile=dumpfile;
 CDlgState m_dlgstate;
    AfxBeginThread(Cappacketlivethread,GetSafeHwnd());
 m_dlgstate.DoModal();
 WaitForSingleObject(m_eventEnd,INFINITE);
 Capoffline(m_filepath);
 UpdateData(true);
 pcap_close(pcap_handle);
}

大家看见了,上面还有一个CDlgState类,是我拿来做统计的,在捕包时就会即时不断更新捕获的包数目和捕包持续时间。 (统计的代码后面再说)

捕包线程里面执行的代码如下:

UINT Cappacketlivethread(LPVOID pParam)
{
 for(int i=0;i<m_exsn;i++)
 {
  if (::WaitForSingleObject(m_stoptimeup,0)==WAIT_OBJECT_0)
  {
   m_excount=i;
      break;
  }
        pcap_next_ex(expcap_handle,&exprotocol_header,&expkt_data);
     if ((exprotocol_header->len)>m_expl)//判断是否在规定长度以内
  {
     i--;
     continue;
  }
  pcap_dump((u_char*)exdumpfile,exprotocol_header,expkt_data);
  m_excount=i;
 }
 m_eventEnd.SetEvent();
 return 0;
}

到现在捕获的所有包已经到文件里面了,剩下的就很好操作了。这是我写的文件读取函数

Capoffline(CString m_open_filepath)
{
 tempcount(m_open_filepath);
 char errbuf[PCAP_ERRBUF_SIZE];
    char source[1024];
    struct pcap_pkthdr *header;
    const u_char *pkt_data;
 CString m_packetlen;
 CString m_protocol;
 CString m_count1;
 u_int i=0;
 pcap_t *fp;
 int m_count=0;
 struct pcap_pkthdr* protocol_header;
    int res;
 if (pcap_createsrcstr(source,         // variable that will keep the source string
                            PCAP_SRC_FILE,  // we want to open a file
                            NULL,           // remote host
                            NULL,           // port on the remote host
                            m_open_filepath,        // name of the file we want to open
                            errbuf          // error buffer
                            ) != 0)
    {
        AfxMessageBox(errbuf);
    }
 if ((fp= pcap_open(source,         // name of the device
                        65536,          // portion of the packet to capture
                                        // 65536 guarantees that the whole packet will be captured on all the link layers
                         PCAP_OPENFLAG_PROMISCUOUS,     // promiscuous mode
                         1000,              // read timeout
                         NULL,              // authentication on the remote machine
                         errbuf         // error buffer
                         ) ) == NULL)
    {
        AfxMessageBox(errbuf);
    }
    my_data=(m_mydata*)malloc((m_excount-1)*sizeof(m_mydata));
 while((res = pcap_next_ex(fp,&protocol_header,&pkt_data))>=0)
    {
  my_data[m_count].len=protocol_header->len;
        my_data[m_count].pkt_data=(unsigned char*)malloc(protocol_header->len);
  memcpy(my_data[m_count].pkt_data,pkt_data,protocol_header->len);
  m_packetlen.Format("%d",protocol_header->len);
        m_protocol=Packetanalyse(pkt_data);//协议分析
     m_count1.Format("%d",m_count+1);//对要添加的信息做格式化处理
     CListCtrl &m_list=GetListCtrl();//以下是对ListView添加信息
     m_list.InsertItem(m_count,m_count1);
     m_list.SetItemText(m_count,1,ctime((const time_t*)&protocol_header->ts.tv_sec));
     m_list.SetItemText(m_count,2,m_packetlen);
     m_list.SetItemText(m_count,3,inet_ntoa(ip_saddress));
     m_list.SetItemText(m_count,4,inet_ntoa(ip_daddress));
  m_list.SetItemText(m_count,5,m_sourcemac);
  m_list.SetItemText(m_count,6,m_destinanionmac);
     m_list.SetItemText(m_count,7,m_protocol);
  m_list.SetItemText(m_count,8,m_info);
  m_count++;
    }

每次读出一条包的信息,先拷贝一份放入自己的结构体然后再做包的分析,分析函数如下:

Packetanalyse(const u_char* temp_data)
{
 CString m_protocol_temp;
 CString temp;
 u_char* mac_string;
 struct ether_header* ether_protocol;//以太网头
 ether_protocol=(ether_header*)temp_data;
 u_short ether_type;
 ether_type=ntohs(ether_protocol->ether_type);
    mac_string=ether_protocol->ether_shost;
    m_sourcemac.Format("%02x:%02x:%02x:%02x:%02x:%02x",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
 mac_string=ether_protocol->ether_dhost;
    m_destinanionmac.Format("%02x:%02x:%02x:%02x:%02x:%02x",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
 switch(ether_type)
 {
 case 0x0800:m_protocol_temp=AnalyseIP(temp_data);
  break;
 case 0x0806:m_protocol_temp=AnalyseARP(temp_data);
  break;
 case 0x0835:m_protocol_temp="RARP";
  break;
 default:m_protocol_temp="unknown";
      m_info="unknown";
 }    
    return m_protocol_temp;
}

然后根据不同的ether_type进入不同的分析程序,其实就是把包的数据区转化为不同的结构体,然后直接通过结构体的不同字段做判断。如下代码略,仅举个分析IP和UDP包的例子,其他都是类式的:

CString CSetupdlgView::AnalyseIP(const u_char *temp_data_ip)
{
 CString m_protocol_temp;
 struct ip_header* ip_protocol;//ip头
 ip_protocol=(ip_header*)(temp_data_ip+14);//去掉以太网头
     switch(ip_protocol->ip_protocol)
  {
      case 6: m_protocol_temp=AnalyseTCP(temp_data_ip);
      break;
      case 17:  m_protocol_temp=AnalyseUDP(temp_data_ip);
      break;
      case 1: m_protocol_temp=AnalyseICMP(temp_data_ip);
      break;
  default:m_protocol_temp="IP::unknown";
   m_info="unknown";
  }
  memcpy((void*)&ip_saddress,(void*)&ip_protocol->ip_souce_address,sizeof(struct in_addr));
     memcpy((void*)&ip_daddress,(void*)&ip_protocol->ip_destination_address,sizeof(struct in_addr));
   return m_protocol_temp;
}

CString CSetupdlgView::AnalyseUDP(const u_char *temp_data_udp)
{
 CString m_protocol_temp="UDP";
 CString m_info_temp;
 struct udp_header* udp_protocol;//UDP头
 udp_protocol=(udp_header*)(temp_data_udp+14+20);//去掉以太网头和ip头
 u_short udp_destinanion_port;
 u_short udp_sport;
 udp_destinanion_port=ntohs(udp_protocol->udp_destinanion_port);
 udp_sport=ntohs(udp_protocol->udp_source_port);
 m_info_temp.Format("%d--->%d",udp_sport,udp_destinanion_port);
 switch(udp_destinanion_port)
 {
 case 138:m_info="UDP::NetBIOS数据报服务";
  break;
 case 137:m_info="UDP::NetBIOS名字服务";
  break;
 case 139:m_info="UDP::NetBIOS会话服务";
  break;
 case 53:m_info="UDP::DNS服务";
  break;
 default:m_info=m_info_temp;
 }
    return m_protocol_temp;

}

这里把定义的结构体贴出来:

struct ether_header
{
 u_int8_t ether_dhost[6];
 u_int8_t ether_shost[6];
 u_int16_t ether_type;
};
struct arp_header
{
 u_int16_t arp_hardware_type;
 u_int16_t arp_protocol_type;
 u_int8_t arp_hardware_length;
 u_int8_t arp_protocol_length;
 u_int16_t arp_operation_code;
 u_int8_t arp_source_ethernet_address[6];
 u_int8_t arp_source_ip_address[4];
 u_int8_t arp_destination_ethernet_address[6];
 u_int8_t arp_destination_ip_address[4];
};
struct ip_header
{
#if defined(WORDS_BIGENDIAN)
 u_int8_t ip_version:4,ip_header_length:4;
#else
 u_int8_t ip_header_length:4,ip_version:4;
#endif
 u_int8_t ip_tos;
 u_int16_t ip_length;
 u_int16_t ip_id;
 u_int16_t ip_off;
 u_int8_t ip_ttl;
 u_int8_t ip_protocol;
 u_int16_t ip_checksum;
 struct in_addr ip_souce_address;
 struct in_addr ip_destination_address;
};
struct udp_header
{
 u_int16_t udp_source_port;
 u_int16_t udp_destinanion_port;
 u_int16_t udp_length;
 u_int16_t udp_checksum;
};
struct tcp_header
{
 u_int16_t tcp_source_port;
 u_int16_t tcp_destinanion_port;
 u_int32_t tcp_sequence_num;
 u_int32_t tcp_acknowledgement;
#ifdef WORDS_BIGENDIAN
 u_int8_t tcp_offset:4,tcp_offset:4;
#else
 u_int8_t tcp_reserved:4,tcp_offset:4;
#endif
 u_int8_t tcp_flags;
 u_int16_t tcp_windows;
 u_int16_t tcp_checksum;
 u_int16_t tcp_urent_pointer;
};
struct icmp_header
{
 u_int8_t icmp_type;
 u_int8_t icmp_code;
 u_int16_t icmp_checksum;
 u_int16_t icmp_id;
 u_int16_t icmp_sequence;
};

到这里基本功能已经大体成型了,今天就先写到这里,明天继续!

随便转载,转载请注明出处http://blog.csdn.net/leotangcw/

欢迎大家和我交流QQ:17371764     Email:tangchengwen@163.com

刚才上FTP想找部电影看看,没新片,玩游戏好的游戏又没法玩(机器太老了),其他游戏又不想玩,接着写吧!

刚才把视图的上面大部分都搞定了,现在开始写下面的那部分,我在左边放的是一个Tree控件,右边放的是一个ActiveX控件(图方便了,没自己写),先来说右边的那个控件吧,主要在codeproject.com看见一个不错的OCX组件http://www.codeproject.com/editctrl/hexeditor.asp就偷懒了,自己写感觉难度比较大,呵呵也没用他的多少功能,就是把数据传过去,让他显示出来就是了。先下这个组件http://www.codeproject.com/editctrl/hexeditor/HexEditorOcx.zip然后运行里面的注册,在工程里面加入这个控件,然后申明一个变量,用的时候把数据传给它就行了,前面在视图里把包的数据传入自己写的结构体就是为了后面用

struct m_mydata
{
 int len;
    unsigned char* pkt_data;
};

在前面的Capoffline函数里的

my_data=(m_mydata*)malloc((m_excount-1)*sizeof(m_mydata));
 while((res = pcap_next_ex(fp,&protocol_header,&pkt_data))>=0)
    {
  my_data[m_count].len=protocol_header->len;
        my_data[m_count].pkt_data=(unsigned char*)malloc(protocol_header->len);
  memcpy(my_data[m_count].pkt_data,pkt_data,protocol_header->len);

传过去的,在Capoffline函数的最后有一个ShowByteData(0);就是做显示的,代码如下:

void CSetupdlgView::ShowByteData(int m_numbyte)
{
 if(m_numbyte>=0)
 {
  COleSafeArray arr;
        arr.CreateOneDim (VT_UI1,my_data[m_numbyte].len,my_data[m_numbyte].pkt_data );
     m_exviewdown->m_hexcon.SetData (arr,0);
        ShowPacketTree(m_numbyte);
 }
}

要显示当然要知道使用者点击了哪一个条目了,然后跟据条目打开相应的包的数据信息,相应listview 的=NM_CLICK消息,代码如下:

void CSetupdlgView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
 // TODO: Add your control notification handler code here
 int nItem = -1;
    LPNMITEMACTIVATE lpNMItemActivate = (LPNMITEMACTIVATE)pNMHDR;
    if(lpNMItemActivate != NULL)
 {
     nItem = lpNMItemActivate->iItem;
 }
    ShowByteData(nItem);
 *pResult = 0;
}

这样相应条目的数据包就可以被显示在HEX控件里面了。

这样就只有左边的Tree控件了,这里面的代码就很冗余了(不是主要部分,也不用考虑效率的问题,因为只是在点击的时候分析一个包的数据,所以没好好写,一大堆的if else switch case不好意思贴了,主要就是m_treectrl.DeleteAllItems();m_treectrl.GetCount();m_treectrl.InsertItem(temp)这几个函数的使用,添加相应包的具体信息就是了,就是上一回中结构体的不同字段的分析)

类式如下:

CString temp;
 CString temp1;
 const u_char* pkt_data;//数据
 struct ether_header* ether_protocol;//以太网头
 struct ip_header* ip_protocol;//ip头
 struct tcp_header* tcp_protocol;//tcp头
 struct udp_header* udp_protocol;//UDP头
 struct icmp_header* icmp_protocol;//icmp头
 struct netbios_header* netbios_protocol;//netbios头
 u_char* mac_string;
 u_short ether_type;
 if(m_exviewdown->m_treectrl.GetCount()!=0)
  m_exviewdown->m_treectrl.DeleteAllItems();
 HTREEITEM htm1;
 temp.Format("这是第%d个数据包",m_numtree+1);
 htm1=m_exviewdown->m_treectrl.InsertItem(temp);
 temp.Format("数据包长度%d",my_data[m_numtree].len);
 m_exviewdown->m_treectrl.InsertItem(temp,htm1);
 CListCtrl &m_list=GetListCtrl();
 temp1=m_list.GetItemText(m_numtree,1);
 temp.Format("数据包捕获时间%s",temp1);
 m_exviewdown->m_treectrl.InsertItem(temp,htm1);
 HTREEITEM htm2;
 htm2=m_exviewdown->m_treectrl.InsertItem("数据包的以太网结构");
 ether_protocol=(ether_header*)my_data[m_numtree].pkt_data;
 mac_string=ether_protocol->ether_shost;
    temp.Format("源以太网地址%02x:%02x:%02x:%02x:%02x:%02x",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
    m_exviewdown->m_treectrl.InsertItem(temp,htm2);
 mac_string=ether_protocol->ether_dhost;
    temp.Format("目的太网地址%02x:%02x:%02x:%02x:%02x:%02x",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
    m_exviewdown->m_treectrl.InsertItem(temp,htm2);
 ether_type=ntohs(ether_protocol->ether_type);
 switch(ether_type)
 {
 case 0x0800:temp="上层结构是IP(0x0800)";
  break;
 case 0x0806:temp="上层结构是ARP(0x0806)";
  break;
 case 0x0835:temp="上层结构是RARP(0x0835)";
  break;
 default:temp="未知的上层结构种类";
 }
    m_exviewdown->m_treectrl.InsertItem(temp,htm2);
 HTREEITEM htm3;

................

................

这样程序就基本上完成了,就剩下统计的界面了,如下:

 

先在这个对话框的OnInitDialog() 里面开始两个计时,一个是每50ms刷新一下屏幕,一个是在设置页里如果设置了时间的话就会在相应的时间终止捕获数据包。

BOOL CDlgState::OnInitDialog()
{
 CDialog::OnInitDialog();
 
 // TODO: Add extra initialization here
 SetTimer(1,50,NULL);
 SetTimer(2,m_exstoptime*1000,NULL);
 m_excount=0;
 begintime= CTime::GetCurrentTime();
 return TRUE;  // return TRUE unless you set the focus to a control
               // EXCEPTION: OCX Property Pages should return FALSE

}

然后相应时间消息在里面添加代码如下:

void CDlgState::OnTimer(UINT nIDEvent)
{
 // TODO: Add your message handler code here and/or call default
 switch(nIDEvent)
 {
 case 1:if((m_excount+1)>=m_exsn)
     m_pnumcount="包捕捉完毕!";
     else
     {
      m_pnumcount.Format("%d",m_excount+1); 
         nowtime= CTime::GetCurrentTime();
         ts=nowtime-begintime;
         m_time=ts.Format( "分钟: %M  秒: %S" );
     }
     UpdateData(false);
  break;
 case 2:
  m_stoptimeup.SetEvent();
  KillTimer(1);
  m_time="时间到!";
        UpdateData(false);
  break;
 }
 
 CDialog::OnTimer(nIDEvent);
}

把IDOK按钮上的字改成停止捕获,添加点击函数:

void CDlgState::OnOK()
{
 // TODO: Add extra validation here
 m_stoptimeup.SetEvent();
 KillTimer(1);
 KillTimer(2);
 CDialog::OnOK();
}

到此,程序已经完成了,呵呵
剩下的就是打包了,就是打成一个安装包**setup.exe

先找个好的ico图标导入到工程把原来那个MFC 的替换掉:

 

www.pconline.com.cn上面有很多好看的图标打包下载,然后编译工程生成**.exe文件,下载小颖安装程序制作专家www.skycn.com上面有,然后看里面的说明一步一步的添加,最后打包生成安装文件。

 

 

 

 

 

 

完成安装以后提示安装winpcap和注册ActiveX组件:

随便转载,转载请注明出处http://blog.csdn.net/leotangcw/

欢迎大家和我交流QQ:17371764     Email:tangchengwen@163.com

        花了两天时间终于把这个软件的大体开发思路和代码写完了,不过由于内容比较多,没法一点一点的仔细讲清楚开发的具体步骤(打字太慢了)在这里总结一下:

       开发 基础:

         1。 这几片文章适合有初级VC开发经验的同学参看,如果您没学过VC,想通过粘代码完成这个软件是不太现实的有很多VC的入门书籍您可以参看,在有一定的VC开发经验以后再来参看这个代码,就可以快速的完成类式的软件。

        2。其次,写网络软件也要求对网络知识有一定的了解,特别是对各个协议的组成要有一定的了解,每个字段多长,是干什么的,应该要比较熟悉。

       3。由于是基于winpcap开发的,所以要求对winpcap开发包有一定的了解,特别是其提供的一些常用的函数,您可以参看winpcap开发手册,在我的博客工具栏就有相应的链接,写的很详尽,还有一些不错的例子,相信对您的开发有很大的帮助。

       开发步骤:

     1。构思您的软件界面,看要使用什么控件,先熟悉这些控件的使用方法。

     2。参看winpcap的开发手册,在例子中理解其是怎么捕获数据包的,在看例子时,同时参看其各个重要函数的说明。(虽然是英文的开发手册,但是写的很通俗易懂,网上也有人翻译成中文了,记得是在沙沙的流水博客上面看过)

     3。在有了上面的基础以后,就是开始写程序了,最好有总体的功能划分以后再开始写,不要像我想到哪写到哪。由易到难,慢慢写,不要着急,每天写几个功能就行了。

     4。在大体成型后就是调试了,看有什么错误或不好的地方就做修改,然后打包。

     我的总体程序思路:

      1。先在设置页获取到网卡列表和其他信息,如:是否使用混杂模式,需要捕捉多少个数据包,需要捕捉多长时间,由于我采用的是每次捕包都存入文件的方式,如果没有选择保存文件,就会存入默认的文件,这样每次捕包时就不会导致内存不断膨胀。这里有一个经验就是一定要使用winpcap中的pcap_findalldevs函数,来得到一个网卡接口链表,不要使用winpcap例子中的pcap_lookupdevs,因为我试过,很多机器的第一个网卡接口是一个虚拟的接口,所以造成无法捕获正常包,有的朋友使用pcap_lookupdevs接着使用pcap_lookupnet想得到IP地址却得到0.0.0.0也是这个原因,所以我在页面左下角做了一个IP和子网掩码的显示,如果选的网卡对了,就可以显示相应的IP和MASK,这样对多网卡支持也要好的多。

     2。设置好了以后点击开始就开始捕包,把捕获数据包的函数放到了另一个线程中,这样程序的性能也要好的多,CPU占用率<1%,这里有一个重要的经验就是不要用在SDK开发时常用的pcap_loop而要选用pcap_next_ex等替代函数,这样才不会有回掉函数的那个问题。捕包同时打开一个对话框做包统计,捕包时对话框里面就可以不断更新已经捕获的包数目和捕包时间,如果设置的捕获时间到了,或是设置的捕获包数目到了,或是点击了停止按钮都会引发停止捕获的事件。

     3。一旦停止捕获,就会从保存的数据包文件里读取出来数据包的内容,添加到ListView里面,同时存入一个自己的结构体,保存在内存里面,这样每点击一个条目就会显示该条目的具体信息和16进制值等。

     4。在完成以上功能以后还可以加入一些别的功能,如winpcap的发包功能,该功能很不错,可以发送原始数据包,也就是可以直接指定数据包的各个字段,包括以太网头。我就用这个功能写了一个ARP欺骗,可以让寝室另一哥们的电脑上不了网。

     5。如果您的VC水平不错,那就不用看我的具体实现了,通过上面的步骤您就可以实现一个不错的捕获程序。如果您还要参看具体的实现,那么这个总结也更利于您看代码时了解我的思路。

一步一步尝试在MFC下开发基于winpcap的数据包捕获和分析软件.(之一)

http://blog.csdn.net/leotangcw/archive/2006/05/11/724976.aspx

到此,这个题目算是写完了,如果您对其有兴趣,欢迎与我交流。

原创粉丝点击