libpcap库过滤数据包

1,932 阅读3分钟

使用libpcap库来过滤数据包

引入问题

数据包的抓取往往都是非常多的,如何过滤掉不需要的数据包呢?
几乎所有的操作系统( BSD, AIX, Mac OS, Linux 等)都会在内核中提供过滤数据包的方法,主要都是基于 BSD Packet Filter( BPF ) 结构的。libpcap 利用 BPF 来过滤数据包

步骤一:设置过滤条件

前文

BPF 使用一种类似于汇编语言的语法书写过滤表达式,不过 libpcap 和 tcpdump 都把它封装成更高级且更容易的语法了,具体可以通过 man tcpdump查看。

image.png

示例

# 只接收源 ip 地址是 192.168.1.177 的数据包
src host 192.168.1.177

# 只接收 tcp/udp 的目的端口是 80 的数据包
dst port 80

# 只接收不使用 tcp 协议的数据包
not tcp

# 只接收 SYN 标志位置位且目标端口是 22 或 23 的数据包( tcp 首部开始的第 13 个字节)
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:e0:09:c1:0e:82 的数据包
ehter dst 00:e0:09:c1:0e:82

# 只接收 ip 的 ttl=5 的数据包(ip首部开始的第8个字节)
ip[8] == 5

用法

我们可以先将过滤条件的语句存储在字符串中

编译BPF过滤规则

作用

编译 BPF 过滤规则,将过滤条件的字符串语句变成struct bpf_program结构。

函数原型

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *buf, int optimize, bpf_u_int32 mask);

参数详解

  • p:pcap_open_live() 返回的 pcap_t 类型的指针
  • fp:存放编译后的 bpf,应用过滤规则时需要用到这个指针
  • buf:过滤条件
  • optimize:是否需要优化过滤表达式
  • mask:指定本地网络的网络掩码,不需要时可写 0

C语言代码示例

#include <stdio.h>
#include <pcap.h>
#define BUFSIZE 65535

int main(){
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 net; 
    bpf_u_int32 mask;
    char *device;
    pcap_t *handle;
    struct bpf_program filter;
    char filter_app[] = "port 23";

    device = pcap_lookupdev(errbuf);
    if (device){
        printf("device: %s\n", device);
        pcap_lookupnet(device, &net, &mask, errbuf);
        handle = pcap_open_live(device, BUFSIZE, 1, 0, errbuf);
        if(handle != NULL){
            //将过滤条件存储在struct bpf_program filter中
            pcap_compile(handle, &filter, filter_app, 0, net);
            printf("do pcap_compile()\n");
        }
        
        pcap_close(handle);
    }
    else{
        printf("errbuf: %s\n", errbuf);
    }
    
    return 0;
}

应用BPF过滤规则

作用

将struct bpf_program结构的过滤条件应用起来,简单理解为让过滤生效

函数原型

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

参数详解

  • p:捕获句柄,可以由pcap_open_live()或者pcap_create()函数返回得到。
  • fp:过滤条件句柄,过滤条件编译出来的结果。

C语言代码示例

#include <stdio.h>
#include <pcap.h>
#define BUFSIZE 65535

int main(){
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 net; 
    bpf_u_int32 mask;
    char *device;
    pcap_t *handle;
    struct bpf_program filter;
    char filter_app[] = "port 23";

    device = pcap_lookupdev(errbuf);
    if (device){
        printf("device: %s\n", device);
        pcap_lookupnet(device, &net, &mask, errbuf);
        handle = pcap_open_live(device, BUFSIZE, 1, 0, errbuf);
        if(handle != NULL){
            //将过滤条件存储在struct bpf_program filter中
            pcap_compile(handle, &filter, filter_app, 0, net);
            printf("do pcap_compile()\n");
            //设置过滤条件
            pcap_setfilter(handle, &filter);
            printf("do pcap_setfilter()\n");
        }
        
        pcap_close(handle);
    }
    else{
        printf("errbuf: %s\n", errbuf);
    }  
    
    return 0;
}

综合示例

前文

综上所述的三个步骤,应用完过滤表达式之后我们便可以使用 pcap_loop() 或 pcap_next() 等抓包函数来抓包了。

C语言代码示例

#include <pcap.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
 
void getPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet){
    int * id = (int *)arg;
  
    printf("id: %d\n", ++(*id));
    printf("Packet length: %d\n", pkthdr->len);
    printf("Number of bytes: %d\n", pkthdr->caplen);
    printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec)); 
   
    for(int i = 0; i < pkthdr->len; ++i){
        printf(" %02x", packet[i]);
        if( (i + 1) % 16 == 0 ){
            printf("\n");
        }
    }

    printf("\n\n");
}
 
int main(){
    char errBuf[PCAP_ERRBUF_SIZE];
    char *dev;
    pcap_t * handle;
    struct bpf_program filter;
    int id = 0;

    dev = pcap_lookupdev(errBuf);

    if(dev){
        printf("success: device: %s\n", dev);
    }
    else{
        printf("error: %s\n", errBuf);
        return 1;
    }

    handle = pcap_open_live(dev, 65535, 1, 0, errBuf);

    if(!handle){
        printf("error: pcap_open_live(): %s\n", errBuf);
        exit(1);
    }

    pcap_compile(handle, &filter, "dst port 80", 1, 0);

    pcap_setfilter(handle, &filter);

    pcap_loop(handle, -1, getPacket, (u_char*)&id);

    pcap_close(handle);

    return 0;
}