BPF-用户包过滤的新架构

426 阅读29分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情


前言

本文为《The BSD Packet Filter:A New Architecture for User-level Packet Capture》的翻译

摘要

许多版本的Unix系统提供了用户级别的包捕获机制,这使得使用通用用户站来做网络监控成为可能。因为网络监控作为用户级别进程存在运行,包必须被复制来越过内核与用户空间的保护边界。这种复制的工作量可以通过在内核部署一个叫做包过滤器的节点来缩小。原有的基于栈设计的Unix包过滤器在现在的RISC架构CPU表现欠佳。我们提出的BSD包过滤器使用了一个新的基于寄存器的包过滤模拟器,比起原有的设计快了20倍。BPF同时使用了直接缓冲策略,使得他的性能在同样的硬件上运行时比Sun的NIT快了100倍。

引言

Unix已经渐渐成为了高质量网络的代名词,并且今天的Unix使用者更依赖于拥有可靠的可信赖的网络访问。不幸的是,这种依赖意味着网络问题可以使得用户难以完成有效地工作,并且提高了用户和系统管理员发现他们花了很多时间在隔离和修复网络问题上的可能。解决问题需要适当的分析和诊断工具,理想的情况下,这些工具应该可以在问题所在的Unix工作站上使用。为了构造这样的工具,内核必须能够包含给用户级别进程访问原始未处理网络流量的设施。大部分现代的工作站的操作系统都有这样的特征,比如SunOs上的Nit,DEC的Ultrix上的Ultrix Packet Filter以及SGI的IRIX上的Snoop。

内核的过滤设施来自于卡内基梅隆大学和斯坦福大学的开创性工作,它们将施乐的Alto包过滤器应用到了Unix内核中。在1980年完成后,CSPF(CMU/Stanford Packet Filter) 提供了更需要的更被广泛使用的设备。但是在现代的机器上,它的性能以及它的后代的性能有很多不尽如人意的地方——一个完全适合64KB PDP-11的设计根本不适合16MB的Sparcstation2。本文介绍了一种新的捕获内核包的内核架构BPF(BSD Packet Filter)。BPF提供了显著的性能提升,在相同的硬件和流量包组合下,比起现有的包过滤器,BPF比Sun的Nit快10到150倍,比cpf快1.5到20倍。性能的提升是两个架构改进的结果:

  • BPF使用了一种重新设计的基于寄存器的过滤器,可以在现在的RISC架构CPU上被有效的实现。CSPF使用的是一种基于内存站的过滤器,其在PDP-11上表现得很好,但是和内存瓶颈的现代CPU不太匹配。
  • BPF使用了一种简单的、非共享的、在现代的更大的地址空间成为可能的缓存模式,这种模型对于数据包捕获的通常情况非常有效。

在本文中,我们介绍了BPF的设计,概述了它是如何和系统的其余部分进行交互的,并且描述了过滤机制的新方法。最后,我们介绍了BPF、NIT和CSPF的性能衡量结果,说明了为什么BPF比起其他的方法表现得更好。

网络栈

BPF有两个主要的组成部分,网络管道和包过滤器。网络管道从网络设备驱动收集网络包的副本并且将他们传送到监听程序。包过滤器决定是否接收这个包,以及将其复制到监听程序的程度。

图一展示了BPF和系统其他部分的接口。当数据包到达网络接口时,链接层设备驱动程序通常将它传输到系统协议栈上。但是当BPF正在监听这个接口时,驱动会先调用BPF。BPF将数据包提供给每个进程的过滤器。用户定义的过滤器决定这个包是否被接收以及每个数据包应该保存多少字节。对于每一个接收数据包的过滤器,BPF将请求的数据复制到和这个过滤器有关的缓存区。设备驱动重新获得控制权。如果包没有被传送到本地主机,驱动将会从中断中返回,否则,就会进入正常的协议处理流程。

