惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
宝玉的分享
宝玉的分享
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
The Register - Security
The Register - Security
T
Threat Research - Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
PCI Perspectives
PCI Perspectives
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
aimingoo的专栏
aimingoo的专栏
F
Full Disclosure
P
Palo Alto Networks Blog
A
About on SuperTechFans
Microsoft Azure Blog
Microsoft Azure Blog
F
Fortinet All Blogs
爱范儿
爱范儿
Recorded Future
Recorded Future
月光博客
月光博客
T
True Tiger Recordings
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tenable Blog
L
Lohrmann on Cybersecurity
博客园 - 聂微东
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
大猫的无限游戏
大猫的无限游戏
S
Security @ Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
C
Check Point Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
The Cloudflare Blog
Malwarebytes
Malwarebytes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
InfoQ
N
Netflix TechBlog - Medium
Recent Announcements
Recent Announcements
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
T
ThreatConnect
博客园 - 叶小钗
B
Blog

Все публикации подряд на Хабре

[Перевод] Любой пользователь интернета может позвонить в вашу дверь Новый экспериментальный препарат для похудения обеспечил резкое снижение веса Хром и скорость Провалила вайтборд, но прошла тестовое — как я делала задание для Т-Банка Космическая линза помогла Уэббу увидеть древнейшую галактику Вселенной Почему custom URI schemes в Telegram Mini Apps ведут себя по-разному на Android, iOS и Desktop Как я сократил рутину QA до пары кликов: генератор API-тестов и тест-кейсов на LLM, которым хочу поделиться ИИ‑спасатель в кармане: как мы сделали агента для помощи при ЧС, который работает без интернета QNAME minimisation на практике: RFC 7816, реализация, грабли Агенты, роботы и мы: как ИИ перекраивает рынок труда в Европе От боли к npm install: TDLib для React-Native, или как я делал проект, а получилась библиотека Написание консольного симулятора баттл-арены на языке С++ с реализацией «умных» ботов Очень много букв… Или кейс по специфической настройке рабочего окружения Segmentation Fault: как оно устроено? Python в enterprise: момент, когда пора открыть Java не только ради собеседований MonoGame — игровой движок для тех, кто любит изобретать велосипеды Спасти рядового Буридана Рефакторинг выпадающих списков: от enum к конфигу-константе Free Porn Storage: передаём мемы в TLS-трафике, не привлекая внимания санитаров Мониторинг цен на Авито: MikroTik RouterOS Script Венесуэльская нефть после января 2026 Разговоры с ИИ Хотел упростить мониторинг проектов и в отпуск — пришлось обучать свой LLM. Часть 4. Тестирование Как вытащить ИТ из кризиса перегрузки, если найм запрещён Как мы подключили LLM к поддержке, а получили идеального лжеца Zero — новый agent-first язык программирования от Vercel, который изменит все (нет) Запускаем рекламу в дачной нише: какие креативы и форматы работают, на что смотреть в аналитике Паттерны организационного дизайна: практическое руководство Почему алгоритмы сливают твой депозит? 3 причины, о которых молчат «успешные» бэктесты Как «спят» вкладки в браузере Приоритет задач определяется не только ощущением срочности [Перевод] Махинации с прибылью Anthropic Project Loom: Virtual Threads, Scoped Values и preview #7 Structured Concurrency Мнения математиков о том, как ИИ опроверг гипотезу Эрдёша Слабоумие и отвага: как я за выходные сделала прототип ИИ-помощника для UX-дизайнера ИИ учит нас писать лучше. Или хуже? Как проектировать ИИ-инструменты, которые делают пользователей лучше «Раньше хотел каждый, сейчас и бесплатно не надо»: гаджеты, про которые мы все забыли ИИ-агенты в бизнесе: почему 80% компаний увольняют людей, но не получают ROI Как я строил ИИ-стартап, или Новые архитектурные риски 2026 4 интересных парадокса, рождающих жаркие дискуссии Рабочее место не-вайбкодера: настраиваем harness Когнитивный инжиниринг Feature Based Clean Architecture. Часть 1: Эволюция NestJS-приложения в неподдерживаемое состояние Как мы перестали бояться «пустых охватов» и сделали инфлюенс-маркетинг управляемым каналом роста Подключили B2B email-платформу к голосовым ассистентам через MCP. Архитектура, код, где ломается [Перевод] Почему AI-агенты ломаются на длинных задачах — и как обвязка помогает им дописывать приложения Облачно, возможны нейросети: кризис датасетов и ахиллесова пята систем машинного зрения — DIY-чтение на выходные Спустя 5 лет и $5 миллионов: почему создание нового языка для веб-разработки оказалось ошибкой Безопасная песочница Облачная LLM на 16 ГБ VRAM — часть 2: LangGraph Server, LangSmith и SDK Современный SSH-клиент для MS-DOS Как продвигать агентство недвижимости: от вывески до прямых эфиров MCP для GitHub + GitLab: инженерный гайд 2026 Вы платите OpenAI $20 в месяц, а он зарабатывает на вас ещё $100 млн за полтора месяца. И это только начало ИИ забирает работу «белых воротничков»: чему учить детей, чтобы выжить в будущем Практический ИИ-агент Python: LangGraph + Qdrant Как я делал ping и traceroute на iOS без entitlements — и почему это оказалось проще, чем UMP-консент для AdMob 4 MVP за 4 месяца, 30 холодных DM, 1 регистрация: building in public по-русски VPS-бастион: доступ к домашнему серверу без белого IP Kampus AI — нейросеть для генерации учебных работ для студентов и школьников Игры, помогающие продавать — примеры интересных рекламных акций с видеоиграми €500 в Telegram Ads принесли сделку на 350 000 ₽. Разбор B2B-кампании Чтение на выходные: «Разработка игр и теория развлечений» Рафа Костера Личный архив: сбор, бэкап, таймлайн фотографий INFOSTART TECH EVENT или INFOSTART A&PM EVENT — как понять, куда вам нужнее? Peer testing на основе Закона Линуса Релиз GitLab 19.0: ИИ-оркестрация, которая наконец-то догнала темп написания кода Как бизнесу оценить готовность к аттестации по новому Приказу ФСТЭК № 117 Технический гайд по сторис – часть 4: как мы добавили видео формат Представительство в арбитражном процессе: правовые различия между внешним защитником и инхаусом «Где новые фичи?» — Как AI-миграция легаси вернет IT-бюджет бизнесу Что нужно знать работнику про увольнение Новые требования Москвы к ЦИМ для АГР: готовый инструмент для проектировщиков в nanoCAD BIM Строительство WireGuard: простота и надёжность современного VPN-туннеля или секретное рукопожатие в тёмной комнате Выйдет ли GTA 6 в 2026 году, и чего ждать от игры Как меня назвали «невовлечённым», а я нашёл офшоры на Кипре Как LLM научила рекомендательную модель видеть больше, чем историю взаимодействий От хаоса к экосистеме: Модель зрелости комьюнити в бизнесе Свет, тьма, VEML7700 и Python Сказ о том, как мы процессы разработки в GRI меняли. Часть 2 Майский «В тренде VM»: громкие уязвимости в Linux, ActiveMQ, SharePoint и Acrobat Reader Статический анализ, заряженный ИИ: как LLM ищут уязвимости в коде и где их границы Блок “Процессы” и почему мы называем его нашим мини-n8n Как поменялся рынок интернет-рекламы: сравнение первых кварталов 2025 и 2026 годов: исследование click.ru Мониторинг Kerio Connect через Zabbix 7: разбор шаблона без агентов и regex по DAT 671 Allow в Claude Code за день: как родился сетап Spec-build 3 известные интересные задачи на логику Как айтишнику позаботиться о менталке и не перерабатывать OpenAI vs Anthropic: битва экс-коллег за корпоративного клиента и $1 трлн на IPO SEO для интернет-магазина в 2026: что поменялось и как с этим работать Сможете ли вы спроектировать Maven‑монорепозиторий для 5 микросервисов? 6 неудобных вопросов про американское произношение, которые айтишники боятся задать Неожиданная встреча: теория графов вновь помогла решить проблему в анализе Фурье Иллюзия трансформации: почему компании платят за спектакль вместо изменений AMD представила Ryzen 9 PRO 9965X3D и еще 5 процессоров, которые пойдут далеко не всем История IDE в Google Первые отзывы на новинки о System Design Влияние параметра planner_upper_limit_estimation на планы выполнения и профиль нагрузки PostgreSQL при использовании 1C Границы 100% разработки с агентами
Обход блокировок внутри iOS-приложения: VLESS + Reality через sing-box, и грабли по дороге
rcq · 2026-05-23 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение6 мин

Охват и читатели0

Кейс

Мы делаем мессенджер. Весной 2026 наш бэкенд начал отваливаться у части пользователей из России: HTTPS-запросы к API таймаутятся, WebSocket не поднимается. Картина знакомая всем, кто держит сервис с одним доменом и одним IP.

Для мессенджера это приговор. Не «неудобно», а именно приговор: приложение, которое не может даже подключиться, бесполезно. И вариант «попросите пользователя сначала включить VPN» нас не устраивал совсем. Ниже разберу, почему мы в итоге встроили обход прямо в приложение, на чём он работает и на какие грабли мы наступили. Без маркетинга, по делу.

Почему не «просто VPN»

Первая мысль у всех одинаковая: пусть юзер поставит VPN. Но если посмотреть на это глазами продукта, всё разваливается.

Во-первых, это убивает воронку. Каждый дополнительный шаг до «приложение заработало» стоит вам части аудитории, а «установите и настройте отдельное приложение» это не шаг, это пропасть. Особенно болезненно, что отваливается ровно та часть пользователей, которой продукт нужнее всего.

Во-вторых, сам VPN это движущаяся мишень. Популярные протоколы (OpenVPN, WireGuard, IKEv2) давно и неплохо детектируются по сигнатурам, диапазоны известных VPN-провайдеров режут пачками. То есть вы делегируете критичную для вас функцию стороннему приложению, которое завтра само перестанет работать.

