Szybkie routowanie i NAT w systemie Linux

W miarę wyczerpywania się adresów IPv4, wielu operatorów telekomunikacyjnych zmierzyło się z koniecznością organizacji dostępu swoich klientów do sieci za pomocą translacji adresów. W tym artykule opowiem, jak można osiągnąć wydajność na poziomie Carrier Grade NAT na serwerach typu commodity.

Historia

Temat wyczerpywania się przestrzeni adresowej IPv4 przestał być nowością. W pewnym momencie w RIPE pojawiły się listy oczekujących, a następnie zaczęły powstawać giełdy, na których handlowano blokami adresów oraz zawierano transakcje dotyczące ich wynajmu. Z czasem operatorzy telekomunikacyjni zaczęli oferować usługi dostępu do Internetu za pomocą translacji adresów i portów. Niektórzy nie zdążyli uzyskać wystarczającej liczby adresów, aby przydzielić "biały" adres każdemu abonentowi, a inni zaczęli oszczędzać, rezygnując z zakupu adresów na rynku wtórnym. Producenci sprzętu sieciowego wsparli tę ideę, ponieważ ten funkcjonalność zazwyczaj wymaga dodatkowych modułów rozszerzeń lub licencji. Na przykład produkując routery MX, firma Juniper (z wyjątkiem najnowszych MX104 i MX204) umożliwia wykonywanie NAPT na oddzielnej karcie serwisowej MS-MIC, podczas gdy na Cisco ASR1k wymagana jest licencja CGN, a na Cisco ASR9k — oddzielny moduł A9K-ISM-100 oraz licencja A9K-CGN-LIC do niego. Generalnie, zabawa ta nie jest tania.

IPTables

Zadanie wykonywania NAT nie wymaga specjalistycznych zasobów obliczeniowych; mogą się nim zająć procesory ogólnego przeznaczenia, które są zainstalowane przykładowo w każdym domowym routerze. Na poziomie operatora telekomunikacyjnego zadanie to można rozwiązać, korzystając z serwerów commodity zarządzanych przez FreeBSD (ipfw/pf) lub GNU/Linux (iptables). Nie będziemy rozważać FreeBSD, ponieważ od dość dawna zrezygnowałem z używania tego systemu operacyjnego, więc skupimy się na GNU/Linux.

Włączenie translacji adresów nie jest wcale trudne. Na początek należy wprowadzić regułę w iptables do tabeli nat:

iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to - --persistent

System operacyjny załaduje moduł nf_conntrack, który będzie monitorował wszystkie aktywne połączenia i dokonywał niezbędnych przekształceń. Istnieje kilka niuansów. Po pierwsze, ponieważ mamy do czynienia z NAT na poziomie operatora telekomunikacyjnego, trzeba dostosować czasy oczekiwania, ponieważ przy domyślnych ustawieniach rozmiar tabeli translacji szybko wzrośnie do katastrofalnych wartości. Poniżej przykład ustawień, które zastosowałem na swoich 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 translacji nie jest dostosowany do pracy w warunkach operatora telekomunikacyjnego, należy go zwiększyć:

net.netfilter.nf_conntrack_max = 3145728

Należy także zwiększyć liczbę buckets w tablicy haszyujących, która przechowuje wszystkie translacje (to opcja modułu nf_conntrack):

options nf_conntrack hashsize=1572864

Po tych prostych manipulacjach otrzymujemy dobrze działającą konstrukcję, która może tłumaczyć dużą liczbę adresów klientów na pulę zewnętrznych. Jednak wydajność tego rozwiązania pozostawia wiele do życzenia. W swoich pierwszych próbach wykorzystania GNU/Linux do NAT (około 2013 roku) udało mi się osiągnąć wydajność około 7Gbit/s przy 0.8Mpps na jeden serwer (Xeon E5-1650v2). Od tego czasu w stosie sieciowym jądra GNU/Linux wprowadzono wiele różnych optymalizacji, a wydajność jednego serwera na tym samym sprzęcie wzrosła praktycznie do 18-19 Gbit/s przy 1.8-1.9 Mpps (były to wartości graniczne), ale potrzeba w zakresie przetwarzanego ruchu przez jeden serwer rosła znacznie szybciej. Ostatecznie opracowano schematy równoważenia obciążenia na różne serwery, co zwiększyło złożoność konfiguracji, utrzymania i zapewnienia jakości świadczonych usług.

NFTables