因为进程可能会想要查询网络上的所有包,而网络上包的间隔很小可能只有几微妙,所以对每一个包都做一次读系统调用是不可能的,所以BPF在监控应用读取时能够收集到包的数据并且做返回。为了维持包的边界,BPF将每个捕获的包封装成为一个报头,里面包含着一个时间戳、包长度以及基于数据对齐的偏移量。

2.1 包过滤器

因为网络监控通常只需要一个网络流量的小的子集,动态的获取可以通过在中断上下文中过滤掉不想要的包。为了最小化内存流量(现代工作站上的最大瓶颈),包应该原地被过滤,比如在网络接口DMA放置他的地方,而不是将包复制到其他的内核缓冲区在进行过滤。因此,如果包不被接受的话,只有那些被进程过滤器需要的字节会被送到主机上。

与之相反的,SunOS的STREAMS NIT在过滤前复制包并且因此承担了性能上的损耗。STREAMS包过滤模型位于包捕获模型的顶部。每个接收到的消息包都会传递给mbuf,然后传递给NIT,随后NiT会分配一个STREAMS信息缓冲并且在包中做复制。消息缓冲区会被向上传递到包过滤器中,由包过滤器决定是否丢弃这个包。因此,每个包都会被复制,而很多的CPU周期都被浪费在了复制不想要的包上。

2.2 管道表现衡量

在讨论包过滤器的细节之前,我们先介绍一些用来比较BPF和Sun 流式缓冲模型的相对成本的衡量方法。这些表现与包过滤器的机制无关。

我们将BPF和NIT配置到同一个SunOs 4.1.1内核工作站中,并且在Sparcstation2进行测量。这些测量反映了在中断处理的时候产生的开销,比如系统将数据包存入到缓冲区花的时间。对于BPF,我们只是简单的测量了在管道调用开始和结束之间的次数,通过Sparcstation的微妙时钟进行计算。对于NIT我们衡量了管道调用snit intr()的时间以及将混杂的数据包复制到mbuf的额外开销。混杂的数据包就是那些没有发送到本地主机的包,这是因为包过滤器在运行而出现的包。换句话说,我们包括了NIT没有就地过滤数据包带来的损失。为了获取具体的时间,在检测代码阶段中断将会被锁定。

数据集被作为处理时间和数据包长度的直方图。我们对两种配置接收所有过滤器和不接受所有过滤器绘制了每个包的平均处理量和处理大小。

在第一个例子中,STREAMS NIT缓存模式被推导了NIT流上,其chunk-size参数被设置成为16K字节。相似的,BPF被配置成使用16K的缓存。通常位于NIT接口和NIT缓冲模块之间的包过滤模块被省略,来实现接收所有的效果。在这两种情况下,没有指定截断限制。数据如图2所示,BPF和NIT都随捕获的数据包大小呈线性增长,这反映了包到过滤器副本的成本。但是BPF的斜率和NIT的斜率不一样,说明BPF做复制的内存速度是148ns每字节,而NIT比BPF慢百分之45,约216ns每字节。y截距给出了固定的每个包的开销,一次BPF调用的开销大约6μs,而NIT的开销大概是BPF开销的15倍,达到89μs每个数据包。如此巨大的差异是因为在异常复杂的AT&T STREAM I/O系统下分配和初始化缓冲区的成本造成的。

图3展示了在拒绝所有包的配置下的结果。在这种情况下,STREAMS包过滤模块配置了一个拒绝所有的过滤器,并且直接推到了NIT接口模块的上方,并没有使用NIT缓存模块。因为过滤器放弃了所有的包,处理时间应该恒定的,和包的大小无关。对于BPF而言这是真的,我们可以看到和上次相同的开销成本,大约5μs而不是6μs,因为拒绝包避免了调用BPF调用,这和数据包的大小无关。但是,和前面解释的那样,NIT并不会就地的过滤数据包,而是会复制包然后在这些副本里调用过滤器。因此运行NIT的开销会随着包的大小增加,即使包会被过滤器放弃掉。对于大的数据包,这种无意义的复制使得NIT的开销大概比BPF高两个数量级。

