ubuntu20.06+libpcap抓包解析各层协议并保存为pcap文件

一、 libpcap介绍

libpcap官网介绍
libpcap(packet capture library),数据包捕获函数库,是linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层的包捕获API接口,为底层网络监控提供了一个可移植的框架。

1.1工作原理

libpcap主要由两部分组成,网络分接头(network tap)和数据过滤器(packet filter)。网络分接头从网络设备驱动程序中收集数据,进行拷贝,过滤器决定是否接收该数据包。libpcap利用BSD packet filter(BPF)算法对网卡接收到的链路层数据包进行过滤,BPF算法的基本思想是在有BPF监听的网络中,网卡驱动将接收到的数据包复制一份交给BPF过滤器,过滤器根据用户定义的规则决定是否接收该数据包,以及需要拷贝该数据包的哪些内容,然后将过滤后的数据交给过滤器关联的上层应用程序。
libpcap的包捕获机制就是在数据链路层加一个旁路处理,当一个数据包到达网络接口时,libpcap首先利用已经创建的套接字从数据链路层驱动程序中获得该数据包的拷贝,再将数据包发给BPF过滤器,BPF过滤器根据用户定义的规则将数据包逐一匹配,匹配成功的放入内核缓冲区,匹配失败直接丢弃。如果没有设置过滤规则,那么多有的数据包都将放入内核缓冲区并且传递给用户层缓冲区。

1.2ubuntu20.06安装libpcap

#第一步,先安装GCC ,一般都会自动安装 
sudo apt-get install build-essential 

#第二步,GNU M4可以从此处 ftp.gnu.org/gnu/m4/ 下载 
sudo tar -zxvf m4-latest.tar.gz 
cd m4-1.4.17 
sudo ./configure 
sudo make 
sudo make install 

#第三步,安装 flex 
sudo apt-get install flex 

#第四步:安装 bison 
sudo apt-get install bison 

#第五步,安装 libpcap 
#网址http://www.tcpdump.org/ ,下载当前最新版本 
wget http://www.tcpdump.org/release/libpcap-1.4.0.tar.gz 

tar -zxvf libpcap-1.4.0.tar.gz 
cd libpcap-1.4.0 
sudo ./configure 
sudo make 
sudo make install
#安装完看看有没有/usr/include/pcap/pcap.h这个文件存在就好了

二、libpcap主要接口函数API

pcap_t *pcap_open_live(char *device, int snaplen,
   int promisc, int to_ms, char *ebuf)
   获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开
   的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定
   是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。
   ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消
   息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
   打开以前保存捕获数据包的文件,用于读取。fname参数指定打开的文
   件名。该文件中的数据格式与tcpdump和tcpslice兼容。"-"为标准输
   入。ebuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传
   递错误消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
   打开用于保存捕获数据包的文件,用于写入。fname参数为"-"时表示
   标准输出。出错时返回NULL。p参数为调用pcap_open_offline()或
   pcap_open_live()函数后返回的pcap结构指针。fname参数指定打开
   的文件名。如果返回NULL,则可调用pcap_geterr()函数获取错误消
   息。
char *pcap_lookupdev(char *errbuf)
   用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
   设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
   错误消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
   bpf_u_int32 *maskp, char *errbuf)
   获得指定网络设备的网络号和掩码。netp参数和maskp参数都是
   bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相
   关的错误消息。
int pcap_dispatch(pcap_t *p, int cnt,
   pcap_handler callback, u_char *user)
   捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。
   cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有
   数据包,直到产生以下错误之一:读取到EOF;超时读取。callback
   参数指定一个带有三个参数的回调函数,这三个参数为:一个从
   pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构
   的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的
   字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用
   pcap_perror()或pcap_geterr()函数获取错误消息。
int pcap_loop(pcap_t *p, int cnt,
   pcap_handler callback, u_char *user)
   功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包
   被处理或出现错误时才返回,但读取超时不会返回。而如果为
   pcap_open_live()函数指定了一个非零值的超时设置,然后调用
   pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。
   cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
   u_char *sp)
   向调用pcap_dump_open()函数打开的文件输出一个数据包。该函数可
   作为pcap_dispatch()函数的回调函数。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
   char *str, int optimize, bpf_u_int32 netmask)
   将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结
   构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果
   代码的优化。netmask参数指定本地网络的网络掩码。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
   指定一个过滤程序。fp参数是bpf_program结构指针,通常取自
   pcap_compile()函数调用。出错时返回-1;成功时返回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
   返回指向下一个数据包的u_char指针。

