https://zhuanlan.zhihu.com/p/437187441
Libpcap(Packet Capture Libray),即数据包捕获函数库。该库提供的C函数接口用于捕获经过指定网络接口的数据包,可以统计流量数据,可以添加过滤规则分析数据包数据内容。
Linux下的tcpdump软件就是以他为基础开发的。Windows上也有基于他的库Winpcap
这些地方可以下载安装包和源码:
Libpcap:
https://github.com/the-tcpdump-group/libpcap
Winpcap:
https://www.winpcap.org/docs/docs_412/html/main.html
Winpcap中文版:
www.ferrisxu.com/WinPcap/html/group__wpcapfunc.html#g659439bf5aa3988b5a92d31990fbf437
libpcap库需要先安装才能用
tar -zxvf libpcap.tar.gz
cd libpcap
sudo ./configure
sudo make
sudo make install
程序被默认安装在/usr/local/lib目录,将程序复制到/usr/lib下:sudo cp libpcap.so.1 /usr/lib/
命令安装缺啥补啥
sudo apt-get install libpcap-dev
1.查找网络设备:pcap_lookupdev()
2.获取网络设备参数:pcap_lookupnet()
3.打开网络设备,返回可操作的句柄:pcap_open_live()
4.将用户制定的过滤策略编译到过滤程序中:pcap_compile()
5.将上一步的策略设置到过滤器:pcap_setfilter()
6.捕获数据包:pcap_loop(),pcap_dispatch(),pcap_next(),pcap_next_ex()
7.关闭网络设备,释放资源:pcap_close()
功能 | 查找可用网络设备(网卡名称);也可以自己指定,不用查 |
参数 | errbuf存放出错信息,大小为PCAP_ERRBUF_SIZE |
返回值 | 返回第一个可用的网络接口名称 |
功能 | 获取指定网卡的IP地址,子网掩码等信息 |
参数 | device,1中返回的网卡名称; netp,传出参数,存放指定网卡的IP地址,需要用inet_ntoa转换成点分十进制字符串;maskp,传出参数,存放指定网卡的子网掩码,需要用inet_ntoa转换成点分十进制字符串;errbuf,存放出错信息,大小为PCAP_ERRBUF_SIZE |
返回值 | 成功返回0;失败返回PCAP_ERROR(-1),并记录信息到errbuf |
功能 | 获取指定接口的可操作句柄,后面的接口需要调用这个句柄 |
参数 | device,1中返回的网卡名称,也可以自己指定; snaplen,设置需要捕获的每个数据包长度,从数据包开头算起;promisc,指定是否打开混杂模式,0表示非混杂,其他值表示混杂,如果要打开混杂模式,网卡也必须打开混杂模式(ifconfig eth0 promisc);to_ms,指定获取数据包需要等待的毫秒数,超时会立即返回,0表示一直等待直到有数据包到达 |
返回值 | 返回指定网卡的pcap_t类型的句柄 |
功能 | 编译构造过滤表达式 |
参数 | p,第1步中返回的网卡名称,也可以自己指定; fp,传出参数,存放编译后的bpf;str,过滤表达式;optimize,是否需要优化过滤表达式,1表示需要优化;netmask,指定需要捕获的网卡的子网掩码,设为0即可 |
返回值 | 成功返回0;失败返回PCAP_ERROR(-1), pcap_geterr(device) or pcap_perror(device)可以用来获取错误信息 |
*str(过滤表达式):
过滤表达式有很多参数,支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息:
proto(tcp/udp/arp/ip/ether/icmp等);
dir(src/dst/src and dst/src or dst等);
type(host/net/port/portrange)
一个基本的表达式单元格式为"proto dir type"
选择只接受某个IP地址的数据包 | src host 127.0.0.1 |
选择只接受TCP/UDP的目的端口是80的数据包 | dst port 80 |
不接受TCP数据包 | not tcp |
只接受SYN标志位置(TCP首部开始的第13个字节)且目标端口号是22或23的数据包 | tcp[13]==0x02 and (dst port 22 or dst port 23) |
只接受icmp的ping请求和ping响应的数据 | icmp[icmptype]==icmp-echoreply or icmp[icmptype]==icmp-echo |
只接受以太网MAC地址为00:00:00:00:00:00的数据包 | ehter dst 00:00:00:00:00:00 |
只接受ip的ttl=5的数据包(ip首位第八的字节为ttl) | ip[8]==5 |
条件组合 | *tcpdump '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.1)))' *tcpdump -i eth0 '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.1)))' *tcpdump '((icmp) and ((ether dst host 00:0A:0B:03:0C:05)))' tcpdump '((tcp) and ((dst net 192.168) and (not dst host 192.168.1.254))' |
功能 | 设置过滤表达式 |
参数 | p,1中返回的网卡名称,也可以自己指定; fp,pcap_compile()的第二个参数,存放编译后的bpf |
返回值 | 成功返回0;失败返回PCAP_ERROR(-1),pcap_geterr(device) or pcap_perror(device)可以用来获取错误信息 |
功能 | 捕获数据包 |
参数 | p,1中返回的网卡名称,也可以自己指定; cnt,设置需要抓包的个数,抓取cnt个包后立即返回,设置为-1表示循环抓包,直到出现错误;callback,回调函数;user,回调函数的参数 |
返回值 | 成功返回0;失败返回PCAP_ERROR(-1)或者PCAP_ERROR_BREAK(-2) |
callback(回调函数):void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
功能 | 当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它 |
回调函数参数 | userarg是pcap_loop的最后一个参数; pkthdr,收到的数据包的pcap_pkthdr类型的指针;packet,收到的数据包数据 |
功能:捕获数据包,与pcap_loop类似,在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)
功能:捕获数据包,封装的pcap_dispatch()函数,参数cnt=1
返回值:成功则返回捕获数据包的地址,失败返回NULL
功能:关闭网络设备,释放资源
struct pcap_pkthdr
{
struct timeval ts; // 抓到包的时间,ts.tv_sec是从1997.01.01到现在所经过的秒数,可以用ctime等函数转化成当前时间的字符串
bpf_u_int32 caplen; // 表示抓到的数据长度,抓取的长度
bpf_u_int32 len; // 表示数据包的实际长度,本来应有长度
}
//数据包最前面的信息, Mac头部,总长度14字节,然后通过eth_type来解析包后面的内容
typedef struct eth_hdr
{
u_char dst_mac[6];//目标mac地址
u_char src_mac[6];//源mac地址
u_short eth_type;//以太网类型:0x0800-IP;0x0806-ARP;0x0835-RARP
}eth_hdr;
//IP头部,总长度20字节,通过protocol判读传输的是什么协议的数据
typedef struct ip_hdr
{
int version:4; //版本
int header_len:4; //首部长度
u_char tos:8;//服务类型
int total_len:16;//总长度
int ident:16;//标志
int flags:16;//分片偏移
u_char ttl:8;//生存时间
u_char protocol:8;//协议:0x01-ICMP;0x06-TCP;0x11-UDP
int checksum:16;//检验和
u_char sourceIP[4];//源IP地址:通过inet_ntoa()函数转换
u_char destIP[4];//目的IP地址
}ip_hdr;
//TCP头部,总长度20字节,通过flags判断是哪种消息
typedef struct tcp_hdr
{
u_short sport;//源端口号,通过 ntohs()转换
u_short dport;//目的端口号
u_int seq;//序列号
u_int ack;//确认号
u_char head_len; //首部长度
u_char flags;//标志位;0x01-FIN,0x02-SYN,0x04-RST,0x08-PSH,0x10-ACK,0x20-URG;
u_short wind_size;//16位窗口大小
u_short check_sum;//16位TCP检验和
u_short urg_ptr;//16为紧急指针
}tcp_hdr;
//UDP头部,总长度8字节
typedef struct udp_hdr
{
u_short sport;//远端口号
u_short dport;//目的端口号
u_short tot_len;//udp头部长度
u_short check_sum;//16位udp检验和
}udp_hdr;
//ICMP头部,总长度4字节
typedef struct _icmp_hdr
{
u_char icmp_type; //类型
u_char code; //代码
u_short chk_sum; //16位检验和
}icmp_hdr;
pcap_dumper_t* out_pcap; //定义输出文件并打开
out_pcap = pcap_dump_open(device,"/home/test.pcap");
pcap_loop(device,10,packet_handler,(u_char *)out_pcap); //获取10个包,并调用回调函数来存数据
pcap_dump_flush(out_pcap); //刷新缓冲区
pcap_dump_close(out_pcap); //关闭资源
//回调函数完成数据存储
void packet_handler(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data)
{
pcap_dump(user, pkt_header, pkt_data);// 输出数据到文件
printf("a packet with length of %d\n", pkt_header->len);// 打印抓到的包的长度
}
//用wireshark打开保存的记录文件test.pcap就可以分析数据了
#include <stdio.h>
#include <pcap.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
struct ip_header{
u_int8_t ip_version: 4;
u_int8_t ip_header_length: 4;
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_source_address;
struct in_addr ip_destination_address;
};
void getPacket(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *packet);
int main(int argc,char *argv[])
{
char errBuf[PCAP_ERRBUF_SIZE]={0};
char *devStr = NULL;
pcap_t *device = NULL;
struct bpf_program filter;
char *filter_app = "port 80";
pcap_dumper_t* out_pcap;
if(argc>1){
devStr = argv[1];
}
else{
/* get a device */
devStr = pcap_lookupdev(errBuf);
}
if(devStr)
{
printf("success: device: %s\n", devStr);
}
else
{
printf("error: %s\n", errBuf);
exit(1);
}
/* open a device, wait until a packet arrives */
device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
if(!device)
{
printf("error: pcap_open_live(): %s\n", errBuf);
exit(1);
}
/* construct a filter */
pcap_compile(device, &filter, filter_app, 1, 0);
pcap_setfilter(device, &filter);
/* wait loop forever */
out_pcap = pcap_dump_open(device,"/home/test.pcap");
pcap_loop(device, 10, getPacket, (u_char*)out_pcap);
pcap_dump_flush(out_pcap); //刷新缓冲区
pcap_dump_close(out_pcap); //关闭资源
pcap_close(device);
return 0;
}
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
pcap_dump(user, pkthdr, packet); // 输出数据到文件
struct ip_header *ip_protocol;
ip_protocol=(struct ip_header*)(packet + 14);
printf("Packet length: %d\n", pkthdr->len);
printf("Number of capture bytes: %d\n", pkthdr->caplen);
printf("Source address:%s\n",inet_ntoa(ip_protocol->ip_source_address));
printf("Destination address:%s\n",inet_ntoa(ip_protocol->ip_destination_address));
printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));
}