Сучасна інфраструктура мережевої безпеки та конфіденційності часто базується на локальних проксі-службах, ретрансляторах або 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 ]
- Локальні ретранслятори RiceGuard: Клієнтські програми передають захищений трафік через внутрішній сокет. Завдяки sockmap обмін даними між процесами відбувається зі швидкістю спільної пам'яті (shared memory), даючи змогу криптографічному рушію RiceGuard працювати без втрат продуктивності.
- Захист від витоків DNS: Служба DNS локально перехоплює запити порту 53 і передає їх через шифровані канали DoH/DoT. Прискорення сокетів усуває затримки обробки між різними просторами імен (network namespaces).
- Ізоляція контейнерних середовищ: На прикордонних шлюзах 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
Поєднання програмованих сокетів у просторі ядра та оптимізованих криптографічних протоколів задає сучасний стандарт швидкодії для розподілених захищених мереж.