int pcap_datalink(pcap_t *p)
   返回数据链路层类型,例如DLT_EN10MB。

int pcap_snapshot(pcap_t *p)
   返回pcap_open_live被调用后的snapshot参数值。
int pcap_is_swapped(pcap_t *p)
   返回当前系统主机字节与被打开文件的字节顺序是否不同。
int pcap_major_version(pcap_t *p)
   返回写入被打开文件所使用的pcap函数的主版本号。
int pcap_minor_version(pcap_t *p)
   返回写入被打开文件所使用的pcap函数的辅版本号。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
   向pcap_stat结构赋值。成功时返回0。这些数值包括了从开始
   捕获数据以来至今共捕获到的数据包统计。如果出错或不支持
   数据包统计,则返回-1,且可调用pcap_perror()或
   pcap_geterr()函数来获取错误消息。
FILE *pcap_file(pcap_t *p)
   返回被打开文件的文件名。
int pcap_fileno(pcap_t *p)
   返回被打开文件的文件描述字号码。
void pcap_perror(pcap_t *p, char *prefix)
   在标准输出设备上显示最后一个pcap库错误消息。以prefix参
   数指定的字符串为消息头。
char *pcap_geterr(pcap_t *p)
   返回最后一个pcap库错误消息。
char *pcap_strerror(int error)
   如果strerror()函数不可用,则可调用pcap_strerror函数替代。
void pcap_close(pcap_t *p)
   关闭p参数相应的文件,并释放资源。