Нам хотелось другого поведения: пользователь открывает мессенджер, и он работает. Без отдельного приложения, без подписки на чужой сервис, без VPN-профиля в системных настройках iOS. Обход должен быть деталью реализации, а не задачей пользователя.

Что именно блокируется

Чтобы понимать, что чинить, надо понимать, что ломается. У нас довольно типичная схема: HTTPS API и WebSocket на одном домене. Заблокировать такое соединение можно несколькими способами, и обычно их комбинируют:

  • по IP-адресу сервера;

  • по имени домена в ClientHello (поле SNI отправляется открытым текстом даже в TLS);

  • по DPI, который смотрит на саму структуру трафика и опознаёт протокол.

Обычное TLS-соединение на ваш домен срезается по SNI на раз. Значит, задача формулируется так: соединение должно выглядеть как обращение к какому-то другому, ничем не примечательному и заведомо разрешённому хосту. Не «зашифровать сильнее», а именно «выглядеть иначе».

Почему VLESS и Reality

Немного истории, она тут важна. Shadowsocks и VMess в своё время решали ровно эту задачу, и какое-то время решали хорошо. Проблема в активном пробинге: цензор не просто смотрит на ваш трафик пассивно, он сам отправляет на подозрительный сервер пробный запрос. Если сервер отвечает как прокси (а ранние реализации отвечали узнаваемо), адрес уезжает в блок. Плюс накопились пассивные сигнатуры, по которым «прокси-трафик» отличался от обычного HTTPS.

