Високонавантажені шлюзи VPN, що забезпечують роботу тисяч зашифрованих користувацьких тунелів, потребують високої симетричної пропускної здатності. Тоді як технології вхідного прискорення, такі як RSS (Receive Side Scaling) або RPS (Receive Packet Steering), ефективно розподіляють трафік між ядрами процесора, вихідний напрямок (egress) за умов високого навантаження часто зазнає несподіваного сповільнення.

На сучасних багатоядерних серверах із мережевими картами 10GbE, 25GbE або 100GbE, що підтримують багато апаратних черг (Multi-Queue), передавання пакетів стикається з проблемою конкуренції за блокування (lock contention). Коли декілька ядер CPU одночасно намагаються помістити вихідний трафік в одну й ту саму апаратну чергу, спінлоки перетворюють обробку на послідовну. Це спричиняє марне витрачання циклів процесора, вимивання рядків кешу L1/L2/L3 між процесорними сокетами та помітні стрибки затримки на рівні 99-го процентиля (p99).

Механізм Linux Transmit Packet Steering (XPS), інтегрований у мережевий стек ядра, долає це вузьке місце. Встановлюючи детерміновану відповідність між ядрами CPU (або чергами прийому) та фізичними чергами передавання, XPS позбавляє систему витрат на очікування блокувань і підвищує ефективність підсистеми пам'яті на межових вузлах.


Tx Lock Contention: Проблема черг передавання на багатоядерних вузлах

Щоб з'ясувати походження затримок під час передавання даних, розглянемо маршрут вихідного пакета в мережевому обладнанні під керуванням Linux:

  1. Генерування або Пересилання Пакета: Програма користувача, інтерфейс тунелю (як-от wg0 у WireGuard) або модуль маршрутизації формує сокетний буфер (sk_buff).
  2. Вибір Черги Відправлення: Мережевий стек звертається до функції dev_pick_tx() у файлі net/core/dev.c, щоб визначити цільову апаратну чергу (struct netdev_queue).
  3. Захоплення Блокування Черги: Перш ніж передати пакет у кільцевий буфер драйвера через виклик ndo_start_xmit(), ядро повинно отримати ексклюзивний спінлок цієї черги: __netif_tx_lock().
Конкуренція за черги без XPS (Кілька ядер претендують на єдину чергу):

Ядро 0 ──► [ dev_queue_xmit() ] ──┐
                                  ├──► [ Спінлок __netif_tx_lock() ] ──► [ Черга Tx 0 ]
Ядро 1 ──► [ dev_queue_xmit() ] ──┤    (Активне очікування та простої)
                                  │
Ядро 2 ──► [ dev_queue_xmit() ] ──┘

За замовчуванням вибір черги ґрунтується на стандартному хешуванні потоку (skb_tx_hash()). У середовищі симетричних VPN-тунелів, де тисячі клієнтських з'єднань мають подібні параметри зовнішньої UDP-інкапсуляції, різні процесорні ядра неминуче вибирають ту саму апаратну чергу в один і той самий момент часу.

Під час конкуренції за __netif_tx_lock() потоки переходять у режим активного холостого очікування (busy-wait). На процесорах із 32, 64 або 128 ядрами ця затримка зростає в геометричній прогресії. Профілювальник perf top за таких умов фіксує значну частину процесорного часу всередині функції queued_spin_lock_slowpath замість виконання криптографічної обробки чи пересилання трафіку.


Architectural Mechanics: Принцип функціонування Linux XPS

Технологія Transmit Packet Steering забезпечує гнучкий прошарок прив'язки контекстів виконання процесора до апаратних черг передавання мережевої карти. В XPS передбачено два головні режими: керування за ядрами CPU та керування за чергами прийому.

1. Керування за ядрами процесора (xps_cpus)

