ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Pcap通信

2026/9/13 17:01:31 拓冰建站 浏览量
Pcap通信
Pcap

pcap抓包库给抓包系统提供了一个高层次的接口pcap在windows和linux都可以使用,头文件都是pcap.h。在windows上lib和dll分别是wpcap.lib和wpcap.dll。其中可以通过安装winpcapNPCAP软件在本地电脑生成 packet.dll、Wpcap.dll。所所以程序要么安装Winpcap(简单粗暴),要么在程序中加上packet.dll、Wpcap.dll。在linux系统上使用libpcap。

winpcap使用

winpcap(windows packet capture)是windows平台下一个免费,公共的网络访问系统。WinPcap是Windows平台下访问网络数据链路层的开源库。 开发winpcap这个项目的目的在于为win32应用程序提供访问网络底层的能力。它用于windows系统下的直接的网络编程。

Winpcap有些方面不能做。它不依靠主机的诸如TCP/IP协议去收发数据包。这意味着它不能阻塞,不能处理同一台主机中各程序之间的通信数据。它只能“嗅探”到物理线路上的数据包。因此它不适用于traffic shapers,QoS调度,以及个人防火墙。

winpcap只能用在windows平台,Winpcap是一个Win32平台下用于抓包和分析的系统。包括一个内核级别的packet filter,一个底层的DLL(packet.dll)和一个高级的独立于系统的DLL(Wpcap.dll所以程序要么安装Winpcap简单粗暴),要么在程序中加上packet.dllWpcap.dll

延伸:NPCAP

 NPCAP库是一种用于在Windows平台上进行网络数据包捕获和分析的库。它是WinPcap库的一个分支,由Nmap开发团队开发,并在Nmap软件中使用。Winpcap已多年无人维护,其官网也推荐Windows XP之后的用户转移到Npcap上。

 

安装winpcap

  1. 首先下载安装winpcap.exe,https://www.winpcap.org/install/default.htm或者下载npcap  网站为:Npcap: Windows Packet Capture Library & Driver  目的是安装相关驱动和dll,安装完成之后基于winpcap的应用程序才能够正常运行。
  2. 下载winpcap的开发包,头问文件和库文件:WinPcap · Developer Resources 开发包是4.1.2版本。 备注:没有专门针对WinPcap 4.1.3的开发者包。当前的4.1.2软件包与WinPcap 4.1.3兼容。下装后下图

解压之后主要是头文件和库文件:

3.在vs中配置相关路径:(根据自己的情况修改)

  •  将WpdPack复制到D盘
  • 在vs中配置:(vc++目录:include目录和lib目录)和(link输入:wpcap.lib,ws2_32.lib
  • 配置属性->C/C++->预处理器->预处理器定义,增加;WPCAP;HAVE_REMOTE;每一个预定义符用";"隔开;

D:\WpdPack\Include

D:\WpdPack\Lib

winpcap函数

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;

bpf_u_int32 flags; /* PCAP_IF_ interface flags */

};

pcap_findalldevs

函数原型:pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf)

功能:获取计算机上所有可用的网络接口(适配器列表)

参数说明:

alldevsp: 指向 pcap_if_t 类型指针的指针,用于存放获取到的网络接口列表

errbuf: 用来存放错误信息的缓冲区

延伸:pcap_findalldevs_ex

PCAP_API int    pcap_findalldevs_ex(const char *source,

        struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf)

source指本地或者远程适配器,可填 PCAP_SRC_IF_STRING或PCAP_SRC_FILE_STRING

参数

定义

内容

PCAP_SRC_IF_STRING

"rpcap://"

此字符串指示用户希望从网络接口打开捕获。

PCAP_SRC_FILE_STRING

"file://"

此字符串指示用户希望从本地文件打开捕获。

pcap_rmtauth:可为NULL

例子:

#include <pcap.h>

int main()

{

    pcap_if_t *alldevs;

    char errbuf[PCAP_ERRBUF_SIZE];

    if (pcap_findalldevs(&alldevs, errbuf) == -1) {

        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);

        return 1;

    }

    // 遍历获取到的网卡列表

    for (pcap_if_t *d = alldevs; d != NULL; d = d->next) {

        printf("Device name: %s\n", d->name);

        printf("Description: %s\n", d->description);

    }

    // 释放资源

    pcap_freealldevs(alldevs);

    return 0;

}

pcap_freealldevs

Void  pcap_freealldevs(pcap_if_t *);

