Szybki routing i NAT w systemie Linux

W miarę wyczerpywania się adresów IPv4 wielu operatorów telekomunikacyjnych staje przed koniecznością zapewnienia swoim klientom dostępu do sieci za pomocą translacji adresów. W tym artykule powiem Ci, jak uzyskać wydajność NAT klasy Carrier Grade na serwerach standardowych.

Trochę historii

Temat wyczerpania przestrzeni adresowej IPv4 nie jest już nowy. W pewnym momencie w RIPE pojawiły się listy oczekujących, potem pojawiły się giełdy, na których handlowano blokami adresów i zawierano umowy na ich dzierżawę. Stopniowo operatorzy telekomunikacyjni zaczęli świadczyć usługi dostępu do Internetu z wykorzystaniem translacji adresów i portów. Niektórym nie udało się pozyskać wystarczającej liczby adresów, aby każdemu abonentowi nadać „biały” adres, inni zaś zaczęli oszczędzać, odmawiając zakupu adresów na rynku wtórnym. Producenci sprzętu sieciowego poparli ten pomysł, ponieważ funkcjonalność ta zwykle wymaga dodatkowych modułów rozszerzeń lub licencji. Przykładowo w linii routerów MX firmy Juniper (z wyjątkiem najnowszych MX104 i MX204) można wykonać NAPT na osobnej karcie serwisowej MS-MIC, Cisco ASR1k wymaga licencji CGN, Cisco ASR9k wymaga osobnego modułu A9K-ISM-100 oraz licencję A9K-CGN -LIC dla niego. Ogólnie rzecz biorąc, przyjemność kosztuje dużo pieniędzy.

IPTables

Zadanie wykonania NAT nie wymaga specjalistycznych zasobów obliczeniowych, można je rozwiązać za pomocą procesorów ogólnego przeznaczenia, które są instalowane na przykład w dowolnym domowym routerze. W skali operatora telekomunikacyjnego problem ten można rozwiązać przy użyciu standardowych serwerów z systemem FreeBSD (ipfw/pf) lub GNU/Linux (iptables). Nie będziemy rozważać FreeBSD, ponieważ... Przestałem używać tego systemu operacyjnego już dawno temu, więc zostanę przy GNU/Linuksie.

Włączenie translacji adresów wcale nie jest trudne. Najpierw musisz zarejestrować regułę w iptables w tabeli nat:

iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to <pool_start_addr>-<pool_end_addr> --persistent

System operacyjny załaduje moduł nf_conntrack, który będzie monitorował wszystkie aktywne połączenia i wykonywał niezbędne konwersje. Jest tu kilka subtelności. Po pierwsze, skoro mówimy o NAT w skali operatora telekomunikacyjnego, konieczne jest dostosowanie limitów czasu, ponieważ przy wartościach domyślnych rozmiar tabeli tłumaczeń szybko wzrośnie do wartości katastrofalnych. Poniżej znajduje się przykład ustawień, których użyłem na moich serwerach:

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

Po drugie, ponieważ domyślny rozmiar tabeli tłumaczeń nie jest przeznaczony do pracy w warunkach operatora telekomunikacyjnego, należy go zwiększyć:

net.netfilter.nf_conntrack_max = 3145728

Konieczne jest także zwiększenie ilości zasobników dla tablicy mieszającej przechowującej wszystkie transmisje (jest to opcja w module nf_conntrack):

options nf_conntrack hashsize=1572864

Po tych prostych manipulacjach uzyskuje się całkowicie działający projekt, który może przetłumaczyć dużą liczbę adresów klientów na pulę adresów zewnętrznych. Jednak wydajność tego rozwiązania pozostawia wiele do życzenia. Podczas moich pierwszych prób użycia GNU/Linux do NAT (około 2013 r.) udało mi się uzyskać wydajność około 7 Gbit/s przy 0.8 Mpps na serwer (Xeon E5-1650v2). Od tego czasu dokonano wielu różnych optymalizacji stosu sieciowego jądra GNU/Linux, wydajność jednego serwera na tym samym sprzęcie wzrosła do prawie 18-19 Gbit/s przy 1.8-1.9 Mpps (były to wartości maksymalne) , ale zapotrzebowanie na wolumen ruchu przetwarzanego przez jeden serwer rosło znacznie szybciej. W rezultacie opracowano schematy równoważące obciążenie różnych serwerów, ale wszystko to zwiększyło złożoność konfigurowania, utrzymywania i utrzymywania jakości świadczonych usług.

Tabele NFT

