avatar

网络技术与应用作业二 WPcap的安装与基本使用

网络技术与应用第二次实验报告

[TOC]

实验内容说明

作业题目:综合性实验(1) 利用WinPcap编程捕获数据包

作业说明:

(1)了解WinPcap(或LibPcap、NPcap)的架构。
(2)学习WinPcap(或LibPcap、NPcap)的设备列表获取方法、网卡设备打开方法,以及数据包捕获方法。(3)学习多线程(或多进程)程序编写方法。
(4)通过WinPcap(或LibPcap、NPcap)编程,实现本机的数据包的捕获,显示以太帧的源地址、目的地址和帧类型/长度。
(5)捕获的数据报不要求硬盘存储,但应以简单明了的方式在屏幕上显示。必显字段包括源MAC地址、目的MAC地址、帧类型/长度。
(6)编写的程序应结构清晰,具有较好的可读性。

实验准备

(1)WinPcap架构。

WinPcap是一个win32平台下的数据报捕获体系架构,它的主要功能是进行数据报捕获和网络分析。它包含了

  • 内核级别的包过滤:在操作系统内核中运行Netgroup Packet Fileter(NPF)设备驱动程序与网络接口驱动直接交互。NPF提供了数据包捕获与发送的基本特性,同时也提供了一个可编程的过滤系统。可以用于限制一个会话,只捕获特定的网络数据包

  • 低层次的动态链接库(packet.dll):提供底层API,可以用来直接访问驱动程序的函数,提供一个独立于微软的不同操作系统的编程接口。

  • 高级别系统无关的函数库(wpcap.dll):提供与其它系统下的pcap程序(如libpcap)的兼容性。

本次实验将利用WinPcap高级别系统无关函数库中提供的函数对流经网卡的数据包进行捕获

一般利用WinPcap捕获数据报需要经过3个步骤

  1. 获取设备列表
  2. 打开网络接口
  3. 在打开的网络接口卡上捕获网络数据包

(2)WinPcap的设备列表获取方法、网卡设备打开方法,以及数据包捕获方法。

==获取设备列表==

函数原型与参数解析:

int pcap_findalldevs_ex(
//指定从哪里获得接口列表,该函数可以获取本机、远程设备和文件的网络接口列表
//如果希望得到本机的网络接口列表,可以使用PCAP_SRC_IF_STRING常数
char* source;
//在获取远程设备的网络接口列表时,如果远程设备需要认证,则需要使用该参数
//该参数对获取本机的网络接口列表没有任何意义,给NULL即可
struct pcap_rmtauth auth,
//函数成功返回后,该参数指向获取的网络接口列表的第一个元素,列表中的所有元素都是一个pcap_if_t结构
pcap_if_t **alldevs,
//用户定义的存放错误信息的缓冲区,缓冲区长度不饿能小于PCAP_ERRBUF_SIZE
char* errbuf;
)

返回值:

调用发生错误时,返回-1,具体错误信息输出到errbuf中
调用成功时返回0

pcap_if_t结构

结构定义:

Typedef struct pcap_if pcap_if_t;

/*
* Item in a list of interfaces.
*/
struct pcap_if {
struct pcap_if *next;//指向下一个网卡
char *name; /* name to hand to "pcap_open_live()" *///网卡名
char *description; /* textual description of interface, or NULL */
struct pcap_addr *addresses;//IP地址,一块网卡上可能有很多个IP地址,使用链表格式存储
bpf_u_int32 flags; /* PCAP_IF_ interface flags */
};
image-20201104173021027

释放设备列表

函数原型:

void pcap_freealldevs(pcap_if_t* alldevs);

==打开网络接口==

在对某一个网络接口卡进行监听之前,首先需要将其打开

函数原型与参数解析:

