Зачем и о чём эта статья?
Если погуглить на тему «openvpn bgp», то можно найти несколько интересных и полезных с практической точки зрения статей (например раз или два). Но начиная решать задачку вынесенную в заголовок, я по многим причинам даже не удосужился погуглить. Идея пришла как-то сама собой в процессе долгой работы с OpenVPN вообще (в рамках вполне типовых задач, с фиксированным набором сетей с обеих сторон), работы с реализацией OpenVPN на системе RouterOS от MikroTik и стыковки между собой систем на Linux и RouterOS. Собственно в процессе осознания причин написания собственной реализации OpenVPN в RouterOS и пришло «озарение» как можно такую задачу решить в рамках вполне себе штатной редакции OpenVPN. Далее была короткая экспериментальная проверка, показавшая полную работоспособность идеи и запуск этого решения в «промышленную» эксплуатацию.
Помятуя, что таковая ситуация вполне типична для разных применений, а решения, описанного ниже, пока представлено не было, я решил поделиться идеей с сообществом.
Суть проблемы («кто виноват?»)
Чем же так существенно различаются штатная версия OpenVPN и та, что реализована в RouterOS? Отличий вероятно несколько, но в данной статье мы рассмотрим только одно: штатный OpenVPN в системах кроме RouterOS (и возможно некоторых других) является комбайном, который содержит в себе и транспортную часть (то есть собственно передачу пакетов, она же forwarding, он же data plane) и маршрутизирующую (то есть обмен информацией о маршрутах, он же routing, он же control plane), а в RouterOS сервис OpenVPN отвечает только за транспортную часть, а маршрутизацией занимается другой процесс системы, что позволяет с одной стороны не дублировать функциональность маршрутизирующей подсистемы (и тем более не держать несколько одинаковых таблиц маршрутов в разных сервисах и постоянно синхронизировать их между собой), а с другой стороны, позволяет поверх такого транспорта прозрачно осуществлять передачу таблиц маршрутов и изменять таблицы маршрутов с обеих сторон на лету.
Кроме того, штатная реализация OpenVPN имеет ещё один недостаток: передача маршрутов происходит только в одну сторону (от сервера к клиенту) и только в момент установления сессии (то есть поднятия туннеля). Никакого штатного способа добавить маршрут во внутреннюю таблицу маршрутов OpenVPN на ходу в процессе работы туннеля нет, так же как и передать маршруты с одной стороны на другую. Более того, невозможно даже получить саму таблицу маршрутов.
Решение проблемы («Что делать»)
Анализируя свои скрипты, автоматизирующие назначение маршрутов разным клиентам, я обратил внимание, что у OpenVPN есть две различных опции, задающих маршруты:
iroute
— задаёт маршруты внутри таблицы маршрутизации процесса OpenVPN.route
— задаёт маршруты, которые процесс OpenVPN передаёт в системную таблицу маршрутов (то есть добавляет в таблицу маршруты через свой туннельный интерфейс при подключении и удаляет их же при отключении).
Возник очевидный вопрос: а что будет, если с помощью iroute
добавить маршрут 0.0.0.0/0 с обеих сторон, а после этого нужные маршруты (в том числе динамически возникающие или пропадающие) добавлять или удалять на самом туннельном интерфейсе средствами например сервиса маршрутизации (routed, zebra/quagga, bird и т. п.)?
Эксперимент показал, что такая схема действительно работает с небольшим ограничением-неудобством: на один серверный туннель можно подключить только одного клиента. В остальном схема оказалась полностью работоспособной.
Схема работает в режим TLS-over-TCP, то есть для настройки предварительно необходимо сгенерировать ключи и сертификаты SSL.
Ниже привожу примерную конфигурацию OpenVPN для серверной и клиентской стороны.
Конфигурация серверной стороны (по одной для каждого клиента).
Файл server_dyn_rt.conf
(сторона сервера)
daemon
compress
ping-timer-rem
persist-tun
persist-key
tls-server
proto tcp-server
topology net30
mode server
script-security 3
keepalive 15 45
tun-mtu 1500
remote-cert-tls client
verify-x509-name <CLIENT_DISTINGUISHED_NAME> name
auth <TLS_AUTH_ALGORITHM>
cipher <CIPHER_ALGORITHM>
local <SERVER_PUBLIC_IP>
lport <SERVER_PUBLIC_PORT>
dev-type tun
dev <TUNNEL_INTERFACE_NAME>
ifconfig <TUNNEL_SERVER_SIDE_IP> <TUNNEL_CLIENT_SIDE_IP>
client-connect client_connect.sh
push "route-gateway <TUNNEL_SERVER_SIDE_IP>"
push "topology net30"
push "persist-tun"
push "persist-key"
<dh>
... Diffie-Hellman data
<</dh>
<ca>
... Certificate Authority certificate data
</ca>
<cert>
... Server certificate data
</cert>
<key>
... Server Private Key data
</key>
Файл client_connect.sh
(сторона сервера)
#!/bin/sh
echo 'ifconfig-push TUNNEL_CLIENT_SIDE_IP TUNNEL_SERVER_SIDE_IP' >> ${1}
echo 'push "iroute 0.0.0.0 0.0.0.0"' >> ${1}
echo 'iroute 0.0.0.0 0.0.0.0' >> ${1}
exit 0
Файл client_dyn_rt.conf
(сторона клиента)
daemon
compress
tls-client
auth <TLS_AUTH_ALGORITHM>
cipher <CIPHER_ALGORITHM>
client
dev-type tun
dev <TUNNEL_INTERFACE_NAME>
script-security 3
remote-cert-tls server
verify-x509-name <SERVER_DISTINGUISHED_NAME> name
remote <SERVER_PUBLIC_IP> <SERVER_PUBLIC_PORT> tcp
<ca>
... Certificate Authority certificate data
</ca>
<cert>
... Client certificate data
</cert>
<key>
... Client Private Key data
</key>
Настройки пакетов и протоколов маршрутизации не привожу как из-за многообразия пакетов, так и из-за многообразия самих настроек (собственно в качестве источника примеров настройки можно использовать вторую из статей, ссылки на которые приведены в начале статьи). Хочу лишь заметить, что вышеприведённая настройка позволяет использовать в частности BGP (который лично мне нравится как своей «управляемостью», так и способностью передавать маршруты различных протоколов в рамках одной сессии). В случае BGP в качестве адреса соседа (neighbor) на стороне «сервера» следует использовать адрес <TUNNEL_CLIENT_SIDE_IP>, а на стороне «клиента» соответственно адрес <TUNNEL_SERVER_SIDE_IP> или же «внутренние» адреса соответствующих сторон, но тогда надо добавлять соответствующие маршруты в конфигурацию сервера и/или клиента.
Плюсы и минусы вышеприведённого решения
Минусы:
- На один сервер должен приходится строго один клиент, поэтому для нескольких клиентов придётся держать активными несколько процессов OpenVPN. Как следствие — некоторый перерасход памяти и всякое такое.
- Нельзя использовать режим общего ключа (preshared key) в OpenVPN, потому что в этом режиме запрещена динамическая передача параметров от сервера клиенту (push/pull). Из-за этого требуется более сложная конфигурация, включающая генерацию набора ключей и сертификатов, а также скрипт генерации куска конфигурации клиента на стороне сервера (который правда можно заменить каталогом статических файлов, заменив опцию
client-connect /path/to/script
на опциюclient-connect-dir /path/to/config/dir
, что повышает уровень безопасности серверной стороны.
Плюсы:
- В отличие от таких протоколов как GRE/IPIP туннели OpenVPN могут иметь MTU равное 1500 байт (потому что процесс OpenVPN скрывает «под капотом» всю фрагментацию/дефрагментацию, отдавая в туннельный интерфейс пакеты полной длины). Это упрощает настройку всяких вторичных туннелей поверх туннеля OpenVPN.
- Туннель OpenVPN одновременно поддерживает передачу как протокола IPv4, так и IPv6, что позволяет сократить количество туннелей между парами узлов, затраты на их настройку и администрирование, а также передавать маршруты IPv6 в рамках той же сессии BGP, что и маршруты IPv4.
- Все плюсы протокола OpenVPN, такие как простота настройки промежуточного сетевого оборудования (или вообще полное отсутствие необходимости в таковой), возможность маскировки трафика под HTTPS, наличие реализации под большинство платформ et cetera, et cetera.
Надеюсь кому-то вышеприведённое руководство окажется полезным.