Obecnie modnym trendem w oprogramowaniu „shifting bags” jest wykorzystanie DPDK i XDP. Na ten temat napisano wiele artykułów, wygłoszono wiele różnych przemówień, pojawiają się produkty komercyjne (np. SKAT od VasExperts). Jednak biorąc pod uwagę ograniczone zasoby programistyczne operatorów telekomunikacyjnych, samodzielne stworzenie jakiegokolwiek „produktu” w oparciu o te frameworki jest dość problematyczne. W przyszłości eksploatacja takiego rozwiązania będzie znacznie trudniejsza, w szczególności konieczne będzie opracowanie narzędzi diagnostycznych. Na przykład standardowy tcpdump z DPDK nie będzie działał w ten sposób i nie „widzi” pakietów wysyłanych z powrotem do przewodów za pomocą XDP. Pośród całej tej dyskusji na temat nowych technologii przesyłania pakietów do przestrzeni użytkownika pozostały one niezauważone raporty и Artykuł Pablo Neira Ayuso, opiekun iptables, o rozwoju odciążania przepływu w nftables. Przyjrzyjmy się bliżej temu mechanizmowi.

Główną ideą jest to, że jeśli router przepuszczał pakiety z jednej sesji w obu kierunkach przepływu (sesja TCP przeszła w stan ESTABLISHED), to nie ma potrzeby przepuszczać kolejnych pakietów tej sesji przez wszystkie reguły firewalla, gdyż wszystkie te kontrole nadal zakończą się przesłaniem pakietu dalej do routingu. I tak naprawdę nie musimy wybierać trasy – wiemy już, do jakiego interfejsu i do jakiego hosta musimy wysłać pakiety w ramach tej sesji. Pozostaje tylko przechowywać te informacje i używać ich do routingu na wczesnym etapie przetwarzania pakietów. Podczas wykonywania NAT konieczne jest dodatkowo zapisanie informacji o zmianach adresów i portów przetłumaczonych przez moduł nf_conntrack. Tak, oczywiście, w tym przypadku różne policje i inne reguły informacyjno-statystyczne w iptables przestają działać, ale w ramach zadania osobnego stałego NAT-a czy np. granicy nie jest to aż tak istotne, gdyż usługi są dystrybuowane na urządzeniach.

Konfiguracja

Aby skorzystać z tej funkcji potrzebujemy:

  • Użyj świeżego jądra. Pomimo tego, że sama funkcjonalność pojawiła się w jądrze 4.16, przez dłuższy czas była bardzo „surowa” i regularnie powodowała panikę w jądrze. Wszystko ustabilizowało się w okolicach grudnia 2019, kiedy wydano jądra LTS 4.19.90 i 5.4.5.
  • Przepisz reguły iptables do formatu nftables, używając całkiem nowej wersji nftables. Działa dokładnie w wersji 0.9.0

Jeśli w zasadzie wszystko jest jasne w pierwszym punkcie, najważniejsze jest, aby podczas montażu nie zapomnieć o uwzględnieniu modułu w konfiguracji (CONFIG_NFT_FLOW_OFFLOAD=m), to drugi punkt wymaga wyjaśnienia. Reguły nftables są opisane zupełnie inaczej niż w iptables. Dokumentacja ujawnia prawie wszystkie punkty, są też specjalne konwertery reguły od iptables do nftables. Dlatego podam tylko przykład ustawienia NAT i odciążenia przepływu. Na przykład mała legenda: , - są to interfejsy sieciowe, przez które przechodzi ruch, w rzeczywistości może być ich więcej niż dwa. , — adres początkowy i końcowy zakresu adresów „białych”.

Konfiguracja NAT jest bardzo prosta:

#! /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
        }
}

W przypadku odciążania przepływu jest to trochę bardziej skomplikowane, ale całkiem zrozumiałe:

#! /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;
        }
}

To właściwie cała konfiguracja. Teraz cały ruch TCP/UDP będzie trafiał do tabeli fastnat i będzie przetwarzany znacznie szybciej.

wyniki

Aby było jasne, o ile jest to „znacznie szybsze”, załączę zrzut ekranu obciążenia na dwóch rzeczywistych serwerach, z tym samym sprzętem (Xeon E5-1650v2), identycznie skonfigurowanymi, używając tego samego jądra Linuksa, ale wykonując NAT w iptables (NAT4) i w nftables (NAT5).

Szybki routing i NAT w systemie Linux

Na zrzucie ekranu nie ma wykresu pakietów na sekundę, ale w profilu obciążenia tych serwerów średni rozmiar pakietu wynosi około 800 bajtów, więc wartości dochodzą do 1.5Mpps. Jak widać serwer z nftables ma ogromną rezerwę wydajności. Obecnie serwer ten przetwarza do 30 Gbit/s przy 3 Mpps i jest w stanie bez wątpienia sprostać ograniczeniom sieci fizycznej wynoszącym 40 Gb/s, mając jednocześnie wolne zasoby procesora.

Mam nadzieję, że ten materiał będzie przydatny dla inżynierów sieciowych próbujących poprawić wydajność swoich serwerów.

Źródło: www.habr.com

Dodaj komentarz