与int pcap_findalldevs(pcap_if_t **, char *)配套使用,当不再需要网卡列表时,用此函数free释放空间

pcap_open

pcap_t *pcap_open(const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf);

用于打开一个网络接口或者一个保存了数据包的文件,并返回一个 pcap_t 结构体指针,以便后续操作该接口或文件

source:需要打开的网络接口名称,即pcap_findalldevs函数返回pcap_if 结构的变量name(例如:"en0")或者是离线数据包文件的名字。

snaplen:每个数据包的最大长度(单位为字节)。最大 65536字节

flags:指定捕获模式。

PCAP_OPENFLAG_PROMISCUOUS:开启混杂模式。

PCAP_OPENFLAG_NONBLOCK:以非阻塞模式打开设备。

PCAP_OPENFLAG_MAX_RESPONSIVENESS:使得系统响应最快,但可能会消耗更多的 CPU 资源。

read_timeout:读取超时时间(单位为毫秒)。当在超时时间内网卡上没有数据到来时对网卡的读操作将返回如 pcap_dispatch() or pcap_next_ex()等函数)

auth:远程服务器的认证信息。

errbuf:错误信息缓冲区。

返回值:pcap_t指针,后面函数用到

延伸:pcap_open_live()

pcap_t *pcap_open_live(const char *, int, int, int, char *);

pcap_open_live()支持pcap_open()支持的大部分功能,相比使用pcap_open()的代码,支持多平台工作

#include <pcap.h>

#include <stdio.h>

int main() {

    char err_buf[PCAP_ERRBUF_SIZE];

    pcap_t *handle;

    // 打开指定的网络接口或文件

    handle = pcap_open("en0", 65535, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, err_buf);

    if (handle == NULL) {

        fprintf(stderr, "Error in pcap_open: %s\n", err_buf);

        return -1;

    }

    // 其他操作...

    // 关闭网络接口或文件

    pcap_close(handle);

    return 0;

}

pcap_close

功能说明:关闭之前打开的网络接口或文件。

参数介绍:之前使用 pcap_open() 或 pcap_open_live() 打开的网络接口或文件的句柄。

pcap_setnonblock

int pcap_setnonblock (pcap_t *p, int nonblock, char *errbuf) 
阻塞和非阻塞模式间切换 

pcap_getnonblock 

int pcap_getnonblock (pcap_t *p, char *errbuf) 
获得一个接口的非阻塞状态信息

pcap_lookupnet

int pcap_lookupnet (const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)
返回接口的子网和掩码  

pcap_datalink

int pcap_datalink(pcap_t *);

返回适配器的链路层的类型

DLT_EN10MB  以太网

pcap_compile

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

编译数据包过滤器,将程序中高级的过滤表达式,转换成能被内核级的过滤引擎所处理的东西.

pt:会话句柄。

fp:表示编译过的过滤规则存储的位置。

str:字符串格式的过滤规则。

optimize:表示过滤规则是否需要的优化(1:need,0:no)

netmask:表示过滤应用的网络的子网掩码.

过滤规则:

过滤规则有一个标识和标识前面的修饰子(类型、方向、协议)组成。

类型:如host、net、port

方向:src、dst

协议:ether、ip、tcp、udp、arp等

一般组成是:协议 方向 类型 标识

如下面具体过滤条件

过滤链路层mac地址

ether dst 00:e0:09:c1:0e:82 and ether src 00:e0:09:c1:0e:81

//只接收以太网目的mac地址是 00:e0:09:c1:0e:82和源mac地址是 00:e0:09:c1:0e:81 的 的数据包

过滤ip层的ip地址

ip src host 130.0.0.2 and ip src dst 130.0.0.5

过滤端口

Tcp src port 1008

pcap_setfilter

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

在捕获过程中绑定一个过滤器

p:会话句柄。

fp:表示编译过的过滤规则存储的位置。

pcap_freecode 

void pcap_freecode (struct bpf_program *fp) 
释放一个过滤器 

pcap_setbuff

int pcap_setbuff(pcap_t *p, int dim);

设置内核缓冲区(环形缓冲区)  

个人见解:相当于socket的缓冲区一样

延伸:int pcap_set_buffer_size(pcap_t *, int);

pcap_setuserbuffer

int pcap_setuserbuffer(pcap_t *p, int size);

设置用户缓冲区 windows函数

pcap_setmintocopy

int pcap_setmintocopy(pcap_t *p, int size);

