Pull to refresh

Linux + 2 ISP. И доступность внутреннего сервера через обоих провайдеров

Reading time 5 min
Views 15K
Есть замечательная статья, в которой рассказывается, как это делается на 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 {ip-адрес, связанный с ppp1} lookup 201
ip rule add fwmark 0x2/0x3 lookup 202
ip rule add from {ip-адрес, связанный с ppp2} 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 {nexthopN} 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.
Tags:
Hubs:
+68
Comments 29
Comments Comments 29

Articles