pcap_dumper_t *pcap_dump_open(pcap_t *p, const char *file)
函数返回pcap_dumper_t类型的指针,file是文件名,可以是绝对路径,例如:/home/iona/packet.pcap;
void   pcap_dump_close(pcap_dumper_t *p);
用来关闭pcap_dump_open打开的文件,入参是pcap_dump_open返回的指针;
int  pcap_dump_flush(pcap_dumper_t *p)
刷新缓冲区,把捕获的数据包从缓冲区真正拷贝到文件;
void   pcap_dump(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
输出数据到文件,与pcap_loop的第二个参数回调函数void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet) 形式完全相同,可以直接当pcap_loop的第二个参数;
/*pcap头文件定义的,它包括数据包被嗅探的时间、大小等信息*/
struct pcap_pkthdr {
   struct timeval ts;     /* 时间戳 */
   bpf_u_int32 caplen;     /* 已捕捉部分的长度 */
   bpf_u_int32 len;         /* 该包的脱机长度 */
};
/* 以太网帧头部 */
struct sniff_ethernet {
   u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
   u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
   u_short ether_type; /* IP:0x0800;IPV6:0x86DD; ARP:0x0806;RARP:0x8035 */
};
/* IP数据包的头部 */
struct sniff_ip {
   #if BYTE_ORDER == LITTLE_ENDIAN
   u_int ip_hl:4, /* 头部长度 */
   ip_v:4; /* 版本号 */
   #if BYTE_ORDER == BIG_ENDIAN
   u_int ip_v:4, /* 版本号 */
   ip_hl:4; /* 头部长度 */
   #endif
   #endif /* not _IP_VHL */
   u_char ip_tos; /* 服务的类型 */
   u_short ip_len; /* 总长度 */
   u_short ip_id; /*包标志号 */
   u_short ip_off; /* 碎片偏移 */
   #define IP_RF 0x8000 /* 保留的碎片标志 */
   #define IP_DF 0x4000 /* dont fragment flag */
   #define IP_MF 0x2000 /* 多碎片标志*/
   #define IP_OFFMASK 0x1fff /*分段位 */
   u_char ip_ttl; /* 数据包的生存时间 */
   u_char ip_p; /* 所使用的协议:1 ICMP;2 IGMP;4 IP;6 TCP;17 UDP;89 OSPF */
   u_short ip_sum; /* 校验和 */
   struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
/* TCP 数据包的头部 */
struct sniff_tcp {
   u_short th_sport; /* 源端口 */
   u_short th_dport; /* 目的端口 */
   tcp_seq th_seq; /* 包序号 */
   tcp_seq th_ack; /* 确认序号 */
   #if BYTE_ORDER == LITTLE_ENDIAN
   u_int th_x2:4, /* 还没有用到 */
   th_off:4; /* 数据偏移 */
   #endif
   #if BYTE_ORDER == BIG_ENDIAN
   u_int th_off:4, /* 数据偏移*/
   th_x2:4; /*还没有用到 */
   #endif
   u_char th_flags;
   #define TH_FIN 0x01
   #define TH_SYN 0x02
   #define TH_RST 0x04
   #define TH_PUSH 0x08
   #define TH_ACK 0x10
   #define TH_URG 0x20
   #define TH_ECE 0x40
   #define TH_CWR 0x80
   #define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
   u_short th_win; /* TCP滑动窗口 */
   u_short th_sum; /* 头部校验和 */
   u_short th_urp; /* 紧急服务位 */
};
/* UDP header */
 struct sniff_udp
 {
  uint16_t sport;        /* source port */
  uint16_t dport;        /* destination port */
  uint16_t udp_length;
  uint16_t udp_sum;        /* checksum */
};

三、根据计算机网络各层特点编写结构体

3.1物理层

这个是通过双绞线传输比特了,现在软件上不做探讨

3.2数据链路层

数据链路层负责根据数据链路层协议传输帧,帧结构如下图所示
在这里插入图片描述
MAC子层是在数据链路层需要封装和解封装的东西,其中包含目标MAC地址(6字节),源MAC地址(6字节),类型(2字节)。剩下为上三层数据和FCS(帧尾)。MAC地址采用的是网络语言,我们要看的话需要转为主机语言。六字节的MAC地址就采用长度为6的字符串数组,类型实际上是四位十六进制的数。

#define ETHERTYPE_IPV4  (0x0800)
#define ETHERTYPE_IPV6	(0x86DD)
#define ETHERTYPE_ARP	(0x0806)
#define ETHERTYPE_RARP	(0x8035)

那就定义为一个无符号短整形,正好是两个字节,也可以很好的完整表示这个四位十六进制。

/* 以太网帧头部 */
struct sniff_ethernet {
	#define ETHER_ADDR_LEN	6
	u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
	u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
	u_short ether_type; /* IP:0x0800;IPV6:0x86DD; ARP:0x0806;RARP:0x8035 */
};
#define ETHERTYPE_IPV4  (0x0800)
#define ETHERTYPE_IPV6	(0x86DD)
#define ETHERTYPE_ARP	(0x0806)
#define ETHERTYPE_RARP	(0x8035)

3.3网络层

在这里插入图片描述
4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4.

4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示最大的 数字是15, 因此IP头部最大长度是60字节.

8位服务类型(Type Of Service): 3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0). 4位 TOS分别表示: 最小延时, 最大吞吐量, 最高可靠性, 最小成本. 这四者相互冲突, 只能选择一个. 对于 ssh/telnet这样的应用程序, 最小延时比较重要; 对于ftp这样的程序, 最大吞吐量比较重要.

16位总长度(total length): IP数据报整体占多少个字节.

16位标识(id): 唯一的标识主机发送的报文. 如果IP报文在数据链路层被分片了, 那么每一个片里面的这个id 都是相同的.

3位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到). 第二位置为1表示禁 止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文. 第三位表示"更多分片", 如果分片了的话, 最 后一个分片置为1, 其他是0. 类似于一个结束标记.

13位分片偏移(framegament offset): 是分片相对于原始IP报文开始处的偏移. 其实就是在表示当前分片在 原报文中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的. 因此, 除了最后一个报文之外, 其他报文的 长度必须是8的整数倍(否则报文就不连续了).

8位生存时间(Time To Live, TTL): 数据报到达目的地的最大报文跳数. 一般是64. 每次经过一个路由, TTL -= 1, 一直减到0还没到达, 那么就丢弃了. 这个字段主要是用来防止出现路由循环

8位协议: 表示上层协议的类型

16位头部校验和: 使用CRC进行校验, 来鉴别头部是否损坏.

32位源地址和32位目标地址: 表示发送端和接收端. 选项字段(不定长, 最多40字节): 略

