Пользователь
18,5
рейтинг
14 декабря 2015 в 13:39

Администрирование → Multihome IPv4 в Linux

Содержимое: как сделать так, чтобы компьютер отвечал в интернете на все свои IP-адреса по всем своим интерфейсам, каждый из которых имеет шлюз по умолчанию. Касается и серверов, и десктопов.

Ключевые слова: policy routing, source based routing

Лирика: Есть достаточно статей про policy routing в Linux. Но они чаще всего разбирают общие, более тонкие и сложные случаи. Я же разберу тривиальный сценарий следующего вида:



Нашему компьютеру (серверу) доступно три интерфейса. На каждом интерфейсе шлюз ему выдал IP (статикой или по dhcp, не важно) и сказал «весь трафик шли мне».

Если мы оставим эту конфигурацию как есть, то будет использоваться принцип «кто последний встал, того и дефолтный шлюз». На картинке выше, если последним поднимется нижний интерфейс (241), то в него будет отправляться весь трафик. Если к нашему серверу придёт запрос на первый интерфейс (188), то ответ на него всё равно пойдёт по нижнему. Если у маршрутизатора/провайдера есть хотя бы минимальная защита от подделки адресов, то ответ просто дропнут, как невалидный (с точки зрения 241.241.241.1 ему прислали из сети 241.241.241.0/24 пакет с src 188.188.188.188, чего, очевидно, быть не должно).

Другими словами, в обычном варианте будет работать только один интерфейс. Чтобы сделать ситуацию хуже, если адреса получены по dhcp, то обновление аренды на других интерфейсах может перезаписать шлюз по умолчанию, что означает, что тот интерфейс, который работал, работать перестанет, а начнёт работать другой интерфейс. Удачной стабильной работы вашему серверу, так сказать.

Решение


Общее решение — policy routing. Довольно объёмная и интересная штука позволяющая выделывать всевозможные чудеса. Из этих чудес мы (в рамках этой статьи) оставим только одно: «отсылать каждому маршрутизатору трафик с „его“ интерфейса». То есть классический source-based routing в самой примитивной форме.

Обзор решения с высоты птичьего полёта:

Мы задаём три варианта маршрутизации трафика: «всё в eth0», «всё в eth1», «всё в eth2», дальше формулируем правила: трафик с IP первого интерфейса отправлять через первый вариант, трафик со второго IP — через второй вариант, третий IP — через третий.

В результате мы получаем такую конструкцию:
source: 188.188.188.188 в таблицу eth0-route default via 188.188.1 via eth0
source: 75.75.75.75 в таблицу eth1-route default via 75.75.75.1 via eth1
source: 241.241.241.241 в таблицу eth2-route default via 241.241.241.1 via eth2


Краткое содержимое:
  1. Настроить утилиту iproute2 с помощью конфига (внезапно, у неё есть конфиг!) — дать имена трём таблицам маршрутизации
  2. Настроить маршруты в трёх таблицах маршрутизации — точнее, задать дефолтные машруты
  3. Указать правила, по которым трафик будет распределяться по трём таблицам маршрутов


Шаги подробнее


Утилита ip (её официальное название iproute2) имеет специальную команду — ip rule для управления policy routing. А добавление маршрутов (ip route add) может принимать аргумент с названием таблицы. Ядро ничего про названия таблиц не знает, а вот iproute2 требует использования имени из своего конфига — /etc/iproute2/rt_tables (которая сопоставляет имена абстрактным числам, которые понимает ядро). Значения «0» и значения ниже 255 (254, 253) заняты, остальные можно использовать.

Настройка iroute2


Мы назовём наши таблицы маршрутизации eth0-route, eth1-route, eth2-route. Возьмём случайные символичные номера — 100, 101, 102.
echo 100 eth0-route >>/etc/iproute2/rt_tables
echo 101 eth1-route >>/etc/iproute2/rt_tables
echo 102 eth2-route >>/etc/iproute2/rt_tables


Настройка маршрутов в трёх таблицах маршрутизации


Для каждой таблицы зададим маршрут по умолчанию (можно и не по умолчанию — фантазия в ваших руках). Эти правила ничего не сломают на сервере и их можно делать на живую. Пока мы не скажем использовать эти таблицы — это всего лишь байтики в памяти и они на работу маршрутизации не влияют (то есть мы не останемся внезапно без коннективити в середине процесса).
ip route add default via 188.188.188.1 dev eth0 table eth0-route
ip route add default via 75.75.75.1 dev eth1 table eth1-route
ip route add default via 241.241.241.1 dev eth2 table eth2-route


Теперь интимный момент: внимательно перечитать написанное. После включения правил опечатка в таблице может оставить сервер без коннективити.

Включение policy routing


ip rule  add from 188.188.188.188 lookup eth0-route
ip rule  add from 75.75.75.75 lookup eth1-route
ip rule  add from 241.241.241.241 lookup eth2-route


