Обход блокировок WireGuard в Египте
В 2021 году VPN протокол WireGuard стал настолько популярен в Египте, что удостоился чести пополнить список заблокированных, несказанно «обрадовав» не только клиентов Cloudflare Warp+, Mullvad Wireguard и других коммерческих VPN-провайдеров, но и некоторых пользователей корпоративных VPN. Предварительные исследования показали, что по всей видимости DPI нацелен на WireGuard Handshake Initiate пакеты, которые имеют фиксированный размер (148 байт) и узнаваемую структуру (первые четыре байта UDP пакета [0×01, 0×00, 0×00, 0×00]).
WireGuard Handshake Initiation
Чтобы понять, как DPI может обнаружить и заблокировать WireGuard нужно привнести немного теории. Сам по себе WireGuard протокол предельно простой, трафик упаковывается в совершенно типовой UDP с добавлением небольшого заголовка. Для согласования WireGuard туннеля, как правило, достаточно двух (трех, если считать Keepalive) пакетов:
Сторона желающая установить туннель (клиент) отправляет Hadshake Initiation другой стороне (сервер).
Если сервер в данный момент готов к подключению, то он отвечает клиенту пакетом Handshake Response.
Клиент получает Handshake Response и отвечает Keepalive пакетом, который представляет собой UDP пакет с WireGuard заголовком типа «данные» нулевого размера. В дальнейшем ключи обновляются повторяя описанную процедуру примерно каждые две минуты.
Таким образом, кажется, что для блокировки WireGuard DPI достаточно отслеживать UDP пакеты размером в 148 байт и проверять в них первые четыре байта на соответствие сигнатуре [0×01, 0×00, 0×00, 0×00]. Однако, стоит упомянуть, что корпоративные реализации WireGuard могут использовать зарезервированные три байта (поле Reserved) для собственных нужд (например, в Jamf Private Access они используются как идентификатор сессии). К тому же не исключено, что рано или поздно им найдется применение и в официальном клиенте. Так что для большей точности имеет смысл ограничиться только первым байтом UDP пакета. С другой стороны блокировка всех UDP пакетов размером в 148 байт с первым байтом 0×01 выглядит довольно рискованно. То же самое можно сказать и о Handshake Response пакете, который так же имеет фиксированный размер (92 байта) и схожую сигнатуру с тремя зарезервированными байтами [0×02, 0×00, 0×00, 0×00].
WireGuard Handshake Response
Как же тогда может выглядеть возможный критерий блокировки WireGuard с минимизированной вероятностью ложных срабатываний? По всей видимости DPI стоит пропустить пакет «похожий» на Handshake Initiate, запомнить состояние сессии (IP адреса и порты) и поставить ее на блокировку только после ответного пакета «похожего» на Handshake Response. Нельзя утверждать наверняка, но по косвенным признакам весьма вероятно, что с чем-то подобным мы и имеем дело.
С чего же началась эта история? Скорее всего я не стал бы специально заниматься этой темой, если бы меня неожиданно не заинтересовал пост на форуме, в котором пользователь описывал любопытный метод обхода блокировки WireGuard с помощью другого VPN. Кому интересно, можете почитать нашу переписку, но если в двух словах, то он использовал Windscribe VPN для того, чтобы активировать WireGuard туннель, затем выключал Windscribe VPN и продолжал пользоваться WireGuard (официальный клиент с конфигурацией от Cloudflare Warp+) как ни в чем ни бывало.
В ходе нашего общения постепенно прояснились и прочие подробности, включая государство, которое таким оригинальным способом «подталкивает» население к более глубокому изучению сетевых технологий. Последний раз я был в Египте в 2010 и не могу сказать, что сильно по нему скучаю. Однако в настоящее время это одна из немногих легкодоступных стран, и я в последнее время всерьёз задумывался не слетать ли туда на дайвинг. Надо ли говорить, что невозможность доступа к домашним сетям оказалась бы крайне неприятным сюрпризом.
Далее предстояло решить, что использовать для маскировки Handshake Initiate и Handshake Response вместо Windscribe VPN. Можно конечно построить самописный сервис для инкапсуляции и форвардинга этих пакетов, тем более что нагрузка на него была бы минимальной (два пакета раз в две минуты на туннель). Однако уже довольно давно существует и более подходящий кандидат на эту роль и имя ему SOCKS5. Пятая версия этого популярного протокола поддерживает UDP, что нам собственно от неё и нужно.
Ранее я уже писал о своём пет-проекте VPN клиента на основе Cloudflare реализации протокола WireGuard. В него то я и решил добавить поддержку SOCKS5. В процессе тестирования промежуточных сборок выяснились кое-какие особенности реализации DPI. Например, если первый Handshake Initiate пролетел мимо SOCKS и попал в DPI, то неважно что потом вы отправляете в рамках данной UDP сессии, DPI будет ее настойчиво блокировать. Что любопытно, ничтожная часть пакетов каким-то образом все таки прорывается (возможно их маршрут проходит мимо DPI). Так же похоже, что верно и обратное, если UDP сессия не началась с Handshake Initiate — Handshake Response, то претензий к данной сессии у DPI не будет, даже если эти пакеты покажутся там в дальнейшем. Это могло бы объяснить почему у пользователя WireGuard туннель не ломался через две минуты при следующем Handshake Initiate.
Для поддержки SOCKS5 в Wiresock VPN Client были добавлены следующие дополнительные параметры:
Socks5Proxy — указывает адрес и порт сервера SOCKS5, например
Socks5Proxy = socks5.sshvpn.me:1080
илиSocks5Proxy = 13.134.12.31:1080
Socks5ProxyUsername — имя пользователя SOCKS5 (опционально)
Socks5ProxyPassword — пароль пользователя SOCKS5 (опционально)
Так же для тестирования нам понадобился SOCKS5 сервер с поддержкой UDP ASSOCIATE. Полагаю большинство читателей вполне способно сконфигурировать такой сервер самостоятельно, поэтому думаю не стоит раздувать размер статьи приводя здесь детальную инструкцию. Остальным же могу порекомендовать памятку, которую я составил для себя по установке Dante от Inferno Nettverk A/S на Ubuntu 20.04 в Oracle Cloud.
Мы протестировали как вариант с применением SOCKS только к Handshake Initiate/Response пакетам, так и полностью ко всем пакетам WireGuard туннеля. Оба варианта прекрасно справились с блокировкой, поэтому остановились на первом, поскольку он более выигрышный с точки зрения производительности и ресурсов. Из неожиданных побочных эффектов, отправка Hanshake Initiate через SOCKS5 прокси «починила» возможность построения вложенного WireGuard туннеля с использованием официального клиента WireGuard for Windows (в качестве внешнего туннеля) и Wiresock VPN Client (в качестве внутреннего), которая «поломалась» где-то между релизами 0.5 и 0.5.3. Официальный клиент внезапно перестал пропускать Handshake Initiate в туннель (возможно это связано с работой по поддержке собственных вложенных туннелей), но после оборачивания в SOCKS он перестал его узнавать и все снова заработало.
Так же хотел отметить, что не представляет большой сложности сделать отдельное Windows приложение для официального клиента, которое будет перехватывать исходящие Handshake Initiate и отправлять их через SOCKS5. Если подобное будет востребовано, то я постараюсь найти время для реализации.
На этом все, надеюсь эта информация будет кому-то полезной!