From 495059b92e393d2e7a2910ac5bf1660b308487a3 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 15 May 2026 00:49:28 +0300 Subject: [PATCH] Harden Android VPN uplink transient retries --- clients/android/app/build.gradle | 4 +- .../java/su/cin/rapvpn/RapVpnService.java | 42 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/clients/android/app/build.gradle b/clients/android/app/build.gradle index 73ca0bf..23d3402 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 183 - versionName "0.2.183" + versionCode 184 + versionName "0.2.184" 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/RapVpnService.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java index 400a489..67eec8b 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -60,6 +61,9 @@ public class RapVpnService extends VpnService { private static final int PRIORITY_QUEUE_CAPACITY = 4096; private static final int UPLINK_SEND_RETRY_COUNT = 2; private static final int UPLINK_SEND_RETRY_SLEEP_MS = 80; + private static final int UPLINK_TRANSIENT_RETRY_COUNT = 10; + private static final int UPLINK_TRANSIENT_RETRY_SLEEP_MS = 250; + private static final int UPLINK_TRANSIENT_RETRY_MAX_SLEEP_MS = 1500; private static final int DOWNLINK_QUEUE_CAPACITY = 8192; private static final int DOWNLINK_FLOW_QUEUE_MAX_COUNT = 8; private static final int DOWNLINK_QUEUE_OFFER_MS = 50; @@ -2475,18 +2479,28 @@ public class RapVpnService extends VpnService { return true; } RapApiClient client = packetRelayClientForUrl(relayUrl); - for (int attempt = 0; attempt <= UPLINK_SEND_RETRY_COUNT && running; attempt++) { + int attempt = 0; + while (running) { try { - client.sendClientPacketBatch(clusterId, vpnConnectionId, batch); - if (attempt > 0) { - writeRuntimeDetail("retry_ok", "uplink retry ok worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " batch=" + batch.size(), "uplink_sender", -1, -1, "", workerIndex); - } - return true; + client.sendClientPacketBatch(clusterId, vpnConnectionId, batch); + if (attempt > 0) { + writeRuntimeDetail("retry_ok", "uplink retry ok worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " batch=" + batch.size(), "uplink_sender", -1, -1, "", workerIndex); + } + return true; } catch (Exception e) { lastError = e; lastUplinkSendErrorMessage = compactException(e); writeRuntimeDetail("retry", "uplink send retry worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " error=" + lastUplinkSendErrorMessage, "uplink_sender", -1, -1, e.getClass().getSimpleName(), workerIndex); - sleepQuietly(UPLINK_SEND_RETRY_SLEEP_MS * (attempt + 1L)); + boolean transientError = isTransientUplinkSendError(e); + int retryLimit = transientError ? UPLINK_TRANSIENT_RETRY_COUNT : UPLINK_SEND_RETRY_COUNT; + if (attempt >= retryLimit) { + break; + } + int baseSleepMs = transientError ? UPLINK_TRANSIENT_RETRY_SLEEP_MS : UPLINK_SEND_RETRY_SLEEP_MS; + int maxSleepMs = transientError ? UPLINK_TRANSIENT_RETRY_MAX_SLEEP_MS : UPLINK_SEND_RETRY_SLEEP_MS * (UPLINK_SEND_RETRY_COUNT + 1); + long sleepMs = Math.min(maxSleepMs, baseSleepMs * (attempt + 1L)); + sleepQuietly(sleepMs); + attempt++; } } if (!switchPacketRelayUrl(relayUrl, lastError == null ? "send_failed" : lastError.getClass().getSimpleName())) { @@ -2499,6 +2513,20 @@ public class RapVpnService extends VpnService { return false; } + private boolean isTransientUplinkSendError(Exception e) { + String message = e == null ? null : e.getMessage(); + if (message == null) { + return false; + } + String lower = message.toLowerCase(Locale.ROOT); + return lower.contains("http 502") + || lower.contains("http 503") + || lower.contains("http 504") + || lower.contains("mesh synthetic route not found") + || lower.contains("forward runtime unavailable") + || lower.contains("forward peer unavailable"); + } + private boolean sendUplinkBatchOverWebSocket(String relayUrl, String clusterId, String vpnConnectionId, List batch, int workerIndex) { if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) { return false;