Протокол захисту транспортного рівня TLS 1.3 є головним криптографічним інструментом сучасного інтернету. В архітектурах віртуальних приватних мереж (VPN), крайових ретрансляторів та захищених інкапсульованих тунелів (таких як DNS-over-HTTPS або потокові мультиплексори) шифрування запобігає перехопленню та підміні пакетів.

Втім, стандартні бібліотеки TLS простору користувача (userspace), такі як OpenSSL, BoringSSL або Rustls, зазнають значного падіння продуктивності під час обробки великих обсягів трафіку. У звичайній конфігурації кожен пакет, що проходить крізь ретранслятор, багаторазово копіюється між буферами сокетів ядра та пам'яттю програми з частим перемиканням контексту.

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

Щоб позбутися зайвого копіювання пам'яті та передати обчислення симетричної криптографії безпосередньо апаратним модулям мережевої карти, у ядрі Linux було створено механізм Kernel TLS (kTLS, CONFIG_TLS) у поєднанні з технологією SmartNIC Device Offload.

+--------------------------------------------------------------------------+
|          ПОРІВНЯННЯ РУХУ ДАНИХ: USERSPACE TLS ТА LINUX KTLS              |
+--------------------------------------------------------------------------+
|                                                                          |
|  [ ЗВИЧАЙНА РЕТРАНСЛЯЦІЯ TLS У ПРОСТОРІ КОРИСТУВАЧА ]                    |
|                                                                          |
|      Вхід NIC ──> sk_buff ──(Копіювання 1)──> Пам'ять Userspace (RAM)    |
|                                                  │                       |
|                                      Дешифрування та шифрування CPU      |
|                                      (Перевантаження кешів L1/L2)        |
|                                                  v                       |
|      Вихід NIC <── sk_buff <──(Копіювання 2)── Пам'ять Userspace (RAM)   |
|      (Потребує 4 копіювання та 2 перемикання контексту на цикл)          |
|                                                                          |
|  ----------------------------------------------------------------------  |
|                                                                          |
|  [ РЕТРАНСЛЯЦІЯ LINUX KTLS ZERO-COPY З АПАРАТНИМ ПРИСКОРЕННЯМ ]          |
|                                                                          |
|      Вхід NIC (DMA)                                                      |
|           │                                                              |
|           v (Буфер сторінок ядра / sk_buff)                              |
|      +-------------------------------------------------------------+     |
|      |                  МЕРЕЖЕВИЙ СТЕК ЯДРА LINUX                  |     |
|      |                                                             |     |
|      |   Модуль kTLS (CONFIG_TLS) / Маршрутизація eBPF Sockmap     |     |
|      |   - Жодного копіювання в пам'ять користувача                |     |
|      |   - Пряме пересилання всередині ядра через splice()         |     |
|      +-------------------------------------------------------------+     |
|           │                                                              |
|           v (Вихідна передача Zero-Copy через DMA)                       |
|      Апаратний крипточип на SmartNIC (Mellanox / Intel)                  |
|           │                                                              |
|           v                                                              |
|      Шифрований потік у лінію (Швидкість 100GbE без навантаження на CPU) |
|                                                                          |
+--------------------------------------------------------------------------+

Обмеження класичного підходу в просторі користувача

У традиційній архітектурі мережевих служб ретрансляція трафіку спричиняє відчутні втрати продуктивності:

  1. Частота системних викликів: виклики функцій read() або SSL_read() змушують процесор постійно перемикатися між простором користувача та ядром.
  2. Подвійне копіювання пам'яті: пакети з буферів мережевого адаптера переносяться в динамічну пам'ять програми, а після аналізу функція write() знову дублює дані в сокет ядра.
  3. Витіснення кешу процесора: розрахунок симетричних шифрів (AES-GCM чи ChaCha20-Poly1305) переповнює кеш L1/L2, створюючи затримки під час обробки мільйонів пакетів на секунду.