/* IP数据包的头部 */
struct sniff_ip {
	#if BYTE_ORDER == LITTLE_ENDIAN//小端模式
	u_int ip_hl:4, /* 头部长度 */
	ip_v:4; /* 版本号 */
	#if BYTE_ORDER == BIG_ENDIAN//大端模式
	u_int ip_v:4, /* 版本号 */
	ip_hl:4; /* 头部长度 */
	#endif
	#endif /* not _IP_VHL */
	u_char ip_tos; /* 服务的类型 */
	u_short ip_len; /* 总长度 */
	u_short ip_id; /*包标志号 */
	u_short ip_off; /* 碎片偏移 */
	#define IP_RF 0x8000 /* 保留的碎片标志 */
	#define IP_DF 0x4000 /* dont fragment flag */
	#define IP_MF 0x2000 /* 多碎片标志*/
	#define IP_OFFMASK 0x1fff /*分段位 */
	u_char ip_ttl; /* 数据包的生存时间 */
	u_char ip_p; /* 所使用的协议:1 ICMP;2 IGMP;4 IP;6 TCP;17 UDP;89 OSPF */
	u_short ip_sum; /* 校验和 */
	struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
#define IPTYPE_ICMP		(1)
#define IPTYPE_IGMP		(2)
#define IPTYPE_IP		(4)
#define IPTYPE_TCP		(6)
#define IPTYPE_UDP		(17)
#define IPTYPE_OSPF		(89)

3.4传输层

传输层协议主要介绍TCP和UDP

3.4.1 UDP

在这里插入图片描述
1.无连接:知道对端的IP和端口号就直接进行传输, 不需要建立连接

2.不可靠:没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返 回任何错误信息

3.面向数据报:不能够灵活的控制读写数据的次数和数量

4.全双工:既可以读也可以写

源端口号:发送的起点

目的端口:发送的终点

报文长度:表示当前的udp数据报的总长度。16个bit位,代表的最大长度为65535(2的16次方-1),单位为字节,因此udp最大报文长度为16k

校验和:为了验证当前的数据是否出现问题,如果校验和不正确,数据一定不正确。
UDP主要用在对实时性要求高的应用中。例如多媒体实时流传输,在线会议等等。

3.4.2 TCP

在这里插入图片描述
源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;

32位序号/32位确认号:tcp协议在具体传输中会根据传输能力,将文件分为若干份 ,这32位序列号就是为数据段打上标记,以便接收端得到数据后,重组数据段。如过接收端没有收到(网络丢包了)就会请求重新发送,保证数据的完整性。

4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60 6位标志位:

URG: 紧急指针是否有效

ACK: 确认号是否有效

PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走

RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段

SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段

FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段

16位窗口大小: 后面再说

16位校验和: 发送端填充,

CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也 包含TCP数据部分.

16位紧急指针: 标识哪部分数据是紧急数据;

40字节头部选项: 暂时忽略
TCP相对于UDP来说更加可靠,可靠性主要来自于三次握手机制,但是可靠性的保证带来的是通信效率的下降,所以TCP一般用于要求可靠性,对实时性要求不高的场合。
没有什么协议一定是最好的,永远都是看具体场合适合用什么协议

3.5 应用层

应用层应用五花八门,现在先不做探讨,后面有空了再挑几个典型的分析一下。

具体代码实现

#include </usr/include/pcap/pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

#define u_char unsigned char
#define u_short unsigned short
#define u_int unsigned int
#define uint16_t unsigned short int

/* 以太网帧头部 */
struct sniff_ethernet {
	#define ETHER_ADDR_LEN	6
	u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
	u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
	u_short ether_type; /* IP:0x0800;IPV6:0x86DD; ARP:0x0806;RARP:0x8035 */
};
#define ETHERTYPE_IPV4  (0x0800)
#define ETHERTYPE_IPV6	(0x86DD)
#define ETHERTYPE_ARP	(0x0806)
#define ETHERTYPE_RARP	(0x8035)
 
 
/* IP数据包的头部 */
struct sniff_ip {
	#if BYTE_ORDER == LITTLE_ENDIAN
	u_int ip_hl:4, /* 头部长度 */
	ip_v:4; /* 版本号 */
	#if BYTE_ORDER == BIG_ENDIAN
	u_int ip_v:4, /* 版本号 */
	ip_hl:4; /* 头部长度 */
	#endif
	#endif /* not _IP_VHL */
	u_char ip_tos; /* 服务的类型 */
	u_short ip_len; /* 总长度 */
	u_short ip_id; /*包标志号 */
	u_short ip_off; /* 碎片偏移 */
	#define IP_RF 0x8000 /* 保留的碎片标志 */
	#define IP_DF 0x4000 /* dont fragment flag */
	#define IP_MF 0x2000 /* 多碎片标志*/
	#define IP_OFFMASK 0x1fff /*分段位 */
	u_char ip_ttl; /* 数据包的生存时间 */
	u_char ip_p; /* 所使用的协议:1 ICMP;2 IGMP;4 IP;6 TCP;17 UDP;89 OSPF */
	u_short ip_sum; /* 校验和 */
	struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
#define IPTYPE_ICMP		(1)
#define IPTYPE_IGMP		(2)
#define IPTYPE_IP		(4)
#define IPTYPE_TCP		(6)
#define IPTYPE_UDP		(17)
#define IPTYPE_OSPF		(89)

typedef u_int tcp_seq;
/* TCP 数据包的头部 */
struct sniff_tcp {
	u_short th_sport; /* 源端口 */
	u_short th_dport; /* 目的端口 */
	tcp_seq th_seq; /* 包序号 */
	tcp_seq th_ack; /* 确认序号 */
	#if BYTE_ORDER == LITTLE_ENDIAN
	u_int th_x2:4, /* 还没有用到 */
	th_off:4; /* 数据偏移 */
	#endif
	#if BYTE_ORDER == BIG_ENDIAN
	u_int th_off:4, /* 数据偏移*/
	th_x2:4; /*还没有用到 */
	#endif
	u_char th_flags;
	#define TH_FIN 0x01
	#define TH_SYN 0x02
	#define TH_RST 0x04
	#define TH_PUSH 0x08
	#define TH_ACK 0x10
	#define TH_URG 0x20
	#define TH_ECE 0x40
	#define TH_CWR 0x80
	#define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
	u_short th_win; /* TCP滑动窗口 */
	u_short th_sum; /* 头部校验和 */
	u_short th_urp; /* 紧急服务位 */
};
 
 
/* UDP header */
struct sniff_udp{
	uint16_t sport;		/* source port */
	uint16_t dport;		/* destination port */
	uint16_t udp_length;
	uint16_t udp_sum;		/* checksum */
};

int pcap_protocal(const struct pcap_pkthdr *pkthdr, const u_char *packet,pcap_dumper_t* arg)
{
    pcap_dump((char *)arg, pkthdr, packet);
	printf("packet size:%u, data len:%u\n",  pkthdr->len, pkthdr->caplen); //数据包实际的长度, 抓到时的数据长度
 
	struct sniff_ethernet *ethernet = (struct sniff_ethernet*)packet;
	unsigned char* src_mac = ethernet->ether_shost;
	unsigned char* dst_mac = ethernet->ether_dhost;
 
	printf("src_mac:%x:%x:%x:%x:%x:%x\n",src_mac[0],src_mac[1],src_mac[2],src_mac[3],src_mac[4],src_mac[5]);
	printf("dst_mac:%x:%x:%x:%x:%x:%x\n",dst_mac[0],dst_mac[1],dst_mac[2],dst_mac[3],dst_mac[4],dst_mac[5]);
	printf("ether_type:%u\n",ethernet->ether_type);
  
	int eth_len = sizeof(struct sniff_ethernet);  //以太网头的长度
	int ip_len = sizeof(struct sniff_ip); //ip头的长度
	int tcp_len = sizeof(struct sniff_tcp);  //tcp头的长度
	int udp_len = sizeof(struct sniff_udp);  //udp头的长度
	printf("eth_len: %d\n",eth_len);
	printf("ip_len: %d\n",ip_len);
	printf("tcp_len: %d\n",tcp_len);
	printf("udp_len: %d\n",udp_len);
	printf("/************************************/\n");

	/*解析网络层  IP头*/
	//ntohs()是一个函数名,作用是将一个16位数由网络字节顺序转换为主机字节顺序。
	if(ntohs(ethernet->ether_type) == ETHERTYPE_IPV4)
	{  //IPV4
	printf("/**********************************************************************/\n");
		printf("It's IPv4!\n");
		struct sniff_ip* ip = (struct sniff_ip*)(packet + eth_len);
		printf("ip->ip_hl:%d\n",ip->ip_hl);
		printf("ip->ip_hl & 0x0f:%x\n",ip->ip_hl & 0x0f);
		ip_len = (ip->ip_hl & 0x0f)*4;            //ip头的长度
		printf("ip->ip_v:%d\n",ip->ip_v);
		// printf("ip->ip_tos:%s\n",ip->ip_tos);
		printf("ip->ip_len:%d\n",ip->ip_len);
		// struct in_addr
		// {
		// 	in_addr_t s_addr;
		// };
		unsigned char *saddr = (unsigned char*)&ip->ip_src.s_addr; //网络字节序转换成主机字节序
		unsigned char *daddr = (unsigned char*)&ip->ip_dst.s_addr;
 
		//printf("eth_len:%u  ip_len:%u  tcp_len:%u  udp_len:%u\n", eth_len, ip_len, tcp_len, udp_len);
		printf("src_ip:%d.%d.%d.%d\n", saddr[0], saddr[1],saddr[2],saddr[3]/*InttoIpv4str(saddr)*/);  //源IP地址
		printf("dst_ip:%d.%d.%d.%d\n", daddr[0],daddr[1],daddr[2],daddr[3]/*InttoIpv4str(daddr)*/);  //目的IP地址
		
		/*解析传输层  TCP、UDP、ICMP*/
 
		if(ip->ip_p == IPTYPE_TCP)
		{         //TCP
			printf("ip->proto:TCP\n");      //传输层用的哪一个协议
			struct sniff_tcp* tcp = (struct sniff_tcp*)(packet + eth_len + ip_len);
			printf("tcp_sport = %u\n", tcp->th_sport);
			printf("tcp_dport = %u\n", tcp->th_dport);
			for(int i=0;*(packet + eth_len + ip_len+tcp_len+i)!='\0';i++)
			{
				printf("%02x ",*(packet + eth_len + ip_len+tcp_len+i));
			}
			
			/**********(pcaket + eth_len + ip_len + tcp_len)就是TCP协议传输的正文数据了***********/
		}
		else if(ip->ip_p  == IPTYPE_UDP)
		{  //UDP
			printf("ip->proto:UDP\n");      //传输层用的哪一个协议	
			struct sniff_udp* udp = (struct sniff_udp*)(packet + eth_len + ip_len);
			printf("udp_sport = %u\n", udp->sport);
			printf("udp_dport = %u\n", udp->dport);
			/**********(pcaket + eth_len + ip_len + udp_len)就是UDP协议传输的正文数据了***********/
		}
		else if(ip->ip_p == IPTYPE_ICMP)
		{   //ICMP
			printf("ip->proto:CCMP\n");      //传输层用的哪一个协议
		}
 
	}
	else if(ntohs(ethernet->ether_type) == ETHERTYPE_IPV6)
	{ //IPV6
		printf("It's IPv6!\n");
	}
	else{
		printf("既不是IPV4也不是IPV6\n");
	}
	printf("============================================\n");
	return 0;
}
 
 
 
 
int main()
{
	int ret32 = -1;
	pcap_t *handle = NULL; /* 会话的句柄 */
	char *dev = "lo"; /* 执行嗅探的设备 */
	char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */
	struct bpf_program filter; /*已经编译好的过滤表达式*/
	char filter_app[] = "port 7890"; /* 过滤表达式*/
	bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
	bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
	const u_char *packet; /* 实际的包 */
	struct pcap_pkthdr header; /* 由pcap.h定义 */
	struct in_addr ip_addr;
	int pcapnum=0;

	/*开启支持PCAP的设备嗅探*/
	// dev = pcap_lookupdev(errbuf);
	ret32 = pcap_lookupnet(dev, &net, &mask, errbuf);//获取指定设备的网络号与掩码,如果出错,返回-1,errbuf存放错误信息
	printf("Device: %s\n", dev);
	if(ret32 < 0)
	{
		printf("pcap_lookupnet return %d, errbuf:%s\n", ret32, errbuf);
	}
	printf("sizeof(mask) = %d, mask:%#x, net:%#x\n",sizeof(mask), mask, net);
	ip_addr.s_addr= net;
	printf("ipaddress is :%s\n",inet_ntoa(ip_addr));


	handle = pcap_open_live(dev, 10*1024, 1, 0, errbuf);
	// printf("handle = %s\n",handle);
	//捕获网络数据包的数据包捕获描述字,打开名为DEV的网络设备,最大捕获字节为1024字节,
	//将网络接口设定为混杂模式,超时时间为0ms意味着一直嗅探直到捕获到数据
	if(handle == NULL)
	{
		printf("pcap_open_live return err,errbuf:%s...\n", errbuf);
		return -1;
	}
	
	pcap_dumper_t* out_pcap;
	out_pcap  = pcap_dump_open(handle,"protocal.pcap");

	while(1)
	{
		pcapnum++;
		if(pcapnum>100)
		{
			break;
		}
		/* 截获一个包 */
		packet = pcap_next(handle, &header);
		if(packet)
		{
			/* 打印它的长度 */
			printf("Jacked a packet with length of [%d]\n", header.len);
			//数据包协议解析
			pcap_protocal(&header, packet,out_pcap);
		}
		else
		{
			printf("pcap_next return err, errbuf:%s\n", errbuf);
			break;
		}
	}
	
	/*flush buff*/
    pcap_dump_flush(out_pcap);
    
    pcap_dump_close(out_pcap);
	/* 关闭会话 */
	pcap_close(handle);
	
	return 0;
}
 

在ubuntu下编译指令

gcc -o protocal protocal.c -lpcap#编译pcap库相关的工程要手动包含库,-lpcap

运行

sudo ./protocal#抓取端口信息需要超级权限

下面是命令行输出部分样式,它也会保存在protocal.pcap文件中,可以采用wiresharp打开。

Jacked a packet with length of [66]
packet size:66, data len:66
src_mac:0:0:0:0:0:0
dst_mac:0:0:0:0:0:0
ether_type:8
eth_len: 14
ip_len: 20
tcp_len: 20
udp_len: 8
/************************************/
/**********************************************************************/
It's IPv4!
ip->ip_hl:5
ip->ip_hl & 0x0f:5
ip->ip_v:4
ip->ip_len:13312
src_ip:127.0.0.1
dst_ip:127.0.0.1
ip->proto:TCP
tcp_sport = 16515
tcp_dport = 53790
01 01 08 0a 69 43 29 06 69 43 29 06 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a 17 f9 f1 15 71 7a 63 52 75 30 20 68 6c 60 c8 4e c3 3c 51 33 fd e7 9d 8c 7a f0 d3 f2 17 ce 1d 2e a4 0f db 66 36 67 88 65 4a 66 23 9f 72 ============================================

编程注意的地方
1、善用pcap库函数,他的库函数非常强大, 自己基本上不用做太多东西
2、善用指针,/* 截获一个包 */ packet = pcap_next(handle, &header);返回的packet是一个指针const u_char *packet; /* 实际的包 */。根据每个网络层次的大小可以移动指针,很方便的得到自己想要的数据。
3、注意sizeof,定义的大小不对会导致读取数据连环出错。
4、善用结构体,每一层有自己的成员数据,使用结构体可以很方便的进行管理。
5、注意网络语言和主机语言的转换。例如下面的例子:

//gcc -o lookup lookup.c -I ./../include -L./../lib -lpcap
#include <stdio.h>
#include <pcap.h>
#include <arpa/inet.h>
/*
char *pcap_lookupdev(char *errbuf)
   用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
   设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
   错误消息。
*/
int main()
{
	int ret32=-1;
	char *dev, errbuf[PCAP_ERRBUF_SIZE];
	bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
	bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
	struct in_addr ip_addr;
	dev = pcap_lookupdev(errbuf);
	ret32 = pcap_lookupnet(dev, &net, &mask, errbuf);//获取指定设备的网络号与掩码,如果出错,返回-1,errbuf存放错误信息
	printf("Device: %s\n", dev);
	printf("net:%#x,   mask:%#x\n",&net,&mask);
	ip_addr.s_addr= net;
	printf("ipaddress is :%s\n",inet_ntoa(ip_addr));
	return(0);
}

其中net和mask就是网络语言,需要转为主机语言才能方便的读取。输出如下所示。

(base) hjw@hjw-PC:~/Desktop/DPDK CESHI$ gcc -o 2 2.c -lpcap
(base) hjw@hjw-PC:~/Desktop/DPDK CESHI$ sudo ./2
请输入密码:
验证成功
Device: wlp8s0
net:0x4b91a9d8,   mask:0x4b91a9dc
ipaddress is :10.12.48.0

最后感谢下述连接中的博主提供思路和图,排名不分先后:
https://blog.csdn.net/a15980352273/article/details/125035711
https://blog.csdn.net/wxlbzy99/article/details/123914257
https://blog.csdn.net/mt71887138/article/details/121743598
https://www.tcpdump.org/index.html
https://blog.csdn.net/yuanbinquan/article/details/116152066

  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值