Iproute2 policy-routing и балансировка трафика между аплинками — проблема сброса соединений

Наткнулся на неприятный подводный камень. Имеем систему с несколькими аплинками, и policy-routing, реализующий балансировку соединений между аплинками с помощью:

ip route replace default scope global
nexthop via 11.22.33.1 dev eth0 weight 1
nexthop via 55.66.77.1 dev eth1 weight 1


(Примерная инструкция здесь)

Проблема заключается в следующем — соединения периодически падают, причём никакой системы нет. Может простоять несколько часов, может упасть через 5-10 минут. Всяким http и torrent'ам это не мешает. В первом случае сессии обычно достаточно короткие, во втором реконнект проходит незаметно и без последствий. Но если мы работаем с ssh?

Разъяснение для тех, кто не знает, как работает такая схема маршрутизации.

Для каждого соединения, идущего через шлюз по умолчанию, выбирается один из имеющихся в наличии каналов, с вероятностью, соответствующей параметру weight.

Затем запись о маршруте для этого соединения заносится в кеш, и все пакеты между этими ip-адресами далее ходят по этому маршруту. Если какое-то время пакетов по этому маршруту нет — запись из кеша удаляется. По умолчанию на это требуется около пяти минут. В этом есть уже как минимум одна проблема — если ваше соединение долго не передаёт никаких данных, запись из кеша маршрутов будет вытерта, хотя из кеша соединений, возможно, ещё и не будет. По умолчанию в модуле nf_conntrack выставлено какое-то очень большое время жизни для tcp-соединений. Что случится? При следующем прохождении пакета в этом соединении, которое ещё считается несброшенным, будет выбран новый маршрут, как если бы оно было вновь установлено. Если повезет — тот же, что и был. Тогда всё продолжит работать. А вот если другой — ничего никуда не пойдёт.

Но на практике это проблема невеликая, довольно мало ситуаций, в которых соединение стоит без дела столько времени, а даже если это, скажем, ssh-сессия, то в ней можно включить keep-alive пакеты. Как и в большинстве других практических случаев. По идее такая ситуация возможна ещё с ftp, но я им давно не пользуюсь, и вам не советую. Да и большинство ftp-клиентов тоже умеют keep-alive.

Хуже другое — в такой схеме падали даже сессии с непрерывным потоком данных. И вот это оказалось непонятно.
Простой обход проблемы, слепленный на скорую руку, оказался в прописывании статических маршрутов для наиболее нужных удалённых хостов, так чтобы маршрут к ним лежал строго через один интерфейс. Но некрасиво же. Неуниверсально и ломает идею connection-failover.

То, что это помогло, и навело меня на мысль, что проблема где-то именно в роутинге. Трёхчасовые раскопки выявили следующее: в ядрах до 2.6.35 (а систем на них очень и очень немало) в настройках роутинга есть параметр net.ipv4.route.secret_interval, в секундах, по умолчанию 600. Отвечает за принудительный сброс кеша маршрутов во избежание его переполнения. В дальнейшем от него было решено отказаться — https://github.com/torvalds/linux/commit/3ee943728fff536edaf8f59faa58aaa1aa7366e3

Таким образом, раз в 10 минут ваш кеш сбрасывается, и маршруты выбираются заново. И не всегда так, как хотелось бы.
Поэтому для стабильной работы policy-routing на системах с несколькими аплинками я рекомендую выставлять этот параметр в 0.

sysctl -w net.ipv4.route.secret_interval=0


Можно конечно поставить патч ядра, устраняющий это поведение целиком, но это уже решение не для всех.

Отключать этот сброс вполне безопасно, поскольку в более поздних ядрах никакого дополнительного механизма защиты от переполнения кеша маршрутов введено не было, имеющиеся были сочтены достаточно надёжными.
Поделиться публикацией

