В предыдущей статье мы использовали eBPF Map в XDP для обмена данными с пользовательским пространством.
В статье перед предыдущей мы написали программу, которая блокирует все пакеты с помощью программы XDP.
На этот раз мы напишем программу, которая будет отвечать на ICMP Echo Request с помощью ICMP Echo Reply.
Как ответить на ICMP Echo Reply
Чтобы ответить на ICMP Echo Request, необходимо выполнить следующие шаги:
- Получить пакет ICMP Echo Request.
- Изменить флаг на ICMP Echo Reply.
- Поменять местами IP-адреса источника и назначения.
- Поменять местами MAC-адреса источника и назначения в заголовке Ethernet.
- Отправить пакет.
Блокировка только 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.
- Изменяем флаг на ICMP Echo Reply
// Изменяем флаг на ICMP Echo Reply
icmp->type = ICMP_ECHOREPLY;
Меняем type с ICMP_ECHO (8) на ICMP_ECHOREPLY (0).
- Поменять местами IP-адреса источника и назначения
// Поменять местами IP-адреса источника и назначения
__u32 tmp = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = tmp;
- Поменять местами 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);
- Отправить пакет с помощью 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)