
Салют, Хабр! На связи снова я, Aragorn, со своим проектом по терроризированию Роскомпозора. В прошлый раз я рассказывал о NoDPI - утилите для «раздеградирования» YouTube и установил личный рекорд — 400 звезд на GitHub и блокировка статьи РКН через три дня после публикации.
Многие мои знакомые и люди в комментариях просили сделать версию под Android и Android TV. Я не очень дружу с Джавой и с Джавой под андроид в особенности, и поэтому такая перспектива меня не очень прельщала, но у меня был опыт написания android-приложений на python и kivy, который я и решил применить. После нескольких дней (и ночей) напряженного труда и танцев с бубном, мне наконец удалось создать NoDPI for Android, который практически не имеет аналогов. Именно о нем я и хочу сегодня рассказать. Надеюсь, статья будет вам полезна и интересна. Поехали!

Немного про потроха
NoDPI4Android — это графическая надстройка над NoDPI. Как работает сам NoDPI я подробно рассказывал в прошлой статье, но так как без V*N её теперь не почитаешь, то вкратце повторю.
NoDPI представляет собой асинхронный прокси-сервер на базе библиотеки asyncio
Он перехватывает tls-рукопожатия (handshake) исходящих соединений и отправляет их на фрагментацию. Если домен присутствует в списке заблоченных, программа разбивает пэйлоад на несколько кусков случайного количества и случайной длины, и склеивает с байтовой последовательностью \x16\x03\x04
(+ data). Т. е. одна tls запись превращается в несколько записей разной длины. После этого они объединяются и отправляются как один пакет. Пока у DPI нет мощностей, чтобы разбираться с таким хаосом в пакетах, и все это благополучно следует к пункту назначения, а мы, довольные, смотрим YouTube.
Как я уже упомянул, NoDPI4Android написан исключительно на python. Кто-то покрутит у виска и скажет, что писать под android на python - это безумие. Да, возможно это не самый подходящий язык для таких целей, но у него есть и ряд преимуществ. В первую очередь, это простота написания и сборки простых приложений, которые не взаимодействуют с сервером и не требуют фоновой активности. Все такие приложения пишутся с использованием фреймворка Kivy, который предоставляет широкие возможности для создания UI и даже имеет свой декларативный язык разметки KV-lang. Чуть выше Kivy стоит KivyMD, который предоставляет множество различных виджетов в стиле Material Design. А работу всего этого на Android обеспечивает python-for-android (p4a), который собирает нативный CPython + NDK + код в APK.
Наше приложение разделено на две части - непосредственно приложение (main.py
), с которым взаимодействует пользователь, и сервис (service.py
), в котором работает прокси.
В приложении используется Kivy и KivyMD и ничего сложного в нем нет - одна графика: кнопка запуска/остановки сервиса, редактирование настроек прокси и черного списка. Для запуска сервиса приходиться немного воспользоваться API Android:
from android import mActivity
from jnius import autoclass
def start_service(name_service: str) -> None:
context = mActivity.getApplicationContext()
service = autoclass(str(context.getPackageName()) + ".Service" + name_service)
service.start(mActivity, "")
При этом за само создание сервиса отвечает p4a и для этого в конфиг сборки надо добавить всего лишь одну строчку с указанием входной точки и параметрами:
services = Proxy:%(source.dir)s/service.py:foreground:sticky
С сервисом немного сложнее. Чтобы Android не прибивал его, мы используем foreground service (а не background), который требует постоянного наличия уведомления. Само уведомление отправлять не надо - система сделает это самостоятельно. Логику прокси я портировал из NoDPI без изменений, изменился лишь способ его запуска:
...
class ProxyServer:
def __init__(self):
if os.path.exists(os.path.join(app_storage_path(), "proxy_config.json")):
try:
with open(os.path.join(app_storage_path(), "proxy_config.json"), "r", encoding="utf-8") as f:
config = json.load(f)
self.host = config.get("host", "0.0.0.0")
self.port = str(config.get("port", "8881"))
except Exception as e:
self.host = "0.0.0.0"
self.port = "8881"
else:
self.host = "0.0.0.0"
self.port = "8881"
self.server = None
self.loop = None
self.running = False
def start(self):
if self.running:
return
self.running = True
self.thread = threading.Thread(target=self._run_server, daemon=True)
self.thread.start()
def _run_server(self):
try:
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
async def server_main():
self.server = await asyncio.start_server(
new_conn, self.host, self.port
)
async with self.server:
await self.server.serve_forever()
self.loop.run_until_complete(server_main())
except Exception as e:
pass
finally:
if self.loop and self.loop.is_running():
self.loop.stop()
if self.loop:
self.loop.close()
self.running = False
def stop(self):
if not self.running:
return
self.running = False
if self.server:
self.server.close()
if self.loop and self.loop.is_running():
self.loop.call_soon_threadsafe(self.loop.stop)
if __name__ == '__main__':
proxy = ProxyServer()
proxy.start()
while proxy.running:
threading.Event().wait(1)
Без запуска прокси в Thread сервис почему-то дохнет, а чтобы работа основного потока не завершалась приходится делать threading.Event().wait(1)
Все! Остается только настроить конфигурацию сборки, которая осуществляется с использованием инструмента buildozer:
buildozer.spec
[app]
source.dir = ./src
source.include_exts = py,png,jpg,kv,txt
version = 1.0
requirements = kivy,https://github.com/kivymd/kivymd/archive/master.zip,android,pyjnius,materialyoucolor,pillow,asynckivy,asyncgui
presplash.filename = ./assets/presplash.png
icon.filename = ./assets/ico.png
orientation = portrait
fullscreen = 0
[android]
title = NoDPI
package.name = nodpi
package.domain = com.gvcoder
services = Proxy:%(source.dir)s/service.py:foreground:sticky
android.permissions = INTERNET,FOREGROUND_SERVICE,POST_NOTIFICATIONS
android.accept_sdk_license = True
[buildozer]
log_level = 1
Запускаем...
buildozer android debug
...и на выходе получаем готовый APK.
Настройка и использование
NoDPI4Android работает на Android 5.0 и выше. После установки приложения (скачать его можно здесь), нужно дать некоторые разрешения и настроить прокси на вашем устройстве.
-
Откройте приложение и нажмите на кнопку START SERVER. Затем дайте разрешение на отправку уведомлений. Без этого приложение работать не будет!
Скрытый текст
-
В настройках приложения отключите оптимизацию, в противном случае Android прибьет сервис через некоторое время (особенно это характерно для MIUI)
Скрытый текст
-
Ну и самое главное - настройка прокси. В большинстве оболочек прокси можно настроить только для WiFi, но в MIUI такая функция доступна и для мобильного интернета. В OneUI прокси включается так:
Скрытый текст
В имени узла прокси у меня стоит 127.0.0.1, но по умолчанию приложение использует 0.0.0.0, поэтому вводить нужно именно его
Все! Теперь можно наслаждаться просмотром. Если с первого раза не заводится, перезапустите приложение и сервер кнопкой START SERVER/STOP SERVER
Известные проблемы
Сервис падает в оболочке MIUI Несмотря на отключение оптимизации, в MIUI сервис почему-то дольше 12 часов не живет и требуется его постоянный перезапуск. Я не знаю с чем это связано, так как в OneUI он проработал две недели без сбоев.
Большой размер приложения Сам APK занимает около 40МБ, а после установки и использования размер вырастает до 100МБ. Это ключевой недостаток разработки на Kivy и связан он с тем, что приложение тащит за собой CPython и все зависимости, которые у него есть.
Невозможно изменить адрес прокси (кнопка SETUP PROXY) Эта проблема наблюдается с клавиатурой MIUI и я никак не могу повлиять на нее. Единственный способ решения - выделить текст и начать вводить символы.
Аналоги
Заключение
Весь исходный код и APK вы можете найти на моем GitHub-е: https://github.com/GVCoder09/NoDPI4Android Там же находится подробная инструкция по сборке приложения в apk. Я искренне надеюсь, что эта программа принесет вам пользу, или, по крайней мере, заинтересует ее идея. Если вы хотите поддержать меня, то это можно сделать единственным способом - поставить плюсик статье :-)
Ну и конечно, я буду рад, если кто-то присоединится к разработке - issues и пул реквесты приветствуются :-)
Barnaby
А это что? https://github.com/romanvht/ByeDPIAndroid
PS: По вашей же ссылке есть раздел "Похожие проекты", где много чего есть.