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

推荐订阅源

The Cloudflare Blog
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
博客园 - 司徒正美
V
Visual Studio Blog
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
S
SegmentFault 最新的问题
T
The Blog of Author Tim Ferriss
D
Docker
Vercel News
Vercel News
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
爱范儿
爱范儿
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Когда пет-проект выходит из-под контроля: пишем свой tun2...
Wendor · 2026-05-04 · via Все публикации подряд на Хабре

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

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

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

Всё начиналось по приколу. Недавно в сети поднялась шумиха вокруг уязвимости VLESS-клиентов: оказалось, что даже при использовании сплит-туннелирования (когда VPN включен только для избранных приложений), любое "шпионское" приложение на телефоне может узнать IP-адрес вашего VPN-сервера.

Уязвимость была тривиальной - ядро клиента открывает локальный SOCKS-прокси, который никак не защищен. Любая софтина на устройстве может постучаться в этот локальный порт и отправить пакет наружу. Ради академического интереса я написал Android-приложение TeapodStream, под капотом которого связал xray-core и tun2socks. Локальный прокси я посадил на случайный порт и закрыл динамическим паролем (подробнее об этом писал в прошлой статье, пиво кстати выдохлось)) ).

Новый дизайн и фишки

Новый дизайн и фишки

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

Но... я сам не заметил, как меня затянуло.

Дыра, о которой не принято говорить

После публикации поста в комментариях всплыл еще один баг безопасности. Куда более противный, и присутствует он чуть ли не во всех популярных VPN-клиентах под Android.

Симптом выглядит так: шпионское приложение, которое не добавлено в список туннелируемых, просто выполняет системный вызов, эквивалентный команде curl --interface tun0https://checkip.amazonaws.com - и пакет благополучно улетает на ваш сервер, раскрывая его IP.

Как это вообще возможно, если работает сплит-туннелинг?

Давайте заглянем под капот Android. При поднятии VPN ваше приложение через VpnService API создает сетевой интерфейс tun0. Дальше операционная система использует Policy-Based Routing (маршрутизацию на основе политик). Когда обычное приложение открывает сокет и пытается выйти в сеть, Android смотрит на его UID. Если UID есть в списке разрешенных для VPN - пакет летит в tun0, если нет - летит через дефолтный маршрут (сотовую сеть или Wi-Fi).

Но есть один нюанс. tun0 - это сетевое устройство уровня ядра системы. А ядру Linux плевать на высокоуровневые политики и Android UID. Если приложение принудительно укажет сокету параметр SO_BINDTODEVICE с именем интерфейса tun0, ядро послушно отправит пакет именно туда, в обход всех правил Android.

Раньше такой фокус требовал root-прав. Но, начиная с ядра Linux версии 5.7, этот вызов разрешили делать обычным пользовательским приложениям. Бинго!

Пишем свой tun2socks (закрываем дыры Android?)

С этой дырой невозможно бороться, просто подставив хитрые параметры запуска для обычного tun2socks или xray-core. Пакет уже провалился в туннель, его нужно отлавливать и фильтровать изнутри.

Поняв это, я принял радикальное решение: написать собственное ядро tun2socks с нуля на Go, используя gVisor (userspace сетевой стек).

Логика работы моего кастомного движка: после получения пакета из tun0, мы собираем из него 5-tuple (протокол, IP источника, порт источника, IP назначения, порт назначения). Затем мы стучимся в нативный API Android (ConnectivityManager.getConnectionOwnerUid) и спрашиваем: "А кому, собственно, принадлежит этот сокет?".

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

На уровне протоколов это выглядит так:

  • С TCP всё просто. Если в туннель падает SYN-пакет (попытка открыть соединение), проверяем UID. Если приложению нельзя в VPN - отправляем в ответ RST (сброс соединения). Если можно - заворачиваем трафик в наш запароленный SOCKS.

  • С UDP всё намного сложнее. Соединения как такового нет, это просто поток датаграмм (нет SYN-пакетов). Нам приходится реагировать на каждый пакет. Мы сверяем UID и кэшируем связку портов, чтобы не дергать тяжелый Android API на каждую датаграмму. Плюс на этом этапе приходится делать ребинд сокетов (Strict Source Binding), чтобы UDP-пакеты не застревали в петле маршрутизации внутри самого туннеля и не утекали.

  • С ICMP - сплошная засада. В ядре определить, от какого именно приложения идет ICMP-пакет (тот же пинг), настолько сложно и ресурсоемко, что это просто теряет смысл для мобилки. Поэтому было принято волевое решение: дать пользователю галочку "Заблокировать весь ICMP в туннеле" от греха подальше.

И оно заработало? Короткий ответ: да. Кастомный tun2socks на Go оказался очень гибким и непробиваемым для SO_BINDTODEVICE.

От пет-проекта к полноценному приложению (боли и радости)

Я был в шоке от фидбэка комьюнити. Столько слов благодарности, столько пожеланий и... столько баг-репортов.

Каждый день я старался выделять время и допиливать проект. Функционал рос: появилась маршрутизация трафика по GeoIP/GeoSite, тайл в шторке быстрых настроек, экспорт интентов (для автоматизаций через Tasker или Macrodroid), профили настроек (чтобы можно было пошарить свой конфиг близким), поддержка Always-On VPN и многое другое.

Драма с редизайном В какой-то момент я решил, что UI приложения выглядит слишком скучно, и выкатил стильное (на мой взгляд) обновление интерфейса. Реакция была мгновенной и беспощадной. В issues, в tg, на почту посыпались жалобы: "моим клиентам стало неудобно", "вынужден отказаться от вашего приложения, если не вернете старый дизайн" и всё в таком духе. В этот момент я осознал: то, что я пилил "по приколу для души", люди уже вовсю используют, в том числе в коммерческом бизнесе для своих клиентов. Местами пришлось искать компромиссы.

Битва за стабильность Но самая большая боль, работа над которой продолжается до сих пор - это стабильность vpn-соединения. Android - суровая среда. Нужно корректно обрабатывать переключения сети (Wi-Fi <-> LTE), уход телефона в глубокий сон (Doze mode) и пробуждение, запуск и остановку туннеля из шторки или автоматизаций (когда UI приложения вообще не загружается в память), поддержку heartbeats и кучу других нюансов.

Я напомню: TeapodStream не является коммерческим проектом. Занимаюсь я им в свободное от работы время. Его код открыт, я не планирую вводить монетизацию и не собираю донаты. Это просто мой способ размять мозги, повеселиться и (надеюсь) сделать интернет чуточку свободнее и безопаснее.

Исходники TeapodStream и моего кастомного tun2socks лежат на GitHub. В приложении хватает мелких багов и это скорее альфа\бета версия. Как думаете, в какую сторону развивать проект дальше?

Всем добра, и спасибо, что дочитали!