Portgen — обходим фильтрацию портов

в 13:53, , рубрики: bash scripting, linux, openvpn, vpn, информационная безопасность, криптография, Программирование, регулирование интернета, цензура интернета

Привет, GT!

Не растекаясь мыслями по деревьям, приступим к делу. Для обеспечения себя быстрым и нецензурируемым интернетом я уже давно использую стандартную схему: OpenVPN и самый простой VPS за рубежом. В качестве транспортного протокола используется UDP.

Проблема
В один «прекрасный» момент я обнаружил, что VPN отвалился и больше не поднимается. Не буду описывать долгое исследование проблемы — скажу сразу итог: помогло изменение номера порта. Помогло ненадолго: через пару-тройку дней туннель оборвался снова и снова был восстановлен сменой порта.

Гипотеза
На фаерволе провайдера статистика моего трафика выглядит примерно так: 100% или чуть меньше ходит по единственному протоколу UDP, через единственный порт и на единственный IP. Таким образом, если представить это себе в виде графика, он будет разительно отличаться от такового у среднестатистического пользователя, где будет множество «пиков» графика на разные протоколы, порты (TCP 80, 443, 993...) и IP-адреса.

Похоже, эвристика их фаервола реагирует именно на это и блокирует соединение, по которому проходит доля трафика, большая некоего порога. При этом, такой фильтр по определению будет срабатывать с некоторым запаздыванием, поскольку статистика — наука, любящая большие числа.

Задача
Нужно разработать решение, которое будет менять номер порта VPN с заданным интервалом, заведомо меньшим интервала пересчёта статистики у фаервола. Порт должен меняться в непредсказуемой манере, чтобы исключить работу фильтра на опережение. Также нужно иметь возможность запустить смену порта вручную на разные форс-мажорные случаи. Смена по времени должна работать даже при полной потере управления сервером.

Алгоритм
В общем виде формула генерации выглядит таким образом:

port = hash(shared_secret + round(time) + ondemand_key) % (port_max - port_min + 1) + port_min

  • hash() — хеш-функция
  • shared_secret — ключ, обеспечивающий непредсказуемость следующего результата без его знания. Единственная секретная часть системы.
  • round(time) — некоторая функция загрубления от текущего времени по UTC. Степень загрубления определяет интервал смены портов, например отбрасывание секунд и минут — интервал 1 час, отбрасывание ещё и часов с сохранением метки AM/PM — 12 часов, и так далее.
  • ondemand_key — ключ, подгружаемый с надёжного стороннего сервера для того, чтобы была возможность вручную сменить порт, изменив этот ключ.
  • port_min и port_max — диапазон используемых портов, минимальный и максимальный соответственно.

Для того, чтобы система быстро реагировала на изменение ondemand-ключа, я запускаю скрипт по планировщику с маленьким интервалом в несколько минут. При этом надо предусмотреть проверку на то, изменились ли параметры на самом деле, чтобы при отрицательном результате скрипт сразу бы завершился.

Посмотрим на выражение выше, которое к моменту проверки уже вычислено. Если не изменилось ни значение hash(), ни границы диапазона — значит, изменений не было. Для этого возьмём хеш ещё раз:

cache = hash(hash(...) + port_min + port_max)

После этого прочитаем старое значение cache из специального файла, сравним, и если они различаются — обновим файл и продолжим выполнение скрипта; если одинаковые — выход.

Далее — дело техники: подставим значение порта в шаблон правила для iptables (этих шаблонов два — для клиентской и серверной роли скрипта), добавим правило в таблицу, прочтём предыдущее правило из другого файла и, если оно существует, удалим его из iptables, обновим файл текущим правилом.

Теоретически, в этот момент соединение уже должно работать на новом порту, однако на практике оно почему-то продолжает использовать старый до перезапуска демона. Буду благодарен, если кто-нибудь объяснит причину такой багофичи, но пока я просто отдаю из скрипта команду на перезапуск. Перебой связи при этом — несколько секунд и неудобства не причиняет.

Система перезапуска по требованию
В качестве хранилища ondemand-ключа очень удобно использовать публичную VCS (у меня Гитхаб). В принципе, подошёл бы любой бесплатный хостинг, однако VCS имеет важное преимущество в хранении истории правок. Может произойти такая ситуация, когда один из концов туннеля обновил свой ключ, а второй по какой-либо причине не может достучаться до репозитория и работает по старому ключу в кэше. В этом случае можно попробовать откатить коммит: если падение связи, которое заставило админа дёрнуть ondemand, вызвано не блокированием порта, а например временным сбоем у аплинка, возврат ключа вскоре поднимет туннель. На практике подобные ситуации пару раз бывали.

Скрипт, который вытягивает ключ, не представляет собой ничего особенного — обыкновенный wget, проверка его кода возврата и кэширование в файл — поэтому описывать его смысла нет, код всё скажет сам за себя.

Скрипт управления ondemand-ключами — более объёмная штука, хотя по сути тоже прост — это обвязка для упрощения операций над репозиторием ключей. Позволяет выполнять в одну команду операции создания, удаления и смены ключей.

Заключение
Считаю, что инструмент для решения поставленной задачи удался, и успешно работает уже больше года. Более того, получившуюся утилиту можно считать универсальным оружием против подобного типа фильтрации, поскольку:

  • помимо OpenVPN скрипты можно использовать для любого сетевого сервиса
  • в качестве криптографического аппарата можно использовать любую хеш-функцию, что даёт задаток на возможную в будущем компрометацию сегодняшних алгоритмов
  • один сервер может обслуживать большое количество клиентов с уникальными ключами — достаточно лишь раскидать экземпляры portgen в индивидуальные папки

Ссылки
github.com/Raegdan/portgen — сама программа portgen
github.com/Raegdan/portgen-ondemand — программа управления ondemand-ключами и мои ключи заодно. Сделано это было исключительно для удобства. Ключи ondemand несекретные, поэтому если кому-то зачем-то вдруг нужен кусочек моего /dev/urandom — мне не жалко ;)

Примечание

Код - для ! Тут у нас космонавты, беспилотники и обзоры гаджетов!

В принципе, не могу не согласиться с таким аргументом, и программный код — действительно больше профиль Хабра. Однако, во-первых, этот проект напрямую относится к борьбе против цензурирования Интернета — тематике, которая была явным образом отселена сюда. Во-вторых, моя единственная до сих пор статья была написана до разделения и осталась на Хабре, что вызывает известные ограничения для моей учётки на GT. Прошу отнестись с пониманием.

Автор: Raegdan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js