在这里的主要教训是,更早的过滤数据包并且就地过滤是有好处的。虽然像STREAM这样的设计看起来是可以模块化的,并且看起来很优雅,但是拆分模块带来的性能影响是需要在设计中被考量的。我们认为即使是基于STREAMS的网络管道也应该包含包过滤器和缓存功能在其底层模块中。将包过滤器单独的拆分设计到流模块中没有什么设计优势,但是将包过滤器和管道放到单个单元中有很大的性能优势。

3 过滤器模型

假设到我们在缓冲模型中花了很多精力设计,其仍然会成为我们接收数据包时的主要开销,而包过滤计算是我们拒绝数据包时的主要开销。因此,包过滤器的好的性能表现对于整体的性能表现是至关重要的。

包过滤器是基于数据包的一个布尔值函数,如果函数的值是真,那么内核复制这个包给应用,而如果值为假,则数据包会被忽略掉。

在历史上有两种主要的过滤抽象方法:布尔表达式树(使用CSPF)和一种有向无环控制流图或CFG(首先由NNStat使用并且在BPF中被使用)。例如,图4表现了两个带有过滤器的模型,通过他们来识别以太网上的数据包是IP数据包还是ARP数据包。在树形模型中,每一个节点代表着一个布尔运算,而叶子则代表着字段上的测试谓词。边代表着操作符和操作数的关系。在CFG模型中,每一个节点代表着包域谓词,而边代表着控制转换。如果谓词结果为真,那么右边的分支将会被遍历,而如果谓词结果为假,则左边的分支会被遍历。在CFG中有两个终止叶,他们分别表示真和假。

这两种过滤模型在计算上是等价的,即,任何一个可以在其中一个表达的模型都可以在另一个语言中进行表达。然而,在实现中,他们是非常不一样的:树模型自然的映射到了栈机器之上,而CFG模型自然的映射到了寄存器机器之上。因为现代的机器都是基于寄存器的,因此我们认为CFG的方法有助于有效的实现。

3.1 CSPF(树)模型

CSPF过滤器引擎基于一个操作符栈。指令要么往栈中压入常数或者包数据,或者在栈顶部对两个元素执行二进制布尔或者位运算。过滤器程序是按顺序执行的指令列表,在评估一个程序之后,如果栈的顶部是一个非0值或者栈是空的,那么这个数据包就会被接收,反之这个数据包会被拒绝。

对于表达式树方法有两个实现上的短板:

  • 我们必须模拟操作符栈。在现代的机器上,这意味着需要生成加减操作来维持一个模拟的栈指针,并实际上对内存进行存取来模拟栈。因为内存往往是现代计算机架构的主要瓶颈,一个使用机器寄存器上值的可以避免这种内存流量的过滤器模型往往表现的更加高效
  • 树模型通常会做不必要和不相关的计算。比如,图4的树会计算‘ether.type==ARP’的值,即使针对IP类型包的测试已经为真。这种问题可以通过在过滤器中添加一些短路或者进行一些剪枝来避免,但是一些低效率的操作是不可避免的:因为网络协议的分层设计,包头必须要被解析才能到达封装的连续层。因为表达式树的每一个叶子都表示一个包域,和其他的叶子独立,我们会在评估这整棵树时做一些冗余的评价。在CFG表示法中,我们总是可能通过重排图的方式来使得任何层最多只进行一次解析。

设计者发现CSPF的另一个问题是,它不能够解析可变长度的包头,例如TCP的包头封装在可变长度的IP包头中。因为CSPF指令设置并不包括间接操作符,只能够获取固定偏移量的包数据。同样的,CSPF模型被限制为单一长度的16比特数据类型,这使得当需要操作像网络地址或者TCP序列号这样的32位数据时,我们需要进行两次操作。最后,这种设计并不允许访问奇数长度包的最后一个字节。