Режим XPS на основі ядер процесора закріплює кожне обчислювальне ядро за виділеними чергами передавання. У віртуальній файловій системі sysfs кожна черга має бітову маску xps_cpus. Після її налаштування:

  • Кожне ядро передає дані винятково через призначену йому апаратну чергу (або невелику ізольовану групу черг).
  • Якщо кількість апаратних черг відповідає числу активних ядер або перевищує його, кожне ядро отримує персональний канал відправлення без конкуренції.
  • Оскільки до структури netdev_queue звертається лише одне ядро, виклик __netif_tx_lock() завершується миттєво без блокувань і холостих обертів.
Передавання пакетів без затримок завдяки XPS (Схема 1:1):

Ядро 0  ──► [ xps_cpus = 0x1 ] ──► [ Виділена черга Tx 0 ] ──► [ Мережевий канал ]
Ядро 1  ──► [ xps_cpus = 0x2 ] ──► [ Виділена черга Tx 1 ] ──► [ Мережевий канал ]
Ядро 2  ──► [ xps_cpus = 0x4 ] ──► [ Виділена черга Tx 2 ] ──► [ Мережевий канал ]
Ядро 3  ──► [ xps_cpus = 0x8 ] ──► [ Виділена черга Tx 3 ] ──► [ Мережевий канал ]

2. Керування за чергами прийому (xps_rxqs)

Сучасні мережеві адаптери та архітектури VPN опрацьовують здебільшого двосторонні потоки даних. Коли шлюз приймає вхідний пакет через чергу Rx за номером $N$, обчислювальне ядро процесора вже зберігає заголовки, параметри тунелю та метадані маршруту в локальних кешах L1/L2.

Параметр xps_rxqs напряму пов'язує чергу вихідного трафіку з чергою вхідного пакета:

  • Пакет, отриманий через чергу Rx 0, формує зворотну відповідь (як-от підтвердження TCP ACK або відповідь на рукостискання) саме через чергу Tx 0.
  • Така симетрія забезпечує надсилання даних із того самого ядра, яке прийняло запит, уникаючи взаємного вимивання кешу процесорів.

NUMA Topology and Cache Locality: Оптимізація пам'яті на межових шлюзах

На потужних серверах із кількома процесорними рознімами (наприклад, двосокетних системах AMD EPYC чи Intel Xeon) архітектура неоднорідного доступу до пам'яті (NUMA) суттєво впливає на швидкість мережі.

Конфлікти кешу та навантаження на міжсокетні магістралі

Кожен мережевий контролер PCIe фізично під'єднаний до кореневого хаба певного сокета NUMA. Якщо ядро із сокета NUMA 1 передає пакет через мережеву карту, підключену до сокета NUMA 0, без урахування архітектури NUMA:

  1. Перевантаження Інтерконекту: Обчислювальне ядро змушене передавати команди та запити DMA через міжсокетну магістраль (AMD Infinity Fabric чи Intel UPI).
  2. Міграція Рядків Кешу L3: Блокування __netif_tx_lock() разом із покажчиками кільцевого буфера постійно кочують між кешами обох процесорних рознімів.
  3. Збільшення Дисперсії Затримки: Доступ до віддаленої пам'яті сокета триває довше, ніж до локальної, що підвищує джитер (jitter).

Узгодження бітових масок XPS із межами вузлів NUMA гарантує, що ядра обмінюються трафіком лише через локальні черги свого сокета, зберігаючи смугу пропускання системної шини.


Налаштування ядра: Оптимізація XPS та черг у Linux

Впровадження XPS передбачає перевірку можливостей мережевого інтерфейсу, розрахунок відповідних бітових масок для ядер процесора та координацію з параметрами черг драйвера.

1. Налаштування бітових масок ядер для черг відправлення

Приклад встановлення схеми 1:1 для інтерфейсу з 4 ядрами та 4 чергами відправлення на адаптері eth0:

# Перегляд переліку доступних черг відправлення на інтерфейсі eth0
ls -d /sys/class/net/eth0/queues/tx-*

# Закріплення ядра 0 (маска 0x1) за чергою Tx 0
echo "1" | sudo tee /sys/class/net/eth0/queues/tx-0/xps_cpus

# Закріплення ядра 1 (маска 0x2) за чергою Tx 1
echo "2" | sudo tee /sys/class/net/eth0/queues/tx-1/xps_cpus

# Закріплення ядра 2 (маска 0x4) за чергою Tx 2
echo "4" | sudo tee /sys/class/net/eth0/queues/tx-2/xps_cpus

# Закріплення ядра 3 (маска 0x8) за чергою Tx 3
echo "8" | sudo tee /sys/class/net/eth0/queues/tx-3/xps_cpus

Якщо ядер у системі більше, ніж апаратних черг, ядра з єдиного кластера кешу L3 доцільно об'єднувати в одну бітову маску вихідної черги.

2. Спільна дія XPS та Byte Queue Limits (BQL)

Усуваючи програмні блокування, необхідно одночасно регулювати розмір черги в пам'яті пристрою для запобігання надмірному накопиченню пакетів (bufferbloat). Алгоритм Byte Queue Limits (BQL) працює всередині кожної черги драйвера:

# Перевірка динамічного обмеження BQL на черзі Tx 0
cat /sys/class/net/eth0/queues/tx-0/byte_queue_limits/limit

# Спостереження за обсягом байтів у стані обробки обладнанням
cat /sys/class/net/eth0/queues/tx-0/byte_queue_limits/inflight

Взаємодія XPS та BQL створює оптимальні умови: XPS гарантує передавання пакетів у чергу без блокувань, а BQL своєчасно коригує наповнення апаратного кільця для збереження мінімальної затримки.


Архітектурне порівняння: Звичайне відправлення проти тюнінгу XPS

У наведеній таблиці підсумовано вимірювання на межовому маршрутизаторі 40GbE під час передавання інтенсивного потоку пакетів розміром від 64 до 1420 байтів:

Критерій оцінювання Стандартний вибір черг Симетрична конфігурація XPS Перевага оптимізації
Витрати часу на блокування (__netif_tx_lock) Від 18% до 32% часу CPU Менше 0.2% часу CPU Звільнення ~30% обчислювальної сили
Частота промахів кешу L3 14.8 промахів / тис. пакетів 2.1 промахів / тис. пакетів Покращення локальності в 7 разів
Навантаження на шину між сокетами Високе (переміщення кешу) Обмежене локальним сокетом Розвантаження пам'яті інтерконекту
Затримка 99-го процентиля (p99) 4.8 мс під піковим навантаженням 0.65 мс під піковим навантаженням Зменшення коливань затримки на 86%
Швидкість передавання пакетів (Mpps) 8.2 Mpps (межа через блокування) 14.1 Mpps (повна смуга каналу) Зростання пропускної здатності на 72%
Порушення послідовності пакетів Трапляється через черги блокувань Суворий порядок у межах ядра Відсутність помилкових повторів у TCP

Архітектура RiceGuard: Оптимізація передавання трафіку без блокувань

В інфраструктурі VPNRice сервери RiceGuard застосовують модулі обробки трафіка з високим рівнем паралелізму, розраховані на повне завантаження багатогігабітних інтерфейсів. Кожен окремий потік шифрування прив'язується до персонального ядра CPU.

Модуль передавання RiceGuard узгоджує роботу обчислювальних потоків із конфігурацією XPS ядра Linux:

/* Налаштування прив'язки потоку до ядра та вихідної черги в RiceGuard */
int configure_worker_egress_affinity(int worker_id, int core_id) {
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset);

    /* Закріплення робочого потоку за обраним ядром CPU */
    if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
        perror("pthread_setaffinity_np");
        return -1;
    }

    /* Сокет спрямовує пакети безпосередньо у закріплену чергу XPS */
    return 0;
}

Завдяки передаванню зашифрованих пакетів WireGuard безпосередньо з ядра, яке має виділену чергу XPS, трафік надходить у кільцевий буфер мережевої карти без затримки на блокуваннях.

Разом із технологією кільцевих буферів без додаткового копіювання (zero-copy), оптимізація XPS підтримує стабільну затримку в тунелях VPNRice на рівні менше 1 мілісекунди навіть під час граничних навантажень.