- PVSM.RU - https://www.pvsm.ru -

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

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

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

Введение

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

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

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

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

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

Проблема

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

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

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

Что такое eBPF?

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

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

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

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

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

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

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

SEC("prog")
int xdp_main(struct xdp_md *ctx)
{
    void *data_end = (void *)(uintptr_t)ctx->data_end;
    void *data = (void *)(uintptr_t)ctx->data;

    struct ethhdr *eth = data;
    struct iphdr *iph = (struct iphdr *)(eth + 1);
    struct icmphdr *icmph = (struct icmphdr *)(iph + 1);

    /* sanity check needed by the eBPF verifier */
    if (icmph + 1 > data_end)
        return XDP_PASS;

    /* matched a pong packet */
    if (eth->h_proto != ntohs(ETH_P_IP) ||
        iph->protocol != IPPROTO_ICMP ||
        icmph->type != ICMP_ECHOREPLY)
        return XDP_PASS;

    if (iph->ttl) {
        /* save the old TTL to recalculate the checksum */
        uint16_t *ttlproto = (uint16_t *)&iph->ttl;
        uint16_t old_ttlproto = *ttlproto;

        /* set the TTL to a pseudorandom number 1 < x < TTL */
        iph->ttl = bpf_get_prandom_u32() % iph->ttl + 1;

        /* recalculate the checksum; otherwise, the IP stack will drop it */
        csum_replace2(&iph->check, old_ttlproto, *ttlproto);
    }

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

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

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

$ 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 может помочь

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

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

Чтобы решить эту проблему, eBPF должен быть загружен с помощью tc и подключен к выходному пути ВМ, потому что tc может загружать и подключать eBPF программы к qdisk. Чтобы промаркировать пакеты, покидающие хост, 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 загружен в нужном месте и пакеты, покидающие ВМ, промаркированы. Проверив пакеты, полученные во второй ВМ, увидим следующее:

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

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

Вывод

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

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

THE END

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

Автор: MaxRokatansky

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/linux/305620

Ссылки в тексте:

[1] «Администратор Linux»: https://otus.pw/rEwm/

[2] открытый урок: https://otus.pw/4xd3/

[3] eBPF (extended BPF): https://lwn.net/Articles/740157/

[4] Red Hat Enterprise Linux 8 Beta: https://developers.redhat.com/blog/2018/11/15/red-hat-enterprise-linux-8-beta-is-here/

[5] Open vSwitch (OVS): https://developers.redhat.com/blog/tag/open-virtual-network/

[6] отладки и трассировки: http://www.brendangregg.com/ebpf.html

[7] фикса Open vSwitch: https://github.com/openvswitch/ovs/commit/9b4f08cdcaf253175edda088683bdd3db9e4c097

[8] bpfilter: https://lwn.net/Articles/747551/

[9] Источник: https://habr.com/ru/post/436528/?utm_campaign=436528