虽然CSPF模型由缺点,但是其推动了一种新的包过滤的出现:在内核中放置一个为机器语言中断提供对描述和实现过滤机制好的抽象的想法。此外,因为CSPF将数据包作为一个字节数组,过滤模型是和协议无关的。指定过滤器的应用负责为底层的网络媒体和协议做适当的编码。

而作为即将在下一部分介绍的BPF模型,是一次尝试保持CSPF的优势同时解决其局限性和其基于栈过滤机器的缺点。

3.2 BPF模型
3.2.1 CFGs vs Trees

BPF使用CFG过滤模型,因为它相对于表达式树模型有着明显的性能优势。树模型也许需要对数据包做无意义的多次解析,CFG模型则允许解析信息来将其内置到流图中,例如,包解析状态会在图中被记住,因为你知道你必须要遍历路径来到达特定的点,而子表达式不需要重复计算,因为控制流图总是可以被组织使得值仅仅在那些跟着初始计算的节点被使用。

例如,图5展示了一个CFG过滤器函数,其接收所有的网络数据地址foo。我们考虑一个场景,其中网络层协议是IP、ARP和RARP歇息,这些协议都包含着源地址和目的地地址。过滤器应该捕获所有情况。因此,链路层数据被首先测试,在IP数据包的情况下,IP目的地地址域被查询,对于ARP数据包,ARP地址域被查询。注意,一旦我们知道数据包是IP协议,我们不需要检查这个数据包是否是ARP协议或者RARP协议。在图6展示的表达式树模型中,需要7个谓词比较和6个布尔操作才能遍历整棵树。而通过CFG的最长路径是5次比较,平均的比较次数是3次。

3.2.2 设计过滤伪代码

使用控制流图而不是表达式树作为过滤器伪机器设计的理论基础是迈向有效实现的必要步骤,但是这还不够。即使在使用了CSPF和NNStat的经验和伪机器模型之后,BPF模型也是经历了多年多代的设计和测试。我们相信现在的版本提供了足够的普遍性,同时并没有牺牲性能。它的发展受到以下设计约束:

  • 它应当是协议独立的。内核不应该被修改来添加新的协议支持
  • 它必须是通用的,指令集应该足够丰富来处理意外的使用
  • 包数据引用应该被最小化
  • 解码指令应该由一个简单的C的switch表达式构成
  • 抽象机器寄存器应该驻留在物理寄存器中

像CSPF一样,约束1通过在模型中不提及任何指令而得到遵守。数据包都被认为是字节数组。

约束2表示我们必须提供足够通用的计算模型,包括控制流、足够的ALU操作以及传统的寻址模式。

约束3要求我们只能接触到被给定的包一次。过滤器通常将一个给定的数据包字段和一系列的值做比较,然后将另一个字段和另一系列的值做比较,以此类推。例如,过滤器可能会将包地址和一系列的机器或一系列的TCP端口做比较。理想的,我们希望能够将包的字段放到寄存器中并且将他和一系列的值进行比较。如果包被封装在一个可变长度的头中,我们必须解析外部头文件才能获取到这个数据。更进一步的,对于mbufs中的数据,访问数据域可能会涉及到遍历mbuf链。这项工作我们做过一次之后,我们就不应该再做了。

约束4意味着我们需要有一个有效地指令解码步骤,但是它排除了一种正交地址模式设计,除非我们愿意面对一个Switch选项的指数级增长。例如,当三个地址指令面对一个真实的处理器(很多工作是并行的)时,中断的顺序执行模型意味着每一个地址描述符会被串行解码。一个单地址指令格式最小化了解码工作,同时保持了足够的通用性。

最后,约束5是一个简单的性能考虑因素,和约束4一起,它们强化了伪机器寄存器集合应该很小的概念。

这些约束促进了一种累加型机器模型的采纳。在这种模型之下,流图中的每一个点计通过将数值累加到累加器中并基于该数值进行分支跳转来计算它相关的谓词。图7使用BPF指令集展示了图5中的过滤函数。