На серверах зі швидкісними лінками 100GbE це обмежує максимальну пропускну здатність одного ядра процесора на рівні 18–25 Гбіт/с.

Архітектура Linux kTLS: розподіл рукостискання та передачі даних

Підсистема Kernel TLS розділяє ці процеси на два автономні рівні:

  • Контур управління в Userspace: завдання з підтримкою стану (перевірка ланцюжків сертифікатів X.509, узгодження ALPN та асиметричний обмін ключами ECDHE чи постквантовими шифрами) виконуються бібліотеками користувацького рівня (OpenSSL, GnuTLS).
  • Контур даних у Ядрі: після завершення рукостискання обчислені сесійні симетричні ключі передаються у сокетний шар ядра через системний виклик setsockopt().

Далі ядро Linux автоматично формує заголовки записів TLS, відстежує послідовність пакетів та здійснює шифрування безпосередньо у функціях tcp_sendmsg і tcp_recvmsg.

+--------------------------------------------------------------------------+
|                  ЕТАПИ ІНІЦІАЛІЗАЦІЇ KTLS У ЯДРІ LINUX                   |
+--------------------------------------------------------------------------+
|                                                                          |
|    1. Встановлення стандартного TCP-з'єднання                            |
|                                                                          |
|    2. Рукостискання TLS 1.3 на рівні Userspace (OpenSSL / BoringSSL)     |
|       - Узгодження алгоритму: TLS_AES_256_GCM_SHA384                     |
|       - Генерація сесійних симетричних ключів та векторів IV             |
|                                                                          |
|    3. Активація протоколу kTLS:                                          |
|       setsockopt(fd, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));            |
|                                                                          |
|    4. Передача криптографічних параметрів у ядро:                        |
|       setsockopt(fd, SOL_TLS, TLS_TX, &crypto_info, sizeof(info));       |
|       setsockopt(fd, SOL_TLS, TLS_RX, &crypto_info, sizeof(info));       |
|                                                                          |
|    5. Прямий обмін даними:                                               |
|       Ядро самостійно пакує та шифрує кадри                              |
|       Застосунки використовують splice(), sendfile() та MSG_ZEROCOPY     |
|                                                                          |
+--------------------------------------------------------------------------+

Налаштування kTLS у прикладних програмах

Реалізація підключення kTLS через OpenSSL 3.0+:

#include <sys/socket.h>
#include <netinet/tcp.h>
#include <linux/tls.h>
#include <openssl/ssl.h>

int enable_ktls_session(SSL *ssl, int socket_fd) {
    /* Крок 1: Увімкнення опції kTLS */
    SSL_set_options(ssl, SSL_OP_ENABLE_KTLS);

    /* Крок 2: Завершення криптографічного рукостискання */
    if (SSL_accept(ssl) <= 0) {
        return -1;
    }

    /* Крок 3: Перевірка готовності kTLS у ядрі */
    if (BIO_get_ktls_send(SSL_get_wbio(ssl))) {
        /* Ядро відповідає за формування та шифрування вихідних записів */
    }
    if (BIO_get_ktls_recv(SSL_get_rbio(ssl))) {
        /* Ядро самостійно розшифровує та верифікує вхідні пакети */
    }

    return 0;
}

Пересилання даних Zero-Copy через splice() та sendfile()

Головною перевагою kTLS є повернення системних викликів zero-copy у сферу захищених з'єднань.

Без kTLS використання splice() неможливе, оскільки дані мають спочатку потрапити до пам'яті застосунку для дешифрування. З kTLS обробка відбувається безпосередньо в мережевому стеку:

/* Пересилання zero-copy між двома мережевими сокетами */
int pipefd[2];
pipe(pipefd);