置每次从内核缓冲区拷贝到用户缓冲区的最小值  windows函数

pcap_sendpacket

int pcap_sendpacket(pcap_t *, const u_char *, int);

发送数据包注意缓冲区中的数据将不被内核协议处理,只是作为最原始的数据流被发送,所以我们必须填充好正确的协议头以便正确的将数据发送

pcap_t *,//适配器

const u_char *, //发送缓冲器   缓冲数据将直接发送到网络,而不会进行任何加工和处理。这就意味着应用程序需要创建一个正确的协议首部,来使这个数据包更有意义

int//发送长度

typedef struct _comm_eth_head{

    uint8_t dst_mac[6];     ///< 目标mac地址

    uint8_t src_mac[6];     ///< 源mac地址

    uint16_t type;          ///<  请求类型  0x0800 ip数据 0x0806 arp 请求 0x8035 rarp 请求

}comm_eth_head_t;    //数据报头

  pcap_t* fp;   //定义文件句柄

    char errbuf[PCAP_ERRBUF_SIZE];

    u_char packet[100];

    int i;

    //打开指定网卡

    if((fp = pcap_open_live(argv[1],100,1,1000,errbuf)) == NULL) {

        qDebug()<<stderr<<"Error opening adapter: "<<errbuf;

    }

    /假设网络环境为ethernet,我们把目的MAC设为1:1:1:1:1:1

    packet[0] = 1;

    packet[1] = 1;

    packet[2] = 1;

    packet[3] = 1;

    packet[4] = 1;

    packet[5] = 1;

    //假设源MAC为2:2:2:2:2:2

    packet[6] = 2;

    packet[7] = 2;

    packet[8] = 2;

    packet[9] = 2;

    packet[10] = 2;

    packet[11] = 2;

    //填充发送包的剩余部分

    for(int i=12; i<100; ++i) {

        packet[i] = i%256;

    }

    //发送包

    pcap_sendpacket(fp,packet,100);

WinPcap调用pcap_sendqueue_transmit()来发送数据包,注意,第三个参数如果非零,那么发送将是同步的,这将站用很大的CPU资源,因为发生在内核驱动的同步发送是通过"brute force"loops的,但是一般情况下能够精确到微秒。

需要指出的是用pcap_sendqueue_transmit()来发送比用pcap_sendpacket()来发送一系列的数据要高效的多,因为他的数据是在内核级上被缓冲。

pcap_loop

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

用于从网络接口或者pcap文件中读取数据包

p:pcap_t类型的指针,指向已经打开的网络接口或者pcap文件。

cnt:读取数据包的个数,如果为负数或者0,则一直读取直到出错或者被强制停止。

callback:回调函数,每次读取到一个数据包时都会被调用。它的原型如下:void callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)。其中,user是用户自定义的参数,h是pcap_pkthdr类型的指针,存储了数据包的一些元信息,比如时间戳和数据包长度;bytes是u_char类型的指针,指向数据包的实际内容。

user:用户自定义的参数,会作为第一个参数传递给callback函数。

pcap_loop()函数会一直阻塞等待数据包,直到满足以下条件之一:

1.读取到cnt个数据包。

2.出现错误。

3.被强制停止(例如,通过pcap_breakloop()函数)。

在pcap_loop()函数调用后,程序会进入循环状态,不断读取数据包并将其传递给回调函数进行处理,直到满足上述任一条件为止。

pcap_next_ex

int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, const u_char **);

网卡上接收数据包如果超时接收不到数据包,则返回。超时时间在pcap_open函数设置

参数: p: 已打开的捕捉实例的描述符

         pkt_header: 报文头

         pkt_data: 报文内容

返回值: 1: 成功

             0: 获取报文超时

            -1: 发生错误

            -2: 获取到离线记录文件的最后一个报文

result = pcap_next_ex( m_usedHandle, &m_pktHeader, &m_pktData) ;

if(result == 0)/* Timeout */

{

return 0;

}

if(result == -1)//an error occured

{

return -1;

}

memset(m_ReceiveBuf,0,sizeof(m_ReceiveBuf));

memcpy(m_ReceiveBuf,m_pktData,m_pktHeader->len);

libpcap和winpcap的区别

