AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 699500
Accepted
Fajela Tajkiya
Fajela Tajkiya
Asked: 2022-04-19 14:07:25 +0800 CST2022-04-19 14:07:25 +0800 CST 2022-04-19 14:07:25 +0800 CST

BPF的理解

  • 772

当我需要使用 捕获一些数据包tcpdump时,我使用如下命令:

tcpdump -i eth0 "dst host 192.168.1.0"

我一直认为dst 主机 192.168.1.0部分称为 BPF,Berkeley Packet Filter。对我来说,这是一种过滤网络数据包的简单语言。但是今天我的室友告诉我,BPF 可以用来捕获性能信息。根据他的描述,它就像perfmonWindows上的工具。这是真的吗?它与我在问题开头提到的 BPF 相同吗?

tcpdump performance
  • 2 2 个回答
  • 946 Views

2 个回答

  • Voted
  1. Best Answer
    forest
    2022-04-19T14:58:03+08:002022-04-19T14:58:03+08:00

    什么是 BPF?

    BPF(或更常见的扩展版本,eBPF)是一种最初专门用于过滤数据包的语言,但它的功能远不止于此。正如您所指出的,在 Linux 上,它可以用于许多其他事情,包括用于安全性的系统调用过滤器和性能监控。虽然 Windows 确实添加了 eBPF 支持,但这不是 Windowsperfmon实用程序使用的。Windows 仅添加了对依赖于操作系统对 eBPF 支持的非 Windows 实用程序的兼容性支持。

    eBPF 程序不在用户空间中执行。相反,应用程序创建一个 eBPF 程序并将其发送到内核,内核将执行它。它实际上是以解释器的形式在内核中实现的虚拟处理器的机器代码,尽管它也可以使用JIT 编译来显着提高性能。该程序可以访问内核中的一些基本接口,包括与性能和网络相关的接口。eBPF 程序然后与内核通信以向其提供计算结果(例如丢弃数据包)。

    eBPF 程序的限制

    为了防止拒绝服务攻击或意外崩溃,内核在编译之前首先验证代码。在运行之前,代码需要经过几个重要的检查:

    • 对于非特权用户,该程序总共包含不超过4096条指令。

    • 除了有界循环和函数调用外,不能发生向后跳转。

    • 没有总是无法访问的指令。

    结果是验证者必须能够证明 eBPF 程序停止。当然,它还没有找到解决停止问题的方法,这就是为什么它只接受它知道会停止的程序的原因。为此,它将程序表示为有向无环图。除此之外,它还试图通过防止指针的实际值被泄露,同时仍然允许对其执行有限的操作来防止信息泄漏和越界内存访问:

    • 指针不能作为可检查的值进行比较、存储或返回。

    • 指针运算只能针对标量(不是从指针派生的值)进行。

    • 没有指针算法可以导致指向指定的内存映射之外。

    验证器相当复杂,而且做得更多,尽管它本身就是严重 安全 漏洞的根源,至少在没有为非特权用户禁用bpf(2)系统调用的情况下。

    查看代码

    该dst host 192.168.1.0命令的组件不是 BPF。这只是tcpdump. 但是,您给它的命令用于生成 BPF 程序,然后将其发送到内核。请注意,在这种情况下使用的不是 eBPF,而是较旧的 cBPF。两者之间有几个重要的区别(尽管内核在内部将 cBPF 转换为 eBPF)。该-d标志可用于查看要发送到内核的 cBPF 代码:

    # tcpdump -i eth0 "dst host 192.168.1.0" -d
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2    jf 4
    (002) ld       [30]
    (003) jeq      #0xc0a80100      jt 8    jf 9
    (004) jeq      #0x806           jt 6    jf 5
    (005) jeq      #0x8035          jt 6    jf 9
    (006) ld       [38]
    (007) jeq      #0xc0a80100      jt 8    jf 9
    (008) ret      #262144
    (009) ret      #0
    

    更复杂的过滤器会导致更复杂的字节码。尝试手册页中的一些示例并附加-d标志以查看将哪些字节码加载到内核中。为了了解如何阅读反汇编,请查看BPF 过滤器文档。如果你正在阅读 eBPF 程序,你应该看看虚拟 CPU 的eBPF 指令集。

    理解代码

    为简单起见,我假设您指定了 192.168.1.1 而不是 192.168.1.0 的目标 IP,并且只想匹配 IPv4,这会大大缩减代码,因为它不再需要处理 IPv6:

    # tcpdump -i eth0 "dst host 192.168.1.1 and ip" -d
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2    jf 5
    (002) ld       [30]
    (003) jeq      #0xc0a80101      jt 4    jf 5
    (004) ret      #262144
    (005) ret      #0
    

    让我们来看看上面的字节码实际上做了什么。每次在指定的接口上接收到数据包时,都会运行 BPF 字节码。数据包内容(包括以太网标头,如果适用)被放入 BPF 代码可以访问的缓冲区中。如果数据包与过滤器匹配,代码将返回捕获缓冲区的大小(默认为 262144 字节),否则返回 0。

    假设您正在运行此过滤器,并且它接收到一个数据包,该数据包从 192.168.1.142 向 192.168.1.1 发送带有空有效负载的 ICMP 消息。源 MAC 是 aa:aa:aa:aa:aa:aa,目标 MAC 是 bb:bb:bb:bb:bb:bb。以太网帧的十六进制内容为:

    aa aa aa aa aa aa bb bb bb bb bb bb 08 00 45 00
    00 1c 77 71 40 00 40 01 3f 92 c0 a8 01 8e c0 a8
    01 01 08 00 c1 c0 36 0e 00 01
    

    第一条指令是ldh [12]. 这会将位于数据包偏移 12 字节的半字(两个字节)加载到 A 寄存器中。这是值 0x0800(请记住,网络数据始终是大端的)。第二条指令是jeq #0x800,它将立即数与 A 寄存器中的值进行比较。如果它们相等,它将跳转到指令 2,否则跳转到 5。以太网帧中该偏移处的值 0x800 指定 IPv4 协议。因为比较结果为真,所以代码现在跳转到指令 2。如果负载不是 IPv4,它会跳转到 5。

    指令 2(第三条)是ld [30]. 这会将偏移 30 处的整个 4 字节字加载到 A 寄存器中。在我们的以太网帧中,这是 0xc0a80101。下一条指令jeq #0xc0a80101将立即数与 A 寄存器的内容进行比较,如果为真则跳转到 4,否则跳转到 5。该值是目标地址(0xc0a80101 是 192.168.1.1 的大端表示)。这些值确实匹配,因此程序计数器现在设置为 4。

    指令 4 是ret #262144。这将终止 BPF 程序并将整数 262144 返回给调用程序。这告诉调用程序,tcpdump在这种情况下,数据包已被过滤器捕获,因此它向内核请求数据包的内容,对其进行更彻底的解码,并将信息写入您的终端。如果目标地址与过滤器查找的内容不匹配,或者协议类型不是 IPv4,则代码​​将跳转到指令 5,在那里它会遇到ret #0. 这将在没有匹配的情况下终止过滤器。

    如果数据包中偏移量 12 处的半字是 0x800 并且偏移量 30 处的字是 0xc0a80101,则这只是返回 262144 的一种方式,否则返回 0。因为这一切都在内核中完成(可选地在被 JIT 引擎转换为本机代码之后),不需要昂贵的上下文切换或在内核空间和用户空间之间传递缓冲区,因此过滤器速度很快。

    更高级的例子

    BPF 代码不限于由tcpdump. 许多其他实用程序可以使用它。xt_bpf您甚至可以使用该模块创建带有 BPF 过滤器的 iptables 规则!但是,在生成字节码时必须小心,tcpdump -ddd因为它需要使用第 2 层标头,而 iptables 不会。要使它们兼容,您所要做的就是调整偏移量。

    此外,还提供了许多辅助函数,这些函数提供了通过读取原始数据包内容无法获得的信息,例如数据包长度、有效负载起始偏移量、接收数据包的 CPU、NetFilter 标记等。过滤器文档:

    Linux 内核也有几个 BPF 扩展,它们与加载指令类一起使用,方法是“重载”具有负偏移量 + 特定扩展偏移量的 k 参数。这种 BPF 扩展的结果被加载到 A 中。

    支持的 BPF 扩展有:

    扩大 描述
    连 skb->len
    原型 skb->协议
    类型 skb->pkt_type
    poff 有效载荷开始偏移
    ifidx skb->dev->ifindex
    拉 类型 X 的 Netlink 属性,偏移量 A
    局域网 类型 X 的嵌套 Netlink 属性,偏移量 A
    标记 skb->标记
    队列 skb->queue_mapping
    半型 skb->dev->类型
    rxhash skb->哈希
    中央处理器 raw_smp_processor_id()
    vlan_tci skb_vlan_tag_get(skb)
    vlan_avail skb_vlan_tag_present(skb)
    vlan_tpid skb->vlan_proto
    兰特 prandom_u32()

    例如,要匹配在 CPU 3 上接收到的所有数据包,您可以执行以下操作:

        ld #cpu
        jneq #3, drop
        ret #262144
    drop:
        ret #0
    

    请注意,这是使用与 兼容的 BPF 程序集语法bpf_asm,而此处的其他程序集清单正在使用tcpdump语法。主要区别在于前者的语法使用命名标签,而后者的 BPF 语法用行号标记每条指令。此程序集转换为以下字节码(逗号分隔指令):

    4,32 0 0 4294963236,21 0 1 1,6 0 0 262144,6 0 0 0,
    

    这可以与iptables使用xt_bpf模块一起使用:

    iptables -A INPUT -m bpf --bytecode "4,32 0 0 4294963236,21 0 1 1,6 0 0 262144,6 0 0 0," -j CPU3
    

    对于在该 CPU 上接收到的任何数据包,这将跳转到目标链CPU3。

    如果这看起来很强大,请记住这都是 cBPF。尽管 cBPF 在内部被翻译成 eBPF,但与原始 eBPF 所能做的相比,这一切都微不足道!

    了解更多信息

    我强烈建议您阅读这篇文章以了解如何tcpdump使用 cBPF。

    读完之后,请阅读这篇关于如何tcpdump将表达式转换为字节码的说明。

    如果您想了解有关它的所有内容,可以随时查看源代码!

    • 17
  2. Qeole
    2022-04-26T00:54:38+08:002022-04-26T00:54:38+08:00

    eBPF 程序不在用户空间中执行。相反,应用程序创建一个 eBPF 程序并将其发送到内核,内核将执行它。

    为了补充@forest 的好答案,我们也许可以详细说明这些程序是如何执行的。

    tcpdump 使用的 cBPF 有几个钩子:它可以附加到套接字,以便在数据包到达时运行(这就是 tcpdump 所做的,过滤在套接字上接收到的数据包,并仅将所需的数据包传递给用户空间),或者它们可以附加到seccomp 钩子上,以便对系统调用及其参数进行一些过滤。

    eBPF的重要特性之一是它可以附加到内核中更广泛的钩子选择(尽管它不做 seccomp)。对于网络,有套接字,还有TC(流量控制)钩子、XDP(用于快速网络的驱动程序级钩子)或其他一些。关于您的问题:程序也可以附加到内核中的跟踪点(某些特定函数上的预定义挂钩,例如内核中的系统调用或“重要”函数),或内核探针(kprobes),使它们能够跟踪内核中的任何函数(前提是它在编译时没有内联)。然后是其他类型存在,例如用于安全用例的 LSM。

    跟踪通常依靠跟踪点或 kprobes将 eBPF 程序附加到函数,并在内核中每次调用该函数时运行它。程序可以访问函数的参数或(如果它附加在出口处)返回值。通过使用映射、特殊的内核内存区域(例如数组或哈希映射),专用于在 eBPF 程序和/或用户空间之间共享数据,程序可以在连续运行之间收集指标或共享状态。

    例如,来自 BCC 的opensnoop将附加到open()和openat()系统调用的入口和出口处的跟踪点。在入口处,它收集正在打开的文件的路径,以及打开它的进程的 PID,并将其存储在哈希映射中。当系统调用退出时,第二个探测器收集返回值,并根据 PID 更新哈希映射中的相关条目。然后用户空间可以收集并转储哈希映射中的所有条目,以显示哪些文件已被哪些进程打开,以及返回值是什么。

    https://ebpf.io/是开始使用 eBPF 的好地方。

    • 3

相关问题

  • 为什么 `sync + drop_caches` 不删除缓存?

  • 为什么`strace`不显示这个过程正在等待什么?

  • 进程创建时间、shell脚本和系统调用开销

  • 使用 tcpdump 记录通过路由器服务器的所有网络活动

  • tcpdump --- 将数据包捕获到非旋转文件

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve