From ac8f0e6ab5404b0f84258dd00b231bbe4cf449aa Mon Sep 17 00:00:00 2001 From: hasti1356 <73002293+hasti1356@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:21:52 +0330 Subject: [PATCH] Fix Android connect crash: pass XRAY_TUN_FD and LD_LIBRARY_PATH (v1.1.8). Android Xray-core reads the VPN TUN fd from XRAY_TUN_FD, not JSON alone. Set LD_LIBRARY_PATH for libxray.so, remove double-start probe on connect, and surface ProcessBuilder errors in-app. --- CHANGELOG.md | 9 ++ CONTRIBUTORS.md | 1 + docs/releases/v1.1.8.md | 44 ++++++++++ .../Services/AndroidJavaCoreProcessHost.cs | 82 +++++++++++++++---- src/v2rayF.Android/v2rayF.Android.csproj | 6 +- src/v2rayF.Core/Services/ICoreProcessHost.cs | 1 + src/v2rayF.Core/Services/LatencyService.cs | 1 + .../Services/ManagedCoreProcessHost.cs | 1 + src/v2rayF.Core/Services/ProxyCoreService.cs | 1 + src/v2rayF/ViewModels/MainWindowViewModel.cs | 34 ++++---- 10 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 docs/releases/v1.1.8.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ee28a22..caf45e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to v2rayF are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.8] - 2026-06-29 + +### Fixed + +- Android connect crash — pass VPN TUN fd to Xray via `XRAY_TUN_FD` environment variable (required on Android) +- Android connect — set `LD_LIBRARY_PATH` when launching Xray so native dependencies load on Samsung and similar devices +- Android connect — removed double Xray start on connect (VPN first, single core launch) +- Android connect — clearer error when ProcessBuilder cannot exec the core instead of silent force-close + ## [1.1.7] - 2026-06-28 ### Fixed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 115da40..48bf31e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -3,5 +3,6 @@ | Name | GitHub | Role | |------|--------|------| | dr mike | [@drmikecrypto](https://github.com/drmikecrypto) | Maintainer | +| hasti1356 | [@hasti1356](https://github.com/hasti1356) | Contributor | Thank you to everyone who reports issues, suggests features, and helps improve v2rayF. diff --git a/docs/releases/v1.1.8.md b/docs/releases/v1.1.8.md new file mode 100644 index 0000000..29a93c0 --- /dev/null +++ b/docs/releases/v1.1.8.md @@ -0,0 +1,44 @@ +# v2rayF v1.1.8 + +Android connect crash fix — TUN fd and process environment. + +--- + +## Highlights + +- Fixes force-close on Connect for Samsung Galaxy M21 and similar Android 12 devices +- Xray now receives the VPN file descriptor the way Android Xray-core expects (`XRAY_TUN_FD`) +- Single connect attempt (no probe double-start) + +--- + +## Downloads + +| Platform | Package | How to run | +|----------|---------|------------| +| Android ARM64 | `v2rayF-android-arm64.zip` | Install `v2rayF-android-arm64.apk` | + +Uninstall v1.1.7 first, then install this APK. + +--- + +## Fixed + +- Connect crash from missing `XRAY_TUN_FD` when VPN TUN inbound starts +- Connect crash from missing `LD_LIBRARY_PATH` for `libxray.so` dependencies +- Redundant probe Xray start before VPN on Android +- `Process.IsAlive` compatibility on API 24–25 + +--- + +## Safe connect (poor networks) + +1. **VPN** — established first with your permission +2. **Xray** — starts once with the TUN fd +3. **Fail** — VPN is torn down immediately; you see an error message instead of a crash + +--- + +## Full changelog + +[CHANGELOG.md](https://github.com/drmikecrypto/v2rayF/blob/main/CHANGELOG.md) diff --git a/src/v2rayF.Android/Services/AndroidJavaCoreProcessHost.cs b/src/v2rayF.Android/Services/AndroidJavaCoreProcessHost.cs index e40443e..7e6fbfb 100644 --- a/src/v2rayF.Android/Services/AndroidJavaCoreProcessHost.cs +++ b/src/v2rayF.Android/Services/AndroidJavaCoreProcessHost.cs @@ -1,10 +1,14 @@ using System; +using System.IO; using System.Threading; using System.Threading.Tasks; +using Android.App; +using Android.OS; using Java.IO; using Java.Lang; using v2rayF.Services; using Process = Java.Lang.Process; +using IOException = Java.IO.IOException; namespace v2rayF.Android.Services; @@ -13,6 +17,8 @@ namespace v2rayF.Android.Services; /// public sealed class AndroidJavaCoreProcessHost : ICoreProcessHost { + private const string TunFdEnvKey = "XRAY_TUN_FD"; + private readonly object _lock = new(); private Process? _process; private string _recentOutput = ""; @@ -22,7 +28,7 @@ public bool IsRunning get { lock (_lock) - return _process is { IsAlive: true }; + return IsAlive(_process); } } @@ -31,7 +37,7 @@ public bool HasExited get { lock (_lock) - return _process is null || !_process.IsAlive; + return _process is null || !IsAlive(_process); } } @@ -39,27 +45,54 @@ public Task StartAsync( string corePath, string configPath, string workingDirectory, + int? tunFd = null, CancellationToken cancellationToken = default) { StopAsync(cancellationToken).GetAwaiter().GetResult(); + if (!System.IO.File.Exists(corePath)) + throw new System.IO.FileNotFoundException("Xray core not found.", corePath); + + if (!System.IO.File.Exists(configPath)) + throw new System.IO.FileNotFoundException("Xray config not found.", configPath); + lock (_lock) _recentOutput = ""; - var cmd = new[] { corePath, "run", "-c", configPath }; - var builder = new ProcessBuilder(cmd); - builder.Directory(new File(workingDirectory)); - builder.RedirectErrorStream(true); + var nativeLibDir = Application.Context?.ApplicationInfo?.NativeLibraryDir ?? workingDirectory; - Process process; - lock (_lock) + try { - _process = builder.Start(); - process = _process; - } + var builder = new ProcessBuilder(corePath, "run", "-c", configPath); + builder.Directory(new Java.IO.File(workingDirectory)); + builder.RedirectErrorStream(true); + + var env = builder.Environment(); + env["LD_LIBRARY_PATH"] = nativeLibDir; + env["TMPDIR"] = workingDirectory; - if (process is not null) - _ = Task.Run(() => DrainOutputAsync(process), cancellationToken); + if (tunFd is int fd && fd >= 0) + { + env[TunFdEnvKey] = fd.ToString(); + env["xray.tun.fd"] = fd.ToString(); + } + + Process process; + lock (_lock) + { + _process = builder.Start(); + process = _process; + } + + if (process is not null) + _ = Task.Run(() => DrainOutputAsync(process), cancellationToken); + } + catch (IOException ex) + { + throw new InvalidOperationException( + $"Xray core failed to start: {ex.Message}. Reinstall the app or check that your device is ARM64.", + ex); + } return Task.CompletedTask; } @@ -73,7 +106,7 @@ public Task StopAsync(CancellationToken cancellationToken = default) try { - if (_process.IsAlive) + if (IsAlive(_process)) _process.Destroy(); } catch @@ -93,7 +126,7 @@ public string GetRecentError() { lock (_lock) { - if (_process is { IsAlive: true }) + if (_process is not null && IsAlive(_process)) return _recentOutput.Trim(); if (_process is not null) @@ -116,6 +149,25 @@ public string GetRecentError() } } + private static bool IsAlive(Process? process) + { + if (process is null) + return false; + + if (Build.VERSION.SdkInt >= BuildVersionCodes.O) + return process.IsAlive; + + try + { + process.ExitValue(); + return false; + } + catch (IllegalThreadStateException) + { + return true; + } + } + private void DrainOutputAsync(Process process) { try diff --git a/src/v2rayF.Android/v2rayF.Android.csproj b/src/v2rayF.Android/v2rayF.Android.csproj index 0058c48..3f1da6a 100644 --- a/src/v2rayF.Android/v2rayF.Android.csproj +++ b/src/v2rayF.Android/v2rayF.Android.csproj @@ -5,11 +5,11 @@ 24 enable com.drmikecrypto.v2rayf - 8 - 1.1.6 + 9 + 1.1.8 apk false - 1.1.6 + 1.1.8 v2rayF v2rayF Android proxy client. https://github.com/drmikecrypto/v2rayF diff --git a/src/v2rayF.Core/Services/ICoreProcessHost.cs b/src/v2rayF.Core/Services/ICoreProcessHost.cs index 3227923..e8ca2f7 100644 --- a/src/v2rayF.Core/Services/ICoreProcessHost.cs +++ b/src/v2rayF.Core/Services/ICoreProcessHost.cs @@ -13,6 +13,7 @@ Task StartAsync( string corePath, string configPath, string workingDirectory, + int? tunFd = null, CancellationToken cancellationToken = default); Task StopAsync(CancellationToken cancellationToken = default); diff --git a/src/v2rayF.Core/Services/LatencyService.cs b/src/v2rayF.Core/Services/LatencyService.cs index 0e9ef36..b8919e6 100644 --- a/src/v2rayF.Core/Services/LatencyService.cs +++ b/src/v2rayF.Core/Services/LatencyService.cs @@ -72,6 +72,7 @@ await _speedtestHost.StartAsync( corePath, configPath, _environment.GetCoresDirectory(), + tunFd: null, cancellationToken).ConfigureAwait(false); await WaitForCoreReadyAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/v2rayF.Core/Services/ManagedCoreProcessHost.cs b/src/v2rayF.Core/Services/ManagedCoreProcessHost.cs index 34ca9c1..d37d45b 100644 --- a/src/v2rayF.Core/Services/ManagedCoreProcessHost.cs +++ b/src/v2rayF.Core/Services/ManagedCoreProcessHost.cs @@ -21,6 +21,7 @@ public Task StartAsync( string corePath, string configPath, string workingDirectory, + int? tunFd = null, CancellationToken cancellationToken = default) { StopAsync(cancellationToken).GetAwaiter().GetResult(); diff --git a/src/v2rayF.Core/Services/ProxyCoreService.cs b/src/v2rayF.Core/Services/ProxyCoreService.cs index 764641a..62fce60 100644 --- a/src/v2rayF.Core/Services/ProxyCoreService.cs +++ b/src/v2rayF.Core/Services/ProxyCoreService.cs @@ -66,6 +66,7 @@ await ProcessHost.StartAsync( ResolveCorePath(), _configPath, ResolveCoresDirectory(), + tunFd, cancellationToken).ConfigureAwait(false); await WaitForCoreReadyAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/v2rayF/ViewModels/MainWindowViewModel.cs b/src/v2rayF/ViewModels/MainWindowViewModel.cs index 290dd1c..298c3b2 100644 --- a/src/v2rayF/ViewModels/MainWindowViewModel.cs +++ b/src/v2rayF/ViewModels/MainWindowViewModel.cs @@ -455,20 +455,6 @@ private async Task ConnectAndroidAsync( AppSettings settings, Action markVpnEngaged) { - RunOnUiThread(() => StatusText = $"Checking {server.Name}…"); - - var probeSettings = new AppSettings - { - RoutingMode = settings.RoutingMode, - CustomDirectRules = settings.CustomDirectRules, - EnableTunMode = false, - EnableSystemProxy = false, - SubscriptionUrl = settings.SubscriptionUrl - }; - - await _proxyCore.StartAsync(server, probeSettings, null).ConfigureAwait(false); - await _proxyCore.StopAsync().ConfigureAwait(false); - RunOnUiThread(() => StatusText = "Starting VPN…"); var tunFd = await AppServices.Platform.EstablishVpnAsync().ConfigureAwait(false); if (tunFd is null) @@ -480,14 +466,22 @@ private async Task ConnectAndroidAsync( markVpnEngaged(true); RunOnUiThread(() => StatusText = $"Starting proxy for {server.Name}…"); - await _proxyCore.StartAsync(server, settings, tunFd).ConfigureAwait(false); - await AppServices.Platform.EnableProxyAsync().ConfigureAwait(false); + try + { + await _proxyCore.StartAsync(server, settings, tunFd).ConfigureAwait(false); + await AppServices.Platform.EnableProxyAsync().ConfigureAwait(false); - RunOnUiThread(() => + RunOnUiThread(() => + { + StatusText = $"Connected — {server.Name} (VPN)"; + IsConnected = true; + }); + } + catch { - StatusText = $"Connected — {server.Name} (VPN)"; - IsConnected = true; - }); + await SafeTeardownAsync(vpnEngaged: true).ConfigureAwait(false); + throw; + } } private async Task SafeTeardownAsync(bool vpnEngaged)