Комментарии 21

    0
    Хм, а почему изменение роутинга должно приводить к разрыву соединения? Разве хосту не пофиг, каким маршрутом пришел пакет? Главное, что бы исходящий адрес не менялся.
      0
      Так он изменится — у другого аплинка внешний ip другой.
        –2
        Это же только для новых соединений и после сброса кеша. Если соединение активно маршрут не изменится. Тут нет вариантов.
          0
          Пост не читали? Речь и идёт о проблемах с самопроизвольным сбросом кеша
            0
            Конечно читал. Не припомню, чтобы на Debian была подобная ситуация. Возможно это дистрибутивозависимо.
              0
              Это ядрозависимо. ЕМНИП, у дебиана ядра новее
        +1
        Да, это вам, батенька, не BGP…
          0
          Ну да, действительно, если во владении нету своей сетки, то по другому и не выкрутишься.
            0
            Ну решение-то в общем изначально наколеночное, для домашнего сервера разве что.
              0
              Угу.

              Я ещё наблюдал странное поведение, даже на более или менее современных ядрах — когда кеш не соответствует таблице маршрутов.

              То есть, всё, вроде бы красиво — дефолтный маршрут на хосте указывает на роутер, который всех в инет выпускает, но произвольные адреса в интернете не пингуются, либо весь интернет недоступен.

              Делаем ip route flush cache — и всё поехало. Проблема вылезала на 3.14, кажется, где-то раз в месяц. Сейчас, на 3.18, не видел пока, может пофиксили.
                0
                С таким не сталкивался. На 2.6.32 предложенный способ отлично работает. Принудительно сбрасывать не приходилось никогда. Насколько велик у вас кеш?
                  0
                  Начиная с ядер 3.6 ipv4 кеш вообще выпилен, т.е. вывод команды ip route cache пустой и flush cache ничего не делает. Тут pdf-презентация с оправданиями, почему так сделали.

                  Multipath на таких ядрах совместно с nat уже мало пригоден для практического применения, что особенно заметно на https-сессиях.
                    0
                    А не подскажите, чем тогда реализовать multipath, например в Centos 7, с ядром 3.10?
                      0
                      Есть пример решения, где помимо правил маршрутизации используется ещё ipset и iptables. Выглядит устрашающе, не знаю, возможно есть другие решения.
                      0
                      Угу, я про это тоже читал в процессе решения проблемы. Но факт остаётся фактом — только ip route flush cache помогал восстановить связь. Ядро было точно не старее 3.14
              0
              А можно я задам простой вопрос: а почему надо использовать недетерминистический алгоритм выбора аплинка? Используйте либо хэш от пары ip-адрес порт («последний бит хеша 0 — налево, 1 — направо»), либо, вообще, последний бит IP-адреса. То есть маршрутизировать исходя из маски 0.0.0.1.

              Автоматически снимает все вопросы со «странными contracker'ами», которые вообще можно отключить. Пакет с чётным dst в IP? Аплинк 1. С нечётным? Аплинк 2.
                +1
                Идея мне нравится. Не подскажете, как реализовать +- штатными средствами, скажем в CentOS?

                А хотя есть тут один подводный камень. Но надо померять предварительно. А чтоб померять — реализовать.
                  0
                  iptables -t mangle -A PREROUTING -i $dev_in -d 0.0.0.0/0.0.0.1 -j MARK --set-mark 0x1
                  iptables -t mangle -A PREROUTING -i $dev_in ! -d 0.0.0.0/0.0.0.1 -j MARK --set-mark 0x2
                  0
                  А собственно, отвечая на ваш вопрос: использовать не надо. Использовать можно. Более менее популярное и документированное решение, без изобретения велосипедов. Как часто случается — не без граблей. Ваш вариант, если он так же решается штатными средствами, наверно просто менее популярен и документирован.
                    0
                    Забыл уточнить — в моём практическом случае полосы пропускания аплинков соотносятся 1:2. Соот-но, и weight у меня стоит 1 и 2. То есть вариант чёт/нечет уже не очень хорош.
                      +1
                      1) Матчим в iptables любые биты как хотим, например как тут www.stearns.org/doc/iptables-u32.current.html
                      2) Создаем IP рулы: ip rule add fwmark
                      3) Указываем соответвущие gw для созданных рулов

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое