在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的强大远远不止抓包分析,其本身也有很多值得去研究的价值。
本文深入探讨了BPF(Berkeley Packet Filter)在Linux下的应用,特别是在网络报文抓取和过滤中的作用。BPF作为一种高性能的包过滤工具,不仅用于tcpdump等网络分析软件,还可在代码中实现精准的网络数据筛选。文章详细解析了BPF的字节码构造及如何在socket层面设置过滤器,揭示了BPF强大的过滤能力。
2785

被折叠的 条评论
为什么被折叠?