3.3 BPF伪机器

BPF抽象包括一个累加器,一个索引寄存器,一个暂存存储器,以及一个隐式程序计数器。在这些元素间的运算可以分为以下几类:

  • LOAD指令将一个值复制到累加器或者索引寄存器中。复制的数可以是一个立即数、固定偏移量的数据包数据、可变偏移量的数据包数据或者暂存存储器的内存存储。
  • STORE指令要么从累加器中要么从索引寄存器中复制值到暂存存储器。
  • ALU指令使用索引寄存器的值或者常数作为操作数在累加器上进行算数或者逻辑运算。
  • BRANCH指令根据在常数或者x寄存器和累加器之间比较的结果来改变控制流的流向。
  • RETURN指令终止过滤器并且指明保存数据包的哪些部分。数据包会被完全丢弃如果过滤器返回值是0的时候。
  • MISCELLANEOUS指令比较其他的所有东西:目前是寄存器转移指令。

固定长度指令格式如下表所示:

image.png opcode操作指令域标明了指令类型和寻址模式。jt和jf域被条件跳转指令使用,他们分别是下一条指令到true的跳转指令和false的跳转指令的偏移量。k域是用于各种目的的通用字段。

表1展示了整个BPF指令集。我们采用了这种汇编语法作为展示BPF过滤器和调试输出的方式。实际的编码是通过C宏定义实现的,具体的细节我们在这里省略。标签为addrmodes的清单列出了在操作码列中列出的每条指令允许的寻址模式。寻址模式的语义在表2中列出。

image.png load指令只是将指定的值复制到累加器(ld、ldh、ldb)或者索引寄存器(ldx)中。索引寄存器不能使用数据包寻址模式。相反的,一个包的值必须被加载到累加器中并通过tax指令转移到索引寄存器中。这不是一个常见的事情,因为索引寄存器主要用于解析可变长度IP头部,这可以通过4*([k]&0xf)的寻址模式直接加载。所有的值都是32位,除了报数据可以以无符号字节或者无符号半字的形式加载到累加器中。类似的,暂存存储器以32位字数组的方式被寻址。指令字段都是按照主机字节序进行排序,加载指令传输数据包从网络顺序到主机顺序。任何对数据包末尾以外的应用都会以返回0的方式结束过滤器,也即,数据包会被丢弃。

ALU操作(例如add、sub)使用累加器和操作数指令指定的操作,并且将数据存储到累加器中。除数为0将会终止过滤。

跳转指令将累加器中的值和常量做比较,jset执行按位与操作,这对条件位测试很有帮助。如果结果是真或者非0的值,那么就会采用真分支,相反就会采用假分支。不太常见的任意比较可以通过减法和0作比较来完成。值得注意的是,没有jlt、jle和jne的操作码因为这些可以通过让以上的指令的分支翻转来实现。因为jump的便宜可以被编码成8位,所以最长的jump距离是256指令。超出这个范围的跳转是可以被想象的,所以一个使用32位偏移量操作域的指令jmp被提供了。

返回指令终止程序并且给出接收数据包的数据量。如果数值是0的话,那么数据包就会被丢弃。实际接收的数量是数据包长度的最小值和过滤器返回的数值。

3.4 例子

我们现在举一些例子来表现数据包过滤器是如何通过BPF指令集来表达的。在下面的所有例子中,我们都假设链路层采用以太网格式。

下面的过滤器接收所有的IP包:

ldh [12]
jeq #ETHERTYPE IP, L1, L2
L1: ret #TRUE
L2: ret #0

第一条指令载入以太网类型域。我们将它和IP类型做比较,如果比较失败,程序会返回0并且数据包将会被拒接。如果比较成功,真值将会被返回,这个数据包将会被接收(真值是一些非0值来代表接收数据的数量)。

下面的过滤器接收所有的不来自于两个特定的源地址IP:128.3.112和128.3.245的数据包。如果以太网类型是IP,IP源地址会被加入然后高24位会被屏蔽。这个值会被和这两个网络地址进行比较:

ldh [12]
jeq #ETHERTYPE IP, L1, L4
L1: ld [26]
and #0xffffff00
jeq #0x80037000, L4, L2
L2: jeq #0x8003fe00, L4, L3
L3: ret #TRUE
L4: ret #0
3.5 解析包头

前面的例子假设了我们需要的数据位于数据包的固定偏移位置。这并不通用,比如,当我们面对TCP包时,TCP数据包会被封装到一个可变长度的IP包头。TCP头的开始位置需要通过IP包头给定的数据计算出来。

IP包头的长度在IP部分第一个字节的第四位中给出,也即以太网包的14位。这个值是一个字偏移量,必须要按照比例放大四倍才能得到相应的字节偏移量。下面的指令将会向累加器中载入这个偏移量:

ldb [14]
and #0xf
lsh #2

一旦IP头部长度被计算,TCP部分的数据就可以被通过间接载入的方式来获取。注意,有效偏移量有三个组成部分:

  • IP数据包头
  • 链路层包头长度
  • 和TCP相关的数据偏移量

比如,一个以太网数据包头是14字节,TCP数据包的目标端口是2字节。因此,在IP包头的长度上添加15个字节就可以获取TCP目标端口的地址。这些代码如下所示,它会扩充用来针对某个值N测试TCP的目标端口:

ldb [14]
and #0xf
lsh #2
tax
ldh [x+16]
jeq #N, L1, L2
L1: ret #TRUE
L2: ret #0

因为IP数据包头长度计算是一个通常的操作,因此我们引入了4*([k]&0xf)的寻址模式。将idx指令代入并且简化过滤器如下:

ldx 4*([14]&0xf)
ldh [x+16]
jeq #N, L1, L2
L1: ret #TRUE
L2: ret #0

然而,只有当我们看到的数据是TCP/IP头部时这种方式才会有效。因此,过滤器必须能够检查链路层类型是IP而且采取的IP协议类型是TCP才行。同样的,IP层可能对TCP数据包进行分段,在这种情况下,TCP报头只会出现在第一个分段中,因此,任何具有非0偏移量的分段都会被拒绝。最终的过滤器模型如下所示:

ldh [12]
jeq #ETHERPROTO IP, L1, L5
L1: ldb [23]
jeq #IPPROTO TCP, L2, L5
L2: ldh [20]
jset #0x1fff, L5, L3
L3: ldx 4*([14]&0xf)
ldh [x+16]
jeq #N, L4, L5
L4: ret #TRUE
L5: ret #0
3.6 过滤模型性能衡量

我们使用一个指令技术分析器,iprof分析了内核外的BPF和CSPF过滤器模型。为了能够完全的比较这两种模型,我们在CSPF中添加了一个间接操作符,让它能够解析IP报头。这种变化是很微小的,并且不会对原始的过滤性能产生不利影响。测试基于UC伯克利校园网络产生的一个忙碌的大的网络数据包追踪文件进行,图8展示了四个相当典型的过滤器的结果。

过滤器1是平凡的,其测试数据包中的16比特是不是定值,这两种模型具有相当的可比性,在这种情况下,BPF大概快了50%。

过滤器2查找特定的IP地址,无论它是在源地址还是在目的地址中,并且展示了更多的差异性,性能差距大约为240%。这里如此大的差距主要是基于这样的一个事实,即CSPF只能操作16位的数据,而当遇到32位数据时,需要做两次比较操作才能确定是否相等。

过滤器3是一个包解析的示例(需要解析定位TCP的目标端口域),并且展现了更大的性能差异。BPF只解析数据包一次,将端口字段加载到累加器中,然而简单的对感兴趣的端口进行级联比较。CSPf则需要多次进行解析,然后重新定位TCP的报头才能进行比较。