Obecnie modnym trendem w programowym „przekładaniu pakietów” jest stosowanie DPDK i XDP. Na ten temat napisano wiele artykułów, odbyło się wiele różnych wystąpień, a na rynku pojawiły się komercyjne produkty (na przykład SKAT od VasExperts). Jednak w warunkach ograniczonych zasobów programistów wśród operatorów telekomunikacyjnych, samodzielne tworzenie jakiegoś „wynalazku” na bazie tych frameworków jest dość problematyczne. Wykorzystywanie takiego rozwiązania w przyszłości będzie o wiele trudniejsze, szczególnie dlatego, że konieczne będzie opracowanie narzędzi diagnostycznych. Na przykład standardowy tcpdump z DPDK nie zadziała, a pakiety wysyłane z powrotem do sieci za pomocą XDP nie będą przez niego „widziane”. W obliczu wszystkich rozmów na temat nowych technologii przesyłania pakietów w przestrzeni użytkownika, umknęły te sprawozdania i artykułu Pablo Neira Ayuso, maintainer iptables, na temat rozwoju flow offloading w nftables. Przyjrzyjmy się temu mechanizmowi bliżej.

Główna idea polega na tym, że jeśli router przepuścił pakiety jednej sesji w obie strony strumienia (sesja TCP przeszła w stan ESTABLISHED), to nie ma potrzeby przepuszczania kolejnych pakietów tej sesji przez wszystkie zasady zapory, ponieważ wszystkie te kontrole i tak zakończą się przekazaniem pakietu dalej do routingu. Właściwie również nie trzeba podejmować decyzji co do trasy — już wiemy, na który interfejs i do którego hosta należy przekazać pakiety w ramach tej sesji. Pozostaje tylko zachować te informacje i wykorzystać je do routingu na wczesnym etapie przetwarzania pakietu. Przy wykonywaniu NAT konieczne jest dodatkowe zachowanie informacji o zmianach adresów i portów, przekształconych przez moduł nf_conntrack. Tak, oczywiście, w takim przypadku przestają działać różne policery i inne informacje/statystyczne zasady w iptables, ale w ramach zadania oddzielnego NAT-u lub na przykład bordera — nie jest to aż tak istotne, ponieważ usługi są rozproszone na urządzeniach.

Konfiguracja

Aby skorzystać z tej funkcji, musimy:

  • Użyć świeżego jądra. Mimo że sama funkcjonalność pojawiła się już w jądrze 4.16, przez długi czas była bardzo „surowa” i regularnie powodowała kernel panic. Stabilizowało się to mniej więcej w grudniu 2019 roku, kiedy to wydano jądra LTS 4.19.90 i 5.4.5.
  • Przepisz zasady iptables w formacie nftables, używając wystarczająco nowej wersji nftables. Działa poprawnie w wersji 0.9.0

Jeśli pierwszy punkt jest w zasadzie jasny, najważniejsze to niezapomnienie o włączeniu modułu w konfiguracji podczas kompilacji (CONFIG_NFT_FLOW_OFFLOAD=m), to drugi punkt wymaga wyjaśnienia. Zasady nftables opisuje się zupełnie inaczej niż w iptables. Dokumentacja ujawnia praktycznie wszystkie aspekty, są też specjalne konwertery reguł z iptables do nftables. Dlatego przekażę tylko przykład konfiguracji NAT i flow offload. Krótkie wprowadzenie do przykładu: , — to interfejsy sieciowe, przez które przechodzi ruch, w rzeczywistości może ich być więcej niż dwa. , — to początkowy i końcowy adres zakresu „białych” adresów.

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
        }
}

Z flow offload jest nieco bardziej skomplikowane, ale całkowicie 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;
        }
}

I to wszystko, teraz cały ruch TCP/UDP trafi do tabeli fastnat i będzie przetwarzany znacznie szybciej.

Wyniki

Aby pokazać, jak duża to 'znaczna' poprawa, dołączę zrzut ekranu z obciążeniem dwóch rzeczywistych serwerów, o identycznych parametrach (Xeon E5-1650v2), identycznie skonfigurowanych, używających tego samego jądra Linux, ale realizujących NAT w iptables (NAT4) i w nftables (NAT5).

Szybkie routowanie 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 osiągają do 1,5Mpps. Jak widać, rezerwa wydajności serwera z nftables jest ogromna. Aktualnie ten serwer przetwarza do 30Gbit/s przy 3Mpps i z pewnością jest w stanie osiągnąć fizyczne ograniczenie sieci 40Gbps, mając jednocześnie wolne zasoby CPU.

Mam nadzieję, że ten materiał będzie przydatny inżynierom sieciowym, starającym się poprawić wydajność swoich serwerów.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster