С нарастващото изчерпване на адресите IPv4, много оператори на телекомуникационни услуги се сблъскват с необходимостта да организират достъпа на своите клиенти до мрежата посредством транслация на адреси. В тази статия ще обясня как може да се постигне производителност на ниво Carrier Grade NAT на обикновени сървъри.
Няколко факта от историята
Темата за изчерпването на адресното пространство IPv4 вече не е нова. В един момент в RIPE се появиха опашки за адреси (waiting list), след това възникнаха борси, на които се търгуваха блокове адреси и се сключваха сделки за тяхното наемане. Постепенно операторите на телекомуникации започнаха да предлагат услуги за достъп до Интернет посредством транслация на адреси и портове. Някои не успяха да получат достатъчно адреси, за да предоставят „бял“ адрес на всеки абонат, а други започнаха да economize средства, отказвайки се от закупуването на адреси на вторичния пазар. Производителите на мрежово оборудване подкрепиха тази идея, тъй като тази функционалност обикновено изисква допълнителни модули или лицензи. Например, при Juniper в сериите маршрутизатори MX (с изключение на последните MX104 и MX204) изпълнението на NAPT може да се извърши на отделна сервизна карта MS-MIC, при Cisco ASR1k е необходима лицензия за CGN, а при Cisco ASR9k – отделен модул A9K-ISM-100 и лицензия A9K-CGN-LIC за него. В общи линии, удоволствието струва доста пари.
IPTables
Изпълнението на NAT не изисква специализирани изчислителни ресурси, задачата може да бъде решена от процесори с общо предназначение, които са инсталирани, например, в всеки домашен маршрутизатор. В мащабите на телекомуникационен оператор, задачата може да се реши с помощта на обикновени сървъри, работещи под управлението на FreeBSD (ipfw/pf) или GNU/Linux (iptables). Няма да разглеждаме FreeBSD, тъй като от доста време се отказах от използването на тази ОС, затова ще се спрем на GNU/Linux.
Включването на транслация на адреси е съвсем лесно. На първо място, необходимо е да запишете правило в iptables в таблицата nat:
iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to - --persistent
Операционната система ще зареди модула nf_conntrack, който ще проследява всички активни връзки и ще извършва необходимите преобразувания. Има няколко важни аспекта. Първо, тъй като става въпрос за NAT в мащабите на доставчик на услуги, е необходимо да се коригират таймаутите, тъй като с подразбиране размерът на таблицата за преобразуване бързо ще нарасне до катастрофични стойности. По-долу е пример за настройките, които използвах на своите сървъри:
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.8Mpps на един сървър (Xeon E5-1650v2). Оттогава в мрежовия стек на ядрото на GNU/Linux бяха извършени много оптимизации, производителността на един сървър на същото оборудване нарасна почти до 18-19 Gbit/s при 1.8-1.9 Mpps (това бяха крайни стойности), но нуждата от обема на трафика, обработван от един сървър, нарастваше много по-бързо. В крайна сметка бяха разработени схеми за балансиране на натоварването на различни сървъри, но всичко това увеличи сложността на настройките, обслужването и поддържането на качеството на предоставените услуги.
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> snat 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.5Mpps. Както се вижда, запасът от производителност на сървъра с nftables е огромен. В момента този сървър обработва до 30Gbit/s при 3Mpps и явно е в състояние да достигне физическото ограничение на мрежата от 40Gbps, като разполага с налични ресурси на CPU.
Надявам се материалът да бъде полезен за мрежовите инженери, които се опитват да подобрят производителността на своите сървъри.
Източник: habr.com
