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)