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

Ядро Linux пропонує архітектурне рішення на базі технологій eBPF (extended Berkeley Packet Filter): поєднання структури BPF_MAP_TYPE_SOCKMAP (або SOCKHASH) та програм BPF_PROG_TYPE_SK_MSG. Працюючи на рівні інтерфейсу сокетів, а не на рівні мережевих пакетів каналу, механізм eBPF sockmap перенаправляє дані безпосередньо з буфера передавання сокета відправника в чергу приймання цільового сокета. Це повністю усуває сегментацію TCP, обчислення маршрутів IP, перевірки Netfilter та обробку програмних переривань на loopback-інтерфейсі.

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

Щоб оцінити технологічний виграш eBPF sockmap, розглянемо звичайний шлях проходження даних крізь локальний проксі-шлюз 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, розраховує контрольні суми, виконує пошук маршрутів і генерує програмні переривання (NET_RX_SOFTIRQ). Коли проксі зчитує ці дані та спрямовує їх у тунельний інтерфейс (як-от WireGuard або RiceGuard), увесь стек опрацьовується повторно.

За умов тисяч активних з'єднань ця надлишкова робота споживає понад 25% ресурсів центрального процесора, знижує ефективність кеш-пам'яті та призводить до різких стрибків затримок передавання пакетів.

Архітектурний аналіз: BPF Sockmap та програми sk_msg

Концепція прямого переспрямування сокетів об'єднує дві програми eBPF через спільну хеш-таблицю ядра:

1. Перехоплення стану сокетів через BPF_PROG_TYPE_SOCK_OPS

Програма операцій із сокетами закріплюється за ієрархією cgroup. Щоразу, коли сокет TCP у cgroup змінює стан, ядро активує програму 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 демонструє відчутні покращення затримки, пропускної здатності та споживання CPU:

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

Вилучення проміжних транспортних заголовків дозволяє ядрам процесора довше утримувати дані в гарячих кешах L1/L2, мінімізуючи накладні витрати на перемикання контексту.

Практичне застосування у VPNRice та протоколі RiceGuard

У сучасній архітектурі захисту приватності роздільне тунелювання (split-tunneling) та запобігання витокам DNS є базовими інженерними вимогами. VPNRice застосовує eBPF sockmap на рівні шлюзових вузлів RiceGuard та клієнтських систем Linux:

[ Браузер робочої станції ]
       |
       | (Пряма комутація сокетів через eBPF sk_msg)
       v
[ Локальний ретранслятор RiceGuard ]
       |
       | Тунельне шифрування ChaCha20-Poly1305 AEAD
       v
[ Глобальний вузол Anycast мережі VPNRice ]
  1. Локальні ретранслятори RiceGuard: Клієнтські програми передають захищений трафік через внутрішній сокет. Завдяки sockmap обмін даними між процесами відбувається зі швидкістю спільної пам'яті (shared memory), даючи змогу криптографічному рушію RiceGuard працювати без втрат продуктивності.
  2. Захист від витоків DNS: Служба DNS локально перехоплює запити порту 53 і передає їх через шифровані канали DoH/DoT. Прискорення сокетів усуває затримки обробки між різними просторами імен (network namespaces).
  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

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

# Список активних таблиць sockhash
sudo bpftool map list type sockhash

# Перегляд журналів діагностики ядра
sudo bpftool prog tracelog

Поєднання програмованих сокетів у просторі ядра та оптимізованих криптографічних протоколів задає сучасний стандарт швидкодії для розподілених захищених мереж.