pcap_t *pcap_open(
const char *source, //指向需要打开的网络接口卡的名字
int snaplen, //获取数据报的最大长度
int flags, //指定以核中方式打开网络接口设备并获取数据包,最常用的时PCAP_OPENFLA_PROMISCUOUS,它通知系统以混杂模式打开网络接口设备
int read_timeout, //数据包捕获函数等待一个数据包的最大时间,如果该段时间内没有捕获到数据包,将以0值返回
struct pcap_rmtauth *auth,//捕获本机时设为NULL
char *errbuf//用户自定义的存放错误信息的缓冲区
);

返回值:

调用出错时返回NULL
成功时返回一个指向pcap_t的指针

==在打开的网络接口卡上捕获网络数据包==

函数原型与参数解析:

int pcap_next_ex(
pcap_t * p,
struct pcap_pkthdr ** pkt_header,//数据包头部基本信息,捕获时间、长度等
const u_char ** pkt_data//数据包具体内容
);

返回值

正确捕获到数据包,返回1,pkt_header保存数据包基本信息,pkt_data指向捕获数据包的完整数据
没有捕获到信息,返回0,超过参数read_timeout限制,此时pkt_header和pkt_data均不可用
调用过程中发生错误,返回-1

(3)多线程(或多进程)编程

函数原型与参数解析:

HANDLE
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,//NULL
_In_ SIZE_T dwStackSize,//NULL
_In_ LPTHREAD_START_ROUTINE lpStartAddress,//线程函数名
_In_opt_ __drv_aliasesMem LPVOID lpParameter,//函数参数
_In_ DWORD dwCreationFlags,//0
_Out_opt_ LPDWORD lpThreadId//线程标号
);

样例:

//全局变量
//多线程
HANDLE hThread;
DWORD dwThreadId;
//主函数
while (1)
{
for (pcap_if_t* d = alldevs; d != NULL; d = d->next)
{
hThread = CreateThread(NULL, NULL, handlerRequest, LPVOID(d->name), 0, &dwThreadId);
//执行完一个线程函数后等待500ms
WaitForSingleObject(hThread, 500);
}
}
DWORD WINAPI handlerRequest(LPVOID lparam)
{
char* name = (char*)lparam;//接受参数
get_info(name);//执行过程
return 0;//返回值
}

实验步骤

