Releases: KOKosaaaa/NetGuard
v1.3.11
v1.3.11 — OpSec fix: random display name in Telemost rooms
Critical: previous versions joined Telemost rooms with hardcoded displayName="NetGuard", which leaked the project name to anyone observing participant lists. This made the bypass technique trivially identifiable.
Fix: TelemostRelayManager.kt now picks a random Russian first name from a 30-name pool (same pool used by the server-side anon-mode creator). Each sendJoin rolls a fresh name, so the device blends in with the existing headless bots already present in the room.
No new features. No protocol or VPN-stack changes.
Files changed
app/src/main/java/com/smarttools/netguard/service/TelemostRelayManager.kt— random display name poolapp/build.gradle.kts—versionCode 53,versionName "1.3.11"
v1.3.1 - Telemost: phantom-lock + joiner slot patch + idle CPU
v1.3.1 (2026-05-27) — Telemost: phantom-lock fix, joiner-side slot patch, idle CPU
Iteration on the Telemost protocol shipped in v1.3.0. Three fixes in
librelay.so, all behind the same telemost:// profile path — no UI
changes, no new settings, just a recompile of the embedded Go binary.
Phantom-tolerant peer lock (server side, deployed in the creator on
the conference-host VM). When a Yandex Telemost room contains more
than one peer (the creator + any leftover phantom participants from
prior sessions), the obfuscator's peerEpoch used to ping-pong between
peers on every other frame, triggering PeerRestart → OnPeerRestart
→ bridge closeAll, which kills every active TCP/UDP tunnel. The
creator now locks to the first peer that delivers real payload (not
keepalive) and silently drops frames from other epochs. 30s idle
release if the locked peer goes quiet. Frames from phantoms decode to
nothing and never reach the relay bridge.
Joiner-side slotsConfig handler rewrite (client side, in
librelay.so). Yandex's slotsConfig is a UI-layout signal — it
shuffles which video tile shows whose track, and per-slot UNBOUND
events fire constantly during layout reshuffles, especially when the
creator publishes multiple video tracks. The previous handler treated
UNBOUND as "peer gone" and called forceReconnect, which caused a
join → leave storm at ~1 cycle per second whenever the creator side
had WLB_EXTRA_VIDEO_TRACKS>0. Now slotsConfig only updates
diagnostic bound-peer state; the real peer-gone signal comes from
removeDescription, and tunnel lifecycle is governed by WebRTC PC
state. The same patch was mirrored on the creator side, since the
creator's own kicking self path fired off the same bug.
Idle dual-ticker in VP8DataTunnel.writerLoop. The writer loop ran
a fixed ~1.4ms ticker (720 Hz) regardless of whether there was
anything to send. With multi-x6 mode this meant six librelay
instances each waking up 720 times a second on the phone. The loop
now uses two tickers — a fast one at sample interval, and a slow
500ms one for keepalive-only periods. After 240 consecutive idle
ticks the writer switches to the slow ticker; the first non-keepalive
chunk flips it back. On real traffic the rate is identical to before;
during idle the CPU/wakeup load drops by about two orders of
magnitude.
Net effect on download throughput: speedtest on the multi-x6
profile improved from ~7 Mbps to 9.46 Mbps down / 4.17 Mbps up
(via Aeza Stockholm). Latency stays high (~1 s ping is normal for any
WebRTC-tunnelled SOCKS path).
Multi-publisher path was explored and abandoned. We tried
publishing 3 simulcast-like video tracks per creator with
sticky-by-connID routing in the tunnel, hoping to break past
Yandex's per-stream ~1.25 Mbps cap. SDP negotiates fine (answer grew
from 1431 to 2755 bytes) and packets do flow, but Yandex SFU
aggressively rate-limits multi-stream output from a single publisher,
so total throughput dropped roughly tenfold compared to single-track.
Single-track multi-x6 stays the best path on Yandex Telemost as the
SFU; the multi-track scaffolding remains in the code for future use
on a different SFU.
v1.3.0 - fsociety theme + Telemost
v1.3.0 (2026-05-26) — fsociety theme + Telemost multi-channel tunnel
fsociety theme. Mr. Robot inspired phosphor-terminal redesign (Settings →
Theme → fsociety). Pure-black background, classic Apple-IIe / VT100 green
text, red destructive accent, monospace typography across the whole app.
Other themes (Dark / Light / OLED / Ocean / Dynamic) are untouched and the
redesign is opt-in.
What changes when the theme is active:
- One-line VT100-style boot sequence on cold start of
MainActivity
(5 lines typed in ~1.4s then faded out, plays once per process). tv_fsoc_headershows the hello, friend. pilot opener above the
status line on the home screen.- Connection status text rewritten:
[ OFFLINE ]/[ DECRYPTING TUNNEL... ]/[ ROOT // ALIVE ]/[ SEGFAULT ], with a blinking
cursor (transparent-color span trick so the line never jitters). - Profile list renders in
vlessctl lsstyle — zero-padded[NN]
index counted over visible profile rows, name uppercased, address
and protocol on one row separated by│. - Settings section headers prefixed with
// SECTION_NAMEwalker
applied recursively to bold wrap-content TextViews in the
fragment root. - Easter egg: tap the profile name 5 times within a 3-second window
to fade in a random Mr. Robot quote (23 quotes, translated to all
15 supported locales; English source line shown, native translation
shown beneath separated by—).
Telemost protocol. New native Protocol.TELEMOST for relaying
Yandex Telemost / Yandex SFU traffic that other obfuscation protocols
can't carry well (per-stream UDP cap on the Yandex backend).
How it works at the wire layer:
- A profile pasted as a single
telemost://...URI (or multiple URIs
in a subscription that gets auto-detected as single/multi) carries
the upstream Telemost relay credentials. TelemostRelayManagerspins up one or more loopbacklibrelay.so
Go binaries (ARM64 static, ~11.7 MB, shipped in
jniLibs/arm64-v8a/) — one per relay-link in the profile.SocksRoundRobinLb(Kotlin, byte-transparent) load-balances each
inbound TCP connect across the healthy local SOCKS5 instances with
a 15-second cooldown for upstreams that fail to handshake.XrayConfig/Ping/Preflightskip the Telemost profile (no
xray inbound is generated for it).TunnelVpnServicedispatches
TELEMOST through the LB watchdog, not the xray pipeline.- Loopback-only path doesn't carry SOCKS5 auth — the byte-transparent
LB used to fragment auth handshakes when round-robining the same
connection across instances. Without auth the relay is restricted
to127.0.0.1and isn't exposed to other apps on the device.
Multi-channel x3 / x6 is what makes the protocol useful: a single
profile spawning N parallel librelay.so workers means new TCP
connects are balanced across N independent upstream channels,
breaking past the per-stream Yandex SFU cap (~1.25 Mbps down /
~23.5 Mbps up per channel in our measurements). Single-channel
Telemost is still useful for reach; multi-channel is what gives
usable bulk throughput.
Subscription parser. parseSubscription no longer feeds plaintext
URI lists through the base64 decoder — if the body already starts with
vless:// / telemost:// / etc. we skip the decode step. Some
providers and copy-paste flows hand us plain text that the base64
decoder accepts as valid garbage, yielding empty profile lists.
Localization. 585 new translation strings added across 15
non-English locales (ar, de, es, fr, hi, in, it, ja, ko, pt, ru, th,
tr, vi, zh-rCN) for theme labels, fsociety status strings, quote
translations, and 11 previously-RU-only keys that needed parity.
Every locale now reports 0 missing keys.
v1.2.2 — handover rewrite + Hysteria2 fix
Что нового
Hysteria2 fix (#2)
xray-core отвергал hysteria2 с ошибкой unknown transport protocol: hysteria2 — streamSettings.network был выставлен в hysteria2, что некорректно. Теперь streamSettings испускается только при TLS, obfs лежит в settings. Hysteria2 теперь работает.
Переписана детекция handover-а сети
Код из v1.2.16 на бумаге выглядел корректно, но на практике не срабатывал. Три сценария (WiFi→cellular, cellular→WiFi, потеря вышки) оставляли VPN привязанным к мёртвой сети до ручного toggle.
Исправлено 5 багов:
registerDefaultNetworkCallbackбесполезен для VpnService (всегда возвращает наш VPN). Вся логика handover переехала в aux callback с фильтромNET_CAPABILITY_NOT_VPN.- aux сравнивал с
cm.activeNetwork(= VPN-self или сама сеть) → теперь сlastPublishedNetwork. - aux
onLostне репикал underlying. Теперь репикает + scheduleReconnect. reconnectTargetNetworkчистился только на success path → залипал на cancel/timeout. Теперь чистится в finally на каждом exit-пути.cancelAndJoindeadlock — главная причина «WiFi off → нет инета». Watchdog заблокирован на uncancellableProcess.waitFor(). Раньше: cancelAndJoin → destroy. Теперь: destroy → waitFor возвращается → watchdog выходит → cancelAndJoin завершается мгновенно.
UI
- Окно логов: снятие выбора с фильтра Error/Warn/Info теперь возвращает «все» (было: продолжало фильтровать).
Известная проблема
После airplane mode keep-tun reconnect к cellular технически отрабатывает, но xray не передаёт трафик на cellular пока WiFi не вернётся. Handover-детекция корректна, баг где-то внутри xray protect/bind. Будет починено в следующем релизе.
versionCode 50, versionName "1.2.2".
v1.2.16 — Robust network handover (WiFi↔cellular, SIM swap)
Что нового
Стабильность handover между сетями
registerDefaultNetworkCallbackвместо обычного network callback — следим только за активной default network, не за всем зоопарком- SIM swap detection через
onCapabilitiesChanged+TelephonyNetworkSpecifier.getSubscriptionId(reflection, API 30+). Раньше swap не ловился, если модем не пересоздавалNetworkобъект (dual-SIM data toggle) waitForValidatedдо 5s перед перезапуском xray — раньше был хардкодdelay(500), xray стартовал на ещё-не-validated сеть и валился в failoverreconnectJobсcancel()предыдущего job'а — только последний event побеждает, нет stale-restart на мёртвой сети
Фикс «при включении VPN телефон кидает на моб данные»
setUnderlyingNetworksтеперь принимает ОДНУ сеть (best of Ethernet > WiFi > cellular, validated приоритет), а не массив всех- Раньше Android merge'ил
CELLULARtransport +METEREDcapability в VPN's caps → приложения думали «я на мобильной даже когда WiFi работает»
APK
NetGuard-v1.2.16.apk
v1.2.15 — BC-mode hardening
Failover bump 3 -> 14
Try the entire subscription before giving up. Under whitelist mode some CF tunnel IPs may pass via vk.com/yandex.ru overlap; previous 3-attempt budget exhausted before reaching them.
ServerPreflight non-fatal
Log warning instead of throwing. TSPU RST-injects plain TCP SYN to foreign IPs in whitelist mode, but real VLESS+Reality TLS handshake with SNI=max.ru still passes (DPI sees whitelisted SNI in ClientHello). v2rayTUN doesn't preflight and works in this scenario — match its behavior. DNS rebinding guard retained.
Whitelist-mode hint
Detect ECONNREFUSED + timeout pattern across multiple failovers, surface human-readable hint instead of abstract TCP error.
Build
versionCode 46, arm64-v8a
v1.2.11
v1.2.11 - Stable HWID binding
Fixed
- Subscription server can now bind a device slot to THIS phone instead of guessing from UA + IP/24. The app sends
x-hwid: SHA256(Settings.Secure.ANDROID_ID)with every subscription fetch. - ANDROID_ID is stable across app updates, reinstalls and reboots, so switching networks (Wi-Fi to mobile), changing language, or updating the app no longer eats an extra device slot or bypasses an admin kick.
- Resets only on factory reset (semantically: a new device).
Raw ANDROID_ID never leaves the device - only its SHA-256 hex digest.
v1.2.1
v1.2.1 — Bugfix
Fixed
- "Test Services" dialog (Servers tab) reappearing every time you switched away and back. Root cause: cached one-shot event in
MutableSharedFlow(replay=1). Migrated toChannel.receiveAsFlow()for proper one-shot delivery. Same fix applied to import Toast.
Includes uncommitted v1.2.0 cosmetic leftovers (layered capsule drawable, subscription header tweaks).
NetGuard v1.2.0
Big UI + plumbing release. The Servers tab now shows subscription metadata (traffic, support / website icons, announcement) and a real subscription header per group. Several long-standing bugs around selection state, ping, and frame stability are fixed.
Subscription headers + metadata
- New
Subscriptionfields:usedBytes,totalBytes,supportUrl,webPageUrl,announce. Parsed from the standard subscription response headers —subscription-userinfo(upload/download/total),support-url,profile-web-page-url,announce(plain UTF-8 orbase64:...prefixed). - DB migration
5 → 6:ALTER TABLE subscriptionsadds the five columns. ProfileAdapterrebuilt as a multi-viewType list: each subscription gets a row of its own (item_subscription_header.xml) before its profile rows. The header shows the subscription name (left, ellipsizes if too long), a traffic capsule centered in the gap (used / total or used / ∞ for unlimited quotas), and clickable globe / paper-plane icons on the right that open the provider's website / support chat. Icons hide automatically when the matching URL is empty.SubscriptionGroupDecorationsimplified: text and icons live in the header row now; the decoration only paints the colored frame around each group. Frame is stable during scroll —top/bottomextrapolate past the viewport when one end of the group is offscreen.- Item-change animations disabled on the servers list — selecting a profile or refreshing pings no longer makes the group frame jump.
Bug fixes
- "Server not selected" while VPN is running.
replaceSubscriptionProfilesnow preservesisSelectedandisFavoriteacross a subscription refresh by matching old → new profiles on the stable identity tuple (protocol + address + port + uuid + password). Previously the auto-update worker wiped the selected server and the home screen showed "no profile selected" while the tunnel kept running on the now-orphaned config. - Hysteria2 / UDP ping.
PingHelper.pingForProfiledispatches by protocol: TCP-handshake for VLESS / VMess / Trojan / Shadowsocks, ICMP echo (via Android's setuid/system/bin/ping) for Hysteria2 (QUIC over UDP). Hy2 servers no longer always show-in the ping column. - Subscription title decoder.
decodeProfileTitlenow tries the standard base64 alphabet before URL-safe; some providers (GruVPN among them) sendbase64:payloads containing/, which the URL-safe decoder rejected.
Servers tab UX
- Sort menu item is a toggle. First tap sorts by ping; while sorted by ping the menu item reads Sort by Subscription and a second tap restores the default order.
- Friendly profile names for the project's own subscriptions (🚀 / 🇷🇺 / 🛡 / ☁ / ⚡ / 🔒) instead of the previous "DE Reality" / "CDN WS WARP" abbreviations.
Theming + status bar
- Light theme now sets
windowLightStatusBar=trueand (API 27+)windowLightNavigationBar=true, so the system clock / battery icons switch to dark on the white status bar. - Card backgrounds for selected / unselected profiles are theme-tied via two new attrs (
cardNormalBackground,cardSelectedBackground); each theme (Default Dark / Light / OLED Black / Ocean / Dynamic) supplies values that stay readable on its own surface palette.
Internals
- New string keys:
sort_by_subscription(en + ru). - New drawables:
ic_globe.xml,ic_paper_plane.xml,bg_traffic_capsule.xml,capsule_progress_drawable.xml. Tints follow?android:attr/textColorPrimaryso a single asset works on every theme. versionCode40,versionName"1.2.0".
v1.1.91 — Onboarding subscriptions, trigger UI, smarter naming
UX + plumbing follow-up to v1.1.9. No new security claims — every change either widens what the app accepts as input, makes existing state visible, or hides a confusing error behind a clearer one.
Onboarding wizard
- Accepts subscription URLs. First-server step now detects
http(s)://input, validates viaSubscriptionRepository.validateUrl, inserts as a subscription, and triggersupdateSubscriptionso the profile list is fetched in-place inside the wizard. Previously rejected subscription links with a confusingCouldn't parseerror. - TLS errors translated.
chain validation failed/trust anchor for certification path not found/notBefore/notAfter/expired/not yet valid→ a one-liner about device date/time, which is the actual cause 90% of the time. Pinning failure and incomplete chain also distinguished. - Wizard skipped on upgrade. Fresh install still shows the wizard. Upgrade from any pre-1.1.8 NetGuard is detected by checking SharedPreferences for any non-
onboarding_donekey — a returning user has saved settings, so we setonboarding_done=trueand go straight to the main UI.
Trigger menu redesign
Header section (switches, explainer, dual-app warning) is collapsed by default behind a Material 3 card that shows the current state at a glance: Disabled · tap to set up or Enabled · strict · 5 apps. The card's chevron icon rotates 180° on expand. Replaces the easy-to-miss Hide info / Show info & switches text button — the apps list now occupies most of the screen.
Subscription naming
- Auto-fills from server.
updateSubscriptionparses theprofile-titleresponse header (plain UTF-8 orbase64:…prefixed, URL-safe + standard alphabets, missing padding tolerated). When the server doesn't expose that header, the URL fragment (#MyName) is used — same convention asvless://…#NodeName. Hostname is the final fallback. The previous behaviour of stamping every subscription with the literal string "Subscription" is gone. - Long-press to rename. Long-press a subscription row to open a rename dialog. Sets
Subscription.userRenamed=true, which protects the user-chosen name from being overwritten on the next refresh. - Add Subscription dialog clearer. Name field hint reads
Name (optional)with a subtext explaining the provider'sprofile-titlewill fill it in. Empty input falls back to the URL host instead of the literal "Subscription". - DB migration 4 → 5.
subscriptions.userRenamed INTEGER NOT NULL DEFAULT 0. Existing rows pick up the auto-fill behaviour on next refresh.
Trigger settings auto-save
Enable / Strict / AutoStop switches persist immediately on toggle via viewModel.updateSettings, no longer waiting for the Save button at the bottom of the screen (which most users couldn't find past the long apps list).
Install
Sideload NetGuard-v1.1.91.apk (arm64-v8a, 27 MB). Upgrade in place from any 1.1.x — settings and profiles preserved, wizard does not re-trigger.