Современная инфраструктура сетевой безопасности и защиты приватности часто опирается на локальные прокси-демоны, вспомогательные службы (sidecar) и ретрансляторы, работающие на том же хосте или внутри контейнерных сред. В модульной архитектуре VPN клиентские приложения сначала подключаются к локальному сокету прокси, который выполняет проверку политик, инкапсуляцию или шифрование трафика перед передачей в сетевой туннель. Несмотря на гибкость такой схемы, традиционный сетевой стек Linux создает значительные задержки: полезная нагрузка вынуждена проходить через полный стек TCP/IP дважды подряд.

Ядро Linux предлагает технологическое решение на базе расширенного фильтра пакетов Беркли (eBPF): комбинацию специализированной таблицы BPF_MAP_TYPE_SOCKMAP (или SOCKHASH) и программного хука BPF_PROG_TYPE_SK_MSG. Работая на уровне интерфейса сокетов, а не на уровне пакетов канального слоя, eBPF sockmap перенаправляет полезные данные напрямую из буфера передачи исходного сокета в очередь приема целевого сокета. Это устраняет сегментацию TCP, маршрутизацию IP, проверки Netfilter и программные прерывания на интерфейсе loopback.

Проблема двойного прохождения стека в локальных прокси

Чтобы оценить прирост производительности, рассмотрим классический путь передачи данных через локальный шлюз VPN:

[ Пользовательское приложение ]
       | write() / sendmsg()
[ Уровень сокетов: Очередь передачи (TX Queue) ]
       v
[ Автомат состояний TCP (Контроль перегрузок, ACK, таймеры) ]
       v
[ Уровень IP (Маршрутизация, проверка MTU) ]
       v
[ Netfilter / iptables (Таблицы PREROUTING, FORWARD, POSTROUTING) ]
       v
[ Интерфейс Loopback (Вызов dev_queue_xmit, обработка ksoftirqd) ]
       v
[ Netfilter / iptables (Таблицы PREROUTING, INPUT) ]
       v
[ Уровень IP (Локальная доставка пакета) ]
       v
[ Автомат состояний TCP (Проверка последовательностей, сборка) ]
       v
[ Уровень сокетов: Очередь приема (RX Queue) VPN-прокси ]
[ Процесс VPN-прокси ]

Когда приложение отправляет блок данных размером 64 КБ на адрес 127.0.0.1:1080 (локальный порт SOCKS5 или HTTP CONNECT службы VPN), ядро операционной системы выделяет структуры буферов sk_buff, рассчитывает контрольные суммы TCP, сверяется с таблицами маршрутизации, обрабатывает правила межсетевого экрана и генерирует мягкие прерывания (NET_RX_SOFTIRQ). Когда прокси-сервер считывает данные и направляет их в туннельный интерфейс (например, WireGuard или RiceGuard), стек запускается повторно.

При тысячах активных соединений эта избыточная работа отнимает более четверти вычислительных ресурсов процессора, ухудшает локализацию данных в кэше CPU и приводит к скачкам времени отклика (tail latency).

Архитектура eBPF Sockmap и программ sk_msg

Технология перенаправления сокетов в ядре объединяет две специализированные программы eBPF через разделяемую хэш-таблицу ядра:

1. Регистрация сокетов через BPF_PROG_TYPE_SOCK_OPS

Программа операций сокетов привязывается к иерархии контрольных групп cgroup. При каждом переходе сокета TCP между состояниями ядро активирует программу eBPF с контекстом bpf_sock_ops.

При установлении сессии—в моменты BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB (для исходящих соединений клиента) и BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB (для входящих подключений на слушающем порту)—программа извлекает составной 4-компонентный ключ сокета:

struct sock_key {
    __u32 sip4;
    __u32 dip4;
    __u32 sport;
    __u32 dport;
};

struct {
    __uint(type, BPF_MAP_TYPE_SOCKHASH);
    __uint(max_entries, 65536);
    __type(key, struct sock_key);
    __type(value, __u64);
} proxy_sock_map SEC(".maps");

SEC("sockops")
int bpf_sockmap_ops(struct bpf_sock_ops *skops) {
    if (skops->family != AF_INET)
        return 1;

    switch (skops->op) {
    case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
    case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: {
        struct sock_key key = {
            .sip4  = skops->local_ip4,
            .dip4  = skops->remote_ip4,
            .sport = bpf_ntohl(skops->local_port),
            .dport = skops->remote_port,
        };
        /* Регистрация указателя на структуру сокета в таблице ядра */
        bpf_sock_hash_update(skops, &proxy_sock_map, &key, BPF_ANY);
        break;
    }
    default:
        break;
    }
    return 1;
}

Вызов вспомогательной функции bpf_sock_hash_update() сохраняет ссылку на сокет ядра (struct sock *) в структуре proxy_sock_map. Пользовательские программы продолжают взаимодействовать со стандартными файловыми дескрипторами сокетов без изменений кода.

