Skip to content

Releases: KOKosaaaa/NetGuard

v1.3.11

28 May 00:38

Choose a tag to compare

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 pool
  • app/build.gradle.ktsversionCode 53, versionName "1.3.11"

v1.3.1 - Telemost: phantom-lock + joiner slot patch + idle CPU

27 May 13:57

Choose a tag to compare

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 PeerRestartOnPeerRestart
→ 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

25 May 23:40

Choose a tag to compare

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_header shows 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 ls style — 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_NAME walker
    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.
  • TelemostRelayManager spins up one or more loopback librelay.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 / Preflight skip the Telemost profile (no
    xray inbound is generated for it). TunnelVpnService dispatches
    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
    to 127.0.0.1 and 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

10 May 21:30

Choose a tag to compare

Что нового

Hysteria2 fix (#2)

xray-core отвергал hysteria2 с ошибкой unknown transport protocol: hysteria2streamSettings.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-пути.
  • cancelAndJoin deadlock — главная причина «WiFi off → нет инета». Watchdog заблокирован на uncancellable Process.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)

07 May 13:28

Choose a tag to compare

Что нового

Стабильность 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 сеть и валился в failover
  • reconnectJob с cancel() предыдущего job'а — только последний event побеждает, нет stale-restart на мёртвой сети

Фикс «при включении VPN телефон кидает на моб данные»

  • setUnderlyingNetworks теперь принимает ОДНУ сеть (best of Ethernet > WiFi > cellular, validated приоритет), а не массив всех
  • Раньше Android merge'ил CELLULAR transport + METERED capability в VPN's caps → приложения думали «я на мобильной даже когда WiFi работает»

APK

NetGuard-v1.2.16.apk

v1.2.15 — BC-mode hardening

04 May 22:52

Choose a tag to compare

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

03 May 21:21

Choose a tag to compare

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

03 May 21:07

Choose a tag to compare

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 to Channel.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

03 May 14:49

Choose a tag to compare

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 Subscription fields: 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 or base64:... prefixed).
  • DB migration 5 → 6: ALTER TABLE subscriptions adds the five columns.
  • ProfileAdapter rebuilt 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.
  • SubscriptionGroupDecoration simplified: 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 / bottom extrapolate 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. replaceSubscriptionProfiles now preserves isSelected and isFavorite across 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.pingForProfile dispatches 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. decodeProfileTitle now tries the standard base64 alphabet before URL-safe; some providers (GruVPN among them) send base64: 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=true and (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/textColorPrimary so a single asset works on every theme.
  • versionCode 40, versionName "1.2.0".

v1.1.91 — Onboarding subscriptions, trigger UI, smarter naming

02 May 22:13

Choose a tag to compare

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 via SubscriptionRepository.validateUrl, inserts as a subscription, and triggers updateSubscription so the profile list is fetched in-place inside the wizard. Previously rejected subscription links with a confusing Couldn't parse error.
  • 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_done key — a returning user has saved settings, so we set onboarding_done=true and 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. updateSubscription parses the profile-title response header (plain UTF-8 or base64:… prefixed, URL-safe + standard alphabets, missing padding tolerated). When the server doesn't expose that header, the URL fragment (#MyName) is used — same convention as vless://…#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's profile-title will 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.