目的:通过WinPcap(或LibPcap、NPcap)编程,实现本机的数据包的捕获,显示以太帧的源地址、目的地址和帧类型/长度。

  • 环境的配置与头文件和库

    • 在官网http://www.winpcap.org下载WinPca软件和相应驱动程序```developer's pack```,执行exe文件并解压压缩包

    • 在目录WpdPack\Include\pcap目录下的pcap.h中添加一行#define WIN32

      • 创建项目,资源管理器的资源文件->添加->导入现有项,添加WpdPack目录下的两个lib文件:Packet.lib和wpcap.lib
    • 项目->属性->C/C++中将winpcap\WpdPack\Include添加到附加包含目录

      image-20201104160420496

    • WPCAPHAVE_REMOTE两个标号添加到预处理器定义中

      image-20201104161550939

      • 在程序中引入相应的包和库。注意,#include "pcap.h"后不需要再引入iostreamstdio.h等头文件

        include "pcap.h"
        #pragma comment(lib,"ws2_32.lib")//链接ws2_32.lib库文件到此项目中
  • 数据结构的定义,数据分为帧首部IP首部,其中帧首部的目的地址源地址帧长度/类型是我们本次实验需要捕获并显示的内容。

    并且,C语言变量以4bytes对齐,不足4bytes的变量编译器自动填充,所以,为了方便数据类型的转换,设置为以1字节对齐。例如,对于类型FrameHeader_t,如果以1bytes对齐,一个该类型变量将占用6+6+2=14bytes,若以4bytes对齐,将补齐为16字节

    #pragma pack(1)//以1byte方式对齐
    typedef struct FrameHeader_t {//帧首部
    BYTE DesMAC[6];//目的地址
    BYTE SrcMAC[6];//源地址
    WORD FrameType;//帧类型
    }frameHeader_t;
    typedef struct IPHeader_t {//IP首部
    BYTE Ver_HLen;
    BYTE TOS;
    WORD TotalLen;
    WORD ID;
    WORD Flag_Segment;
    BYTE TTL;
    BYTE Protocol;
    WORD Checksum;
    ULONG SrcIP;
    ULONG DstIP;
    }IPHeader_t;
    typedef struct Data_t {//包含帧首部和IP首部的数据包
    FrameHeader_t FrameHeader;
    IPHeader_t IPHeader;
    }Data_t;

    #pragma pack()//恢复4bytes对齐
  • 获取本机设备列表

    //PCAP_SRC_IF_STRING为const char* 型,需要转换为char*类型
    char *pcap_src_if_string = new char[strlen(PCAP_SRC_IF_STRING)];
    strcpy(pcap_src_if_string, PCAP_SRC_IF_STRING);
    void find_alldevs()	//获取本机的设备列表
    {
    //该函数第一个参数类型为char*类型
    //使用pcap_findalldevs_ex函数,将设备列表存储到alldevs中
    if (pcap_findalldevs_ex(pcap_src_if_string, NULL, &alldevs, errbuf) == -1)
    {
    //如果出现错误,则打印错误信息
    printf("%s", "error");
    }
    }
  • pcap_open函数打开特定网络接口卡

    //指定网卡名称
    pcap_t* p = pcap_open(name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 100, NULL, errbuf);
    if (p == NULL)
    {
    printf("error");
    return;
    }
  • 利用函数pcap_next_ex捕捉网卡信息,并将数据内容信息存储再pkt_data中,如果成功捕获,则返回1

    pcap_pkthdr* pkt_header;
    const u_char* pkt_data;
    int rtn = pcap_next_ex(p, &pkt_header, &pkt_data);
  • 为了便于信息提取,需要将捕获到的char*类型数据pkt_data转换为Data_t类型

    //printf("%s\t%s\n", d->name, d->description);
    Data_t* IPPacket;
    //WORD RecvChecksum;
    IPPacket = (Data_t*)pkt_data;
  • 对于源MAC地址和目的MAC地址的打印,由于FrameHeader.DesMAC和SrcMAC为byte类型数组,需要将其元素以16进制数的形式打印

    //输出目的MAC地址
    printf( "目的MAC地址:");
    for (int i = 0; i < 6; i++)
    {
    printf("%02x", IPPacket->FrameHeader.DesMAC[i]);
    }
    printf( " 源MAC地址:");
    //输出源MAC地址
    for (int i = 0; i < 6; i++)
    {
    printf("%02x", IPPacket->FrameHeader.SrcMAC[i]);
    }
  • 由于网络序和主机b序采用的大小端约定不同,所以对于WORD类型的帧类型/长度,需要使用ntohs函数转换

    printf( " 帧类型/长度:");
    //ntohs((u_short)IPPacket->FrameHeader.FrameType);
    printf("%02x", ntohs(IPPacket->FrameHeader.FrameType));
    printf("H\n");
  • 为了同时监听所有网卡,需要使用多线程编程

    //线程函数定义
    DWORD WINAPI handlerRequest(LPVOID lparam)
    {
    char* name = (char*)lparam;
    get_info(name);//监听网卡,捕获信息相关函数
    return 0;
    }
    //线程函数调用
    while (1)
    {for (pcap_if_t* d = alldevs; d != NULL; d = d->next)
    {
    hThread = CreateThread(NULL, NULL, handlerRequest, LPVOID(d->namec), 0, &dwThreadId);
    //延迟,减少无用的线程以减小CPU负担
    WaitForSingleObject(hThread, 500);
    }
    }
  • 关闭网卡

    pcap_freealldevs(alldevs);

实验结果

image-20201104130630244

其中

  • 类型/长度=0800H,指定是IP类型数据报
  • 目的MAC地址=ff:ff:ff:ff:ff:ff,是指广播发送
Author: Michelle19l
Link: https://gitee.com/michelle19l/michelle19l/2021/01/30/大三上/计网/网技第二次实验/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