-2

我是 ebpf 和 xdp 主题的新手,想学习它。我的问题是如何使用 ebpf 过滤器过滤特定有效负载匹配的数据包?例如,如果数据包的数据(有效负载)为 1234,则将其传递给网络堆栈,否则将阻止该数据包。我达到了有效载荷长度。例如,如果我想匹配消息有效负载长度,它可以正常工作,但是当我开始匹配有效负载字符时出现错误。这是我的代码:

int ret_val;
unsigned long payload_offset;
unsigned long payload_size;
const char *payload = "test";
struct ethhdr *eth = data;

if ((void*)eth + sizeof(*eth) <= data_end) {
    struct iphdr *ip = data + sizeof(*eth);
    if ((void*)ip + sizeof(*ip) <= data_end) {
        if (ip->protocol == IPPROTO_UDP ) {
            struct udphdr *udp = (void*)ip + sizeof(*ip);
            if ((void*)udp + sizeof(*udp) <= data_end) {
                if (udp->dest == ntohs(5005)) {
                    payload_offset = sizeof(struct udphdr);
                    payload_size = ntohs(udp->len) - sizeof(struct udphdr);
                    unsigned char *s = (unsigned char *)&payload_size;

                    if (ret_val == __builtin_memcmp(s,payload,4) == 0) {
                        return XDP_DROP;
                    }
                }
            }
        }
    }
}

错误已删除,但无法比较有效负载...我正在从 python 套接字代码发送 UDP 消息。如果我比较有效载荷长度,它工作正常。

4

2 回答 2

2

你尝试了什么?您可能应该阅读更多关于 eBPF 的内容以尝试了解如何处理数据包,您给出的基本示例听起来并不太复杂。

基本上,您必须解析标头以查看有效负载的开始位置。简单的 BPF 解析示例可能会帮助您理解原理:

  1. 从报头的开头开始(例如首先是以太网)
  2. 检查数据包是否足够长以容纳标头(否则在尝试访问上层时您将面临越界访问的风险)
  3. 添加标头长度以获得下一个标头的偏移量(例如 IPv4,然后例如 TCP ...)
  4. 冲洗并重复。

在您的情况下,您将处理所有标头,直到获得数据有效负载的偏移量。请注意,如果您尝试匹配的流量始终具有相同的标头(例如始终具有 IPv4 和 UDP),这很简单,但如果存在混合(IPv4 + IPv6、封装、IPv4 选项... )。

获得数据的偏移量后,只需将此偏移量处的数据与您的模式进行比较(您可以在 BPF 程序中硬编码或从 BPF 映射中获取,具体取决于您的用例)。请注意,您无权访问strcmp(),但__builtin_memcmp()如果您需要比较超过 64 位,则可以使用。

(以上所有内容当然适用于 C 程序,您可以将其编译成包含带有 LLVM 后端的 eBPF 指令的目标文件。)

如果您要在有效负载中的任意偏移处搜索字符串,请知道 eBPF 从内核 5.3 开始支持(有界)循环(如果我没记错的话)。

于 2020-05-26T23:32:45.747 回答
1

您的编辑几乎是一个新问题,所以这里有一个更新的答案。请考虑在将来打开一个新问题。

您的程序中有许多错误的地方。尤其是:

1|    payload_offset = sizeof(struct udphdr);
2|    payload_size = ntohs(udp->len) - sizeof(struct udphdr);
3|    unsigned char *s = (unsigned char *)&payload_size;
4|
5|    if (ret_val == __builtin_memcmp(s, payload, 4) == 0) {
6|        return XDP_DROP;
7|    }
  • 在第 1 行,您的payload_offset变量不是偏移量,它只包含 UDP 标头的长度。您需要将其添加到 UDP 标头的开头以获取实际的有效负载偏移量。
  • 2号线没问题。
  • 第3行没有任何意义!您使s(您稍后与您的模式进行比较)指向有效负载的大小?(又名“我在评论中告诉过你!:)”)。相反,它应该指向......有效载荷的开始,也许?所以,基本上,data + payload_offset(一旦偏移量固定)。
  • 在第 3 行和第 5 行之间,缺少对有效负载长度的检查。当您尝试访问s( __builtin_memcmp(s, payload, 4)) 中的有效负载时,您尝试比较四个字节的数据包数据;您必须确保数据包足够长以读取这四个字节(就像您每次从以太网、IP 或 UDP 标头字段读取之前检查长度一样)。
  • 在此期间,我们还可以检查有效负载的长度是否等于要匹配的模式的长度,如果它们不同则退出,而无需比较字节。
  • 如评论中所述,第 5 行有一个==而不是。=易于修复。但是,我对您的程序没有运气__builtin_memcmp(),似乎 LLVM 不想内联它并将其变成失败的函数调用。没关系,没有它我们也可以工作。对于您的示例,您可以int直接转换并比较四字节长值。对于更长的模式和最近的内核(或者如果模式大小固定,则通过展开),我们可以使用有界循环。

这是您的程序的修改版本,适用于我的设置。

#include <arpa/inet.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>

int xdp_func(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    char match_pattern[] = "test";
    unsigned int payload_size, i;
    struct ethhdr *eth = data;
    unsigned char *payload;
    struct udphdr *udp;
    struct iphdr *ip;

    if ((void *)eth + sizeof(*eth) > data_end)
        return XDP_PASS;

    ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return XDP_PASS;

    if (ip->protocol != IPPROTO_UDP)
        return XDP_PASS;

    udp = (void *)ip + sizeof(*ip);
    if ((void *)udp + sizeof(*udp) > data_end)
        return XDP_PASS;

    if (udp->dest != ntohs(5005))
        return XDP_PASS;

    payload_size = ntohs(udp->len) - sizeof(*udp);
    // Here we use "size - 1" to account for the final '\0' in "test".
    // This '\0' may or may not be in your payload, adjust if necessary.
    if (payload_size != sizeof(match_pattern) - 1) 
        return XDP_PASS;

    // Point to start of payload.
    payload = (unsigned char *)udp + sizeof(*udp);
    if ((void *)payload + payload_size > data_end)
        return XDP_PASS;

    // Compare each byte, exit if a difference is found.
    for (i = 0; i < payload_size; i++)
        if (payload[i] != match_pattern[i])
            return XDP_PASS;

    // Same payload, drop.
    return XDP_DROP;
}
于 2020-06-09T01:00:39.007 回答