diff --git a/clients/android/app/build.gradle b/clients/android/app/build.gradle index 7423100..5545f1a 100644 --- a/clients/android/app/build.gradle +++ b/clients/android/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "su.cin.rapvpn" minSdk 26 targetSdk 35 - versionCode 185 - versionName "0.2.185" + versionCode 186 + versionName "0.2.186" buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\"" buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\"" buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\"" diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java index 366ff13..b66ba0f 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java @@ -69,6 +69,10 @@ public class RapDiagnosticService extends Service { private static final long COMMAND_STALE_MS = 45000; private static final long COMMAND_ORPHAN_MS = 60000; private static final long POLL_FORCE_MS = 45000; + private static final int RECOVERY_TCP_TIMEOUT_MS = 8000; + private static final int RECOVERY_PAGE_TIMEOUT_MS = 25000; + private static final int RECOVERY_DOWNLOAD_CONNECT_TIMEOUT_MS = 8000; + private static final int RECOVERY_DOWNLOAD_READ_TIMEOUT_MS = 12000; private volatile boolean running; private Thread worker; private Thread supervisor; @@ -478,14 +482,14 @@ public class RapDiagnosticService extends Service { } if (isRecoverableVPNProbe(type) && looksLikeVPNStall(result)) { String firstResult = result; - Thread.sleep(1500); - String fastRetry = runVPNProbeCommand(type, params); - if (!looksLikeVPNStall(fastRetry)) { - result = firstResult + " | fast_retry=" + fastRetry; + String recovery = controlledRestartVPNRuntime(client, clusterId); + Thread.sleep(2500); + String recoveryRetry = runVPNProbeCommand(type, recoveryProbePayload(type, params), true); + if (!looksLikeVPNStall(recoveryRetry)) { + result = firstResult + " | recovery=" + recovery + " | recovery_retry=" + recoveryRetry; } else { - String recovery = controlledRestartVPNRuntime(client, clusterId); - Thread.sleep(4000); - result = firstResult + " | fast_retry=" + fastRetry + " | recovery=" + recovery + " | retry=" + runVPNProbeCommand(type, params); + Thread.sleep(1500); + result = firstResult + " | recovery=" + recovery + " | recovery_retry=" + recoveryRetry + " | final_retry=" + runVPNProbeCommand(type, recoveryProbePayload(type, params), true); } } lastCommandType = type; @@ -661,6 +665,10 @@ public class RapDiagnosticService extends Service { } private String runVPNProbeCommand(String type, JSONObject payload) { + return runVPNProbeCommand(type, payload, false); + } + + private String runVPNProbeCommand(String type, JSONObject payload, boolean recoveryAttempt) { if ("vpn_http_get".equals(type)) { return runVPNHttpGet(payload.optString("url", "http://192.168.200.61:18080/")); } @@ -671,11 +679,37 @@ public class RapDiagnosticService extends Service { return runVPNTCPConnect(payload.optString("host", "192.168.200.95"), payload.optInt("port", 3389), payload.optInt("timeout_ms", 7000)); } if ("vpn_download_test".equals(type)) { + if (recoveryAttempt) { + return runVPNDownloadTest( + payload.optString("url", "http://192.168.200.61:18080/downloads/rap-android-rdp-vpn-build.json"), + RECOVERY_DOWNLOAD_CONNECT_TIMEOUT_MS, + RECOVERY_DOWNLOAD_READ_TIMEOUT_MS); + } return runVPNDownloadTest(payload.optString("url", "http://192.168.200.61:18080/downloads/rap-android-rdp-vpn-build.json")); } return "retry skipped: unsupported probe " + type; } + private JSONObject recoveryProbePayload(String type, JSONObject payload) { + JSONObject copy; + try { + copy = payload == null ? new JSONObject() : new JSONObject(payload.toString()); + } catch (Exception e) { + copy = new JSONObject(); + } + try { + if ("vpn_tcp_connect".equals(type) || "vpn_rdp_probe".equals(type)) { + int requested = copy.optInt("timeout_ms", RECOVERY_TCP_TIMEOUT_MS); + copy.put("timeout_ms", Math.max(1000, Math.min(requested, RECOVERY_TCP_TIMEOUT_MS))); + } else if ("vpn_page_probe".equals(type)) { + int requested = copy.optInt("timeout_ms", RECOVERY_PAGE_TIMEOUT_MS); + copy.put("timeout_ms", Math.max(10000, Math.min(requested, RECOVERY_PAGE_TIMEOUT_MS))); + } + } catch (Exception ignored) { + } + return copy; + } + private String controlledRestartVPNRuntime(RapApiClient client, String clusterId) { SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); String connectionId = prefs.getString(PREF_VPN_CONNECTION_ID, ""); @@ -1051,6 +1085,10 @@ public class RapDiagnosticService extends Service { } private String runVPNDownloadTest(String target) { + return runVPNDownloadTest(target, 15000, 20000); + } + + private String runVPNDownloadTest(String target, int connectTimeoutMs, int readTimeoutMs) { try { Network vpn = waitForVPNNetwork(5000); if (vpn == null) { @@ -1058,8 +1096,8 @@ public class RapDiagnosticService extends Service { } URL url = new URL(target); HttpURLConnection connection = (HttpURLConnection) vpn.openConnection(url); - connection.setConnectTimeout(15000); - connection.setReadTimeout(20000); + connection.setConnectTimeout(Math.max(1000, connectTimeoutMs)); + connection.setReadTimeout(Math.max(1000, readTimeoutMs)); connection.setInstanceFollowRedirects(false); int code = connection.getResponseCode(); int bytes = 0;