<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>ebpf | 伪架构师</title>
    <link>/tags/ebpf/</link>
      <atom:link href="/tags/ebpf/index.xml" rel="self" type="application/rss+xml" />
    <description>ebpf</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Sun, 26 Jun 2022 14:51:18 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>ebpf</title>
      <link>/tags/ebpf/</link>
    </image>
    
    <item>
      <title>介绍一个小工具：Inspektor Gadget</title>
      <link>/post/intro-k8s-gadget/</link>
      <pubDate>Sun, 26 Jun 2022 14:51:18 +0800</pubDate>
      <guid>/post/intro-k8s-gadget/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;各位好，今天 6 月 26 号，吃了么您呐。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;例行查看 krew index 的时候，发现有个新插件 &lt;code&gt;gadgit&lt;/code&gt;，翻翻来历，居然是 &lt;a href=&#34;https://kinvolk.io/&#34; target=&#34;_blank&#34;&gt;Kinvolk&lt;/a&gt; 的作品，公司不太出名，印象里最早做服务网格 Benchmark 的就是他。插件功能介绍很简单：&lt;code&gt;Collection of gadgets for Kubernetes developers&lt;/code&gt;，但是用法就很了不得了，非常有字数越小事越大的感觉：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;Available Commands:
  advise      Recommend system configurations based on collected information
  audit       Audit a subsystem
  completion  generate the autocompletion script for the specified shell
  deploy      Deploy Inspektor Gadget on the cluster
  help        Help about any command
  profile     Profile different subsystems
  snapshot    Take a snapshot of a subsystem and print it
  top         Gather, sort and periodically report events according to a given criteria
  trace       Trace and print system events
  traceloop   Get strace-like logs of a pod from the past
  undeploy    Undeploy Inspektor Gadget from cluster
  version     Show version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;过滤掉辅助功能，可以看到几个主要内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;advise：根据搜集信息，推荐系统配置内容&lt;/li&gt;
&lt;li&gt;audit：对子系统进行审计&lt;/li&gt;
&lt;li&gt;profile：对不同子系统进行侧写&lt;/li&gt;
&lt;li&gt;snapshot：给子系统进行快照并打印&lt;/li&gt;
&lt;li&gt;top：根据制定规则，搜集、排序和周期性地报告事件&lt;/li&gt;
&lt;li&gt;trace：跟踪和打印系统事件&lt;/li&gt;
&lt;li&gt;traceloop：获取类似 strace 格式的历史日志&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其实说了跟没说一样是不是？不如一条条看过去了。&lt;/p&gt;

&lt;h2 id=&#34;部署&#34;&gt;部署&lt;/h2&gt;

&lt;p&gt;首先使用 krew 安装这个插件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl krew install gadget
Updated the local copy of plugin index.
Installing plugin: gadget
...
 | Use this plugin:
 |      kubectl gadget
...
 |  | $ kubectl gadget deploy | kubectl apply -f -
...
WARNING: You installed plugin &amp;quot;gadget&amp;quot; from the krew-index plugin repository.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上文可以看到，使用插件之前要安装到及群里 &lt;code&gt;kubectl gadget deploy | kubectl apply -f -&lt;/code&gt;，可以看到，除了 RBAC 内容之外，还有 Daemonset 和 CRD 这两个东西。为了跟踪 Pod 行为，Inspektor Gadget 把 BPF 程序附加到内核函数上，当函数被执行时，内核也会运行这些被注入的程序。因此，BPF 程序需要检测触发该函数的系统调用，是否来自 Inspektor Gadget 的追踪目标。为了做到这一点，程序在包含要追踪的 Pod 列表的 BPF Map 中查找当前的 cgroup id，如果没有找到，程序会提前退出。最后，BPF程序收集要追踪的信息，例如，系统调用参数，并将它们 Ring Buffer 或 BPF Map。Inspektor Gadget 的用户空间工具在 Ring Buffer 或 BPF 地图上监听或读取，并获取新的事件。追踪结束后，BPF 程序将会被删除。&lt;/p&gt;

&lt;h2 id=&#34;network-policy-advise&#34;&gt;Network Policy Advise&lt;/h2&gt;

&lt;p&gt;这个功能由 Monitor 和 Report 两个部分构成，分别是启动特定命名空间内工作负载的网络监听，生成跟踪记录；以及根据跟踪记录生成网络策略两部分，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise network-policy monitor  --output /tmp/result.txt
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v&amp;quot; ready.
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc&amp;quot; ready.
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-nj0k&amp;quot; ready.
^C
Stopping...

$ more /tmp/result.txt
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;connect&amp;quot;,&amp;quot;remote_kind&amp;quot;:&amp;quot;pod&amp;quot;,&amp;quot;port&amp;quot;:2021,&amp;quot;local_pod_namespace&amp;quot;:&amp;quot;gadget&amp;quot;,&amp;quot;local_pod_name&amp;quot;:&amp;quot;gadget-dzb7g&amp;quot;,&amp;quot;local_pod_labels&amp;quot;:{&amp;quot;controller-revision-hash&amp;quot;:&amp;quot;8f55cc94f&amp;quot;,&amp;quot;k8s-app&amp;quot;:&amp;quot;gadget&amp;quot;,&amp;quot;pod-template-generation&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;remote_pod_namespace&amp;quot;:&amp;quot;kube-system&amp;quot;,&amp;quot;remote_pod_name&amp;quot;:&amp;quot;pdcsi-node-lpqln&amp;quot;,&amp;quot;remote_pod_labels&amp;quot;:{&amp;quot;controller-revision-hash&amp;quot;:&amp;quot;69cdc7c487&amp;quot;,&amp;quot;k8s-app&amp;quot;:&amp;quot;gcp-compute-persistent-disk-csi-driver&amp;quot;,&amp;quot;pod-template-generation&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;debug&amp;quot;:&amp;quot;4649087588182 cpu#1 connect 3293 otelsvc 10.138.15.229:33032 10.138.15.229:2021 4026531992\n&amp;quot;}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行一段时间后使用 Ctrl+C 终止命令，可以看到指定的输出文件中包含了一堆类似 JSON 的记录内容，可以用这个文件生成网络策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise network-policy report --input=/tmp/result.txt
...
      podSelector:
        matchLabels:
          k8s-app: konnectivity-agent
    ports:
    - port: 10250
      protocol: TCP
  podSelector:
    matchLabels:
      k8s-app: gadget
  policyTypes:
  - Ingress
  - Egress
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，网络策略已经生成。&lt;/p&gt;

&lt;h2 id=&#34;seccomp-profile-advise&#34;&gt;Seccomp Profile Advise&lt;/h2&gt;

&lt;p&gt;这一功能是用 &lt;code&gt;advise seccomp-profile&lt;/code&gt; 模块完成的，这个模块有三个子命令，分别是 &lt;code&gt;start&lt;/code&gt;、&lt;code&gt;list&lt;/code&gt; 和 &lt;code&gt;stop&lt;/code&gt;，例如要跟踪一个 Calico Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise seccomp-profile start --podname=calico-node-t6hwg
HAmaTrPcxTLDNfSo
$ kubectl gadget advise seccomp-profile list
NAMESPACE      NODE(S)                                                                                                                         POD                  CONTAINER    TRACEID
kube-system    gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc,gke-gcp-vlab-k8s-default-pool-d3fe3442-nj0k,gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v    calico-node-t6hwg                 HAmaTrPcxTLDNfSo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面 &lt;code&gt;start&lt;/code&gt; 命令执行后出现的 &lt;code&gt;HAmaTrPcxTLDNfSo&lt;/code&gt; 就是跟踪 ID，开始一段时间之后，可以调用 &lt;code&gt;stop&lt;/code&gt; 命令结束跟踪，跟踪结束后会显示这个 Pod 的 Seccomp：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl gadget advise seccomp-profile stop HAmaTrPcxTLDNfSo
{
  &amp;quot;defaultAction&amp;quot;: &amp;quot;SCMP_ACT_ERRNO&amp;quot;,
  &amp;quot;architectures&amp;quot;: [
    &amp;quot;SCMP_ARCH_X86_64&amp;quot;,
    &amp;quot;SCMP_ARCH_X86&amp;quot;,
    &amp;quot;SCMP_ARCH_X32&amp;quot;
  ],
  &amp;quot;syscalls&amp;quot;: [
    {
      &amp;quot;names&amp;quot;: [
        &amp;quot;accept4&amp;quot;,
        &amp;quot;access&amp;quot;,
        &amp;quot;arch_prctl&amp;quot;,
...
 ],
      &amp;quot;action&amp;quot;: &amp;quot;SCMP_ACT_ALLOW&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;profile&#34;&gt;Profile&lt;/h2&gt;

&lt;p&gt;这个模块包括 &lt;code&gt;block-io&lt;/code&gt; 和 &lt;code&gt;cpu&lt;/code&gt; 两个指令，例如监控某个节点的 &lt;code&gt;block-io&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt; kubectl gadget profile block-io --node=gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc
Tracing block device I/O... Hit Ctrl-C to end.^C

     usecs               : count     distribution
         0 -&amp;gt; 1          : 0        |                                        |
         2 -&amp;gt; 3          : 0        |                                        |
         4 -&amp;gt; 7          : 0        |                                        |
         8 -&amp;gt; 15         : 0        |                                        |
        16 -&amp;gt; 31         : 0        |                                        |
        32 -&amp;gt; 63         : 0        |                                        |
        64 -&amp;gt; 127        : 1        |                                        |
       128 -&amp;gt; 255        : 1        |                                        |
       256 -&amp;gt; 511        : 0        |                                        |
       512 -&amp;gt; 1023       : 2        |                                        |
      1024 -&amp;gt; 2047       : 54       |****************                        |
      2048 -&amp;gt; 4095       : 44       |*************                           |
      4096 -&amp;gt; 8191       : 49       |***************                         |
      8192 -&amp;gt; 16383      : 128      |****************************************|
     16384 -&amp;gt; 32767      : 118      |************************************    |
     32768 -&amp;gt; 65535      : 11       |***                                     |
     65536 -&amp;gt; 131071     : 5        |*                                       |
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到以微秒为单位的统计记录和分布情况。&lt;code&gt;cpu&lt;/code&gt; 子命令的用法如下，其中 &lt;code&gt;-K&lt;/code&gt; 开关意思是只关注内核空间的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl gadget profile cpu -p calico-node-t6hwg -K
Capturing stack traces... Hit Ctrl-C to end.^C

calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;ksys_write;vfs_write;pipe_write;__wake_up_sync_key;_raw_spin_unlock_irqrestore;_raw_spin_unlock_irqrestore 1
calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;ksys_read;vfs_read;pipe_read;anon_pipe_buf_release;anon_pipe_buf_release 1
ip 1
calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;__se_sys_nanosleep;get_timespec64;_copy_from_user;copy_user_generic_unrolled;copy_user_generic_unrolled 1
calico-node 9
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;snapshot&#34;&gt;Snapshot&lt;/h2&gt;

&lt;p&gt;Snapshot 模块分为 &lt;code&gt;process&lt;/code&gt; 和 &lt;code&gt;socket&lt;/code&gt; 两个子命令，分别用于记录进程和网络。（&lt;code&gt;v0.5.1&lt;/code&gt; 版本的 &lt;code&gt;process&lt;/code&gt; 子命令好像无法工作）。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget snapshot socket \  
    --node=gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v \
    -o custom-columns=namespace,pod,protocol,status
NAMESPACE       POD             PROTOCOL STATUS
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;top&#34;&gt;Top&lt;/h2&gt;

&lt;p&gt;这个模块有三个子命令，&lt;code&gt;block-io&lt;/code&gt;、&lt;code&gt;tcp&lt;/code&gt; 和 &lt;code&gt;file&lt;/code&gt;，跟 Linux 系统的 &lt;code&gt;top&lt;/code&gt; 命令类似，例如下面的命令列出的 top file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget top file \
    -o custom-columns=container,pid,comm,reads
CONTAINER        PID     COMM             READS
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
gke-metrics-agent 56606   otelsvc          2
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
gke-metrics-agent 56606   otelsvc          2
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;trace&#34;&gt;Trace&lt;/h2&gt;

&lt;p&gt;这个模块针对系统事件进行跟踪，目前支持包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bind：Scoket 绑定&lt;/li&gt;
&lt;li&gt;capabilities：Capability 检查&lt;/li&gt;
&lt;li&gt;dns：DNS 请求&lt;/li&gt;
&lt;li&gt;exec：新进程&lt;/li&gt;
&lt;li&gt;fsslower：&lt;code&gt;open&lt;/code&gt;、&lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt; 和 &lt;code&gt;fsync&lt;/code&gt; 操作时长超过阈值&lt;/li&gt;
&lt;li&gt;mount：&lt;code&gt;mount&lt;/code&gt; 和 &lt;code&gt;umount&lt;/code&gt; 操作&lt;/li&gt;
&lt;li&gt;oomkill：OOM Killer 被触发&lt;/li&gt;
&lt;li&gt;open：&lt;code&gt;open&lt;/code&gt; 系统调用&lt;/li&gt;
&lt;li&gt;signal：跟踪进程收到的信号&lt;/li&gt;
&lt;li&gt;sni: TLS 请求中的 SNI&lt;/li&gt;
&lt;li&gt;tcp：TCP 的 &lt;code&gt;connect&lt;/code&gt;、&lt;code&gt;accept&lt;/code&gt; 和 &lt;code&gt;close&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tcpconnect：&lt;code&gt;connect&lt;/code&gt; 调用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如对 &lt;code&gt;open&lt;/code&gt; 的跟踪：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget trace open -o custom-columns=container,path

CONTAINER        PATH
fluentbit        /var/log/containers
fluentbit        /var/log/pods
fluentbit        /var/log/containers
fluentbit        /var/log/pods
fluentbit        /var/run/google-fluentbit/pos-files
...
csi-driver-registrar /usr/bin/runc
csi-driver-registrar /sys/kernel/mm/hugepages
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;然后&#34;&gt;然后&lt;/h2&gt;

&lt;p&gt;以后没 eBPF 支持连 Ops 都不好做了？&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>eBPF 概念和基本原理</title>
      <link>/post/what-is-ebpf/</link>
      <pubDate>Fri, 13 Nov 2020 21:16:35 +0800</pubDate>
      <guid>/post/what-is-ebpf/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://gravitational.com/blog/what-is-ebpf/&#34; target=&#34;_blank&#34;&gt;What is eBPF and How Does it Work?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/virag-mody-650974a9&#34; target=&#34;_blank&#34;&gt;Virag Mody&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;大约一年前，有个朋友想要用 Rust 开发一个 &lt;a href=&#34;https://medium.com/mycrypto/the-ethereum-virtual-machine-how-does-it-work-9abac2b7c9e&#34; target=&#34;_blank&#34;&gt;EVM Assembler&lt;/a&gt;。在他的一再要求之下，我开始帮忙编写单元测试。那时候我还不大了解操作系统的相关知识，只好开始学习一些语法和词法方面的东西。很快这个事情就无以为继了，然而我对操作系统有了一些整体了解。之后他对 eBPF 赞赏有加时，我觉得我的春天又来了。&lt;/p&gt;

&lt;p&gt;eBPF 的门槛有点高，有 500 字的小品，也有 &lt;a href=&#34;https://cilium.io/&#34; target=&#34;_blank&#34;&gt;Cilium&lt;/a&gt; 铺天盖地的文档。我编写本文的目的，是针对这一新技术读者提供一个全面的入门介绍，用户可以以此为基础，进行进一步的探索。后续可以阅读 &lt;a href=&#34;https://lwn.net/Articles/740157/&#34; target=&#34;_blank&#34;&gt;Linux Weekly News&lt;/a&gt;、&lt;a href=&#34;http://www.brendangregg.com/index.html&#34; target=&#34;_blank&#34;&gt;Brenden Gregg 的网站&lt;/a&gt; 以及 &lt;a href=&#34;https://docs.cilium.io/en/stable/bpf/&#34; target=&#34;_blank&#34;&gt;Cilium 文档&lt;/a&gt;学习更多相关知识。本文设计的内容包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eBPF 的用处&lt;/li&gt;
&lt;li&gt;eBPF 的原理&lt;/li&gt;
&lt;li&gt;eBPF 的实例&lt;/li&gt;
&lt;li&gt;如何开始使用 eBPF&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;ebpf-的用处&#34;&gt;eBPF 的用处&lt;/h2&gt;

&lt;p&gt;有了 eBPF，无需修改内核，也不用加载内核模块，程序员也能在内核中执行自定义的字节码。eBPF 和内核紧密联系，下面先介绍一些相关的基本概念。&lt;/p&gt;

&lt;p&gt;Linux 系统分为内核空间和用户空间。内核空间是操作系统的核心，对所有硬件都具备不受限制的完整的访问能力，例如内存、存储以及 CPU 等。内核既然具备了这样的超级权限，势必需要严加保护，仅允许运行最可靠的代码。而用户空间运行的就是非内核的进程——例如 I/O、文件系统等。这些进程仅能通过内核开放的系统调用，对硬件进行有限的访问。换句话说，用户空间的程序一定要经过内核空间的过滤。&lt;/p&gt;

&lt;p&gt;系统调用接口能够满足绝大多数需要，开发者在面对新的硬件、文件系统、网络协议甚至自定义的系统调用时，还是需要更多的弹性的。在不修改内核源码的情况下，用户代码要直接访问硬件怎么办呢？可以使用 &lt;a href=&#34;https://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html&#34; target=&#34;_blank&#34;&gt;Linux 内核模块（LKM）&lt;/a&gt;。用户空间一般是需要通过系统调用来访问内核空间，而 LKM 是直接加载到内核的，是内核的一部分。LKM 最有价值的特点之一，就是可以在运行时加载，不用编译内核也不用重启机器。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;image/what-is-ebpf-1.png&#34; alt=&#34;what is ebpf 1&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图 1：LKM 的动态加载和卸载&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LKM 非常有用，但是也引入了很多风险。内核和用户空间不同，要进行不同的安全考量。内核空间是为了操作系统内核这样的特权代码准备的。系统调用连接了内核和用户空间，让用户空间能够对硬件进行合适的操作。换个说法，LKM 是能够让内核崩溃的。模块和内核的紧密关系，使得安全和升级成本直线升高。&lt;/p&gt;

&lt;h3 id=&#34;ebpf-是什么&#34;&gt;eBPF 是什么&lt;/h3&gt;

&lt;p&gt;eBPF 是一个用于访问 Linux 内核服务和硬件的新方法。这一新技术已经用于网络、出错、跟踪以及防火墙等方面。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dtrace&lt;/code&gt; 是一个 Solaris 和 BSD 操作系统上的动态跟踪工具，eBPF 受到 &lt;code&gt;dtrace&lt;/code&gt; 的启发，原意是设计一个更好的 Linux 跟踪工具。跟 &lt;code&gt;dtrace&lt;/code&gt; 不同的是，Linux 无法获取运行中系统的鸟瞰视图，它被系统调用、库调用以及函数所限制。一小撮工程师在 Berkeley Packet Filter（BPF）基础之上，构建一个内核虚拟机级别的包过滤机制，提供了类似 &lt;code&gt;dtrace&lt;/code&gt; 的功能。2014 年第一个版本适配了 Linux 3.18，提供的功能相对较少。要使用完整的 eBPF，需要 Linux 4.4 或以上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/what-is-ebpf-2.png&#34; alt=&#34;what is ebpf 2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;上图对 eBPF 架构进行了一个简单的展示。eBPF 程序需要满足一系列的需求，才能被加载到内核。&lt;a href=&#34;https://github.com/torvalds/linux/blob/master/kernel/bpf/verifier.c&#34; target=&#34;_blank&#34;&gt;Verifier&lt;/a&gt; 中有一万多行代码用来对 eBPF 程序进行检查。Verifier 会遍历对 eBPF 程序在内核中可能的执行路径进行遍历，确保程序能够在不出现导致内核锁定的循环的情况下运行完成。除此之外还有其它必须满足的检查，例如有效的寄存器状态、程序大小以及越界等。安全控制方面，eBPF 和 LKM 是颇有差异的。&lt;/p&gt;

&lt;p&gt;如果所有的检查都通过了，eBPF 程序被加载并编译到内核中，并监听特定的信号。该信号以事件的形式出现，会被传递给被加载的 eBPF 程序。一旦被触发，字节码就会根据其中的指令执行并收集信息。&lt;/p&gt;

&lt;p&gt;所以 eBPF 到底做了什么？程序员能够在不增加或者修改内核代码的情况下，就能够在 Linux 内核中执行自定义的字节码。虽说还远不能整体取代 LKM，eBPF 程序可以自定义代码来和受保护的硬件资源进行交互，对内核的威胁最小。&lt;/p&gt;

&lt;h2 id=&#34;ebpf-的机制&#34;&gt;eBPF 的机制&lt;/h2&gt;

&lt;p&gt;前面介绍了 eBPF 的基础架构。这些能力是由多个组件协同实现的，每一种都有自己的复杂度。&lt;/p&gt;

&lt;h3 id=&#34;ebpf-程序剖析&#34;&gt;eBPF 程序剖析&lt;/h3&gt;

&lt;h4 id=&#34;事件和钩子&#34;&gt;事件和钩子&lt;/h4&gt;

&lt;p&gt;eBPF 程序是在内核中被事件触发的。在一些特定的指令被执行时时，这些事件会在钩子处被捕获。钩子被触发就会执行 eBPF 程序，对数据进行捕获和操作。钩子定位的多样性正是 eBPF 的闪光点之一。例如下面几种：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;系统调用：当用户空间程序通过系统调用执行内核功能时。&lt;/li&gt;
&lt;li&gt;功能的进入和退出：在函数退出之前拦截调用。&lt;/li&gt;
&lt;li&gt;网络事件：当接收到数据包时。&lt;/li&gt;
&lt;li&gt;kprobe 和 uprobe：挂接到内核或用户函数中。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;辅助函数&#34;&gt;辅助函数&lt;/h4&gt;

&lt;p&gt;eBPF 程序被触发时，会调用辅助函数。这些特别的函数让 eBPF 能够有访问内存的丰富功能。例如 Helper 能够执行一系列的任务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在数据表中对键值对进行搜索、更新以及删除。&lt;/li&gt;
&lt;li&gt;生成伪随机数。&lt;/li&gt;
&lt;li&gt;搜集和标记隧道元数据。&lt;/li&gt;
&lt;li&gt;把 eBPF 程序连接起来，这个功能被称为 &lt;code&gt;tail call&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;执行 Socket 相关任务，例如绑定、获取 Cookie、数据包重定向等。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些助手函数必须是内核定义的，换句话说，eBPF 程序的调用能力是受到一个白名单限制的。这个&lt;a href=&#34;https://man7.org/linux/man-pages/man7/bpf-helpers.7.html&#34; target=&#34;_blank&#34;&gt;名单&lt;/a&gt;很长，并且还在持续增长之中。&lt;/p&gt;

&lt;h4 id=&#34;map&#34;&gt;Map&lt;/h4&gt;

&lt;p&gt;要在 eBPF 程序和内核以及用户空间之间存储和共享数据，eBPF 需要使用 Map。正如其名，Map 是一种键值对。Map 能够支持多种数据结构，eBPF 程序能够通过辅助函数在 Map 中发送和接收数据。&lt;/p&gt;

&lt;h3 id=&#34;执行-ebpf-程序&#34;&gt;执行 eBPF 程序&lt;/h3&gt;

&lt;h4 id=&#34;加载和校验&#34;&gt;加载和校验&lt;/h4&gt;

&lt;p&gt;所有 eBPF 程序都是以字节码的形式执行的，因此需要有办法把高级语言编译成这种字节码。eBPF 使用 &lt;a href=&#34;https://llvm.org/&#34; target=&#34;_blank&#34;&gt;LLVM&lt;/a&gt; 作为后端，前端可以介入任何语言。因为 eBPF 使用 C 编写的，所以前端使用的是 &lt;a href=&#34;https://clang.llvm.org/&#34; target=&#34;_blank&#34;&gt;Clang&lt;/a&gt;。但在字节码被 Hook 之前，必须通过一系列的检查。在一个类似虚拟机的环境下用&lt;a href=&#34;https://elixir.bootlin.com/linux/latest/source/kernel/bpf/verifier.c&#34; target=&#34;_blank&#34;&gt;内核 Verifier&lt;/a&gt;阻止带有循环、权限不正确或者导致崩溃的程序运行。如果程序通过了所有的检查，字节码会使用 &lt;code&gt;bpf()&lt;/code&gt; 系统调用被载入到 Hook 上。&lt;/p&gt;

&lt;h4 id=&#34;jit-编译器&#34;&gt;JIT 编译器&lt;/h4&gt;

&lt;p&gt;校验结束后，eBPF 字节码会被 JIT 编译器转译成本地机器码。eBPF 是 64 位编码，共有 11 个寄存器，因此 eBPF 和 x86、ARM 以及 arm64 等硬件都能紧密对接。虽然 eBPF 受到 VM 限制，JIT 过程保障了它的运行性能。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/what-is-ebpf-3.png&#34; alt=&#34;what-is-ebpf-3.png&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;总结&#34;&gt;总结&lt;/h3&gt;

&lt;p&gt;上面的概念们放在一起，eBPF 程序通过安全检查后插入钩子，被事件触发之后，程序会启动执行，用辅助函数和 Map 来对数据进行存储和操作。下一届我们来研究一下它们的协同方式。&lt;/p&gt;

&lt;h2 id=&#34;一个例子&#34;&gt;一个例子&lt;/h2&gt;

&lt;p&gt;在 Gravitational 有一个叫做 Teleport 的开源项目，其中使用了 eBPF 程序进行跟踪和网络操作。有的组织希望知道 SSH 会话中发生了什么，&lt;a href=&#34;https://gravitational.com/teleport&#34; target=&#34;_blank&#34;&gt;Teleport&lt;/a&gt; 提供 SSH 访问途径的同时，加入了对用户行为的记录。可以通过对命令编码、在 Shell 脚本中运行命令的方式来进行混淆，从而阻碍对会话的记录。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://gravitational.com/blog/teleport-release-4-2&#34; target=&#34;_blank&#34;&gt;Teleport 4.2&lt;/a&gt; 中，我们引入了会话记录，其中用了三个 eBPF 程序来获取 SSH 会话，并将其转化为结构化的事件。&lt;/p&gt;

&lt;p&gt;例如 &lt;code&gt;echo Y3VybCBodHRwOi8vd3d3LmV4YW1wbGUuY29tCg== | base64 --decode | sh&lt;/code&gt;，我们能够在终端抓取到这个命令，但是这并无意义，用户已经对命令进行了混淆，但是有了 eBPF，我们就能知道，用户试图隐藏的是 &lt;code&gt;curl&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;event&amp;quot;: &amp;quot;session.command&amp;quot;,
  &amp;quot;path&amp;quot;: &amp;quot;/bin/sh&amp;quot;,
  &amp;quot;program&amp;quot;: &amp;quot;sh&amp;quot;,
  &amp;quot;argv&amp;quot;: [],
  &amp;quot;login&amp;quot;: &amp;quot;centos&amp;quot;,
  &amp;quot;user&amp;quot;: &amp;quot;jsmith&amp;quot;
}
{
  &amp;quot;event&amp;quot;: &amp;quot;session.command&amp;quot;,
  &amp;quot;path&amp;quot;: &amp;quot;/bin/base64&amp;quot;,
  &amp;quot;program&amp;quot;: &amp;quot;base64&amp;quot;,
  &amp;quot;argv&amp;quot;: [
    &amp;quot;--decode&amp;quot;
  ],
  &amp;quot;login&amp;quot;: &amp;quot;centos&amp;quot;,
  &amp;quot;user&amp;quot;: &amp;quot;jsmith&amp;quot;
}
{
  &amp;quot;event&amp;quot;: &amp;quot;session.command&amp;quot;,
  &amp;quot;path&amp;quot;: &amp;quot;/bin/curl&amp;quot;,
    &amp;quot;argv&amp;quot;: [
    &amp;quot;http://www.example.com&amp;quot;
  ],
  &amp;quot;program&amp;quot;: &amp;quot;curl&amp;quot;,
  &amp;quot;return_code&amp;quot;: 0,
  &amp;quot;login&amp;quot;: &amp;quot;centos&amp;quot;,
  &amp;quot;user&amp;quot;: &amp;quot;jsmith&amp;quot;
}
{
  &amp;quot;event&amp;quot;: &amp;quot;session.network&amp;quot;,
  &amp;quot;program&amp;quot;: &amp;quot;curl&amp;quot;,
  &amp;quot;src_addr&amp;quot;: &amp;quot;172.31.43.104&amp;quot;,
  &amp;quot;dst_addr&amp;quot;: &amp;quot;93.184.216.34&amp;quot;,
  &amp;quot;dst_port&amp;quot;: 80,
  &amp;quot;login&amp;quot;: &amp;quot;centos&amp;quot;,
  &amp;quot;user&amp;quot;: &amp;quot;jsmith&amp;quot;,
  &amp;quot;version&amp;quot;: 4
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;借助 eBPF 的能力，我们把这种混淆行为转换为事件流，便于导出和分析。Teleport 用 &lt;code&gt;execsnoop&lt;/code&gt;、&lt;code&gt;opensnoop&lt;/code&gt; 以及 &lt;code&gt;tcpconnect&lt;/code&gt; 来恢复这些事件。特别会关注的是 &lt;code&gt;tcpconnect&lt;/code&gt;，它最终将信息以 JSON 的形式返回来。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/what-is-ebpf-4.png&#34; alt=&#34;what-is-ebpf-4.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tcpconnect&lt;/code&gt; 会跟踪 TCP 连接。像 Teleport 这样用 SSH 证书管理访问的工具来说，必须要知道 TCP 连接的发起情况。&lt;code&gt;tcpconnect&lt;/code&gt; 能跟踪 &lt;code&gt;connect()&lt;/code&gt; 系统调用，该调用会在 Socket 上初始化一个连接。要对这个情况进行跟踪，tcpconnect 在内核中插入了一个 &lt;code&gt;kprobe&lt;/code&gt;，能够动态进入任何例程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-C&#34;&gt;# initialize
BPF b = BPF(text=bpf_text) b.attach_kprobe(event=&amp;quot;tcp_v4_connect&amp;quot;, fn_name=&amp;quot;trace_connect_entry&amp;quot;) b.attach_kretprobe(event=&amp;quot;tcp_v4_connect&amp;quot;, fn_name=&amp;quot;trace_connect_v4_return&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;程序被触发以后，&lt;code&gt;tcpconnect&lt;/code&gt; 会开始输出信息，下表展示的就是这样的信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ ./tcpconnect
PID   COMM  SADDR             DADDR           DPORT
-----------------------------------------------------
2315  curl  172.31.43.104     93.184.216.34   80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所有这些数据都是用辅助函数收集而来。如果看看 &lt;a href=&#34;https://github.com/iovisor/bcc/blob/ec3747ed6b16f9eec36a204dfbe3506d3778dcb4/tools/tcpconnect.py&#34; target=&#34;_blank&#34;&gt;Python 代码&lt;/a&gt;，会发现 &lt;code&gt;tcpconnect&lt;/code&gt; 试用了来自 &lt;a href=&#34;https://github.com/iovisor/bcc/blob/master/src/cc/export/helpers.h&#34; target=&#34;_blank&#34;&gt;bcc 的 BPF 库&lt;/a&gt;的辅助函数来对上述输出内容进行格式化。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;...
struct ipv4_data_t data4 = {.pid = pid, .ip = ipver}; 
data4.saddr = skp-&amp;gt;__sk_common.skc_rcv_saddr; 
data4.daddr = skp-&amp;gt;__sk_common.skc_daddr; 
data4.dport = ntohs(dport); 
bpf_get_current_comm(&amp;amp;data4.task, sizeof(data4.task));
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;ebpf-入门&#34;&gt;eBPF 入门&lt;/h2&gt;

&lt;p&gt;行文至此，我希望读者已经对 eBPF 有了一个最基础的了解，知道了他的重要性以及基本原理。是时候浏览更多技术文档和文章了。本文中提供了不少链接，不过这里最推荐的是 &lt;a href=&#34;https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/&#34; target=&#34;_blank&#34;&gt;Quinten Monnet&lt;/a&gt; 的博客。&lt;/p&gt;

&lt;p&gt;自行编写代码，开发自己的 eBPF 可能有点难。但是很多开源的开发工具链正在涌现，简化了很多 eBPF 的相关场景。下面介绍几个最流行的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/iovisor/bcc&#34; target=&#34;_blank&#34;&gt;BCC&lt;/a&gt;：BCC 是一个工具包用于创建高效的内核跟踪和处理程序，并包含了很多有用的工具和示例。BCC 简化了 BPF 程序的开发，内核指令使用 C 指令（包含了 LLVM 的封装），前端使用的是 Python 和 LUA。BCC 有很多用途，例如性能分析和网络流量控制。BCC 还为其它程序提供了 API。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/ajor/bpftrace&#34; target=&#34;_blank&#34;&gt;bpftrace&lt;/a&gt;：BPFtrace 是一个高级跟踪语言，用 LLVM 作为后端把脚本编译为 BPF 字节码，并用 BCC 和 Linux BPF 系统进行交互，并支持现有的 Linux 跟踪能力：kprobe、uprobe 以及 &lt;code&gt;tracepoint&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/iovisor/gobpf&#34; target=&#34;_blank&#34;&gt;Go&lt;/a&gt;、&lt;a href=&#34;https://github.com/libbpf/libbpf&#34; target=&#34;_blank&#34;&gt;C/C++&lt;/a&gt; 以及 &lt;a href=&#34;https://github.com/redsift/redbpf&#34; target=&#34;_blank&#34;&gt;Rust&lt;/a&gt; 的通用库。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;eBPF 还是个很新鲜的技术，让程序员在不修改内核空间的情况下，能够在内核中执行自定义的字节码并从内核函数中获取更多信息。原本这些目标需要通过系统调用或内核模块来完成，eBPF 降低了所需的复杂度和危险性。简单来说，eBPF 的工作流程：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;把 eBPF 程序编译成字节码。&lt;/li&gt;
&lt;li&gt;在载入到 Hook 之前，在虚拟机中对程序进行校验。&lt;/li&gt;
&lt;li&gt;把程序附加到内核之中，被特定事件触发。&lt;/li&gt;
&lt;li&gt;JIT 编译。&lt;/li&gt;
&lt;li&gt;在程序被触发时，调用辅助函数处理数据。&lt;/li&gt;
&lt;li&gt;在用户空间和内核空间之间使用键值对共享数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;推荐阅读&#34;&gt;推荐阅读&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gravitational.com/blog/scp-familiar-simple-insecure-slow/&#34; target=&#34;_blank&#34;&gt;SCP - Familiar, Simple, Insecure, and Slow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gravitational.com/blog/teleport-discovery-protocol/&#34; target=&#34;_blank&#34;&gt;Greed is Good: Creating Teleport’s Discovery Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gravitational.com/blog/golang-ssh-bastion-graceful-restarts/&#34; target=&#34;_blank&#34;&gt;Gracefully Restarting a Go Program Without Downtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;相关链接&#34;&gt;相关链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is eBPF and How Does it Work?&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/blog/what-is-ebpf/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virag Mody&lt;/strong&gt;：&lt;code&gt;https://www.linkedin.com/in/virag-mody-650974a9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EVM Assembler&lt;/strong&gt;：&lt;code&gt;https://medium.com/mycrypto/the-ethereum-virtual-machine-how-does-it-work-9abac2b7c9e&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cilium&lt;/strong&gt;：&lt;code&gt;https://cilium.io/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux Weekly News&lt;/strong&gt;：&lt;code&gt;https://lwn.net/Articles/740157/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brenden Gregg 的网站&lt;/strong&gt;：&lt;code&gt;http://www.brendangregg.com/index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cilium 文档&lt;/strong&gt;：&lt;code&gt;https://docs.cilium.io/en/stable/bpf/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux 内核模块（LKM）&lt;/strong&gt;：&lt;code&gt;https://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;what is ebpf 1&lt;/strong&gt;：&lt;code&gt;image/what-is-ebpf-1.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;what is ebpf 2&lt;/strong&gt;：&lt;code&gt;images/what-is-ebpf-2.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verifier&lt;/strong&gt;：&lt;code&gt;https://github.com/torvalds/linux/blob/master/kernel/bpf/verifier.c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;名单&lt;/strong&gt;：&lt;code&gt;https://man7.org/linux/man-pages/man7/bpf-helpers.7.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLVM&lt;/strong&gt;：&lt;code&gt;https://llvm.org/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clang&lt;/strong&gt;：&lt;code&gt;https://clang.llvm.org/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核 Verifier&lt;/strong&gt;：&lt;code&gt;https://elixir.bootlin.com/linux/latest/source/kernel/bpf/verifier.c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;what-is-ebpf-3.png&lt;/strong&gt;：&lt;code&gt;images/what-is-ebpf-3.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teleport&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/teleport&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teleport 4.2&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/blog/teleport-release-4-2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;what-is-ebpf-4.png&lt;/strong&gt;：&lt;code&gt;images/what-is-ebpf-4.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 代码&lt;/strong&gt;：&lt;code&gt;https://github.com/iovisor/bcc/blob/ec3747ed6b16f9eec36a204dfbe3506d3778dcb4/tools/tcpconnect.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bcc 的 BPF 库&lt;/strong&gt;：&lt;code&gt;https://github.com/iovisor/bcc/blob/master/src/cc/export/helpers.h&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quinten Monnet&lt;/strong&gt;：&lt;code&gt;https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BCC&lt;/strong&gt;：&lt;code&gt;https://github.com/iovisor/bcc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bpftrace&lt;/strong&gt;：&lt;code&gt;https://github.com/ajor/bpftrace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;：&lt;code&gt;https://github.com/iovisor/gobpf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C/C++&lt;/strong&gt;：&lt;code&gt;https://github.com/libbpf/libbpf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust&lt;/strong&gt;：&lt;code&gt;https://github.com/redsift/redbpf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SCP - Familiar, Simple, Insecure, and Slow&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/blog/scp-familiar-simple-insecure-slow/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Greed is Good: Creating Teleport’s Discovery Protocol&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/blog/teleport-discovery-protocol/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gracefully Restarting a Go Program Without Downtime&lt;/strong&gt;：&lt;code&gt;https://gravitational.com/blog/golang-ssh-bastion-graceful-restarts/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
