Для понимания сути вопроса — немного истории:
Жила-была CRM, частная, корпоративная — хостилась на удаленном сервере у надежного хостера, сотрудники из разных филиалов заходили в нее и работали свою работу.
Никаких тысяч серверов, никакого кровавого энтерпрайза — всё тихо, мирно, почти по-домашнему.
И работало это долго, пока враги не начали ломать интернет.
У части пользователей пропадал доступ к сайту, работать они не могли — сначала помогал VPN, но потом враги начали бороться и с VPN.
Попробовали разнести точки входа по серверам по локациям, сохранив единую базу — враги начали блокировать межсерверные соединения.
А тут еще у одного знакомого вдруг сервер отключили (другие враги, не местные) — значит, надо забирать базу локально.
У другого знакомого локальный сервер поломался — значит, нужно делать удаленную резервную площадку.
Ну ок, системы разнесли, резервы понастроили — а как к ним обращаться, если напрямую нет соединения? Ну прокинули очередной VPN, повесили на какой-то IP адрес канала — а завтра этот канал не работает и этот адрес теперь недоступен, надо менять. Утро начинается не с кофе, утро начинается с проверки «что сегодня и как заблокировали».
Решение, конечно, нашлось, и я о нем писал: каждому серверу был выделен уникальный IP-адрес, не связаный ни с чем, ни с каналами, ни с провайдерами, все сервисы — на этих адресах, а маршрутизация между ними — средствами OSPF через временные по сути каналы связи. Отвалится один — не страшно, мы пойдем другим путем.
OpenVPN отвалился первым, WireGuard вторым, IPsec за пределами локальной сети так и не заработал, но этим, к счастью, варианты не ограничиваются.
Называть мы их не будем, когда приходится играть в карты с шулерами — лучше держать в рукаве десяток запасных тузов.
И вот, появился Qeli. Плюс в том, что в нем сразу предусмотрены несколько возможных протоколов, он один — как нож со сменными лезвиями.
Решено было добавить его в систему каналов. Во-первых, потестировать, а во-вторых — если не получится, ну, не получится, бывает.
По топологии — это “звезда”: к одному серверу может подключаться сразу несколько клиентов, и обмениваться трафиком. То есть, строго говоря, можно назначить один из узлов сервером — остальные подключатся к нему и будут через него работать.
Очевидный минус — между двумя “лучами” задержка суммируется — до сервера и от сервера.
Кроме того, получаем единую точку отказа: если что-то пойдет не так — связь потеряется у всех.
Но ведь можно каждый узел сделать сервером, и каждый же — клиентом для всех остальных.
Конечно, лютый оверинжиниринг, но и условия у нас, скажем так, далеки от лабораторных.
Основных проблем виделось две: много ручной работы по настройке, и разные версии ОС на серверах, а бинарник qeli привязан к конкретной версии GLIBC, для которой он был собран.
Начнем, конечно, со второй:
если создать docker контейнер с ОС нужной версии — программу можно запустить в контейнере!
docker run -ti --name qeli --privileged --network host -v /opt:/opt debianapt updateapt upgradeapt install iproute2cd /opt./qeli server -c server.conf &...
Заранее положив в /opt бинарник и конфиги.
Важный нюанс — нужно установить пакет iproute2, в котором утилита ip — иначе ничего не будет работать.
Серверный конфиг, например, такой:
[auth]require_client_key_proof = truebind_static_to_session = truebrute_force.max_attempts = 5brute_force.window_secs = 60brute_force.lockout_secs = 300[logging]level = infofile = /var/log/qeli/server.log[web]enabled = false[profile:udp-obfs]enabled = truebind.address = 0.0.0.0bind.port = MY_PORTbind.transport = udptun.mtu = 1460tun.queues = 0tun.name = qserver# 240 netmask, nets 0_, 16_, 32, 48, 64, 80, 96, 112, 128, 141, 160, 176, 224, 240tun.netmask = 255.255.255.240pool.cidr = 192.168.168.32/28tun.address = 192.168.168.33pool.exclude = 192.168.168.33routing.nat.enabled = falserouting.forward_private = truerouting.client_to_client = truedns.enabled = falseobf.mode = obfsobf.obfs_key = MY_KEYobf.padding.enabled = falseobf.heartbeat.enabled = trueobf.heartbeat.interval_ms = 5000obf.heartbeat.jitter_ms = 2000perf.connection.max_clients = 64perf.connection.handshake_timeout_secs = 10perf.connection.idle_timeout_secs = 0
Заранее рассчитаны несколько подсетей для нескольких серверов:
Например, подсеть 192.168.168.128/28, адрес сервера будет 192.168.168.129 (128 + 1), адреса клиентов соответственно от 130 до 139, 140 бродкаст и 141 — следующая сеть.
Методика расчета простая: записываем биты последнего октета, из расчета «маска подсети — 24», в данном случае меняем 4 первых бита:
echo $(( 2#01110000 )) = 112echo $(( 2#10000000 )) = 128echo $(( 2#10010000 )) = 141
При старте нового сервера формируется новый серверный key, что-то типа такого: 88936461a63b49214745065699c7eac148a4260fcf4695f1dc1f7d710b71371b
Его можно получить командой:
./qeli show-identity
Также, нужно завести юзера:
./qeli add-client username
Можно указать сразу пароль, а можно получить некий случайный JKj6788sdfJJK2333
Юзеры с паролями сохраняются в /etc/qeli/users.conf.
В принципе, каталог /etc/qeli можно тоже копировать между серверами, подключая при запуске контейнера, это уменьшит количество операций по настройке, но почему-то решил делать для разных серверов разные
Теперь настраиваем конфиг клиента для этого сервера:
[qeli]server = SERVER_IP:MY_PORTproto = udpuser = usernamepass = JKj6788sdfJJK2333key = 88936461a63b49214745065699c7eac148a4260fcf4695f1dc1f7d710b71371bmode = obfsobfs_key = MY_KEYdev = qelialfamtu = 1460mtu_probe = falsegateway = falseroute_local = truekill_switch = falseallow_ipv6_leak = truedns = offautostart = false#post_up = /etc/qeli/up.sh#post_down = /etc/qeli/down.sh[logging]level = infofile = /var/log/qeli/client.log
Сохраняем его в файл типа client-alfa.conf, и идем на следующий сервер.
Устанавливаем, настраиваем подсеть, порты, адреса, юзера и ключи, сохраняем файл client-bravo.conf.
И так далее для всех N серверов. Почти везде — копипаста.
А потом пишем скрипты запуска всего этого зоопарка, типа такого:
cd /optsetsid ./qeli server -c server.confsetsid ./qeli client -c client-alfa.confsetsid ./qeli client -c client-bravo.confsetsid ./qeli client -c client-charly.conf...
Убираем только запуск сам-на-себя.
И запускаем:
docker start qelidocker exec qeli /opt/startup
Эти команды можно записать, например, в /etc/rc.local или какой другой стартап-скрипт.
Всё, первая часть выполнена: сервера связаны каждый с каждым, появилось по N-1 сетевых интерфейсов с соответствующими именами, пинги ходят туда-сюда.
Теперь нужно весь этот бардак упорядочить.
Имена интерфейсов на каждом узле известны, однотипны — qelialfa, qelibravo, и т.д.
Нужно только обьявить на интерфейсе lo уникальные IP-адреса и настроить связность по OSPF.
OSPF — это система обмена маршрутами к узлам сети между роутерами.
Роутер в данном случае — сам компьютер, узел сети, на котором запущены демоны маршрутизации — OSPF и BFD.
Демону OSPF указывается список интерфейсов и адресов, с которым он работает, и если с той стороны интерфейса слушает другой демон OSPF — они обмениваются сообщениями.
BFD контролирует активность интерфейсов.
Как-то так:
ospfd: — у меня есть узел 123.45.67.89, надо кому?
iface1: — принято, записал, 123.45.67.89 это к тебе, а у меня есть сеть 112.11.11.0/24
ospfd: — принято, эта сеть — к тебе
bfdd: — там похоже iface2 сдох, потыкай его палочкой
ospfd: — и правда, сдох. Через него работало 33.23.22.33, кто-нибудь знает как туда еще попасть? Никто? Вычеркиваем…
Для настройки OSPF можно использовать пакет frr в котором уже есть нужные демоны, и запускается он также в докере:
mkdir /etc/frrdocker run -ti -v /etc/frr:/etc/frr --name frr --privileged --network host debianapt updateapt upgradeapt install frrexit
Включаем нужных демонов, остальное не трогаем:
vim /etc/frr/daemonsospfd=yospf6d=ybfdd=y
Запускаем, и делаем первичную настройку:
docker start frrdocker exec frr /usr/lib/frr/frrinit.sh startdocker exec frr vtyshconfigute terminalinterface loip address 192.168.200.1/32ipv ospf area 0ipv6 ospf6 area 0exitrouter ospfospf router-id 1.4.0.1maximum-path 1exitrouter ospf6ospf6 router-id 1.6.0.1maximum-path 1exitexitwrite memoryexit
Теперь у нас OSPF подхватит и раздаст адреса на интерфейсе lo, но пока не указано кому.
И вот тут возникла неожиданная проблема:
Для работы OSPF нужно, чтобы линки между узлами пропускали мультикаст-пакеты, 224.0.0.1, причем в обе стороны. А qely версии 0.7.10 не пропускает.
Частично вопрос можно решить, обьявив в OSPF интерфейс как non-broadcast — но всё равно от сервера к клиенту пакеты с IP, отличающимся от подсети сервера, не проходят, а это значит что связи не будет.
Узлы будут знать, куда отправлять пакет — но его не пропустит qeli.
Но тут удачно вспомнилось существование протокола IPv6. Qeli его тоже не пропускает, но зато поверх линков можно создать ipv6-туннели, и делается это довольно просто:
ip tunnel add IF_NAME mode sit local LOCAL_QELI_IP remote REMOTE_QELI_IP ttl 255ip link set dev IF_NAME up mtu 1440
Сделать это нужно на обоих сторонах линка, поменяв местами local и remote.
Параметр MTU должен быть на 20 байт меньше, чем MTU линка, а его задали как 1460.
У полученного интерфейса автоматически появляются link-local ipv6 адреса, по которым прекрасно работает OSPF, точнее OSPF6, для 6 версии протокола.
И теперь, если на узлах добавить эти интерфейсы в OSPF6 — установится связь.
Только уникальный адрес на интерфейсе lo должен быть из ipv6 — например, fd00:cafe::1, fd00:cafe::2 и т.п.
Соответственно, между ними заработает сеть ipv6, в которой каждый узел будет иметь свой заданный независимый адрес, что и требовалось.
Справедливости ради — можно создать туннели mode ipip, точно такие же — но для ipv4, и они тоже работают хорошо.
Но у них не будет link-local адресов, их нужно назначать вручную, и ничего не перепутать — и вот это довольно сложно автоматизировать для N серверов.
Что ж, это лишний повод попытаться перейти на ipv6, но пока просто автоматизируем это всё, потому что создавать ручками кучу линков — ну это такое себе.
За основу берем два факта:
Во-первых, у qeli есть функция получения списка активных коннектов.
./qeli list-client
получаем список текущих подключений с выданнными адресами, а свой адрес мы знаем, он 1 в той же подсети. Это позволяет создать половинки туннелей с серверной стороны.
Во-вторых, нам известен список возможных клиентских интерфейсов — мы их задавали в конфигах.
Можно получить локальный выданный адрес, а адрес сервера мы опять знаем, он 1 в той же подсети. Это позволит создать клиентские половинки туннелей.
Получаем созданные интерфейсы, и добавляем их в OSPF через командный инерфейс vtysh.
Остается совместить одно с другим
#!/bin/bashPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin; export PATHuser='username'mtu=1440ifaces="qelialfa qelibravo qelicharly qelidelta ..."docker exec qeli /ext/qeli list-clients | while IFS= read -r line ; do ip=$( echo $line | grep "$user" | awk '{ print $2}' ) if [ "x$ip" != "x" ]; then last_num=$( echo $ip | sed -E 's/^.*\.([0-9]+)/\1/') net_num=$(( ($last_num & 2#11110000) + 1 )) local_ip="192.168.168.$net_num" if_ip="qsit${last_num}" ip link show dev $if_ip if [ $? -ne 0 ]; then echo "create" ip tunnel add $if_ip mode sit local $local_ip remote $ip ttl 255 fi ip link set dev $if_ip up mtu $mtu docker exec frr vtysh \ -c "configure terminal" \ -c "interface $if_ip" \ -c "ipv6 ospf6 area 1" \ -c "ipv6 ospf6 priority 10" \ -c "ipv6 ospf6 bfd" \ -c "exit" \ -c "exit" fidone# clientsfor i in $ifaces ; do ip link set dev $i up if [ $? -eq 0 ] ; then ip=$( ip addr show dev $i | grep 'inet ' | awk '{ print $2 }' | sed 's/\/.*//' ) if [ "x$ip" != "x" ]; then last_num=$( echo $ip | sed -E 's/^.*\.([0-9]+)/\1/') net_num=$(( ($last_num & 2#11110000) + 1 )) server_ip="192.168.168.$net_num" # add to frr if_ip="qsit${last_num}" ip link show dev $if_ip if [ $? -ne 0 ]; then echo "create" ip tunnel add $if_ip mode sit remote $server_ip local $ip ttl 255 fi ip link set dev $if_ip up mtu $mtu docker exec frr vtysh \ -c "configure terminal" \ -c "interface $if_ip" \ -c "ipv6 ospf6 area 1" \ -c "ipv6 ospf6 priority 10" \ -c "ipv6 ospf6 bfd" \ -c "exit" \ -c "exit" fi fidone
И просто запускаем скрипт после установки соединений. Можно несколько раз, можно хоть каждую минуту — лишнее создаваться не будет.
Только не забыть добавить на lo интерфейс ipv6 адреса, типа fd00:cafe::1/128
Вторая часть, построение сети — готова: с любого узла проходят пинги на соседний fd00:cafe::x
Причем они проходят по шифрованному каналу, по наименьшему пути, и вообще оно работает «как-то само», особенно если начать отрубать то одно, то другое соединения — просто сеть перестраивается на лету.
Третья задача — дать доступ к этой сети нескольким машинам в локалке, сидящей где-то сбоку за одним из узлов.
Можно, конечно, в лоб: еще одно qeli-соединение, еще один туннель, который включится в общую сеть.
А можно по другому:
Во-первых, можно поднять в локальной сети свою ipv6-сеть, просто добавив адреса на шлюзе
ip addr add fd00:bebe::1/64 dev iface
и на целевой машине
ip addr add fd00:bebe::2/64 dev ifaceip -6 route add default via fd00:bebe::1
а потом добавить на шлюзе правила в ospf:
docker exec frr vtyshconfigure terminalrouter ospf6redistribute staticexitipv6 route fd00:bebe::/64 ifaceexitexit
Во-вторых, можно локально ipv6 не поднимать, добавить адрес прямо из серверной сети на целевую машину и настроить статический роутинг через link-local адрес интерфейса на шлюзе:
ip -6 addr show dev iface...inet6 fe80::32a6:12ff:fe0e:951a/64 scope link...
на целевой машине:
ip addr add fd00:cafe::beer/128 dev ifaceip -6 add default via fe80::32a6:12ff:fe0e:951a dev iface
и потом добавить правила:
docker exec frr vtyshconfigure terminalrouter ospf6redistribute staticexitipv6 route fd00:cafe::beer/128 ifaceexitexit
И так и этак будет работать: с машины в сеть, и из сети — на эту машину.
Зачем? Затем, что теперь эта машина может взаимодействовать с узлами нашей сети так, как будто все они находятся в одной локалке.
Например, на локальной машине может находиться бекенд вебсервера, «морда» которого находится на узле Alfa.
Для этого на Alfa устанавливаем, например, nginx и настраиваем upstream:
vim /etc/nginx/sites-available/alfa.comupstream beer { server [fd00:cafe::beer]:3000;}server { listen 80; server_name alfa.com; location / { return 301 https://$host$request_uri; }}server { listen 443 ssl; http2 on; server_name alfa.com; keepalive_timeout 70; ssl_certificate /root/.acme.sh/alfa.com_ecc/fullchain.cer; ssl_certificate_key /root/.acme.sh/alfa.com_ecc/alfa.com.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://beer; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-HTTPS 0; }}
Теперь при заходе на сервер alfa.com реальное соединение идет с бекендом на сервере Beer, который стоит где-то у вас совсем в другом месте, вовсе не на Alfa.
При этом сервер bravo.com тоже может быть реально на Beer. А может быть не на Beer, а на Beef, который стоит в совсем другой локации — но всё это для вас «локальная сеть».
Мало ли для чего может пригодиться локальная сеть большого радиуса действия…
А самое интересное тут, что ipv6 оказывается действительно удобен. И это даже в режиме «локальных» fd00-адресов.
ссылка на оригинал статьи https://habr.com/ru/articles/1059102/