Reality решает это аккуратным трюком. Прокси-сервер не предъявляет собственный TLS-сертификат. Вместо этого во время рукопожатия он проксирует TLS-handshake на реальный, посторонний, популярный сайт. Для пассивного наблюдателя и для активного пробера ваш сервер неотличим от этого сайта: валидный сертификат, валидное рукопожатие, валидная цепочка. И только клиент, у которого есть правильный публичный ключ, после рукопожатия «переключается» на настоящий туннель. Постороннему пробингу переключиться не на что, он видит обычный крупный сайт.

Что это даёт на практике:

  • нет своего домена, который можно занести в блок по SNI;

  • нет своего сертификата, по которому можно опознать «вот это прокси»;

  • активный пробинг упирается в чужой легитимный сайт.

VLESS тут это лёгкий транспорт без собственного слоя шифрования (шифрование берёт на себя TLS), с маленьким и невыразительным отпечатком. А поток xtls-rprx-vision дополнительно сглаживает паттерн «TLS внутри TLS», который иначе виден по характерным размерам пакетов.

Ничего из этого мы не изобретали. Reality и sing-box это чужая, и очень хорошая, работа. Интересная часть начиналась дальше: как затащить это внутрь обычного приложения так, чтобы пользователь вообще ничего не заметил.

sing-box внутри приложения

sing-box это универсальная прокси-платформа на Go, и среди прочего она умеет быть клиентом VLESS + Reality. Обычно её запускают как отдельный процесс с конфигом. Нам отдельный процесс не нужен, нам нужно, чтобы это жило внутри iOS-приложения.

Здесь помогает gomobile. Через gomobile bind Go-код собирается в нативный .xcframework, который линкуется в приложение как обычная зависимость. То есть sing-box у нас не CLI рядом, а фреймворк внутри бинарника. Запускаем его прямо в процессе приложения.

Поднятый таким образом sing-box открывает локальный inbound (тип mixed, то есть SOCKS и HTTP сразу) на 127.0.0.1 на случайном порту. Дальше есть развилка, и она принципиальная.

Можно сделать системный VPN через NEPacketTunnelProvider: тогда через туннель пойдёт весь трафик устройства. Можно сделать прокси уровня приложения: тогда через туннель пойдёт только трафик вашего приложения. Мы выбрали второе, и вот почему. Нам не нужно гнать через relay весь телефон, нам нужно довести до сервера только трафик мессенджера. А раз так, то Network Extension с его архитектурой, отдельным процессом-расширением, лимитами по памяти и дополнительными вопросами на ревью в App Store нам просто не нужен. Меньше движущихся частей, меньше точек отказа.

На практике это выглядит так: sing-box крутит локальный прокси, а сетевой стек приложения (URLSession) мы заворачиваем на этот локальный адрес.

let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
    "SOCKSEnable": 1,
    "SOCKSProxy": "127.0.0.1",
    "SOCKSPort": localPort,
]

Весь остальной телефон при этом не затронут, никакого VPN-профиля в настройках не появляется. Минус подхода честный: это не универсальный VPN, через него не пойдут другие приложения. Но нам это и не требовалось.

Конфиг для sing-box генерируем в рантайме. Если убрать лишнее, ядро выглядит так:

{
  "inbounds": [{
    "type": "mixed",
    "listen": "127.0.0.1",
    "listen_port": 0
  }],
  "outbounds": [{
    "type": "vless",
    "server": "RELAY_ADDR",
    "server_port": 443,
    "uuid": "...",
    "flow": "xtls-rprx-vision",
    "tls": {
      "enabled": true,
      "server_name": "www.посторонний-крупный-сайт.com",
      "utls": { "enabled": true, "fingerprint": "chrome" },
      "reality": {
        "enabled": true,
        "public_key": "...",
        "short_id": "..."
      }
    }
  }]
}

