Быстрый роутинг и 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 SNAT --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.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. Небольшая легенда для примера: <i_if>, <o_if> — это сетевые интерфейсы, через которые проходит трафик, реально их может быть больше двух. <pool_addr_start>,<pool_addr_end> — начальный и конечный адрес диапазона «белых» адресов.

    Конфигурация 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.

    Надеюсь, этот материал будет полезен сетевым инженерам, пытающимся улучшить производительность своих серверов.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 26

      0
      Интересно было бы сравнить с этим модулем:
      github.com/andrsharaev/xt_NAT

      И вопрос, в случае NFTables ALG популярных протоколов работает?
        +1
        Интересно было бы сравнить с этим модулем:
        github.com/andrsharaev/xt_NAT

        Предположу, что быстрее. Объясню почему. Это код модуля для iptables, соответственно, для этого должен работать iptables, который медленнее, чем nftables из-за иной организации правил и хуков в ядре. А в случае с flow offload в передаче пакетов в установленной сессии не участвует ни iptables, ни nftables. Поэтому даже чистый роутинг с помощью flow offload получается быстрее, чем использование пустых правил iptables без conntrack.


        И вопрос, в случае NFTables ALG популярных протоколов работает?

        Да, любые доступные iptables-helpers можно использовать в этом случае. Пример конфига:


        table raw {
                ct helper pptp-gre {
                        type "pptp" protocol tcp;
                }
        
                chain prerouting { 
                        type filter hook prerouting priority -300;
                        tcp dport 1723 ct helper set "pptp-gre"
                }
        }

        Offload в таком случае не будет работать для соединений, у которых настроен ALG и они свалятся в обычный conntrack

          +1
          Спасибо за ответ.
          Не поделитесь, какой еще тюнинг сетевого стека применяете?
          Делаете ли распределение очередей сетевой карты по ядрам, отключение каких-либо затратных фич, типа, проверки CRC, etc?
            +1

            Слегка оптимизированная сборка ядра, отключение всех mitigation, отключение audit и выставление максимальной производительности CPU в ущерб энергопотреблению.
            Распределение очередей сетевой карты по ядрам сейчас выполняется автоматически практически во всех драйверах сетевых карт. Отключение CRC ничего не дает — все высокоскоростные карты умеют делать это аппаратно. Отключение flow-control дает равномерное наполнение буферов карт. Ну и включение GRO+GSO даёт значительную экономию CPU.

              0
              Ну и включение GRO+GSO даёт значительную экономию CPU.

              а оно разве работает с форвардингом пакетов?

                0

                конечно работает. GSO разбирает пакет, перед отправкой в провода, ровно до того состояния, которое было до GRO. Из-за этого ограничения эффективность GRO ниже, чем LRO, но нет ограничения на роутинг

        0

        А насколько утилизируется процессор при ваших 30 гигабитах и сколько при этом сессий? Если не секрет конечно же.
        Спрашиваю, а не экстраполирую данные из графика в статье потому что оно может очень нелинейно расти при увеличении pps и числа сессий.

          +1

          Процессор в пике был до 50%, сессий около 1.6 млн. Экстраполирование чуть не срабатывает, потому что мы перешли на 40Gbps интерфейсы вместо 2х10Gbps в LAG, чем немного выиграли CPU. Нелинейный рост начинается после достаточно высокой загрузки CPU, когда в ядре набирается большое количество отложенных операций, например, удаление протухших сессий, или удаления старых данных после RCU grace period. Выглядит как резкий всплеск нагрузки на одно или нескольких ядрах. Поэтому лучше не доводить стабильную нагрузку на сервер больше 80% CPU.

          0
          Не тестировали ли случайно DPDK doc.dpdk.org/guides/prog_guide/packet_framework.html решения, типа такого
          vigor-nf.github.io?
            +1

            Как я и писал в статье — использование DPDK-based решений накладывает дополнительные требования на эксплуатацию. Как минимум, какие инструменты использовать для диагностики сетевой части (tcpdump, просмотр fib, динамическое изменение rib)? И второе — какие инструменты использовать для мониторинга? Счетчики байт и пакетов на интерфейсах внутри DPDK показывают только текущую сетевую нагрузку, но что мониторить, чтобы понимать запас производительности? DPDK использует CPU в режиме poll — нагрузка на задействованых ядрах всегда 100% вне зависимости от того, 0 пакетов обработано за цикл или миллионы — как понять, сколько CPU осталось и когда начнутся потери пакетов?

            +1

            Здравствуйте коллеги! Требуется помощь знатоков.
            Есть немолодой NAT сервер. 2 xeon 56xx по 4 ядра/8 потоков. 4 intel gigabit ethernet собраных в bond. Потоки серевых карт равномерно распределены по всем ядрам. Oracle Linux 6 с UEK ядром 2.6.39 (которое на базе 3.0). Используются iptables (NAT, ipt-netflow, ipt-ratelimit), ipset.
            В нормальных условиях без проблем прокачивает все 4 гигабита. Загрузка процессоров в пределе 10%. LA — в пределах 1. Conntrack count около 150 тыс.
            Но бывает активизируются вирусы/ботнеты, которые со страшной силой начинают перебирать IP-адреса, этим создавая сверхвысокую нагрузку на сервер. При этом загрузка всех ядер достигает 100% (в основном si), LA — десатки единиц, conntrack резко возрастает, трафик и pps резко падают практически до нуля. Зачастую один такой абонент способен поставить сервер колом.
            На сколько я понимаю — сервер не способен обработать такое количество новых соединений. Что можно сделать в подобной ситуации, что-бы улучшить состояние сервера?

              +1

              Начать надо с обновления ядра, потому что 2.6.39, да и 3.0 — это очень старые ядра. Со времен тех ядер в сетевую подсистему внесли огромное количество изменений, избавившись от глобального spinlock'a, оптимизировав conntrack до возможности добавления до 1 млн новых сессий в секунду. Правда, параллельная обработка пакетов на платформе E56xx приводит к тому, что разные процессоры начинают "сражаться" за общую шину PCIe, но, кажется, 4Гбит/с можно обрабатывать и одним процессором.

                +1
                Совершенно точно написали выше, нужно оставлять один физ. CPU, HT отключать.
                У вас лимиты на сессии для абонентов настроены? Это должно спасти от проблемных абонентов.
                  0

                  Лимиты сессий настроены с помощью iptables connlim, но это не спасает от огромного потока новых соединений.
                  Пробовали отключать HT, кардинальным образом ничего не изменило (при нормальной нагрузке), решили оставить.
                  Сервер старенький, поэтому ОС и ядро тех времён. Стоит ли пробовать более новое ядро под rhel 6 из elrepo? Или сразу думать о современной ОС? Тогда какое ядро лучше использовать под rhel, стандартное, oracle uek или elrepo?
                  Интересный момент. На текущем ядре модуль igb имеет параметры (RSS, LRO и т.п.), а на новых ядрах этот модуль без параметров. Это как-то влияет на производительность?

                    0
                    Сервер старенький, поэтому ОС и ядро тех времён. Стоит ли пробовать более новое ядро под rhel 6 из elrepo? Или сразу думать о современной ОС?

                    Новая ОС — смотря что используется в user-space части этого сервера. Если всё сосредоточено в ядре/iptables и новые glibc/libc не принесут профита — можно оставить старую ОС, поменяв ядро и утилиты типа iproute2 и tcpdump, чтобы они соответствовали возможностям ядра. Я стараюсь использовать LTS ядра, и бэкпортировать в них что-то, что прям очень хорошее появилось в новых версиях, но это редко и скорее для теста производительности.


                    Интересный момент. На текущем ядре модуль igb имеет параметры (RSS, LRO и т.п.), а на новых ядрах этот модуль без параметров. Это как-то влияет на производительность?

                    По поводу драйвера — а вы пользуетесь этими параметрами? для роутинга нельзя использовать LRO, конфигурить RSS — ну так себе, профит изменения количества очередей не ясен, у сетевух в igb обычно не более 8 очередей — куда уж меньше. Но если хочется поиграться с настройками — лучше смотреть в сторону альтернативного драйвера — проект e1000 на sourceforge.

                      0
                      LRO, конечно, отключено. RSS подгоняется под общее количество ядер, например 8 ядер, 4 сетевухи — по 2 потока на сетевую, каждый поток прибивается в своему ядру. На профильных форумах пишут, что это самый оптимальный вариант, и увеличивать количество потоков на ядро смысла нет.

                      В разрезе Centos 6 пробовали ядра от Oracle.
                      R2 (2.6.39) лучший результат, самая маленькая нагрузка на процессор.
                      R3 (3.2.13) нагрузка явно выше в разы, особенно в час пик.
                      R4 (4.1.12) то-же какие-то проблемы были. Подробностей не помню.
                      Поэтому пока остались на Uek-R2

                      На текущий момент у Оракла самое свежее ядро R6 5.4.17, но только с 7й версии rhel.
                  +1
                  Правила nftables описываются совсем не так, как в iptables.

                  Ну вот зачем менять правила описания? Есть гибкий iptables, синтаксис которого схож ещё ipchains, он был описан в куче мануалов (как и логика работы файровола). А теперь плодятся ufw, firewalld с новыми инструкциями…
                    0

                    На ufw вы зря наговариваете. Это фактически упрощённый интерфейс к iptables для непрофессионалов.

                      +1

                      упрощённый? но что там упрощать?

                    0
                    Сейчас модным направлением в программном «перекладывании пакетиков» является использование DPDK и XDP. На эту тему написана куча статей, сделано много разных выступлений, появляются коммерческие продукты (например, СКАТ от VasExperts). Но в условиях ограниченных ресурсов программистов в операторах связи, пилить самостоятельно какое-нибудь «поделие» на базе этих фреймворков довольно проблематично. Эксплуатировать такое решение в дальнейшем будет намного сложнее, в частности, придется разрабатывать инструменты диагностики. Например, штатный tcpdump с DPDK просто так не заработает, да и пакеты, отправленные назад в провода с помощью XDP, он не «увидит».

                    ИМХО DPDK — тупиковый путь.
                    фактически это ещё один сетевой стек (притом весьма «специфический»). да, сегодня он актуален, но по мере роста производительности CPU и оптимизации сетевого стека в ядре, он потеряет свою привлекательность.

                      0

                      Не согласен, что это прям тупик. Несмотря на все оптимизации, сетевая часть ядра Linux — это монстр, который не может работать очень быстро из-за своей универсальности. А зависимостей между модулями столько, что не всегда получается оставить только то, что действительно необходимо. При этом на DPDK можно сделать очень производительное решение под конкретную частную задачу. Например, я не представляю, какое железо должно быть под задачу firewall ipv6 трафика с большим количеством правил (миллионы, например). При этом решение на DPDK вполне справлялось с задаче на 100Gbps line-rate, не забывая потом еще маршрутизировать этот трафик, и всё на 8 ядрах 2Ghz. Поэтому, правильнее сравнивать решения на DPDK с решениями от производителей сетевого оборудования. Например, Juniper MX204, позиционируется как бордер, P/PE маршрутизатор. Стоит сейчас больше 3 млн рублей. Сервис контракт на год на него стоит еще минимум шестизначную сумму. При этом фактически нужен для быстрого RMA, потому что time to market новых фич или багфиксов (если вообще получилось продавить тех поддержку) — минимум 6 месяцев. В качестве альтернативы — сервер на Supermicro X10, карты Mellanox ConnectX5 c такими же 100G интерфейсами. Цена выходит около 500 тыс. Если таких устройств нужно больше 4х, то ценой сервис контракта вполне можно закрыть годовую зарплату разработчика для DPDK. При этом time-to-market новых фич будет 1 спринт в agile. Решить эту же задачу в похожих объемах на чистом Linux скорее всего не выйдет, или выйдет дороже, и time-to-market будет сопоставим с Juniper

                        0
                        Решить эту же задачу в похожих объемах на чистом Linux скорее всего не выйдет, или выйдет дороже, и time-to-market будет сопоставим с Juniper

                        речь шла о перспективе, а не о сегодняшнем положении дел.
                        то, что сегодня использование DPDK в некоторых задачах оправдано — несомненно. но, я думаю, со временем (речь о годах и даже десятилетиях) таких задач будет всё меньше и меньше.

                          0

                          Можете поделиться инфой, где почитать про 100G на 8-ми ядрах?

                            0

                            Это было внутреннее тестирование, в Open Source проект не ушел. Решалась очень специфическая задача.

                        0

                        А есть какие инструменты для управления nftables через веб? Ищем решение для малого офиса с постоянным трафиком 800мбит/с от камер видеонаблюдения. Сейчас есть kerio control, но он иногда дико лагает и функционала недостает.

                          0

                          Насколько я знаю проект OpenWRT в своем веб-интерфейсе Luci разрешает задействовать механизм flow offload. Не совсем понятно, какого именно функционала вам не хватает, но если нужно что-то больше, чем роутинг/nat, то nftables тут не поможет

                          Only users with full accounts can post comments. Log in, please.