Швидкий роутинг і NAT в Linux

По мірі вичерпання адрес IPv4, багато операторів зв’язку зіткнулися з необхідністю організовувати доступ своїх клієнтів до мережі з допомогою трансляції адрес. У цій статті я розповім, як можна отримати продуктивність рівня Carrier Grade NAT на commodity серверах.

Трохи історії

Тема вичерпання адресного простору IPv4 вже не нова. У якийсь момент в RIPE з’явилися черги очікування (waiting list), потім виникли біржі, на яких торгували блоками адрес і укладалися угоди щодо їх оренди. Поступово оператори зв’язку почали надавати послуги доступу в Інтернет за допомогою трансляції адрес і портів. Хтось не встиг отримати достатньо адрес, щоб видати «білий» адреса кожному абоненту, а хтось почав економити кошти, відмовившись від покупки адрес на вторинному ринку. Виробники мережевого устаткування підтримали цю ідею, тому що цей функціонал зазвичай вимагає додаткових модулів розширення або ліцензій. Наприклад, у Juniper в лінійці маршрутизаторів MX (крім останніх MX104 і MX204) виконувати NAPT можна на окремій сервісної карті MS-MIC, на Cisco ASR1k потрібна ліцензія СGN license, на Cisco ASR9k — окремий модуль A9K-ISM-100 і ліцензія A9K-CGN-LIC до нього. Загалом, задоволення коштує чималих грошей.

IPTables

Завдання виконання NAT не вимагає спеціалізованих обчислювальних ресурсів, її в змозі вирішувати процесори загального призначення, які встановлені, наприклад, у будь-якому домашньому роутері. У масштабах оператора зв’язку цю задачу можна вирішити використовуючи commodity сервери під управлінням FreeBSD (ipfw/pf) або GNU/Linux (iptables). Розглядати FreeBSD не будемо, оскільки я досить давно відмовився від використання цієї ОС, так що зупинимося на GNU/Linux.
Включити трансляцію адрес зовсім не складно. Для початку необхідно прописати правило iptables в таблиці nat:

iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j nat джерела є специфічною формою --to <pool_start_addr>-<pool_end_addr> --persistent

Операційна система завантажить модуль nf_conntrack, який буде стежити за всіма активними сполуками і виконувати необхідні перетворення. Тут є кілька тонкощів. По-перше, оскільки мова йде про NAT в масштабах оператора зв’язку, то необхідно підкрутити timeout’и, тому що зі значеннями за замовчуванням розмір таблиці трансляцій досить швидко зросте до катастрофічних значень. Нижче приклад налаштувань, які я використовував на своїх серверах:

net.ipv4.ip_forward = 1
net.ipv4.ip_local_port_range = 8192 65535 net.netfilter.nf_conntrack_generic_timeout = 300
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_established = 600
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 45
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 30
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 300
net.netfilter.nf_conntrack_tcp_timeout_unacknowledged = 300
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 60
net.netfilter.nf_conntrack_icmpv6_timeout = 30
net.netfilter.nf_conntrack_icmp_timeout = 30
net.netfilter.nf_conntrack_events_retry_timeout = 15
net.netfilter.nf_conntrack_checksum=0

І по-друге, оскільки за замовчуванням розмір таблиці трансляцій не розрахований на роботу в умовах оператора зв’язку, його необхідно збільшити:

net.netfilter.nf_conntrack_max = 3145728

Також необхідно збільшити і кількість buckets для хеш-таблиці, де зберігаються всі трансляції (це опція модуля nf_conntrack):

options nf_conntrack hashsize=1572864

Після цих нехитрих маніпуляцій виходить цілком працююча конструкція, яка може транслювати велику кількість клієнтських адрес в пул зовнішніх. Однак, продуктивність цього рішення залишає бажати кращого. У своїх перших спробах використання GNU/Linux для NAT (приблизно 2013 рік) я зміг отримати продуктивність близько 7Gbit/s при 0.8 Mpps на один сервер (Xeon E5-1650v2). З того часу в мережевому стеку ядра GNU/Linux було зроблено багато різноманітних оптимізацій, продуктивність одного сервера на тому ж залозі зросла практично до 18-19 Gbit/s при 1.8-1.9 млн. пакетів в секунду (це були граничні значення), але потреба в обсязі трафіку, оброблюваного одним сервером, зростала набагато швидше. У результаті були вироблені схеми балансування навантаження на різні сервери, але все це збільшило складність налаштування, обслуговування і підтримки якості надаваних послуг.

NFTables

Зараз модним напрямком в програмному «перекладанні пакетиків» є використання DPDK і XDP. На цю тему написана купа статей, зроблено багато різних виступів, з’являються комерційні продукти (наприклад, СКАТ від VasExperts). Але в умовах обмежених ресурсів програмістів в операторах зв’язку, пиляти самостійно яке-небудь «поділився» на базі цих фреймворків досить проблематично. Експлуатувати таке рішення надалі буде набагато складніше, зокрема, доведеться розробляти інструменти діагностики. Наприклад, штатний tcpdump з DPDK просто так не заробить, так і пакети, відправлені назад до проводу за допомогою XDP, він не «побачить». На тлі всіх розмов про нові технології виведення форвардингу пакетів в user-space, непоміченими залишилися доповіді і статті Pablo Neira Ayuso, меинтейнера iptables, про розробку flow offloading в nftables. Давайте розглянемо детальніше цей механізм.
Основна ідея полягає в тому, що якщо роутер пропустив пакети однієї сесії в обидві сторони потоку (TCP сесія перейшла в стан ESTABLISHED), то немає необхідності пропускати наступні пакети цієї сесії через всі правила firewall, оскільки всі ці перевірки все одно закінчаться передачею пакета далі в роутинг. Та й власне вибір маршруту виконувати не треба — ми вже знаємо в який інтерфейс і якому хосту треба переслати пакети межах цієї сесії. Залишається тільки зберегти цю інформацію і використовувати її для маршрутизації на ранній стадії обробки пакета. При виконанні NAT необхідно додатково зберегти інформацію про зміни адрес і портів, перетворених модулем nf_conntrack. Так, звичайно, в цьому випадку перестають працювати різні полисеры та інші інформаційно-статистичні правила iptables, але в рамках окремого завдання стоїть NAT або, наприклад, бордера — це не так вже важливо, тому що сервіси розподілені по пристроям.

Конфігурація

Щоб скористатися цією функцією нам треба:

  • Використовувати свіже ядро. Незважаючи на те, що сам функціонал з’явився ще в ядрі 4.16, досить довго він було дуже «сирий» і регулярно викликав kernel panic. Стабілізувався все приблизно в грудні 2019 року, коли вийшли LTS ядра 4.19.90 і 5.4.5.
  • Переписати правила iptables у формат nftables, використовуючи досить свіжу версію nftables. Точно працює у версії 0.9.0

Якщо з першим пунктом все в принципі зрозуміло, головне не забути включити модуль в конфігурацію при складанні (CONFIG_NFT_FLOW_OFFLOAD=m), то другий пункт вимагає пояснень. Правила nftables описуються зовсім не так, як в iptables. Документація розкриває практично всі моменти, так само є спеціальні конвертори правил iptables в nftables. Тому я наведу лише приклад налаштування NAT і flow offload. Невелика легенда для прикладу: — це мережеві інтерфейси, через які проходить трафік, реально їх може бути більше двох. , — початковий і кінцевий адресу діапазону «білих» адрес.
Конфігурація NAT дуже проста:

#! /usr/sbin/nft -f table nat { chain postrouting { type nat hook postrouting priority 100; oif <o_if> nat джерела є специфічною формою to <pool_addr_start>-<pool_addr_end> persistent
}
}

З flow offload трохи складніше, але цілком зрозуміло:

#! /usr/sbin/nft -f table inet filter { flowtable fastnat { hook ingress priority 0 devices = { <i_if>, <o_if> }
} chain forward { type filter hook forward priority 0; policy accept; ip protocol { tcp , udp } flow offload @fastnat;
}
}

Ось, власне, і вся настройка. Тепер весь TCP/UDP-трафік буде потрапляти в таблицю fastnat і оброблятися набагато швидше.

Результати

Щоб стало зрозуміло, наскільки це «набагато швидше», я докладу скріншот навантаження на два реальних сервера, з однією начинкою (Xeon E5-1650v2), однаково налаштованих, які використовують одне і теж ядро Linux, але виконують NAT в iptables (NAT4) і в nftables (NAT5).

На скріншоті немає графіка пакетів в секунду, але в профілі навантаження цих серверів середній розмір пакета в районі 800 байт, тому значення доходять до 1.5 Mpps. Як видно, запас продуктивності сервера з nftables величезний. На поточний момент цей сервер обробляє до 30Gbit/s при 3Mpps і явно здатний впертися у фізичне обмеження мережі 40Gbps, маючи при цьому вільні ресурси CPU.
Сподіваюся, цей матеріал буде корисний мережевим інженерам, які намагаються поліпшити продуктивність своїх серверів.

Джерело

Поділитися
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
Реклама

226

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Схожі записи

Почніть набирати текст зверху та натисніть "Enter" для пошуку. Натисніть ESC для відміни.

Повернутись вверх