фильтр пакетов ebpf при сопоставлении полезной нагрузки - PullRequest
0 голосов
/ 27 мая 2020

Я новичок в ebpf & xdp topi c и хочу выучить его. Мой вопрос: как использовать фильтр ebpf для фильтрации пакета по определенному c соответствию полезной нагрузки? например, если данные (полезная нагрузка) пакета составляют 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;
                    }
                }
            }
        }
    }
}

Ошибка была удалена, но невозможно сравнить полезную нагрузку ... Я отправляю сообщение UDP с кода сокета python. Если я сравниваю длину полезной нагрузки, она работает нормально.

Ответы [ 2 ]

1 голос
/ 27 мая 2020

Что ты пробовал? Вероятно, вам следует прочитать немного больше о eBPF, чтобы попытаться понять, как обрабатывать пакеты, приведенный вами пример basi c не кажется слишком сложным.

В основном вам нужно будет проанализировать заголовки, чтобы увидеть, где полезная нагрузка начинается. Простые примеры синтаксического анализа BPF могут помочь вам понять принципы:

  1. Начать с начала заголовка (например, Ethe rnet сначала)
  2. Проверить длину пакета достаточно, чтобы удерживать заголовок (иначе вы рискуете получить доступ за пределами границ при попытке доступа к верхним уровням).
  3. Добавьте длину заголовка, чтобы получить смещение вашего следующего заголовка (например, IPv4, затем, например, TCP ...)
  4. Промойте и повторите.

В вашем случае вы должны обрабатывать все заголовки, пока не получите смещение полезной нагрузки данных . Обратите внимание, что это тривиально, если трафик c, который вы пытаетесь сопоставить, всегда имеет одни и те же заголовки (например, всегда IPv4 и UDP), но вы получаете больше случаев для сортировки, если есть сочетание (IPv4 + IPv6, инкапсуляция, параметры IPv4 ...).

Когда у вас есть смещение для ваших данных, просто сравните данные с этим смещением с вашим шаблоном (который вы можете жестко запрограммировать в программе BPF или получить из карты BPF , в зависимости от вашего варианта использования). Обратите внимание, что у вас нет доступа к strcmp(), но __builtin_memcmp() доступен, если вам нужно сравнить более 64 бит.

(Все вышеперечисленное, конечно, применимо к C программа, которую вы бы скомпилировали в объектный файл, содержащий инструкции eBPF, с помощью серверной части LLVM.)

Если бы вы искали строку с произвольным смещением в полезной нагрузке, знайте, что eBPF теперь поддерживает ( bounded), начиная с ядра 5.3 (если я правильно помню).

0 голосов
/ 09 июня 2020

Ваше изменение - это новый вопрос, поэтому здесь обновленный ответ. Пожалуйста, подумайте о том, чтобы вместо этого в будущем открыть новый вопрос.

В вашей программе есть ряд ошибок. В частности:

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)), вы пытаетесь сравнить четыре байта пакетных данных; вы должны убедиться, что пакет достаточно длинный для чтения этих четырех байтов (точно так же, как вы проверяли длину каждый раз перед чтением из поля заголовка Ethe rnet, 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...