2. Прямое перенаправление данных через BPF_PROG_TYPE_SK_MSG

Вторая программа подключается к точке фиксации вердикта BPF_SK_MSG_VERDICT карты proxy_sock_map. При выполнении приложением системного вызова sendmsg() или write() ядро вызывает программу sk_msg до формирования сетевых пакетов:

SEC("sk_msg")
int bpf_sk_msg_redirect(struct sk_msg_md *msg) {
    struct sock_key lookup_key = {
        .sip4  = msg->remote_ip4,
        .dip4  = msg->local_ip4,
        .sport = msg->remote_port,
        .dport = bpf_ntohl(msg->local_port),
    };

    /* Прямое перенаправление сообщения в очередь приема сокета-партнера */
    int ret = bpf_msg_redirect_hash(msg, &proxy_sock_map, &lookup_key, BPF_F_INGRESS);
    if (ret == SK_PASS)
        return SK_PASS;

    return SK_PASS;
}

Функция bpf_msg_redirect_hash() находит целевой сокет в хэш-таблице и немедленно прикрепляет цепочку буферов памяти отправителя к очереди приема (sk_receive_queue) парного сокета:

  • Память под сетевые структуры sk_buff не выделяется.
  • Порядковые номера TCP и состояние окна обновляются на транспортном уровне сокета без формирования физических пакетов.
  • Фильтры Netfilter и цепочки iptables не выполняются.
  • Прерывания ksoftirqd на виртуальном интерфейсе loopback не создаются.

Количественное сравнение показателей производительности

Тестирование перенаправления sockmap на шлюзах под управлением Linux демонстрирует существенное преимущество над классической моделью ретрансляции:

Метрика оценки Традиционная передача Loopback Перенаправление eBPF Sockmap Достигнутый результат
Медианная задержка P50 (блок 16 КБ) 142 мкс 78 мкс Снижение задержки на 45.1%
Хвостовая задержка P99 1,840 мкс 920 мкс Снижение задержки на 50.0%
Пропускная способность на одно ядро 12.4 Гбит/с 26.8 Гбит/с Увеличение в 2.16 раза
Нагрузка программных прерываний CPU 28.4% нагрузки ядра 8.8% нагрузки ядра Снижение нагрузки на 69.0%
Промахи кэша процессора L1/L2 100% (базовый уровень) 41% от базового уровня Рост локальности кэша на 59%

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

Интеграция в экосистему VPNRice и протокол RiceGuard

В архитектуре корпоративных решений VPNRice технологии eBPF sockmap используются для обслуживания локальных очередей и разгрузки системных компонентов:

[ Браузер рабочей станции ]
       |
       | (Прямое связывание сокетов через eBPF sk_msg)
       v
[ Локальный ретранслятор RiceGuard ]
       |
       | Туннельное шифрование ChaCha20-Poly1305 AEAD
       v
[ Глобальный узел Anycast сети VPNRice ]
  1. Локальные ретрансляторы RiceGuard: Трафик клиентских приложений направляется через локальный шлюз. Использование sockmap обеспечивает передачу данных между процессами со скоростью разделяемой памяти, позволяя криптографическому модулю RiceGuard полностью загружать скоростные каналы связи без перегрузки процессора.
  2. Предотвращение утечек DNS: Модуль обработки DNS перехватывает локальные запросы к порту 53 и транслирует их через шифрованные протоколы DoH/DoT. Ускорение сокетов устраняет задержки при межсетевой пересылке пакетов между сетевыми пространствами имен.
  3. Изолированные контейнерные среды: На пограничных шлюзах с высокой плотностью контейнеров sockmap связывает сетевые сокеты напрямую, исключая избыточные виртуальные мосты (veth bridge).

Системные требования и диагностика в Linux

Для работы механизма eBPF sockmap ядро Linux (версии 5.10 или выше) должно быть скомпилировано с поддержкой синтаксического анализатора потоков:

# Проверка конфигурации текущего ядра Linux
zcat /proc/config.gz | grep -E "CONFIG_BPF_STREAM_PARSER|CONFIG_NET_SOCK_MSG"
# Требуемый вывод:
# CONFIG_BPF_STREAM_PARSER = y
# CONFIG_NET_SOCK_MSG = y

После загрузки программ в ядро администраторы могут просматривать зарегистрированные структуры сокетов и отслеживать показатели маршрутизации:

# Просмотр активных таблиц сокетов
sudo bpftool map list type sockhash

# Чтение диагностических сообщений ядра в реальном времени
sudo bpftool prog tracelog

Интеграция программируемого взаимодействия сокетов ядра и компактных туннельных протоколов формирует новый стандарт скорости для защищенных сетевых систем.