Наткнулся на неприятный подводный камень. Имеем систему с несколькими аплинками, и 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)


  1. lorc
    21.09.2015 17:41

    Хм, а почему изменение роутинга должно приводить к разрыву соединения? Разве хосту не пофиг, каким маршрутом пришел пакет? Главное, что бы исходящий адрес не менялся.


    1. Ilandslc
      21.09.2015 17:43

      Так он изменится — у другого аплинка внешний ip другой.


      1. mureevms
        21.09.2015 19:34
        -2

        Это же только для новых соединений и после сброса кеша. Если соединение активно маршрут не изменится. Тут нет вариантов.


        1. Ilandslc
          21.09.2015 19:37

          Пост не читали? Речь и идёт о проблемах с самопроизвольным сбросом кеша


          1. mureevms
            22.09.2015 13:15

            Конечно читал. Не припомню, чтобы на Debian была подобная ситуация. Возможно это дистрибутивозависимо.


            1. Ilandslc
              22.09.2015 13:23

              Это ядрозависимо. ЕМНИП, у дебиана ядра новее


    1. blind_oracle
      21.09.2015 17:57
      +1

      Да, это вам, батенька, не BGP…


      1. lorc
        21.09.2015 18:07

        Ну да, действительно, если во владении нету своей сетки, то по другому и не выкрутишься.


      1. Ilandslc
        21.09.2015 20:34

        Ну решение-то в общем изначально наколеночное, для домашнего сервера разве что.


        1. blind_oracle
          21.09.2015 20:55

          Угу.

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

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

          Делаем ip route flush cache — и всё поехало. Проблема вылезала на 3.14, кажется, где-то раз в месяц. Сейчас, на 3.18, не видел пока, может пофиксили.


          1. Ilandslc
            21.09.2015 21:37

            С таким не сталкивался. На 2.6.32 предложенный способ отлично работает. Принудительно сбрасывать не приходилось никогда. Насколько велик у вас кеш?


          1. cruxacrux
            22.09.2015 00:19

            Начиная с ядер 3.6 ipv4 кеш вообще выпилен, т.е. вывод команды ip route cache пустой и flush cache ничего не делает. Тут pdf-презентация с оправданиями, почему так сделали.

            Multipath на таких ядрах совместно с nat уже мало пригоден для практического применения, что особенно заметно на https-сессиях.


            1. wmlex
              22.09.2015 07:12

              А не подскажите, чем тогда реализовать multipath, например в Centos 7, с ядром 3.10?


              1. cruxacrux
                22.09.2015 10:20

                Есть пример решения, где помимо правил маршрутизации используется ещё ipset и iptables. Выглядит устрашающе, не знаю, возможно есть другие решения.


            1. blind_oracle
              22.09.2015 10:16

              Угу, я про это тоже читал в процессе решения проблемы. Но факт остаётся фактом — только ip route flush cache помогал восстановить связь. Ядро было точно не старее 3.14


  1. amarao
    22.09.2015 00:32

    А можно я задам простой вопрос: а почему надо использовать недетерминистический алгоритм выбора аплинка? Используйте либо хэш от пары ip-адрес порт («последний бит хеша 0 — налево, 1 — направо»), либо, вообще, последний бит IP-адреса. То есть маршрутизировать исходя из маски 0.0.0.1.

    Автоматически снимает все вопросы со «странными contracker'ами», которые вообще можно отключить. Пакет с чётным dst в IP? Аплинк 1. С нечётным? Аплинк 2.


    1. Ilandslc
      22.09.2015 00:36
      +1

      Идея мне нравится. Не подскажете, как реализовать +- штатными средствами, скажем в CentOS?

      А хотя есть тут один подводный камень. Но надо померять предварительно. А чтоб померять — реализовать.


      1. demon_odinok
        22.09.2015 09:32

        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


    1. Ilandslc
      22.09.2015 00:40

      А собственно, отвечая на ваш вопрос: использовать не надо. Использовать можно. Более менее популярное и документированное решение, без изобретения велосипедов. Как часто случается — не без граблей. Ваш вариант, если он так же решается штатными средствами, наверно просто менее популярен и документирован.


    1. Ilandslc
      22.09.2015 01:21

      Забыл уточнить — в моём практическом случае полосы пропускания аплинков соотносятся 1:2. Соот-но, и weight у меня стоит 1 и 2. То есть вариант чёт/нечет уже не очень хорош.


      1. alex_www
        22.09.2015 04:27
        +1

        1) Матчим в iptables любые биты как хотим, например как тут www.stearns.org/doc/iptables-u32.current.html
        2) Создаем IP рулы: ip rule add fwmark
        3) Указываем соответвущие gw для созданных рулов