Примерно с 2019 года я использую OpenVPN для связи локальных сетей между разными локациями. Это достаточно удобно, поскольку в отличие от новомодных WireGuard и Zerotier поддержка OpenVPN «искаропки» есть практически в любом современном роутере. Так как конфигурация сети с момента запуска примерно и не менялась, мне не приходилось лезть «под капот», чтобы что-нибудь там изменить или тюнинговать. До вчерашнего дня.
Нет, конечно же ради интереса я и раньше ставил эксперименты над OpenVPN, пытаясь определить, какая конфигурация и какой транспорт лучшим образом будут влиять на те или иные условия передачи данных. Но эти эксперименты всегда были понятными и очевидными, а помимо всего прочего — ещё и хорошо задокументированными. Вчера же передо мной встала более интересная задача — сделать так, чтобы на некоторых локациях OpenVPN работал по UDP, а на некоторых — по TCP через прокси.
Тут понятное дело, что достичь этого можно, запустив со стороны сервера два разных конфига OpenVPN — один с TCP-транспортом, другой — с UDP. А вот с прокси всё оказалось намного интереснее, чем может показаться на первый взгляд.
У OpenVPN уже сотню лет существует конструкция, которая заставляет клиент подключаться через HTTP-прокси с аутентификацией, выглядит это примерно так:
http-proxy 127.0.0.1 3128
<http-proxy-user-pass>
proxy_username
proxy_password
</http-proxy-user-pass>
Реквизиты для аутентификации можно передать как в виде файла, так и спросить в консоли, однако в моём случае чем проще — тем лучше, поэтому весь конфиг должен размещаться в одном файле. Указанный выше фрагмент — на 100% рабочий, с ним всё хорошо и всё чётко работает. Но у меня возникла другая идея: а почему бы не передавать через конфиг не IP-адрес прокси-сервера, а его доменное имя? Так как сервер под прокси я взял в аренду на краткий срок для экспериментов, мне явно не хотелось бы потом снова повторять процедуру обновления конфигов везде, где прописан чёткий IP-адрес.
Да, действительно, в директиве http-proxy можно указать не только IP-адрес, но и доменное имя. Однако не всё так просто. Доменное имя должно резолвится только в IPv4-адрес, ни в коем случае никак иначе. Если доменное имя имеет помимо A-записи ещё и AAAA-запись, то OpenVPN Connect зачем-то запрашивает именно вторую, а затем выбрасывает в лог сообщение о том, что hostname резолвится не в IPv4 и он не знает, что дальше с этим делать. Казалось бы, логичным ходом здесь будет отключение IPv6 в настройках OpenVPN Connect (эта опция скрыта под экспертными в основных настройках), но нет — после отключения IPv6 ошибка с резолвингом hostname прокси-сервера уходит, но приходит новая беда — ovpnagent не может передать TAP-устройству настройки, т.к. получает отказ в регистрации полученного от OpenVPN-сервера IPv6-адреса.
Логика OpenVPN Connect в данном случае мне, безусловно, не ясна в двух моментах:
- Зачем запрашивать AAAA-запись для домена, если ты хочешь получить его IPv4-адрес и ничто больше?
- Зачем при отключённом в настройках IPv6 пытаться зарегистрировать сетевое устройство с IPv6-адресом (не проще ли просто проигнорировать всё, что касается IPv6)?
На этом моменте, в принципе, я и перестал ковыряться в OpenVPN Connect и перешёл обратно на более привычный мне OpenVPN GUI клиент. На том, что правда, приколы нашего городка не завершились.
Оказалось, что OpenVPN GUI категорически отказывается авторизоваться на HTTP-прокси, если после порта в директиве http-proxy отсутствует ключевое слово basic. Ладно, добавил. Хотя раньше всё отлично работало и без него.
ПК-клиент завёлся, туннель поднялся, сеть заработала. Я, удовлетворённый своей сегодняшней работой, откинулся на спинку стула, взял телефон в руки и попытался настроить тот же конфиг на мобильном OpenVPN-клиенте. А вот фигушки. Оказывается, OpenVPN Connect для смартфона не поддерживает директиву basic и воспринимает её как адрес файла, к которому нужно обратиться и из которого необходимо получить реквизиты для аутентификации на HTTP-прокси. И да, версия клиента для телефона — последняя доступная в AppStore.
Честно говоря, это просто удивительно, что один и тот же протокол имеет различные наборы директив в разных клиентах. Клиентах причём, стоит заметить, официальных, так как будь они выпущены разными издателями — я бы ещё мог понять, с чем связаны такие странные расхождения.
Хорошо. Делаю отдельный конфиг для мобильного клиента, из которого изымаю директиву basic, всё заводится и начинает работать как ожидалось. Я доволен, но, как говорится, осадочек остался.
На следующий день я решил продолжить свои эксперименты и заменить прокси с HTTP на SOCKS. Помнится, совсем недавно всякие серые интеграции с Instagram нахваливали протокол SOCKS как единственный корректно работающий и надёжный.
Поднимаю SOCKS-прокси с аутентификацией, пытаюсь переделать конфиг OpenVPN под работу с SOCKS вместо HTTP. Первое, с чем сталкиваюсь — это очередное расхождение в директивах. Для http-proxy реквизиты аутентификации можно передать посредством блока http-proxy-user-pass, а для SOCKS — нельзя. И аналогов этому блоку для SOCKS-прокси просто нет. Единственный вариант — передавать реквизиты отдельным файлом. Ну или же использовать прокси без авторизации. Передавать что-то отдельным файлом в данном случае — не наш вариант, так как согласно условий, которые я ставил изначально, весь конфиг OpenVPN должен умещаться в одном файле без дробления. Поэтому было принято решение об использовании SOCKS-прокси без аутентификации, но с ограничениями на источники и цели подключения, что, в принципе, вполне покрывает мои потребности, т.к. IP-адреса во всех случаях фиксированные.
socks-proxy 127.0.0.1 1080
Опять же, подключаю полученный конфиг на ПК-клиенте OpenVPN GUI — всё отлично работает. Но имея негативный опыт взаимодействия с OpenVPN Connect на мобильном телефоне сразу же принимаюсь тестировать полученный конфиг там. И что бы вы могли подумать? Всё заводится с полуоборота.
Но есть одно «но»: OpenVPN Connect для мобильного просто тупо игнорирует директиву socks-proxy, не пытаясь установить с ним соединение. Соединение сразу устанавливается с сервером OpenVPN. По всей видимости, мобильный клиент OpenVPN Connect банально не поддерживает работу с SOCKS-прокси, при этом не считает наличие такой директивы в конфиге и ошибкой. И в то же время не считает нужным сообщить о том, что директива проигнорирована. Прекрасно.
Кстати, немного об OpenVPN + SOCKS-прокси. Чисто технически, SOCKS-прокси в отличие от HTTP-прокси вполне себе способен проводить через себя и UDP-трафик, поэтому в теории OpenVPN-соединение должно установиться как по TCP-транспорту (именно о нём я и говорил выше), так и по UDP-транспорту.
С последним, что правда, мне не удалось кашу сварить. OpenVPN GUI при этом ничего плохого в лог соединения не выбрасывает и вроде как всё идёт по плану: сервер сперва обращается к прокси, затем получает от прокси порт, на который следует кидать UDP-пакеты. Но они то ли не доходят, то ли сервер их не принимает — я пока не понял до конца. Так или иначе, соединение с OpenVPN по транспорту UDP через SOCKS-прокси для меня завершается не начавшись — исходящий от клиента трафик идёт, судя по счётчикам внутри OpenVPN GUI, а входящего ответного трафика нет.
Расследованием, кто в этом виноват и по какой причине возникает такая проблема, я, пожалуй, займусь как-нибудь в другой раз. На сегодня с меня хватит двухдневного OpenVPN-марафона. Но могу сказать, что это было очень интересно. Я не думал, что обработка конфигов одного и того же протокола может так сильно зависеть от различий клиента.