Быстрый старт: Введение в XDP Часть 3 (ICMP Echo Reply, Часть 1)
сервер сеть программирование
Lastmod: 2025-03-06
Published: 2025-03-05

В предыдущей статье мы использовали eBPF Map в XDP для обмена данными с пользовательским пространством.

В статье перед предыдущей мы написали программу, которая блокирует все пакеты с помощью программы XDP.

На этот раз мы напишем программу, которая будет отвечать на ICMP Echo Request с помощью ICMP Echo Reply.

Как ответить на ICMP Echo Reply

Чтобы ответить на ICMP Echo Request, необходимо выполнить следующие шаги:

  1. Получить пакет ICMP Echo Request.
  2. Изменить флаг на ICMP Echo Reply.
  3. Поменять местами IP-адреса источника и назначения.
  4. Поменять местами MAC-адреса источника и назначения в заголовке Ethernet.
  5. Отправить пакет.

Блокировка только ICMP Echo Request

Сначала мы хотим, чтобы программа могла обрабатывать ICMP Echo Request, поэтому давайте напишем программу, которая будет блокировать только ICMP Echo Request.

  • xdp_echo_request_drop.c
    #include <linux/bpf.h>
    #include <bpf/bpf_helpers.h>
    #include <linux/if_ether.h>
    #include <linux/ip.h>
    #include <linux/icmp.h>
    #include <arpa/inet.h>
    
    SEC("xdp")
    int xdp_drop_icmp(struct xdp_md *ctx) {
        void *data_end = (void *)(unsigned long)ctx->data_end;
        void *data = (void *)(unsigned long)ctx->data;
    
        // Получаем указатель на заголовок Ethernet
        struct ethhdr *eth = data;
        if ((void *)eth + sizeof(*eth) > data_end)
            return XDP_PASS;
    
        // Проверяем, содержит ли Ethernet-рамка IP-пакеты
        if (eth->h_proto != htons(ETH_P_IP))
            return XDP_PASS;
    
        // Получаем указатель на заголовок IP
        struct iphdr *ip = data + sizeof(*eth);
        if ((void *)ip + sizeof(*ip) > data_end)
            return XDP_PASS;
    
        // Проверяем, является ли протокол ICMP
        if (ip->protocol != IPPROTO_ICMP)
            return XDP_PASS;
    
        // Получаем указатель на заголовок ICMP
        struct icmphdr *icmp = (void *)ip + sizeof(*ip);
        if ((void *)icmp + sizeof(*icmp) > data_end)
            return XDP_PASS;
    
        // Пропускаем пакеты, которые не являются ICMP Echo Request
        if (icmp->type != ICMP_ECHO)
            return XDP_PASS;
    
        // Блокируем пакет ICMP Echo Request
        return XDP_DROP;
    }
    
    char _license[] SEC("license") = "MIT";
    

Это почти такая же программа, как и та, которую мы создали в Быстрый старт: Введение в XDP.

  // Пропускаем пакеты, которые не являются ICMP Echo Request
  if (icmp->type != ICMP_ECHO)
      return XDP_PASS;

В этом фрагменте программы мы пропускаем пакеты, которые не являются ICMP Echo Request.

Ответ на ICMP Echo

Теперь давайте напишем программу, которая будет отвечать на ICMP Echo Request с помощью ICMP Echo Reply.

  1. Изменяем флаг на ICMP Echo Reply
// Изменяем флаг на ICMP Echo Reply
icmp->type = ICMP_ECHOREPLY;

Меняем type с ICMP_ECHO (8) на ICMP_ECHOREPLY (0).

  1. Поменять местами IP-адреса источника и назначения
// Поменять местами IP-адреса источника и назначения
__u32 tmp = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = tmp;
  1. Поменять местами MAC-адреса источника и назначения в заголовке Ethernet
// Поменять местами MAC-адреса источника и назначения в заголовке Ethernet
unsigned char tmp_mac[ETH_ALEN];
__builtin_memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
__builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
__builtin_memcpy(eth->h_source, tmp_mac, ETH_ALEN);
  1. Отправить пакет с помощью XDP_TX
// Отправляем пакет
return XDP_TX;

Теперь программа для ответа ICMP Echo Reply готова.