libpcap和winpcap都是用于网络数据包捕获的库,但是它们有一些异同点。
1. 平台支持:libpcap可以在Linux、Unix、Mac OS X等操作系统上运行,而winpcap只能在Windows上运行。
2. 应用范围:libpcap主要用于开发网络监控、网络分析和网络安全相关的应用程序,而winpcap主要用于开发Windows平台下的网络监控和网络安全相关的应用程序。
3. 数据包捕获方式:libpcap使用标准的Unix网络接口(例如socket)来捕获网络数据包,而winpcap使用Winsock接口来捕获网络数据包。
4. 性能:winpcap的性能比libpcap更好,因为它使用了更高效的驱动程序来捕获网络数据包。
5. 兼容性:winpcap兼容libpcap,可以使用libpcap的应用程序在Windows平台下运行,但是libpcap不兼容winpcap,不能使用winpcap的应用程序在Linux平台下运行。
总之,libpcap和winpcap都是非常有用的网络数据包捕获库,选择哪个库取决于应用程序的需求和运行环境。

协议文件格式定义

pcap文件的格式为:
   文件头    24字节
    数据报头  数据包头为16字节,后面紧跟数据报

数据报 

文件头(Pcap Header): 每一个pcap文件只有一个文件头,总共占24字节,以下是总共7个字段的含义

typedef struct _pcap_file_head{

    uint32_t  magic;     //标记文件开始,并用来识别文件和字节顺序。值可以为0xa1b2c3d4或者0xd4c3b2a1,如果是0xa1b2c3d4表示是大端模式(按照原来的顺序一个字节一个字节的读).如果是0xd4c3b2a1表示小端模式(面的字节都要交换顺序). 网络字节序一般是大端存储,主机x86字节序一般是小端存储。

    uint16_t  version_major;    ///< 当前文件的主要版本号,一般为0x0200

    uint16_t  version_minor;    ///< 当前文件的次要版本号,一般为0x0400

    uint32_t  thiszone;     ///< 当地的标准时间,如果用的是GMT则全零,一般全零

    uint32_t  sigfigs;    ///< 时间戳的精度,一般为全零

    uint32_t  snaplen;    ///< 最大的存储长度,设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将值设置为65535

    uint32_t  linktype;   ///< 链路类型。解析数据包首先要判断它的LinkType,所以这个值很重要。一般的值为1,即以太网

}pcap_file_t;

数据报头(Packet Header):总共占16字节 数据包头可以有多个,每个数据包头后面都跟着真正的数据包。数据包头则依次为:时间戳(秒)、时间戳(微妙)、抓包长度和实际长度,依次各占4个字节。

typedef struct _frame_head{

    uint32_t Timestamp_sec;  ///< 时间戳高位,精度 秒

    uint32_t Timestamp_usec; ///< 时间戳低位,精度 微秒

    uint32_t Caplen;      ///当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。

    uint32_t Len;      ///<离线数据长度,网路中实际数据帧的长度,一般不大于Caplen,多数情况下和Caplen值一样}frame_head_t;

数据报(Packet Data):Packet是链路层的数据帧,长度就是Packet Header中定义的Caplen值 。结合抓包工具wireshark, 数据报就是 Frame: 数据报的长度就是92字节

注:wireshark的Frame 即是物理层的数据帧概况。

延伸:以太网帧(Ethernet)链路层、IP包、TCP、UDP的长度范围

以太网帧(Ethernet)链路层

以太网帧(Ethernet)属于链路层的一种。

以太网地址头部:目的地址(6字节)、源地址(6字节)、以太网类型(2字节)总共14字节。结构字段如下:

typedef struct _comm_eth_head{

    uint8_t dst_mac[6];     ///< 目标mac地址

    uint8_t src_mac[6];     ///< 源mac地址

    uint16_t type;          ///<  请求类型  0x0800 ip数据 0x0806 arp 请求 0x8035 rarp 请求

}comm_eth_head_t;

以太网封装IP数据包的最大长度是1500字节

以太网最大帧长应该是7字节前导同步+1字节帧开始定界符+6字节的目的MAC+6字节的源MAC+2字节的帧类型+1500+4字节的FCS最大帧应该是1526字节

最大帧应该是1526字节,但是实际上我们抓包得到的最大帧是1514字节,为什么不是1526字节呢?原因是当数据帧到达网卡时,在物理层上网卡要先去掉前导同步码和帧开始定界符,然后对帧进行CRC检验,如果帧校验和错,就丢弃此帧。如果校验和正确,就判断帧的目的硬件地址是否符合自己的接收条件(目的地址是自己的物理硬件地址、广播地址、可接收的多播硬件地址等),如果符合,就将帧交“设备驱动程序”做进一步处理。这时我们的抓包软件才能抓到数据,因此,抓包软件抓到的是去掉前导同步码、帧开始分界符、FCS之外的数据,只留下了目的地址,源地址,类型字段,其最大值是6+6+2+1500=1514

以太网规定,以太网帧数据域部分最小为46字节,也就是以太网帧最小是6+6+2+46+4=64。除去4个字节的FCS,因此,抓包时就是60字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面填充以满足数据帧长不小于64字节

结合抓包工具wireshark, 链路层就是Ethernet II。

IP数据包

IP头大小最小为20字节。所以,网络层的MTU=数据链路层的MTU1500-20=1480字节

由于IP协议提供为上层协议分割和重组报文的功能,在IP头中,用2个字节来描述报文的长度,2个字节所能表达的最大数字就是65535。所以,IP数据包的最大长度就是64K字节(65535)

注:最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。以太网MTU最大值为1500字节。

typedef struct _comm_ip_head{

    uint8_t version:4,  ///< 版本

            headlen:4;  ///< 头部实际长度

    uint8_t TOS;        ///< type of service

    uint16_t iplen;     ///< ip数据报总长度

    uint16_t flag;      ///< 16位标识

    uint16_t falg:3,    ///< 3位标志

             offset:13;///< 13位片偏移

    uint8_t  ttl;   ///< time to live 生存时间

    uint8_t  protocol;

    uint16_t checksum;

    uint32_t src_ip;

    uint32_t dst_ip;

    /*变长部分暂不关心*/                                                                  

}comm_ip_head_t;

结合抓包工具wireshark,ip层就是Internet Protocol

TCP(传输层)

TCP头部选项是一个可变长的信息,这部分最多包含40字节,因为TCP头部最长60字节,(其中还包含前面20字节的固定部分).

typedef struct _comm_tcp_head{

    uint16_t sport;         ///< 源端口

    uint16_t dport;         ///< 目的端口

    uint32_t seq;           ///< 序号字段                                                       

    uint32_t ack;           ///< 确认序号应当是上次已成功收到数据字节序号加 1

    uint16_t head_len:4,    ///< 首部长度

             reserved:6,    ///< 保留位

             UGR:1,     ///< 紧急指针

             ACK:1,     ///< 确认序号有效

             PSH:1,     ///< 接收方应该尽快将这个报文段交给应用层

             RST:1,     ///< 重建连接

             SYN:1,     ///< 同步序号用来发起一个连接

             FIN:1;     ///< 发端完成发送任务

    uint16_t win_size;      ///< 窗口大小,T C P的流量控制由连接的每一端通过声明的窗口大小来提供

    uint16_t checksum;      ///< 检验和覆盖了整个的 T C P报文段:T C P首部和T C P数据

    uint16_t urgent_ptr;    ///< T C P的紧急方式是发送端向另一端发送紧急数据的一种方式。

        /*其他选项*/

}comm_tcp_head_t;

TCP包头中就没有“包长度”字段,而完全依靠IP层去处理分帧。这就是为什么TCP常常被称作一种“流协议”的原因,开发者在使用TCP服务的时候,不必去关心数据包的大小,只需讲SOCKET看作一条数据流的入口,往里面放数据就是了,TCP协议本身会进行拥塞/流量控制。

M SS: 最大报文段长度(MSS: Maximum Segment Size)表示TCP传往另一端的最大块数据的长度. MSS值通常设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。对于一个以太网, MSS值可达1460字节(1500-20-20)。使用tcp协议传输数据时,每一帧最好不要超过1460字节,这样数据就不用分包。

结合抓包工具wireshark,tcp层就是Transmission Control Protocol

UDP(传输层)

UDP包的首部要占用8字节,因为UDP提供无连接服务,它的数据包包头,是固定长度的8字节,不存在可选字段,可以减少很多传输开销,所以它无需使用首部字段长,因为它的首部就是固定的。

UDP包的最大值是:IP数据包的最大长度65535-IP头的大小20-UDP头的大小8=65507字节。最小值是0

UDP包就不受以太网MTU最大值1500字节的限制。可以一次性传输更多的数据。

typedef struct _comm_udp_head{

    uint16_t  sport;

    uint16_t  dport;

    uint16_t  headlen;  ///< UDP首部和UDP数据的字节长度

    uint16_t  check_sum;///< udp校验和

}comm_udp_head_t;

结合抓包工具wireshark,udp层就是user Control Protocol