Всё. С этого момента сервер начнёт отвечать по всем трём адресам. Приятная новость: policy routing имеет более высокий приоритет, чем dhcp'шный default route, так что обновление аренды не сломает маршрутизацию.
Неприятная новость: если при обновлении аренды вам выдадут другой адрес, то он перестанет работать. Это можно исправить либо сделав правило с маской (from 241.241.241.0/24), либо прибив адрес гвоздями в конфиге dhcp-сервера (вообще, серверам не принято выдавать динамические адреса по DHCP...)

Отлитие в конфигах


(debian/ubuntu)
Отличное место для этих правил — в /etc/network/interfaces. Для нужного интерфейса пишем:
iface eth0 inet static
   address 188.188.188.188
   netmask 255.255.255.0
   broadcast: 188.188.188.255
   pre-up ip route add default via 188.188.188.1 dev eth0 table eth0-route
   pre-up ip rule  add from 188.188.188.188 lookup eth0-route


Примечания


Замечание о маршрутизации: source routing в общем случае открывает уязвимость в системе (вам шлют пакет с фальшивым source, а вы его передаёте дальше, как будто так и надо), но эта узявимость уровня directly connected. Кроме того, если вы ограничиваетесь только своим адресом в from в правилах, то линукс такого не пропустит, плюс вам надо иметь включенную маршрутизацию. На обычном сервере такая конфигурация совершенно безопасна и довольно надёжна.

Замечание о load-balancing: Прописав все три адреса в A-записи DNS-зоны, вы получите бесплатный round-robin по всем трём интерфейсам без бондинга и поэтесс. Так как обрабатываться они будут одним и тем же сервером и одним и тем же приложением, это позволит балансировать даже stateful-сессии.
Георгий Шуклин @amarao
карма
268,0
рейтинг 18,5
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Администрирование

