Главная » Хабрахабр » Отладка cети с помощью eBPF (RHEL 8 Beta)

Отладка cети с помощью eBPF (RHEL 8 Beta)

Всех с прошедшими праздниками!

Ну и, соответственно, мы предлагаем интересные статьи и открытый урок. Нашу первую статью после праздников мы решили посвятить линуксу, то есть под наш замечательный курс «Администратор Linux», который у нас входит в когорту самых динамичных курсов, то есть с наиболее актуальным материалами и практиками.

Автор статьи: Matteo Croce
Оригинальное название: Network debugging with eBPF (RHEL 8 Beta)

Введение

Устранение неполадок может быть сложным занятием, также как и попытки воспроизвести неправильное поведение, происходящее “в полевых условиях”. Работа с сетью — увлекательное занятие, но избежать проблем удается не всегда.

Простые сетевые настройки могут быть воспроизведены с помощью сетевых пространств имен и veth-устройств, в то время как более сложные настройки требуют соединения виртуальных машин программным мостом и использования стандартных сетевых инструментов, например, iptables или tc, для симуляции некорректного поведения. К счастью, существуют инструменты, способные с этим помочь: сетевые пространства имен, виртуальные машины, tc и netfilter. При наличии проблемы с ICMP-ответами, сгенерированными при падении SSH-сервера, iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable в правильном пространстве имен может помочь решить проблему.

eBPF — сравнительно новая технология, проект находится на ранней стадии, поэтому документация и SDK пока не готовы. В этой статье описывается устранение сложных проблем сети с помощью eBPF (extended BPF), расширенной версии Пакетного Фильтра Беркли. Но будем надеяться на улучшения, особенно с учетом того, что XDP (eXpress Data Path) поставляется с Red Hat Enterprise Linux 8 Beta, которую можно загрузить и запустить уже сейчас.

Уверен, он будет играть действительно важную роль в будущем сетей. eBPF не решит всех проблем, но все равно это мощный инструмент для сетевой отладки, который заслуживает внимания.

Проблема

Анализ показал, что первый TCP-пакет каждого соединения с PSH-флагом отправлялся в неправильном порядке: только первый и только один за соединение. Я занимался отладкой проблемы сети Open vSwitch (OVS), которая затрагивала очень сложную установку: некоторые TCP-пакеты были разрознены и доставлялись в неправильном порядке, а пропускная способность виртуальных машин падала со стабильных 6 Гб/с до колеблющихся 2-4 Гб/с.

Я попытался воспроизвести эту настройку с двумя виртуальными машинами и, спустя множество справочных статей и поисковых запросов, обнаружил, что ни iptables, ни nftables не могут манипулировать флагами TCP, в то время как tc может, но только перезаписывая флаги и прерывая новые соединения и TCP в целом.

Возможно, удалось бы решить проблему с помощью комбинации iptables, conntrack и tc, но я решил, что это отличная работа для eBPF.

Что такое eBPF?

Она привносит большое количество улучшений в BPF. eBPF — расширенная версия Пакетного Фильтра Беркли. В частности, позволяет писать в памяти, а не только читать, поэтому пакеты можно не только фильтровать, но и редактировать.

Часто eBPF называют просто BPF, а сам BPF называют cBPF (classic (классический) BPF), поэтому слово “BPF” может использоваться для обозначения обеих версий, в зависимости от контекста: в этой статье я всегда говорю о расширенной версии.

В eBPF есть ограничения, защищающие его от злонамеренного использования: “Под капотом” у eBPF очень простая виртуальная машина, которая может выполнять небольшие фрагменты байт-кода и править некоторые буферы памяти.

  • Циклы запрещены, чтобы программа всегда завершалась в определенное время;
  • Он может получить доступ к памяти только через стек и scratch-буфер;
  • Могут быть вызваны только разрешенные функции ядра.

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

  • Подключенной через XDP к началу RX-пути физической или виртуальной сетевой платы;
  • Подключенной через tc к qdisc во входе или выходе.

Чтобы создать eBPF программу для подключения, достаточно написать код на C и сконвертировать его в байт-код. Ниже представлен простой пример с использованием XDP:

SEC("prog")
int xdp_main(struct xdp_md *ctx)
return XDP_PASS;
} char _license[] SEC("license") = "GPL";

Фрагмент выше, без выражений include, хелперов и опционального кода — XDP-программа, которая меняет TTL получаемых эхо-ответов ICMP, а именно pong’ов, на случайное число. Основная функция получает структуру xdp_md, в которой находится два указателя на начало и конец пакета.

Clang поддерживает его и создает байт-код eBPF путем уточнения bpf в качестве цели во время компиляции: Для компиляции нашего кода в eBPF байт-код, требуется компилятор с соответствующей поддержкой.

$ clang -O2 -target bpf -c xdp_manglepong.c -o xdp_manglepong.o

Команда выше создает файл, который, на первый взгляд, кажется обычным объектным файлом, но при ближайшем рассмотрении, оказывается, что указанным типом компьютера будет Linux eBPF, а не нативный тип операционной системы:

$ readelf -h xdp_manglepong.o
ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF <--- HERE [...]

Получив обертку обычного объектного файла, eBPF программа готова к загрузке и подключению к устройству через XDP. Это можно сделать с помощью ip из пакета iproute2со следующим синтаксисом:

# ip -force link set dev wlan0 xdp object xdp_manglepong.o verbose

Эта команда указывает целевой интерфейс wlan0 и, благодаря параметру -force, перезаписывает любой существующий eBPF код, который уже был загружен. После загрузки eBPF байт-кода, система ведет себя следующим образом:

$ ping -c10 192.168.85.1
PING 192.168.85.1 (192.168.85.1) 56(84) bytes of data.
64 bytes from 192.168.85.1: icmp_seq=1 ttl=41 time=0.929 ms
64 bytes from 192.168.85.1: icmp_seq=2 ttl=7 time=0.954 ms
64 bytes from 192.168.85.1: icmp_seq=3 ttl=17 time=0.944 ms
64 bytes from 192.168.85.1: icmp_seq=4 ttl=64 time=0.948 ms
64 bytes from 192.168.85.1: icmp_seq=5 ttl=9 time=0.803 ms
64 bytes from 192.168.85.1: icmp_seq=6 ttl=22 time=0.780 ms
64 bytes from 192.168.85.1: icmp_seq=7 ttl=32 time=0.847 ms
64 bytes from 192.168.85.1: icmp_seq=8 ttl=50 time=0.750 ms
64 bytes from 192.168.85.1: icmp_seq=9 ttl=24 time=0.744 ms
64 bytes from 192.168.85.1: icmp_seq=10 ttl=42 time=0.791 ms --- 192.168.85.1 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 125ms
rtt min/avg/max/mdev = 0.744/0.849/0.954/0.082 ms

Каждый пакет проходит через eBPF, который в конечном итоге вносит некоторые изменения и решает бросить ли пакет или пропустить.

Как eBPF может помочь

Писать код для этого сценария совсем несложно: настройте две виртуальные машины, соединенные OVS-мостом, и просто подключите eBPF к одному из виртуальных устройств ВМ. Возвращаясь к изначальной сетевой проблеме, вспомним, что нужно было маркировать несколько TCP флагов, по одному за соединение, и ни iptables, ни tc не могли этого сделать.

Звучит как отличное решение, но стоит учитывать, что XDP поддерживает только обработку полученных пакетов, и подключение eBPF к пути rx принимающей виртуальной машины не окажет никакого воздействия на коммутатор.

Чтобы промаркировать пакеты, покидающие хост, eBPF необходимо подключить к выходному qdisk. Чтобы решить эту проблему, eBPF должен быть загружен с помощью tc и подключен к выходному пути ВМ, потому что tc может загружать и подключать eBPF программы к qdisk.

Но это не проблема. При загрузке eBPF программы, между XDP и tc API есть некоторые отличия: по умолчанию разные названия разделов, отличается тип структуры аргумента главной функции, разные возвращаемые значения. Ниже представлен фрагмент программы, маркирующей TCP при присоединении к tc-действию:

#define RATIO 10 SEC("action")
int bpf_main(struct __sk_buff *skb)
{ void *data = (void *)(uintptr_t)skb->data; void *data_end = (void *)(uintptr_t)skb->data_end; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct tcphdr *tcphdr = (struct tcphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if ((void *)(tcphdr + 1) > data_end) return TC_ACT_OK; /* skip non-TCP packets */ if (eth->h_proto != __constant_htons(ETH_P_IP) || iph->protocol != IPPROTO_TCP) return TC_ACT_OK; /* incompatible flags, or PSH already set */ if (tcphdr->syn || tcphdr->fin || tcphdr->rst || tcphdr->psh) return TC_ACT_OK; if (bpf_get_prandom_u32() % RATIO == 0) tcphdr->psh = 1; return TC_ACT_OK;
} char _license[] SEC("license") = "GPL";

Компиляция в байт-код произведена, как показано в XDP примере выше, с помощью следующего:

clang -O2 -target bpf -c tcp_psh.c -o tcp_psh.o

Но загрузка отличается:

# tc qdisc add dev eth0 clsact
# tc filter add dev eth0 egress matchall action bpf object-file tcp_psh.o

Теперь eBPF загружен в нужном месте и пакеты, покидающие ВМ, промаркированы. Проверив пакеты, полученные во второй ВМ, увидим следующее:

Всего лишь 20 строк C-кода понадобилось для выборочной маркировки TCP-пакетов, покидающих виртуальную машину, воспроизведения ошибки, происходящей “на бою”, и все без перекомпиляции или даже перезапуска! tcpdump подтверждает, что новый eBPF-код работает, и примерно 1 из каждых 10 TCP-пакетов имеют установленный флаг PSH. Это значительно упростило проверку фикса Open vSwitch, чего невозможно было добиться с помощью других инструментов.

Вывод

Также стоит отметить, что проекты на базе eBPF, например bpfilter, становятся все популярней, и, как следствие, многие поставщики оборудования начинают внедрять поддержку eBPF напрямую в сетевые платы. eBPF — довольно новая технология, и у сообщество есть четкое мнение по поводу ее внедрения.

Уверен, он сыграет важную роль в будущем сетей. eBPF не решит всех проблем, поэтому не стоит им злоупотреблять, но все же он остается очень мощным инструментом для сетевой отладки и заслуживает внимания.

THE END

Ждём ваши комментарии тут, а так же приглашаем посетить наш открытый урок, где если что можно так же позадавать вопросы.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Поиск задач в JIRA (простым языком). Часть 1: Быстрый и базовый поиск

В последнее время JIRA активно используют организации, не имеющие прямой связи с IT. Специалистам, не знакомым ранее с JIRA, бывает сложно понять структуру JQL-запросов, если не привести примеры. И начнем мы с «базового» и «быстрого» поиска. Для упрощения восприятия, мы ...

Если связь — просто жесть, то ее нужно закопать

Любой советский школьник, собиравший подобную схему знал, что без заземления — никак. Нынешнее поколение Z, взращенное айфонами, сомневается даже в необходимости антенн! Эта статья показывает важность и необходимость двух данных элементов в условиях слабого сигнала. У истинных связистов методика используемая ...