在BPF_CGROUP_INET_INGRESS挂钩上访问入口数据包信息

huangapple go评论70阅读模式
英文:

Access ingress packet data on BPF_CGROUP_INET_INGRESS hook

问题

我想检查特定 cgroup 上传入数据包的数据。我发现 BPF_PROG_TYPE_CGROUP_SKB 程序类型与 BPF_CGROUP_INET_INGRESS 附加类型一起使用,可以操作 __sk_buff 结构。然而,似乎 __sk_buff 结构只暴露数据包头部,而不是数据本身?

以下是我的 BPF 程序:

#include <linux/in.h>
#include <linux/tcp.h>

#include <linux/bpf.h>
#include <sys/socket.h>

#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "GPL";

SEC("cgroup_skb/ingress")
int bpf_print_cgroup_skb(struct __sk_buff *skb) {
  long err, data_len;
  char skb_str[100];
  long len = skb->len;

  // 获取数据包的长度
  if (len > 0) {
    data_len = skb->data_end - skb->data;
    bpf_printk("Packet length: %ld Data length: %ld\n", len, data_len);

    if (len > 100) {
      err = bpf_skb_load_bytes(skb, 0, &skb_str, 100);
      if (err < 0) {
        bpf_printk("ERR: bpf_skb_load_bytes 失败: %ld\n", err);
        return SK_PASS;
      }
      bpf_printk("Message: %s\n", skb_str);
    }
  }

  return SK_PASS;
}

这是如何在用户空间程序中加载程序到 cgroup 的方式:

static const char *__doc__ = "User space program to load the cgskb bpf program to register sockets\n";

#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#include "../common/common_params.h"
#include "../common/common_user_bpf_xdp.h"

const char *cgroup_dir = "/sys/fs/cgroup/unified";
const char *cgroup_name = "<cgroup_name_here>";

int main(int argc, char **argv) {
  int err, len;
  int cgroup_fd;
  char cgroup_filename[PATH_MAX];
  const char *cgskb_file = "bpf_cgroup_skb.o";
  struct bpf_object *cgskb_obj;

  // 打开 cgroup fd -- 用于附加和分离操作
  len = snprintf(cgroup_filename, PATH_MAX, "%s/%s", cgroup_dir, cgroup_name);
 
  fprintf(stdout, "打开 cgroup 文件 %s\n", cgroup_filename);
  cgroup_fd = open(cgroup_filename, O_RDONLY);

  // 打开、加载并附加 cgskb_obj(如果尚未附加)
  cgskb_obj = bpf_object__open_file(cgskb_file, NULL);
 
  struct bpf_program *cgskb_prog = bpf_object__find_program_by_name(cgskb_obj, "bpf_print_cgroup_skb");
 
  // 加载 cgskb 程序
  err = bpf_object__load(cgskb_obj);
 
  // 附加 cgskb 程序
  // 由于 libbpf 尚不支持 cgskb,因此使用核心 BPF API。
  err = bpf_prog_attach(bpf_program__fd(cgskb_prog), cgroup_fd, BPF_CGROUP_INET_INGRESS, 0);
 
  fprintf(stdout, "成功加载 BPF 程序。\n");

  return 0;

exit_cgroup:
  close(cgroup_fd);

fail:
  return -1;
}

是否有其他建议的 eBPF 程序类型来获取入口数据包/套接字消息数据?

英文:

I want to inspect the packet data for incoming packets on a specific cgroup. I found the BPF_PROG_TYPE_CGROUP_SKB program type for the BPF_CGROUP_INET_INGRESS attach type can be used to operate over the __sk_buff struct. However, it seems that the __sk_buff struct only exposes the packet headers, and not the data?

For reference, this is my bpf program:

#include &lt;linux/in.h&gt;
#include &lt;linux/tcp.h&gt;

#include &lt;linux/bpf.h&gt;
#include &lt;sys/socket.h&gt;

#include &lt;bpf/bpf_endian.h&gt;
#include &lt;bpf/bpf_helpers.h&gt;

char LICENSE[] SEC(&quot;license&quot;) = &quot;GPL&quot;;

SEC(&quot;cgroup_skb/ingress&quot;)
int bpf_print_cgroup_skb(struct __sk_buff *skb) {
  long err, data_len;
  char skb_str[100];
  long len = skb-&gt;len;

  // Get length of the packet
  if (len &gt; 0) {
    data_len = skb-&gt;data_end - skb-&gt;data;
    bpf_printk(&quot;Packet length: %ld Data length: %ld\n&quot;, len, data_len);
    
    if (len &gt; 100) {
      err = bpf_skb_load_bytes(skb, 0, &amp;skb_str, 100);
      if (err &lt; 0) {
        bpf_printk(&quot;ERR: bpf_skb_load_bytes failed: %ld\n&quot;, err);
        return SK_PASS;
      }
      bpf_printk(&quot;Message: %s\n&quot;, skb_str);
    }
  }

  return SK_PASS;
}

