如何在Linux编程中像tcpdump一样过滤报文?——BPF

xiaoxiao2025-09-05  221

在linux下使用tcpdump进行报文抓取是理解和分析网络信息交互过程的重要步骤。相信有不少同学在设计网络程序时有过这样的冲动——能否在代码中也像tcpdump一样过滤获得我想要的报文呢?答案当然是“没问题”。这里介绍的BPF就是编程中使用的工具,当然似乎tcpdump也是利用该工具来实现的。

一、关于BPF

BPF(Berkeley Packet Filter)伯克利包过滤器。其最初构想提出于 1992 年,其目的是为了提供一种过滤包的方法,并且要避免从内核空间到用户空间的无用的数据包复制行为。它最初是由从用户空间注入到内核的一个简单的字节码构成,它在那个位置利用一个校验器进行检查 —— 以避免内核崩溃或者安全问题 —— 并附着到一个套接字上,接着在每个接收到的包上运行。几年后它被移植到 Linux 上,并且应用于一小部分应用程序上(例如,tcpdump)。其简化的语言以及存在于内核中的即时编译器(JIT),使 BPF 成为一个性能卓越的工具。

二、使用BPF

1.  预置条件:创建socket,并确保从socket中读取的是packet,也就是说是 MAC头+IP头+TCP/UDP头。

2.  参考说明:http://www.gsp.com/cgi-bin/man.cgi?section=4&topic=bpf#1

3.  使用步骤:

                    a.  创建socket                   

                    b.  初始化BPF过滤器

                    c.  使用setsockopt将过滤器SO_ATTACH_FILTER 到了socket 上

      注:a和b无先后顺序。

4.  代码分析:

//dump arp 0x0806 struct sock_filter CODE_BPF [] = { {0x20, 0, 0, 0x0000000c}, {0x15, 0, 1, 0x00000806}, {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000} }; int packet_handle_init(struct lib_cap *p) { int sock = -1; struct sock_fprog Filter; struct sockaddr_ll sll; if (NULL == p) { return -1; } // init filter settings Filter.len =4; Filter.filter = CODE_BPF; //set default value p->ifindex = -1; p->fd = -1; p->buffer = NULL; p->buf_len = 0; if ( (sock = socket(PF_PACKET, SOCK_RAW, htons(ETHERTYPE_SADP))) < 0) { return -1; } if ( setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter)) < 0); }

其中sock_fprog和sock_filter为BPF库特固定的结构体,其定义如下:

struct sock_fprog { unsigned short len; struct sock_filter *filter; } struct sock_filter { __u16 code; /* actual filter code */ __8 jt; /* jump true */ __8 jf; /* jump false */ __u32 k; /* Generic multiuse field */ }

三、字节码分析

什么是字节码?即上文代码中定义的结构体struct sock_filter CODE_BPF初始化编码,

//dump arp 0x0806 struct sock_filter CODE_BPF [] = { {0x20, 0, 0, 0x0000000c}, {0x15, 0, 1, 0x00000806}, {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000} };

其具体的含义,需要借助于tcpdump来说明:

上述代码的生成,使用命令tcpdump -dd ether proto 0x0806

{ 0x20, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0x00000806 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }

其意义即过滤以太网协议中类型是0x0806(arp)的数据包。这段数字到底实现了什么功能呢?-d 选项来可获得其意义:

tcpdump -d ether proto 0x0806

(000) ldh [12] (001) jeq #0x806 jt 2 jf 3 (002) ret #262144 (003) ret #0

查阅相关资料可知:

ldh:高字节加载, 即从帧第12字节起载入内存,即IP报文中12字节src与dst mac后的两字节,如下图所示

jeq: 如果类型字段是 0x0806 的话就返回 262144个字节,不是的话就返回 0字节。

至此就已达到过滤类型为0x0806数据包的目的。

四、扩展

有过一些tcpdump使用经验的同学可能会发现,CODE_BPF数组的有时候非常复杂,其实这与其过滤条件有关,如:

tcpdump ip -dd -s 2048 host 192.168.1.2

tcpdump ip -d -s 2048 host 192.168.1.2

不同的过滤条件会有不同的过滤BPF code。

通过以上分析和实验过程,可以清楚的看到,tcpdump的强大远远不止抓包分析,其本身也有很多值得去研究的价值。

 

转载请注明原文地址: https://www.6miu.com/read-5035816.html

最新回复(0)