Комментарии (24)

  • +4
    Почему никто не использует 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24? Адреса специально выделенные для использования в документации.
    • +10
      Потому что никто не читает документацию.
    • –3
      Потому что на живых IP легче показывать. Это не пример настройки сетевого оборудования (где кривой роут может невинным людям поломать интернеты), а на реальных (похожих на использующиеся) диапазонах проще понять о чём речь и где тут серые адреса, а где белые.
  • 0
    С серверными приложениями понятно. А с какого адреса из трёх будут слать пакеты клиентские приложения на таком компе?
    • 0
      От приложений трафик пойдёт по маршруту по-умолчанию с соответствующим адресом источника.
    • 0
      Это вечная боль — когда приложение хочет послать что-то, оно чаще всего не выбирает кого использовать, а за него это решает ОС. Приложение может осознанно выбрать, но большинство десктопных приложений не выбирает и не даёт этого выбора пользователю.

      Через какой из интерфейсов пойдёт трафик в этом примере — я думаю, никому не известно.
      • +1
        Ну можно прикостылить в принципе к каждому приложению свой IP на выход (правда только для TCP):
        daniel-lange.com/archives/53-Binding-applications-to-a-specific-IP.html
        • 0
          Да, есть такой метод.

          К сожалению, костыльный. Жаль, что нет красивого системного решения.
      • 0
        Кстати вот вспомнилось.
        Семь лет назад баловался тем, что на хостинге через curl использовал для запросов не только свой легальный ip, но и все ip сервера.
        function mybot2($url,$ip=FALSE,$user_agent="Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)")
            {
            // получим контент
            $ch = curl_init();    // initialize curl handle
            if($ip<>FALSE) curl_setopt($ch, CURLOPT_INTERFACE, $ip);
            curl_setopt($ch, CURLOPT_URL, $url); // set url to post to
            curl_setopt($ch, CURLOPT_FAILONERROR, 1);              // Fail on errors
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable
            curl_setopt($ch, CURLOPT_TIMEOUT, 15); // times out after 15s
            curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
            $document = curl_exec($ch);
            curl_close($ch);
            return $document;
            }
        

        Интересно, от этого можно как-то просто защищаться?
      • 0
        Как вариант, изолировать приложение в своём netns и роутить его трафик через определённый интерфейс.
        • 0
          Да, и прочие ужасные системные вещи. А вот изящный user-friendly вариант — отсутствует. Хотя очевидно, что использование переменной среды окружения (типа DEFAULT_IFACE=eth0) должно было бы сделать шелловую жизнь лучше. Ну или хотя бы системное на уровне ядра «если не знаешь как юзать — юзай этот».
  • 0
    Подходящего места для systemd-networkd еще не придумано, как я погляжу?
    • 0
      Я так понял, они и не претендуют на всеохватность в systemd-networkd. По крайней мере, пока. Так, для контейнеров, простых серверков и десктопов.
    • 0
      К сожалению, там нет поддержки source routing, а очень бы хотелось, очень.
      Кто-нибудь подскажет вообще хоть что-нибудь, чем можно управлять source routing? Для OpenWRT есть mwan3, а под обычный линукс вообще нет ни софта, ни скриптов. Есть только bird, но он не совсем для этого. Очень давно ищу, ничего найти не могу.
  • 0
    Было уже неоднократно, например тут habrahabr.ru/post/225965
    Но за напоминание спасибо.
    • 0
      Оно то же средство для другой задачи использовало — на исходящий трафик. Меня интересовал вопрос равноправного присутствия в роли сервера.
  • +1
    А зачем, LARTC мало? Точный же клон раздела split access.
  • 0
    А на Windows source-based routing как-нибудь можно реализовать?
    • 0
      RAS должен уметь, по-идее.
    • +1
      Что-то есть прямо в сетевом стеке, но на практике не проверял.
  • +2
    Если к нашему серверу придёт запрос на первый интерфейс (188), то ответ на него всё равно пойдёт по нижнему. Если у маршрутизатора/провайдера есть хотя бы минимальная защита от подделки адресов, то ответ просто дропнут, как невалидный (с точки зрения 241.241.241.1 ему прислали из сети 241.241.241.0/24 пакет с src 188.188.188.188, чего, очевидно, быть не должно).
    Не совсем.
    По умолчанию, наверное, в большинстве дистрибутивов (Ubuntu, Fedora, ArchLinux) уже стоит net.ipv4.conf.*.rp_filter=1. Знаю только Debian, где по умолчанию 0.
    Если этот параметр установлен в значение 1, приложение, получающее входящий пакет, пришедший, скажем, на интерфейс с IP 188, вообще не получит его, его отбросит ядро как Martian packet.
    Такие пакеты можно логировать, что очень удобно:
    # sysctl net.ipv4.conf.*.log_martians=1

    Если же rp_filter отключен, то в Linux ответ действительно пойдет с неправильным IP через неправильный интерфейс, независимо от протокола, и, с большой вероятностью, отбросится провайдерскими маршрутизаторами. А вот в Windows и OS X все интересней: в Windows, при использовании TCP, никакой дополнительной настройки source routing не требуется — ответ на пакет, пришедший на определенный интерфейс, пойдет через этот же интерфейс и с правильным IP, аналогичная ситуация для TCP наблюдается и в OS X.
    А вот с UDP все интересней: как в Windows, так и в OS X, ответ на пакет, пришедший с одного интерфейса, уйдет через другой интерфейс с правильным IP другого интерфейса!

    Надеюсь, в середине недели расскажу об этом более подробно отдельной статьей — такая, казалось бы, очевидная вещь, с которой сталкивался любой, кто настраивал Multi WAN и Multihoming, может использоваться для деанонимизации пользователей VPN.
  • 0
    А почему вы про приоритеты не упомянули? На мой взгляд, их лучше указывать вручную. Очень гибкая вещь. И про встроенные таблицы тоже сказать нужно, в какой последовательности они обрабатываются ядром.
    Приятная новость: policy routing имеет более высокий приоритет, чем dhcp'шный default route, так что обновление аренды не сломает маршрутизацию.

    Тут как раз не нужно default route получать по dhcp и прописывать его в таблицу «main», вы же его уже настроили для каждого интерфейса, получайте по dhcp только адрес. А если у вас в главной таблице не будет маршрута по умолчанию. То можно сделать так:
    ip rule add table main pref 3050
    Главное чтобы приоритет был ниже (по номеру), чем у правил для таблиц по умолчанию (в вашем случае, скорее всего, у них 3276{5,4,3}).
    ip rule будет выдавать что-то наподобие:
    0: from all lookup local
    3050: from all lookup main
    32763:…
    32764:…
    32765: from 188.188.188.188 lookup eth0-route
    32766: from all lookup main
    32767: from all lookup default

    Не большое замечание по безопасности.
    Как вы правильно упомянули в примечание. Крайне не желательно расширять это правило на сеть (from 188.188.188.0/24). И даже если указан только ваш IP. Возможен такой сценарий:
    Кто-то из сети 188.188.188. пошлет от вашего имени (подменив свой IP адрес на 188.188.188.188) вам пакет на любой другой адрес в Интернет, то ваш комп., согласно вашим правилам маршрутизации (32765: from 188.188.188.188 lookup eth0-route), в зависимости от настроек iptables, может переслать его по назначению.
    • 0
      Спасибо за замечание.

      роуты от dhcp обычно получает не человек, а клиент. И надо клиент отдельно явно убеждать «использовать роут или нет».

      Было бы, кстати, правильно, так изнасиловать dhcp-клиент, чтобы он для каждого интерфейса дефолтный роут (и все остальные роуты) засовывал в эту таблицу. Это позволило бы сососуществовать с конкурирующими specific route'ами в нескольких VPN'ах, например, каждый из которых предлагает свой 192.168 ему слать и т.д.
      • 0
        чтобы он для каждого интерфейса дефолтный роут (и все остальные роуты) засовывал в эту таблицу.
        Легко. Надо только свой доп. скриптик положить в директорию dhclient.d.Если надо, могу опубликовать у меня для СентОС6 и Демьяна6 где-то было, там все просто, сделано на основе Демьяновского debug скрипта. Кстати, если вы не увеличите приоритет правила для таблицы main (ip rule add table main pref 3050). То скорее всего из локальной сети вам будет доступен только шлюз. Если вас не интересуют другие машины в локальной сети, например из диапазона 188.188.188.2-187,189-254, то можно не заморачиваться.

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