And this is how I load the program on a cgroup using the userspace program:

static const char *__doc__ = &quot;User space program to load the cgskb bpf program to register sockets\n&quot;;

#include &lt;bpf/bpf.h&gt;
#include &lt;bpf/libbpf.h&gt;
#include &lt;errno.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;

#include &quot;../common/common_params.h&quot;
#include &quot;../common/common_user_bpf_xdp.h&quot;

const char *cgroup_dir = &quot;/sys/fs/cgroup/unified&quot;;
const char *cgroup_name = &quot;&lt;cgroup_name_here&gt;&quot;;

int main(int argc, char **argv) {
  int err, len;
  int cgroup_fd;
  char cgroup_filename[PATH_MAX];
  const char *cgskb_file = &quot;bpf_cgroup_skb.o&quot;;
  struct bpf_object *cgskb_obj;

  // Open the cgroup fd -- needed for both attach and detach operations.
  len = snprintf(cgroup_filename, PATH_MAX, &quot;%s/%s&quot;, cgroup_dir, cgroup_name);
 
  fprintf(stdout, &quot;Opening cgroup file %s\n&quot;, cgroup_filename);
  cgroup_fd = open(cgroup_filename, O_RDONLY);

  // Open, load and attach cgskb_obj if not already attached
  cgskb_obj = bpf_object__open_file(cgskb_file, NULL);
 
  struct bpf_program *cgskb_prog = bpf_object__find_program_by_name(cgskb_obj, &quot;bpf_print_cgroup_skb&quot;);
 
  // Load the cgskb program
  err = bpf_object__load(cgskb_obj);
 
  // Attach the cgskb program
  // Using core BPF API as libbpf doesn&#39;t support cgskb yet.
  err = bpf_prog_attach(bpf_program__fd(cgskb_prog), cgroup_fd, BPF_CGROUP_INET_INGRESS, 0);
 
  fprintf(stdout, &quot;Successfully loaded BPF program.\n&quot;);

  return 0;

exit_cgroup:
  close(cgroup_fd);

fail:
  return -1;
}

Is there any other recommend eBPF program type to get the ingress packet / socket message data.

答案1

得分: 1

以下是已翻译的内容:

"它确实看起来像是故意禁止读取头部以外的数据。添加程序类型的原始提交 https://github.com/torvalds/linux/commit/0e33661de493db325435d565a4a722120ae4cbf3 表明:

这个程序类型类似于BPF_PROG_TYPE_SOCKET_FILTER,但不允许BPF_LD_[ABS|IND]指令,并连接了bpf_skb_load_bytes()助手。

这种类型的程序将附加到cgroups以进行网络过滤和计费。

因此,通过传统方法(在直接数据访问之前)加载数据包数据在其创始时被阻止。

稍后的提交添加了直接数据访问 https://github.com/torvalds/linux/commit/b39b5f411dcfce28ff954e5d6acb2c11be3cb0ec。但它的消息说:

BPF_PROG_TYPE_CGROUP_SKB类型的BPF程序需要访问skb中的头部。此补丁启用了这些程序对skb的直接访问。

引入了两个助手函数bpf_compute_and_save_data_end()和bpf_restore_data_end()。它们在__cgroup_bpf_run_filter_skb()中使用,用于计算BPF程序的适当data_end,并在之后恢复原始数据。

而这个bpf_compute_and_save_data_end()函数将data_end限制为仅包括头部,而不包括主体:

cb->data_end = skb->data + skb_headlen(skb);

我仍然没有找出这个限制背后的原因。"

英文:

It indeed seems like reading data other than the headers has been purposefully disallowed. The original commit to add the program type https://github.com/torvalds/linux/commit/0e33661de493db325435d565a4a722120ae4cbf3 states:

> This program type is similar to BPF_PROG_TYPE_SOCKET_FILTER, except that
it does not allow BPF_LD_[ABS|IND] instructions and hooks up the
bpf_skb_load_bytes() helper.
>
> Programs of this type will be attached to cgroups for network filtering
and accounting.

So loading packet data via the legacy method (before direct-data access) was blocked at its inception.

A later commit adds direct-data access https://github.com/torvalds/linux/commit/b39b5f411dcfce28ff954e5d6acb2c11be3cb0ec. But its message states:

> BPF programs of BPF_PROG_TYPE_CGROUP_SKB need to access headers in the
skb. This patch enables direct access of skb for these programs.
>
> Two helper functions bpf_compute_and_save_data_end() and
bpf_restore_data_end() are introduced. There are used in
__cgroup_bpf_run_filter_skb(), to compute proper data_end for the
BPF program, and restore original data afterwards.

And this bpf_compute_and_save_data_end() function limits the data_end to only include the headers, never the body:

> cb-&gt;data_end = skb-&gt;data + skb_headlen(skb);

I have yet to find out what the reason behind this limitation is.

huangapple
  • 本文由 发表于 2023年6月12日 17:20:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76455199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定