最后,过滤器4展现了CSPF为在图6和图5中描述的不必要的计算的影响。

4 应用

BPF已经有两年的历史了,并且已经在一些实际的应用中投入使用。其中使用最广泛的是tcpdump,一种网络监控和数据采集工具。Tcpdump执行三个主要任务:过滤转换、数据包获取和数据包展示。这里有趣的是数据包转换机制。过滤器是用用户友好高级描述语言来指定的。Tcpdump有一个内置的编译器和优化器,他们能够将高等级的过滤器转换到BPF程序中,当然,这种转换对用户是透明的。

Arpwatch是一个被动监控程序用来追踪以太网到IP地址的映射。当建立新的连接或者发现不寻常的行为时,它通过电子邮件通知系统管理员。一个常见的管理麻烦是当多个物理主机使用一个IP地址时,Arpwatch会尽职的检测和报告这些IP地址。

BPF的一个非同寻常的应用是将它融合到一个图标编程语言的变体中。Icon解释器有两个内置的数据类型,一个包和一个包生成器。信息报作为第一类记录类型出现,允许方便的对信息包头进行点操作。包生成器可以直接在网络外初始化,也可以从先前收集的追踪文件中生成。Icon是一个解释型的动态类型语言,其有着高级字符串扫描功能和丰富的数据结构。通过BPF拓展,他能够很好的适配网络分析工具的快速原型化。

Netload和histo是两个网络虚拟工具,他们能够在X显示器上实时的生成网络统计数据。Netload使用tcpdumo风格过滤器规范实时绘制利用率数据,而Histo生成带时间戳的多媒体网络数据包的动态到达时间直方图。

反向ARP协议,也即RARP协议,其守护进程使用BPF接口来读和写RARP请求并且直接响应到本地网络。我们开发这个程序是为了能够让我们在我们的SunOs4操纵系统上完全替换掉NIT。每一个基于Sun Nit的应用(etherfind、traffic和rarpd)现在都有一个BPF模拟器。

最后,可以将最新版本的NNStat和nfswatch配置在BPF上运行,以及在NIT上运行。

5 结论

BPF已经被证明是一种有效地、可拓展的、可移植的网络监控接口。我们的比较研究表名,它在缓冲区管理方面比NIT更优秀,在过滤机制方面比CSPF更强大。它的可编程伪机器模型表现出了出色的通用性和可拓展性,因为特定协议的所有数据都可以从内核中解析出来。最后,该系统是可移植的,它可以运行在大部分的BSD和BSD衍生系统上,并且可以和各种数据链路层进行交互。

6 如何获得

BPF可以通过匿名的ftp主机ftp.ee.lbl.gov获取,其作为tcpdump分发的一部分,目前在文件tcpdump-2.2.1.tar.z中。最后,我们计划将BPF分解发行到它自己的发行版中,比如我们可以期待未来的bpf-*.tar.z。Arpwatch和netload也可以从这个站点获得。

7 致谢

如果没有Jeffrey Mogul的鼓励,这篇论文就不会发表。Jeff将tepdump移植到Ultrix,并添加了小端支持,发现了我们的字节排序漏洞。他还通过迫使我们考虑解析DECNET包头的艰巨任务来启发jset指令。Mike Karels建议过滤器不仅要决定是否接受一个数据包,还要决定保留多少。CraigLeres是BPF/tcpdump的第一个主要用户,负责发现和修复两者中的许多bug。Chris Torek帮助进行了包处理性能测量,并提供了关于各种BSD特性的见解。最后,我们感谢互联网上许多BPF'tcpdump的用户和扩展者,感谢他们的建议、bug修复、源代码和许多问题,这些年来,极大地拓宽了我们对网络世界和BPF在其中的位置的看法。

最后,我们要感谢Vern Paxson,Craig Leres,Jeff Mogul,Sugih Jamin和审稿人对本文草稿的宝贵意见。