SEC("xdp")
int xdp_echo_reply(struct xdp_md *ctx)
{
    void *data_end = (void *)(unsigned long)ctx->data_end;
    void *data = (void *)(unsigned long)ctx->data;

    // Получаем указатель на заголовок Ethernet
    struct ethhdr *eth = data;
    if ((void *)eth + sizeof(*eth) > data_end)
        return XDP_PASS;

    // Проверяем, содержит ли Ethernet-рамка IP-пакеты
    if (eth->h_proto != htons(ETH_P_IP))
        return XDP_PASS;

    // Получаем указатель на заголовок IP
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return XDP_PASS;

    // Проверяем, является ли протокол ICMP
    if (ip->protocol != IPPROTO_ICMP)
        return XDP_PASS;

    // Получаем указатель на заголовок ICMP
    struct icmphdr *icmp = (void *)ip + sizeof(*ip);
    if ((void *)icmp + sizeof(*icmp) > data_end)
        return XDP_PASS;

    // Пропускаем пакеты, которые не являются ICMP Echo Request
    if (icmp->type != ICMP_ECHO)
        return XDP_PASS;

    // Изменяем флаг на ICMP Echo Reply
    icmp->type = ICMP_ECHOREPLY;

    // Поменять местами IP-адреса источника и назначения
    __u32 tmp = ip->saddr;
    ip->saddr = ip->daddr;
    ip->daddr = tmp;

    // Поменять местами MAC-адреса источника и назначения в заголовке Ethernet
    unsigned char tmp_mac[ETH_ALEN];
    __builtin_memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
    __builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
    __builtin_memcpy(eth->h_source, tmp_mac, ETH_ALEN);

    // Блокируем пакет ICMP Echo Request
    // return XDP_DROP;
    return XDP_TX;
}

Загрузка программы в NIC

Компилируем программу и загружаем ее в NIC.

$ clang -O2 -target bpf -c xdp_echo_reply.c -o xdp_echo_reply.o
$ sudo ip link set dev eth0 xdp obj xdp_echo_reply.o

Проверка работы

  • Отправляем ping с клиента

    $ ping 192.168.XXX.XXX
    
  • Запускаем tcpdump на сервере и проверяем, что ICMP Request не был получен. Так как XDP обрабатывает пакеты напрямую, их невозможно видеть с помощью tcpdump, и становится ясно, что ядро Linux не отправляет Echo Reply.

    $ sudo tcpdump -i eth0 icmp
    
  • Когда отправляем ping с клиента, мы можем подтвердить, что ICMP Echo Reply возвращается.

    $ sudo tcpdump -i eth0 icmp -vvv
    XX:XX:XX.160507 IP (tos 0x0, ttl 64, id 53521, offset 0, flags [DF], proto ICMP (1), length 84)
        192.168.XXX.1 > 192.168.XXX.2: ICMP echo request, id 58371, seq 684, length 64
    XX:XX:XX.161394 IP (tos 0x0, ttl 64, id 53521, offset 0, flags [DF], proto ICMP (1), length 84)
        192.168.XXX.2 > 192.168.XXX.1: ICMP echo reply, id 58371, seq 684, length 64 (неправильная контрольная сумма ICMP 767b (->7e7b)!)
    

Мы подтвердили, что ICMP Echo Reply возвращается.
Однако, поскольку отображается сообщение неправильная контрольная сумма ICMP, это указывает на то, что контрольная сумма заголовка ICMP не была правильно рассчитана.

Как правильно рассчитать контрольную сумму ICMP и вернуть ее, я подумаю, и, если у меня будет желание писать, я запишу об этом.

Резюме

  • Мы реализовали XDP-программу, которая отвечает ICMP Echo Reply на ICMP Echo Request.
  • Мы описали шаги, как обнаружить ICMP Echo Request, сгенерировать ответный пакет, поменять местами MAC и IP-адреса источника и назначения, и отправить ответ напрямую без прохождения через ядро с помощью XDP_TX.
  • В итоге возникла ошибка неправильная контрольная сумма ICMP, что показало необходимость повторного вычисления контрольной суммы ICMP.

Следующие шаги (Обновлено: 06.03.2025)

Быстрый старт: Введение в Super XDP Часть 3 (ICMP Echo Reply - Часть 2)