while (1) {
    /* Переміщення розшифрованих даних із сокета в буфер каналу ядра */
    ssize_t bytes = splice(inbound_fd, NULL, pipefd[1], NULL, 65536, SPLICE_F_MOVE);
    if (bytes <= 0) break;

    /* Пересилання в захищений сокет kTLS із шифруванням на льоту */
    splice(pipefd[0], NULL, outbound_fd, NULL, bytes, SPLICE_F_MOVE);
}

Апаратне розвантаження на SmartNIC

Окрім швидкісної програмної реалізації kTLS, мережеві адаптери дата-центрів (NVIDIA Mellanox ConnectX-6 Dx / BlueField та Intel Ethernet 800) підтримують повне апаратне розвантаження на пристрій.

Порівняння режимів kTLS

+--------------------------------------------------------------------------+
|                  ПОРІВНЯННЯ РЕЖИМІВ РОБОТИ ТЕХНОЛОГІЇ KTLS               |
+--------------------------------------------------------------------------+
| Режим               | Виконавчий модуль     | Навантаження CPU | 100GbE  |
+---------------------+-----------------------+------------------+---------+
| Програмний Userspace| ЦП (Простір процесів) | 100%             | Обмежено|
| Програмний kTLS (SW)| ЦП (Оптимізація ядра) | 35-50%           | Можливо |
| kTLS Device Offload | Чипсет SmartNIC       | <2%              | Повна лінія|
+--------------------------------------------------------------------------+

У разі апаратного розвантаження:

  1. Ядро позначає пакети sk_buff дескриптором контексту шифрування, не змінюючи самі дані.
  2. Контролер DMA на мережевій карті напряму зчитує незашифровані дані з RAM.
  3. Вбудовані блоки AES виконують шифрування під час передачі в оптичну лінію.

Увімкнення апаратного розвантаження:

# Перевірка можливостей інтерфейсу
ethtool -k eth0 | grep tls-hw

# Увімкнення апаратного шифрування на передачу та прийом
ethtool -K eth0 tls-hw-tx-offload on
ethtool -K eth0 tls-hw-rx-offload on

Поєднання з eBPF Sockmap для маршрутизації в ядрі

Для максимальної швидкодії kTLS інтегрується з технологією eBPF sockmap (BPF_MAP_TYPE_SOCKMAP та BPF_PROG_TYPE_SK_MSG).

Програма eBPF читає розшифровані дані безпосередньо в черзі ядра та виконує bpf_msg_redirect_map(), перенаправляючи трафік у вихідний тунель без переривань:

Вхідний адаптер ──> Розшифрування kTLS ──> Фільтр eBPF ──> Шифрування kTLS ──> Вихідний адаптер
                  [ Повний цикл у ядрі без перемикання контексту ]

Практичне значення для RiceGuard та VPNRice

В інфраструктурі VPNRice технологія kTLS забезпечує ключові переваги:

  1. Обробка значних навантажень: крайові вузли, що обслуговують DoH та DoT, одночасно обробляють сотні тисяч запитів із затримкою менше ніж 1 мілісекунда.
  2. Зниження тепловиділення: перенесення розрахунків AES-GCM на мережеві карти зменшує навантаження на процесори на 80%, запобігаючи перегріванню серверів.
  3. Захист пам'яті: розшифрований трафік не зберігається в динамічній пам'яті програм, що виключає ризик його витоку через аварійні дампи.

Експлуатаційні поради

Для надійного функціонування у робочому середовищі:

  • Ядро системи: використовувати версію Linux 5.15 LTS або новішу, а для SmartNIC — Linux 6.1+.
  • Привілеї: надати процесам права конфігурації сокетів ULP або видати CAP_NET_ADMIN.
  • Шифри: надавати перевагу алгоритмам TLS_AES_128_GCM_SHA256 та TLS_AES_256_GCM_SHA384 для сумісності з апаратними прискорювачами.

Поєднання Linux kTLS, передачі zero-copy та апаратних засобів SmartNIC задає якісний стандарт побудови сучасних швидкісних шлюзів VPN.