Programming with pcap
流程
- 决定要嗅探的接口
- 初始化pcap,告诉pcap需要嗅探的接口,创建会话
- 定义嗅探的一些规则
- 让pcap进入执行循环
- 关闭嗅探会话
设置接口
通过设置参数执行参数指定需要嗅探的接口
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
char *dev = argv[1];//将命令中指定接口的参数赋值给dev
printf("Device: %s\n", dev);
return(0);
}
通过pcap自动检测出接口
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
//dev用来存储接口
//errbuf用来存放错误信息
char *dev, errbuf[PCAP_ERRBUF_SIZE];
//通过pcap_lookupdev函数检测接口
//错误信息存放到errbuf中
dev = pcap_lookupdev(errbuf);
if (dev == NULL) {
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
return(2);
}
printf("Device: %s\n", dev);
return(0);
}
设置会话(session handler)
新建一个会话session handler
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
参数
- device指定接口
- snaplen指定可以被pcap捕获的最大的字节数量
- promisc为真(1)就会进入混杂模式(promiscuous mode)
- 混杂模式就是接收所有经过网卡的数据包,包括不是发给本机的包,即不验证MAC地址。
- 普通模式下网卡只接收发给本机的包(包括广播包)传递给上层程序,其它的包一律丢弃。
- to_ms指定超时时间,单位是毫秒ms,0表示没有超时时间
- ebuf用来存储错误信息
返回值
这个函数返回session handler(就是一个会话)
举例子
#include <pcap.h>
pcap_t *handle;//一个会话
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
检测链路层的类型
不同的设备可能支持不同的链路层类型,有时我们需要检测设备支持的类型我们,这时我们就能够使用pcap_datalink函数来获取设备的类型。
if (pcap_datalink(handle) != DLT_EN10MB) {
fprintf(stderr, "Device %s doesn't provide Ethernet headers - not supported\n", dev);
return(2);
}
pcap_datalink返回一个特定的志来代表链路层的类型,具体到这里看;
这里用以太网为例:
链路类型 | 数值 | 对应的名称 | 描述 |
---|---|---|---|
LINKTYPE_ETHERNET | 1 | DLT_EN10MB | IEEE 802.3 Ethernet (10Mb, 100Mb, 1000Mb, and up); the 10MB in the DLT_ name is historical. |
流量过滤
pcap设置的机制是:
- 将过滤规则编译成一个过滤器filter
- 将过滤器filter应用到会话session handler上
编译过滤器
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
参数
- p,指定会话
- fp,存储编译完成的过滤器(filter)
- str,指定表达式(过滤规则)
- optimize,用来指定表达式是否需要被‘optimized’
- netmask,指定应用过滤器的网络的子网掩码
返回值
- -1,表示失败
- 其他,表示成功
将过滤器应用到相应的会话上
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
参数
- p,指定会话
- fp,指定过滤器filter
返回值
- -1,表示失败
- 其他,表示成功
探寻一个ipv4的地址和子网掩码
使用pcap_lookupnet函数探寻一个ipv4的地址和子网掩码,这一步是必须的,因为过滤器编译的时候需要子网掩码。
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
参数
- dev,指定设备名
- net,返回一个ip地址
- mask,返回对应的子网掩码
- errbuf,用来存储错误信息
返回值
- -1,表示失败
- 其他,表示成功
举个例子
下面的例子是在设备rl0上用混杂模式嗅探23端口
#include <pcap.h>
pcap_t *handle; /* Session handle */
char dev[] = "rl0"; /* Device to sniff on */
char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
struct bpf_program fp; /* The compiled filter expression */
char filter_exp[] = "port 23"; /* The filter expression */
bpf_u_int32 mask; /* The netmask of our sniffing device */
bpf_u_int32 net; /* The IP of our sniffing device */
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return(2);
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_gete(handle));
return(2);
}
抓包
一次抓一个包
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
参数
- p,指定会话
- h,存放捕获的包的信息,包括在线抓包的长度和离线包的长度,结构体代码如下:
struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present 在线抓包的长度*/
bpf_u_int32 len; /* length this packet (off wire) 离线包的长度*/
};
/*
这里解释一下离线包
离线包 是将包括 HTML、Javascript、CSS 等页面内静态资源打包到一个压缩包内。您预先下载该离线包到本地,然后通过客户端打开,直接从本地加载离线包,从而最大程度地摆脱网络环境对 H5 页面的影响。
*/
返回
返回一个u_char指针指向所捕获的包
举例
#include <pcap.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
pcap_t *handle; /* Session handle */
char *dev; /* The device to sniff on */
char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
struct bpf_program fp; /* The compiled filter */
char filter_exp[] = "port 23"; /* The filter expression */
bpf_u_int32 mask; /* Our netmask */
bpf_u_int32 net; /* Our IP */
struct pcap_pkthdr header; /* The header that pcap gives us */
const u_char *packet; /* The actual packet */
/* Define the device */
dev = pcap_lookupdev(errbuf);
if (dev == NULL)
{
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
return (2);
}
/* Find the properties for the device */
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1)
{
fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
net = 0;
mask = 0;
}
/* Open the session in promiscuous mode */
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL)
{
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return (2);
}
/* Compile and apply the filter */
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1)
{
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return (2);
}
if (pcap_setfilter(handle, &fp) == -1)
{
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return (2);
}
/* Grab a packet */
packet = pcap_next(handle, &header);
/* Print its length */
printf("Jacked a packet with length of [%d]\n", header.len);
/* And close the session */
pcap_close(handle);
return (0);
}
循环抓n个包
pcap用pcap_loop和pcap_dispatch两个函数来进行循环抓包,这两个函数在抓到包时会启动指定的回掉函数(callback function)进行处理。
pcap_loop
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
参数
- p,指定会话
- cnt,指定需要抓包的数量
- callback,指定回掉函数,可以是NULL
- user,用来存放抓的包
callback函数的形式
如下就是一个回掉函数callback function:
void got_packet(u_char *args, const struct pcap_pkthdr *header,const u_char *packet);
callback function对应参数:
- args,对应pcap_loop的最后一个参数,pcap_loop的将最后一个参数user传给它
- header,指定嗅探的时间间隔,在线抓包的长度,离线包的长度,具体如下:
struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
- packet,它指向整个包的头一个字节(用这个指针可以轻松的找到一个包协议对应的相应的内容)