Отдельно отмечу utls с отпечатком chrome. Стандартная Go-реализация TLS имеет свой узнаваемый ClientHello, и это само по себе сигнатура. uTLS подменяет ClientHello так, чтобы он совпадал с настоящим Chrome. Деталь мелкая, но из таких мелочей и складывается «выглядит как обычный браузер».

Грабли с relay

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

Первый relay мы подняли на дешёвом VPS у типичного хостера. Адрес сгорел быстро. Диапазоны датацентров и хостингов, на которых исторически много прокси, отслеживаются и режутся либо превентивно, либо очень оперативно. Reality прекрасно прячет протокол, но он никак не прячет сам факт «трафик идёт на IP из подозрительного диапазона».

Мы перенесли relay на адрес в диапазоне крупного облачного провайдера. И вот он живёт. Логика простая: вырезать оптом адресное пространство большого облака дорого по сопутствующему ущербу, на тех же адресах висит масса легитимных сервисов, которые ломать никто не хочет.

Вывод, который мы для себя записали: с Reality слабое место это не протокол, а IP. Горит именно адрес. Значит, relay по определению расходник, и относиться к нему надо как к расходнику.

Адрес relay не должен быть зашит в бинарник

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

Поэтому параметры relay (адрес, ключи, SNI) приложение должно получать отдельно от своей сборки. Тогда смена relay это смена конфига, а не релизный цикл. Конкретный механизм доставки конфига можно выбрать разный, важен сам принцип: то, что горит часто, не живёт в бинарнике.

Честно про границы

Туннель это не магия, и продавать его как магию нечестно.

Оператор relay (в нашем случае мы сами) видит ваш трафик к нашим серверам ровно так же, как его видел бы ваш провайдер при прямом подключении. Туннель меняет то, как соединение выглядит для цензора по дороге, и не меняет того, кто стоит на концах. Содержимое переписки у нас в любом случае защищено сквозным шифрованием на уровне приложения (libsignal), и туннель к этому ничего не добавляет и ничего не отнимает. Это важно разделять: обход блокировок и приватность переписки это две разные задачи, которые решаются разными слоями.

Reality хорошо держит активный пробинг, но «навсегда» в этой области не существует. IP горят, сигнатуры накапливаются, методы детекта развиваются. Это гонка, и относиться к ней надо как к гонке: иметь запас адресов, уметь их менять быстро, мониторить, что именно отвалилось.

У нас обход по умолчанию выключен. Приложение сначала пробует подключиться напрямую, и только если прямое соединение не проходит, поднимает туннель. Никакого смысла гонять трафик через relay там, где сеть и так открыта, нет.

Что стоит забрать из этого текста

Если вы решаете похожую задачу, три вещи, которые сэкономят вам время:

Прокси уровня приложения часто лучше системного VPN. Если вам надо довести до сервера только свой трафик, NEPacketTunnelProvider это лишняя сложность. Локальный inbound плюс connectionProxyDictionary закрывают задачу меньшими силами.

Протокол это лёгкая часть. VLESS + Reality + sing-box это решённая задача, всё уже написано до вас. Тяжёлая часть это инфраструктура: какие адреса вы используете, как быстро вы их меняете, как вы это всё мониторите.

Планируйте ротацию с первого дня. Не «когда-нибудь вынесем relay в конфиг», а сразу. Потому что первый же сгоревший IP покажет вам, есть у вас ротация или нет, и узнавать это в проде неприятно.


Всё описанное живёт в нашем мессенджере RCQ, сейчас он в открытой бете на iOS. Клиент под iOS у нас с открытым исходным кодом, так что при желании можно посмотреть, как именно устроена работа с транспортом, а не верить на слово:

github.com/rcq-messenger/rcq-ios

Если занимаетесь чем-то похожим и набили свои шишки на инфраструктуре, расскажите в комментариях, особенно интересно про практику ротации адресов.