tcpdump与 bpf 指令集

8 篇文章 5 订阅

前言

tcpdump 这个工具经常使用,却对其背后的原理一直没有太深入的研究。在学习 ebpf 在网络上的应用时,对 tcpdump 背后的一些原理有了更进一步的认识,在本篇博客中记录一下。

bpf overview

在一头栽进 bpf 内部之前,用下面这个图来建立对 bpf 的认识:
在这里插入图片描述

bpf 虚拟机器的基础元素

tcpdump 依附标准的 bpf 机器来运行,tcpdump 的过滤规则会被转化为一段 bpf 指令并被加载到内核中的 bpf 虚拟机器上执行,这一切都是 tcpdump 自动完成的,并不为用户可见。

从内核源代码 Documentation/networking/filter.txt 文件中摘录下面这些 bpf 虚拟机器的基础元素介绍内容。

bpf 虚拟机器寄存器

  Element          Description

  A                32 bit wide accumulator
  X                32 bit wide X register
  M[]              16 x 32 bit wide misc registers aka "scratch memory
                   store", addressable from 0 to 15

常用的寄存器是 A 累加寄存器,X 寄存器,这两个寄存器都是 32 位宽度。

bpf 虚拟机器指令集

bpf 虚拟机指令格式规则如下:

  op:16, jt:8, jf:8, k:32

op 表示 16-bit 操作码,唯一标识具体的指令,jt 与 jf 表示 8 位宽度的跳转目标,8 位限制了跳转范围为 256 字节。k 是一个工具混杂的参数,可以被每一个指令按照不同的规则解释。

指令集包括 load、store、branch、alu、miscellaneous 和 return 等指令,支持的指令功能列表如下:

  Instruction      Addressing mode      Description

  ld               1, 2, 3, 4, 12       Load word into A
  ldi              4                    Load word into A
  ldh              1, 2                 Load half-word into A
  ldb              1, 2                 Load byte into A
  ldx              3, 4, 5, 12          Load word into X
  ldxi             4                    Load word into X
  ldxb             5                    Load byte into X

  st               3                    Store A into M[]
  stx              3                    Store X into M[]

  jmp              6                    Jump to label
  ja               6                    Jump to label
  jeq              7, 8, 9, 10          Jump on A == <x>
  jneq             9, 10                Jump on A != <x>
  jne              9, 10                Jump on A != <x>
  jlt              9, 10                Jump on A <  <x>
  jle              9, 10                Jump on A <= <x>
  jgt              7, 8, 9, 10          Jump on A >  <x>
  jge              7, 8, 9, 10          Jump on A >= <x>
  jset             7, 8, 9, 10          Jump on A &  <x>

  add              0, 4                 A + <x>
  sub              0, 4                 A - <x>
  mul              0, 4                 A * <x>
  div              0, 4                 A / <x>
  mod              0, 4                 A % <x>
  neg                                   !A
  and              0, 4                 A & <x>
  or               0, 4                 A | <x>
  xor              0, 4                 A ^ <x>
  lsh              0, 4                 A << <x>
  rsh              0, 4                 A >> <x>

  tax                                   Copy A into X
  txa                                   Copy X into A

  ret              4, 11                Return

中间一列用逗号隔开的数字代表不同指令支持的寻址类型,总共有 13 种,每一种的描述信息如下:

  Addressing mode  Syntax               Description

   0               x/%x                 Register X
   1               [k]                  BHW at byte offset k in the packet
   2               [x + k]              BHW at the offset X + k in the packet
   3               M[k]                 Word at offset k in M[]
   4               #k                   Literal value stored in k
   5               4*([k]&0xf)         Lower nibble * 4 at byte offset k in the packet
   6               L                    Jump label L
   7               #k,Lt,Lf             Jump to Lt if true, otherwise jump to Lf
   8               x/%x,Lt,Lf           Jump to Lt if true, otherwise jump to Lf
   9               #k,Lt                Jump to Lt if predicate is true
  10               x/%x,Lt              Jump to Lt if predicate is true
  11               a/%a                 Accumulator A
  12               extension            BPF extension

有了上面这些基础信息,下面我用一个实例具体将这些内容串联起来。

一个 tcpdump 过滤功能的实例

tcpdump 程序支持使用 -d 参数来 dump 出过滤规则转化后的 bpf 指令,下面是一个具体的示例,这一示例用来过滤端口号为 8080 的 tcp ipv4 报文。

$ sudo tcpdump -d 'ip and tcp port 8080'
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 12
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 12
(004) ldh      [20]
(005) jset     #0x1fff          jt 12	jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 14]
(008) jeq      #0x1f90          jt 11	jf 9
(009) ldh      [x + 16]
(010) jeq      #0x1f90          jt 11	jf 12
(011) ret      #262144
(012) ret      #0

这个例子选自《linux 内核观测技术 ebpf》第六章,它是个非常经典的例子,在 tcpdump 经典论文中也能够看到。

上述指令中,加载指令地址偏移量是从报文的 ether 头开始算的,有了这个认识再加上对以太网头部、IP 头部、TCP 头部字段的认识,不难理解上面这些指令。

我重点讲一下下面这几行指令的含义:

(004) ldh      [20]
(005) jset     #0x1fff          jt 12	jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 14]

偏移为 4 处的指令从报文起始偏移为 20 字节处加载一个半字(16-bit)到 A 寄存器中,这个内容表示的值为 IP 报文中 3 bits 的 Flags 与 13 bits 的 Fragment Offset 的值。

偏移为 5 处的指令将 A 寄存器值的低 13 位与 0x1fff 进行与运算并根据结果进行跳转,实现当 Fragment Offset 为 0 的时候才继续判断端口号非 0 则直接跳到 12 行

偏移为 6 处的指令比较难懂,它的功能是加载实际的 ip 头长度,[14] & 0xf 对应的是 ip 报文中 IHL 的内容。由于 IHL 单位为 4 字节因此对 IHL 的值乘了 4,就将真实的 ip 头长度字节数保存到了 X 寄存器中。

偏移为 7 处的指令以 X 寄存器中 ip 报文的长度为基础,加上 14 ( ether header 长度)得到 TCP 源端口的偏移量,并读取其值到 A 寄存器中。

总结

tcpdump 工具在定位网络问题中经常用到,可是我以前并不清楚其背后的一些细节。本篇文章中借 tcpdump 描述了 bpf 指令集并根据一个经典的过滤例子将这些指令串联了起来。

使用虚拟机器的形式解决包过滤问题是 tcpdump 的一大亮点,依赖 bpf 虚拟机tcpdump 实现了一套更高效的从内核捕获报文到用户态的架构。

bpf 是 ebpf 的基础,相较 ebpf 虚拟机而言它更简单,却与 ebpf 的原理有共同之处,在进军 ebpf 内部原理前,不妨先研究研究 bpf。

参考文档

The BSD Packet Filter: A New Architecture for User-level Packet Capture

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值