AGP 9 strict full-mode keep rules no longer keep no-arg constructors of
-keep'd classes, so WorkManager's reflectively created Room database
lost its constructor and the app crashed in InitializationProvider.
Restore AGP 8 keep-rule semantics and keep RoomDatabase constructors.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Built-in Kotlin (kotlin-android removed), Java 11 compile options (compose 1.12),
proguard-android-optimize.txt (the non-optimize file is rejected by AGP 9),
ExposedDropdownMenu import, minSdk 23 kept via tools:overrideLibrary for
material3 1.5 libs that declare minSdk 24. -PshortNdkCommands for deep worktree paths.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop unused navigation-compose and material3-window-size-class.
alpha19+/beta01 need AGP 9.1 + compileSdk 37, so alpha18 is pinned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Direct destinations that stay unreachable past the cooldown are now
routed through the VPN tunnel until the network changes. The Home
availability card marks such hosts "via tunnel" and offers a one-tap
"Via VPN" action for the rest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
underlyingNetworkLabel() returned the first non-VPN network from
allNetworks, whose order is unspecified - Android keeps the cellular
network agent alive in parallel with Wi-Fi, so on Wi-Fi the details
sheet could show "Сотовая сеть". Wi-Fi now wins when present (same
precedence as NetworkMonitor's trust decision); cellular is only
reported when no Wi-Fi network exists.
- ConnectionDetailsSheet: session traffic split "via VPN / direct (RU)"
from the native engine counters (userspace mode only; system-routes
mode shows no split - the engine never sees that traffic), public exit
IP + country via cloudflare cdn-cgi/trace through the tunnel, current
underlying network (Wi-Fi + SSID / cellular)
- ConnectionCard: one-glance "N% direct" caption when the RU split moves
traffic directly
- dead direct hosts surfaced in UI: native snapshot of the negative-cache
table via new get_dead_targets (mutex-guarded, sorted by failures) +
JNI getDeadDirectTargets; section on the Routing tab and a badge in the
Home availability card naming dst, failure count, remaining cooldown
and the owning app (from the 2.13.15 owner-attribution cache)
- HomeViewModel: split/exit/deadTargets as separate hot flows; exit
refreshed with the probe round (60s cache), dead targets every round
minSdk 23 flipped AGP's useLegacyPackaging default to false (uncompressed
.so, extractNativeLibs=false): the APK grew 12 -> 23 MB. Updates are
downloaded over cellular, so pin useLegacyPackaging = true; APK is back
to ~12 MB (2.14.0 re-published with the same versionCode, sha updated in
the manifest before any device fetched it).
- minSdkVersion 21 -> 23 (device is Android 17, no other users); jni
Application.mk: APP_PLATFORM android-23 + -DHAVE_SIGWAITINFO (the NDK
sysroot declares sigwaitinfo since API 23, conflicting with strongSwan's
fallback prototype; the system implementation is used now)
- replace the bottom NavigationBar with NavigationSuiteScaffold (bottom bar
on phones, navigation rail in landscape/on tablets); item colors keep the
brand palette via NavigationSuiteItemColors(bar/rail/drawer)
- HomeScreen applies statusBarsPadding itself (the adaptive scaffold no
longer pads the content area)
- predictive back was already enabled (android:enableOnBackInvokedCallback)
Log-driven fixes from the 10-04 diag/charon analysis:
- direct_tcp: negative-cache window now escalates 45s x2 up to 5 min
(Telegram was opening ~3000 connects/h to each of two dead hosts,
waking the radio for every leaked 45s probe); skip the unconditional
fresh-socket retry on ECONNREFUSED (a refusal is deterministic on the
network the socket is bound to; roam-time sockets are already aborted
by reprotect)
- new dead-destination diag line (dst, fail count, cooldown) via JNI
callback when the window escalates or hits every 100th failure
- VpnStateService: double-start guard - a second connect() for the same
profile while a healthy attempt is in flight is ignored (four starters
raced through here within 20 s after the 2.13.14 self-update)
- NetworkMonitor/AutoConnectManager: flap detection (>=2 kind changes in
5 min stretches the auto-connect delay to 10 s; a Wi-Fi flap produced
a 7-second-lived session and a wasted IKE handshake)
- hardcode diag TAGs obfuscated by R8 ("a: update: up to date" ->
"UpdateManager: ..."), same for UpdateInstaller/VpnShortcutManager
Diag/charon logs from 2026-09-30 16:45 showed that after a Wi-Fi->cellular
roam every direct-TCP connect failed asynchronously with EHOSTUNREACH for
minutes, while the IKE socket worked and a Java socket explicitly bound to
the underlying network connected fine: a bare VpnService.protect() left
fresh sockets routed over the vanished network.
- CharonVpnService.protectDirect(fd): protect + Network.bindSocket to the
active non-VPN network (API 23+, best-effort, adoptFd/detachFd)
- charonservice_protect_direct_socket() JNI wrapper; relay and UDP flow
sockets use it at creation
- direct_tcp: one retry on a fresh socket when an async connect fails;
reprotect aborts still-connecting relays (RST) instead of letting the
kernel take ~90s to report the dead route; failures log dst=ip:port
- direct_udp: reprotect closes all flows; they re-open per packet with a
fresh, network-pinned socket
- TUN reader: EBADF after a reauth re-establish is no longer logged
Published as 2.13.10 (versionCode 147).
Replaces the fragment-based UI with a full Jetpack Compose app
(HOME/profiles/routing/settings/trusted networks in Compose, adaptive
brand theming, notification/vector drawables), adds the SHX release
pipeline (build-local.sh, UpdateManager self-update from the S3 bucket,
DiagLog diagnostics rotated into diag.log with crash capture) and the
watchdog/auto-connect/reconnect hardening series.
RU-GeoIP split now has two modes (Routing screen):
- OS routes (default): coarsened complement of the RU list installed
as system routes, bounded by the binder budget (2.12.x behavior)
- Userspace routing (beta): only the default routes point into the
TUN; every packet is classified natively against the exact RU list
(ring-buffer queue, fixed flow tables - allocation-free hot path)
and RU TCP/UDP leaves through protected sockets. TCP is terminated
by a vendored lwIP 2.2.0 (tun2socks model, err_t renamed to avoid
the strongSwan typedef clash, wildcard-port listener patch, asserts
into diag.log). IPv4 addresses are compared in Java's numeric byte
order and ranges sorted natively; direct flows re-protect on roam;
relay errors log errno. Route diagnostics block 12s after connect.
Also: SIM2-crash hardening, connectivity watchdog with wakeup alarm,
profile export, connectivity self-check screen, tests.
Published as 2.13.9 (versionCode 146).
- Added new entries to .gitignore to exclude Android build artifacts and keystore files.
- Updated build.gradle files to include Kotlin Gradle plugin dependencies.
- Enhanced gradle.properties with JVM arguments for better performance.
- Modified AndroidManifest.xml to include additional permissions for network and location access.
- Refactored package names in several classes to use a new namespace.
- Removed deprecated LogActivity, SelectedApplicationsActivity, SettingsActivity, and TrustedCertificatesActivity.
- Introduced a new scheduling mechanism in the Scheduler class to handle exact alarms on Android 13+.
- Updated MainActivity to integrate new UI components.
These changes improve the overall structure and functionality of the Android application.
Since 5.9.6, the number of half-open SAs includes queued initial IKE
messages. So that number could be higher than the number of managed
SAs, which causes an underflow in the output.
Fixes: b866ee88bf ("ike: Track unprocessed initial IKE messages like half-open IKE_SAs")
The previous code did not synchronize some of the flags, so it was
possible that `cancel()` did not work properly (e.g. no APC queued).
This change uses atomics to set/read the flags, it also avoids using
the global thread lock for synchronization as getting the condvar before
queuing the APC avoids having to sync with `docancel()`. However, to
make sure we don't use a stale and potentially already freed condvar
when the thread is canceled a new lock is added and held until the
condvar has been signaled (using atomics for that member doesn't work as
the ordering rules enforced by atomics can not avoid that the thread in
`cancel()` potentially reads a stale value, or the race between reading
and signaling it).