Используем 2+ провайдера (первая часть)
Здесь я хочу рассказать о настройке шлюза на Linux’e, для использования 2-х (и более) провайдеров интернета.
Для настройки мы будем использовать возможности iptables и утилиты ip из пакета, который как правило называется iproute2. А для решения поставленной задачи пакеты мы будем маршрутизировать на основе «policy routing» (т.е. маршрутизация на основе политик), а не «destination routing» (маршрутизация на основе адреса получателя).
Итак, приступим. Для начала определимся с переменными:
P — это шлюзы по умолчанию у наших провайдеров
Policy routing позволяет выполнять маршрутизацию на основе адреса источника поэтому перечислим сервера которые будут учавствовать:
Здесь SRV11 и SRV12 — это два айпишника одного и тогоже сервера (это важно!), это позволяет одному серверу обрабатывать входящие соединения с двух провайдеров. Конечно же, существуют и другие варианты реализовать эту возможность, но я буду использовать именно айпишники, мне кажется для начала так будет проше.
Ну а теперь самое интересное — пишем правило для маршрутизации.
Первое что мы должны сделать это добавить свои таблицы маршрутизации, для этого необходимо отредактировать файл /etc/iproute2/rt_tables, например так:
Тоесть мы добавляем маршруты, в которых указываем что попасть в подсеть первого провайдера можно через первый интерфейс. Во второй строчке мы добавляем шлюз по умолчанию.
Тоже самое и во второй:
Затем разберемся с основной таблицей, которая называется «main». Ее мы видим, когда набираем ip route:
ip route add $P1_NET dev $IF1 src $IP1
ip route add $P2_NET dev $IF2 src $IP2
ip route add default via $P1 metric 10
Первые две строчки аналогичны предыдущим записям, только опущено «table main». В третьей строчке задается маршрут по умолчанию с указанием метрики.
На этом с маршрутизацией разобрались, чтобы посмотреть что у нас находится в таблице маршрутизации можно выполнить команду «ip route show table ». Теперь приступим к правилам. Как раз по правилам и будет приниматься решения какой пакет по какой таблице будет маршрутизироваться.
Здесь мы указали, что если адрес источника равен первому внешнему адресу, тогда маршрутизация выполняется по таблице T1. Аналогично вторая запись.
И наконец самое интересное:
Используя iptables мы можем маркировать интересующие нас пакеты и маршрутизировать их на основе этих меток. Собственно здесь мы добавили два правила: для пакетов, имеющих метку 10, использовать таблицу T1, для пакетов с меткой 20 — T2. Сейчас возможно не очень понятно для чего это может потребоваться, но из правил iptables все станет ясно. Для просмотра правил выполняем «ip rule», при маршрутизации они проверяются по порядку.
Ну вот половина работы сделана осталось написать правила для iptables, об этом мы поговорим во второй части.
p.s. Написано, чтобы понять самому и рассказать другим.
Linux + 2 ISP. И доступность внутреннего сервера через обоих провайдеров
Есть замечательная статья, в которой рассказывается, как это делается на Cisco. Но мы не хотим тратить $100500 на приобретение штампованных оттисков «Cisco Systems» на корпусе маршрутизатора.
Описание проблемы
Итак, суть проблемы: имеется два NAT через двух разных провайдеров, локальная сеть, в которой есть сервер и который должен быть публичным и доступным через оба NAT. У провайдеров разные приоритеты: сначала задействуется первый, потом второй.
Если пакет вошёл через первого провайдера, он NAT-ится на наш сервер, обрабатывается, образуется ответный пакет, который выходит через первого провайдера и уходит туда, откуда пришёл первый пакет. Хорошо.
Если пакет вошёл через второго провайдера, он NAT-ится на наш сервер, обрабатывается, образуется ответный пакет, который выходит через первого провайдера… а почему? Потому, что сначала в Linux происходит маршрутизация, а потом уже SNAT. Итак, при маршрутизации пакету назначается следующий узел — шлюз первого провайдера (по умолчанию). Потом происходит отслеживание соединения — conntrack замечает, что этот пакет является ответом на другой, и заменяет адрес отправителя адресом, который выдал нам второй провайдер. А потом пакет направляется через интерфейс первого провайдера на его шлюз. Как правило, провайдер блокирует пакеты, адресом отправителя которых указан адрес не из их подсети. Плохо.
Как должно быть
А нельзя ли как-то поменять порядок — сначала отслеживать, какой нужен провайдер, а потом уже на основании этого выбирать next hop?
Можно. Для этого в Linux есть интерфейс к его отслеживателю соединений — conntrack match и CONNTRACK target. Первый из них — это условие соответствия пакета правилу, согласно которому будут обработаны только те пакеты, в которых специальная метка — метка соединения — имеет определённое значение. Второй — это средство управления меткой соединения.
Метки соединений отличаются от обычных меток пакетов (MARK) тем, что дополнительно обслуживаются и поддерживаются модулем conntrack. Если мы назначаем пакету метку соединения, то в дальнейшем эта метка будет обнаружена на всех пакетах, относящихся к этому же соединению. Отслеживание соединений и восстановление метки происходит после завершения обработки таблицы raw (цепочка PREROUTING или OUPTUT), перед обработкой этих же цепочек таблицы mangle, а запоминание метки соединения в conntrack — в цепочке POSTROUTING после обработки таблицы nat.
Мы можем с самого начала, ещё даже до того, как произошёл наш DNAT на внутренний сервер, назначить соединению метку, например, в зависимости от интерфейса, через который попал этот пакет на маршрутизатор. После этого, все последующие пакеты и ответы мы будем видеть с этой же меткой — т.е. в любой момент времени для любого ответа знать, через какой интерфейс он должен выйти.
В Linux есть RPDB — Routing Policy DataBase — это то же самое, что в Cisco называется route-map — база данных политик маршрутизации. При этом, мы создаём несколько разных таблиц маршрутизации, а выбор, по какой из них будет происходить обработка пакета, будет делаться на основании политик. Критериями выбора таблицы могут быть: интерфейс, через который вошёл наш пакет; метка netfilter (nfmark); адрес отправителя; адрес назначения и другие.
Метка netfilter (nfmark) — подходящий для этого случая критерий. Это не то же самое, что метка соединения (ctmark), но что нам мешает установить nfmark на основании ctmark? Напротив, в CONNTRACK target есть специальная команда как раз для этого.
Настройка
Для начала, настроим RPDB. Я не буду назначать таблицам имён — это выходит за рамки темы, тем более, что в данном случае нагляднее будут именно номера.
Правила условной маршрутизации добавляются задаются при поднятии соответствующих интерфейсов, убиваются при останове.
ip rule add fwmark 0x1/0x3 lookup 201 ip rule add from lookup 201 ip rule add fwmark 0x2/0x3 lookup 202 ip rule add from lookup 202
ip rule add fwmark — это как раз те самые правила: если стоит метка 1, маршрутизируй через 201, если 2 — через 202. Другие два правила — это обычный split-access (как описано в LARTC). Если ни один из критериев не совпадёт, маршрутизация пойдёт по таблице по умолчанию (это справедливо для начальных пакетов соединений, инициированных из локальной сети или на маршрутизаторе).
В таблицы маршрутизации мы (также динамически, при поднятии интерфейсов) добавляем правила:
ip route add default dev ppp2 table 202 ip route add default dev ppp1 table 201 ip route add default dev ppp2 metric 2000 ip route add default dev ppp1 metric 1000
(если у нас не ppp, то правила будут такими: default via table 20N или metric 2000)
Это, собственно, тоже по LARTC. Метрика задаёт приоритет провайдера (будет выбран маршрут с минимальной метрикой), но для помеченых пакетов будут обрабатываться таблицы 201 и 202 соответственно, в каждой из которых по одному провайдеру.
Осталось начать метить пакеты.
# все пакеты, содержащие метки соединения - идентификаторы внешнего интерфейса iptables -t mangle -N out-marking iptables -t mangle -A PREROUTING -m connmark ! --mark 0x0/0x3 -j out-marking # если пакет вошёл через любой внутренний интерфейс - копируем метку соединения в метку пакета iptables -t mangle -A out-marking -i eth0 -j CONNMARK --restore-mark --mask 0x3 iptables -t mangle -A out-marking -i eth2 -j CONNMARK --restore-mark --mask 0x3 # все новые соединения iptables -t mangle -N in-marking iptables -t mangle -A PREROUTING -m conntrack --ctstate NEW -j in-marking # пакет вошёл через ppp1 - ставим метку 1, чтобы машрутизация любых ответов пошла по таблице 201 iptables -t mangle -A in-marking -i ppp1 -j CONNMARK --set-xmark 0x1/0x3 # пакет вошёл через ppp2 - ставим метку 2, чтобы маршрутизация любых ответов пошла по таблице 202 iptables -t mangle -A in-marking -i ppp2 -j CONNMARK --set-xmark 0x2/0x3
Нужно обратить внимание на три момента.
Во-первых, вся обработка происходит в цепочке PREROUTING таблицы mangle. Это потому, что мы хотим метить пакеты (поэтому mangle) до того, как произойдёт обработка этих меток при маршрутизации (поэтому PREROUTING).
Во-вторых, задание метки соединения происходит только для головных пакетов (-ctstate NEW) — остальные и так её имеют. Если новый пакет не имеет метки соединения, то его вообще никак обслуживать не надо — он пойдёт по таблице по умолчанию. Это справедливо для всех соединений, инициированных из локалки.
Под «номера провайдеров» задействовано 2 бита, т.е. мы таким способом мы можем сделать 3 аплинка (0 всегда остаётся под «неопределённый провайдер», для таких пакетов будет использоваться маршрут по умолчанию). Поэтому везде метки написаны с маской /0x3 — наши команды никак не будут влиять на все остальные биты меток, и их можно задействовать под другие цели. У меня, например, другие биты задействованы под шейпинг трафика.
А можно и по-другому
Назначаем внутреннему серверу второй адрес. Для этого адреса в rpdb назначаем выход через второго провайдера, и DNAT для пакетов, попавших к нам через второго провайдера, делаем на этот второй адрес.
Проще? В каком-то смысле да. Зато придётся обслуживать по N адресов на внутреннем сервере (по число провайдеров) и N правил DNAT.