В современном мире разработки сетевых систем и контейнеризации Linux предлагает невероятно мощные инструменты для создания изолированных окружений с оптимизированной шинной связью. Одним из таких инструментов являются интерфейсы netkit и возможности расширяемого фильтра ядра eBPF. Комбинируя эти технологии, можно построить уникальное решение - "Йогуртовый телефон", который служит метафорой простого устройства для связи, построенного на абстрактных, но очень функциональных механизмах Linux. Эта идея берёт начало из детского эксперимента, когда два "йогуртовых" стаканчика соединяли ниткой, чтобы проговариваться друг с другом и прочувствовать принципы передачи звука по проводу. В контексте Linux "Йогуртовый телефон" - это соединение двух сетевых пространств имен, которые имитируют отдельные изолированные сети, но общаются между собой посредством виртуальной "нитки" - в данном случае с помощью netkit интерфейсов и eBPF программ, выступающих в роли интеллектуального посредника и фильтра.
Понимание основ таких технологий, как netkit, начинается с знаний о сетевых пространствах имен в Linux. Каждое пространство имен обладает собственной таблицей сетевых интерфейсов, маршрутизацией и правилами, изолированными от других пространств. Это обеспечивает безопасность и гибкость при развертывании контейнеризированных приложений и процессов. Netkit добавляет возможность создавать пары виртуальных интерфейсов, один из которых закреплен за хостом, а другой - за контейнером, причем в режиме "blackhole" пакеты доставляются напрямую, минуя физические сетевые устройства. Однако, несмотря на богатство функций, документация по netkit остаётся фрагментированной и довольно скудной, что заставляет энтузиастов и разработчиков самостоятельно изучать и экспериментировать.
Именно здесь на сцену выходит eBPF (extended Berkeley Packet Filter) - современный механизм, позволяющий гибко программировать обработку сетевых пакетов на уровне ядра. С его помощью можно создавать собственные фильтры, маршруты и манипулировать трафиком без необходимости вносить изменения в ядро Linux. Задача создания "Йогуртового телефона" сводится к обеспечению беспрепятственного обмена пакетами между двумя netkit парами, каждую из которых обслуживает отдельное пространство имен. При этом все данные остаются внутри одного хоста, то есть сетевые пакеты не выходят за пределы машины, что повышает безопасность и производительность. Значимым условием является то, что приложения в основном пространстве имен пользователя не должны иметь возможности прослушивать или вмешиваться в связь между "йогуртами".
Начальный этап требует создания двух пространств имен с помощью команды ip netns add, за которыми регистрируются пары netkit интерфейсов в режиме blackhole. Через скрипты конфигурируются IP-адреса и интерфейсы поднимаются в активное состояние. Этот процесс во многом аналогичен работе с классическими виртуальными Ethernet (veth) парами, но с некоторыми особенностями, свойственными netkit, такими как имена интерфейсов и политика blackhole. После базовой настройки следует следующая техническая задача - развертывание eBPF программ на интерфейсах для реализации логики передачи и контроля трафика. В отличие от классических методов, здесь требуется глубокое понимание поведения пакетов, состояния сетевых интерфейсов и внутренних механизмов Linux.
Для отладки и наблюдения за системой используются простые ping-запросы и анализ их прохождения с помощью tcpdump, а также встроенный трассировщик ядра с помощью функций bpf_printk. Важным открытием стало понимание, что маршрутизация трафика между netkit интерфейсами в пространстве имен осуществляется не напрямую, а через _host-side_ интерфейс. Несмотря на некоторую условность, это ограничение связано с архитектурой внутреннего переправления пакетов в ядре и не может быть обойдена текущими API eBPF. Для перенаправления пакетов применяется функция bpf_redirect(), однако она работает корректно только при адресации на интерфейс хостовой части пары. Основная логика маршрутизации реализуется посредством проверки индекса интерфейса исходящего пакета и вызова соответствующего перенаправления.
Таким образом, eBPF программа на "peer" интерфейсе фиксирует получение пакета, ставит специальный "магический" маркер в рамках поля cb пакета, который служит для передачи дополнительной информации между хуками eBPF, и инициирует перенаправление на интерфейс другой сети. Затем программа, установленная на "primary" интерфейсе, уже руководствуется значением этого маркера cb[0], чтобы определить, следует ли пропускать пакет дальше. Если отметка отсутствует, пакет блокируется, что предотвращает нежеланное взаимодействие с основной сетью и обеспечивает своеобразную изоляцию. Важно отметить, что использование поля cb, представляющего собой массив из пяти 32-битных целых чисел без жёстко заданного значения, является нестандартным трюком. Оно позволяет обмениваться контекстом между двумя eBPF программами и обходить ограничение на утерю "марки" пакета, очищаемой стандартной логикой netkit.
Однако на практике выяснилось, что поведение поля cb не всегда однозначно. В серверной части соединения TCP наблюдаются записанные в cb[1] Значения TCP-флагов, что свидетельствует о вмешательстве ядра Linux в эту область данных при формировании пакетов, например, при TCP handshake (SYN+ACK). Это накладывает ограничения на использование cb, создавая потенциальные риски несанкционированного перезаписывания или конфликтов. Тем не менее, с учётом тщательной отладки и наблюдения, применение этого поля оправдано и функционирует стабильно в рассматриваемом сценарии. Одним из ключевых выводов является то, что прямая связь "peer-to-peer" между контейнерами с помощью netkit без прохождения через хостовое пространство имен на данный момент невозможна.
Тем не менее, подобное ограничение не мешает организовать эффективное взаимодействие с использованием eBPF, при этом сохраняя контроль и безопасность обмена данными. Что касается безопасности, стоит отметить, что root-пользователь хоста потенциально имеет возможность наблюдать трафик на хостовых интерфейсах или вмешиваться в передачу. Для полноценной изоляции рекомендуется обеспечить отсутствие IP-адресов и маршрутов к хостовым частям netkit пар, а также ограничить доступ к этим интерфейсам. Более строгие меры могут быть реализованы с помощью дополнительных LSM (Security Module) и eBPF фильтров, но это выходит за рамки базового построения "Йогуртового телефона". Итогом стал довольно компактный, состоящий из 31 строки eBPF код, который позволяет организовать двунаправленную связь между netkit интерфейсами разных пространств имен, эмулируя простое телефонное соединение по проводу.
Полученный опыт продемонстрировал не только глубину имеющихся механизмов Linux, но и вызовы, с которыми сталкиваются разработчики при исследовании и построении новых решений. Тем самым, эксперимент с "Йогуртовым телефоном" показывает, как, с одной стороны, можно использовать современные расширяемые инструменты ядра Linux, а с другой - продолжать сталкиваться с ограничениями и открытыми задачами в документации и архитектуре. Всё это лишь подчёркивает активное развитие экосистемы и перспективы дальнейших усовершенствований. Для тех, кто хочет освоить эти технологии, крайне рекомендуется ознакомиться с основами сетевых пространств имен, основами работы с netkit, а также изучить материалы и инструменты для написания и отладки eBPF программ, в том числе практические примеры перехвата и маршрутизации трафика. Подытоживая, создание "Йогуртового телефона" в Linux - не просто забавный эксперимент, это глубокое обучение современным методам сетевой виртуализации и программирования ядра, объединяющим уникальные возможности для создания безопасных, производительных и настраиваемых сетевых схем внутри одного физического хоста.
.