Решение достаточно простое и по моему даже "статьи" не стоит.
Меня больше интересует кривизна моих рук. Иными словами ваше мнение.
Есть 2 интернет канала - первый дорогой, лимитный и стабильный, второй - дешевый, безлимитный, но несколько нестабильный. Эти каналы обеспечивают 2 маршрутизатора - Cisco 2600XM и Draytek Vigor2910VG. За маршрутизаторами стоят внешние (если их так можно назвать) сервера, смотрящие в Интернет внешними интерфейсами, соответственно внутренними в подсеть. Изнутри создать некий FailOver канал большого труда не составило - весь HTTP, FTP и т.п. трафик изнутри гнался по умолчанию через шлюз, который в случае падения своего default-router'a автоматически переключался скриптом, основной смысл имеющий:
Код: Выделить всё
route delete default 192.168.0.2
route add default 192.168.0.1
Код: Выделить всё
route change default 192.168.0.1
Примерно то же самое происходило и с остальными серверами. Здесь все просто. Вопрос встал в другом - а как тогда быть с обращениями извне на сервера, учитывая, что их внешних несколько. и соответственно оба канала должны одинаково обеспечивать доступность внешних сервисов (HTTP/S, SMTP, IMAP и т.д. и т.п.) .
Схема значит такая:
router_1 (канал лимитный) - обеспечивает работу внешних служб серверов.
router_2 (канал безлимитный) - обеспечивает интернетами людей.
Оба маршрутизатора воткнуты в отдельный VLAN коммутатора Cisco Catalyst 2950 вместе с внешними интерфейсами серверов.
Задача - что-бы при падении одного из каналов и сервера оставались доступными извне и люди довольными. Людьми и доступными изнутри серверами на "выход" уже понятно, разобрались.
Чтобы не нагружать количеством демонов, протоколов, Jail'ов, допустим у нас 2 канала и 2 сервера со службами:
server_1 = SSH-сервер, имеющий шлюз (default_gateway в rc.conf) router_1
server_2 = WEB-сервер со шлюзом (default_gateway в rc.conf) router_2
router_1 (WAN интерфейс) = 1.1.1.1
router_2 (WAN интерфейс) = 2.2.2.2
router_1 (LAN интерфейс) = 192.168.0.1
router_2 (LAN интерфейс) = 192.168.0.2
server_1 (WAN интерфейс) = 192.168.0.3
server_2 (WAN интерфейс) = 192.168.0.4
Сначала возникла мысль просто тупо пробросить порт. к примеру, на router_1, что-то типа:
Код: Выделить всё
ip nat inside source static tcp 192.168.0.4 80 1.1.1.1 80 extendable
Клиент, устанавливающий соединение, посылает пакет с флагом SYN и ждет от сервера ответа - либо пакет с флагом SYN+ACK, что будет расцениваться клиентом, как удавшееся соединение, либо пакет с флагом RST, после которого клиент, получивший отказ разрывает соединение.
Что получится, если я посылаю пакет на WAN-интерфейс router_2 с запросом на соединение по SSH на 22 порт, где правила port-forwarding'а будут пернаправлять пакет на server_1 ? server_1 ответит пакетом SYN+ACK, но направит его через свой указанный default_gateway, а именно через router_2. В итоге этот пакет зарубится сразу же, а клиент вылетит по таймауту коннекта. И тем самым, естественно часть внешних сервисов серверов будет недоступна. Будут доступны только сервисы, находящиеся на тех серверах, у которых указан рабочий шлюз.
Немного погуглив с минут 10, так ничего толком и не нашел. Были некоторые напоминания про директивы route-to, reply-to, round robin в pf, но примеры не соответствовали моей задаче, во всяком случай вникать в очередной раз в скупой мануал OpenBSD Packet Filter большого желания не было.
В итоге поступил так:
Появилась мысль пробрасывать порты на маршрутизаторах не непосредственно на конечный пункт сервисов, а на сам сервер, который непосредственно имеет default_gateway данного роутера. Иными словами это выглядит как-то так:
Код: Выделить всё
rdr on $ExtIf inet proto tcp from any to ($ExtIf) port 22 -> 192.168.0.3 port 22
NAT ? NAT !
NAT подменит дейтаграмму IP-пакета source address на адрес непосредственно внешнего интерфейса server_2, в итоге мы имеем достаточно удобную и гибкую картину фильтрации, учитывая еще тот момент, что за маршрутизаторами сервера имеют свою "серую" подсеть + применим тегирование пакетов:
Код: Выделить всё
rdr on ($ExtIf) inet proto tcp from any to ($ExtIf) port 22 tag SSH_REDIRECT -> 192.168.0.3 port 22
nat on ($ExtIf) inet proto tcp from any to 192.168.0.3 port 22 tagged SSH_REDIRECT -> ($ExtIf) port 1024:65535
Что нам скажет tcpdump ?
server_2
Код: Выделить всё
[19:45 root@blackice /home/alex]# tcpdump -nettti pflog0 port 22
tcpdump: WARNING: pflog0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pflog0, link-type PFLOG (OpenBSD pflog file), capture size 96 bytes
000000 rule 16/0(match): pass in on xl0: x.x.x.x.56175 > 192.168.0.4.22: tcp 12 [bad hdr length 16 - too short, < 20]
000028 rule 41/0(match): pass out on xl0: 192.168.0.4.61080 > 192.168.0.3.22: tcp 28 [bad hdr length 0 - too short, < 20]
^C
2 packets captured
11 packets received by filter
0 packets dropped by kernel
Код: Выделить всё
[19:46 root@darkstar /home/alex]# tcpdump -nettti pflog0 port 22
tcpdump: WARNING: pflog0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pflog0, link-type PFLOG (OpenBSD pflog file), capture size 96 bytes
00:00:00.000000 rule 34/0(match): pass in on msk1: 192.168.0.4.16079 > 192.168.0.3.22: tcp 28 [bad hdr length 0 - too short, < 20]
00:00:09.985336 rule 129/0(match): pass out on msk1: 192.168.0.3.60888 > 192.168.0.4.22: tcp 28 [bad hdr length 0 - too short, < 20]
Ну правила типа на обоих серверах
Код: Выделить всё
pass in log on ... tagged SSH_REDIRECT ...
pass out log on ... tagged SSH_REDIRECT ...
Тоже самое делаем и на server_2, только уже применяя к 80-му порту.
___________________________________
Не знаю, насколько красивым получилось данное решение, но факт остается фактом - сервера прекрасно обрабатывают запросы с обоих каналов.
P.S. Предлагающие BGP, OSPF - идут лесом в сад. Нужен был простой способ реализовать PBR, применимый к pf.