- PVSM.RU - https://www.pvsm.ru -
Всех с прошедшими праздниками!
Нашу первую статью после праздников мы решили посвятить линуксу, то есть под наш замечательный курс «Администратор 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 не решит всех проблем, но все равно это мощный инструмент для сетевой отладки, который заслуживает внимания. Уверен, он будет играть действительно важную роль в будущем сетей.
Проблема
Я занимался отладкой проблемы сети 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 есть ограничения, защищающие его от злонамеренного использования:
Программа может быть загружена в ядро разными способами при помощи отладки и трассировки [6]. В нашем случае, интересует работа eBPF с сетевыми подсистемами. Есть два способа использования eBPF программы:
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 загружен в нужном месте и пакеты, покидающие ВМ, промаркированы. Проверив пакеты, полученные во второй ВМ, увидим следующее:
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
Нажмите здесь для печати.