Harden Android VPN uplink transient retries

This commit is contained in:
2026-05-15 00:49:28 +03:00
parent 4e694dc903
commit 495059b92e
2 changed files with 37 additions and 9 deletions
+2 -2
View File
@@ -30,8 +30,8 @@ android {
applicationId "su.cin.rapvpn" applicationId "su.cin.rapvpn"
minSdk 26 minSdk 26
targetSdk 35 targetSdk 35
versionCode 183 versionCode 184
versionName "0.2.183" versionName "0.2.184"
buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\"" buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\""
buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\"" buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\""
buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\"" buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\""
@@ -33,6 +33,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue; 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 PRIORITY_QUEUE_CAPACITY = 4096;
private static final int UPLINK_SEND_RETRY_COUNT = 2; private static final int UPLINK_SEND_RETRY_COUNT = 2;
private static final int UPLINK_SEND_RETRY_SLEEP_MS = 80; 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_QUEUE_CAPACITY = 8192;
private static final int DOWNLINK_FLOW_QUEUE_MAX_COUNT = 8; private static final int DOWNLINK_FLOW_QUEUE_MAX_COUNT = 8;
private static final int DOWNLINK_QUEUE_OFFER_MS = 50; private static final int DOWNLINK_QUEUE_OFFER_MS = 50;
@@ -2475,7 +2479,8 @@ public class RapVpnService extends VpnService {
return true; return true;
} }
RapApiClient client = packetRelayClientForUrl(relayUrl); RapApiClient client = packetRelayClientForUrl(relayUrl);
for (int attempt = 0; attempt <= UPLINK_SEND_RETRY_COUNT && running; attempt++) { int attempt = 0;
while (running) {
try { try {
client.sendClientPacketBatch(clusterId, vpnConnectionId, batch); client.sendClientPacketBatch(clusterId, vpnConnectionId, batch);
if (attempt > 0) { if (attempt > 0) {
@@ -2486,7 +2491,16 @@ public class RapVpnService extends VpnService {
lastError = e; lastError = e;
lastUplinkSendErrorMessage = compactException(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); 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())) { if (!switchPacketRelayUrl(relayUrl, lastError == null ? "send_failed" : lastError.getClass().getSimpleName())) {
@@ -2499,6 +2513,20 @@ public class RapVpnService extends VpnService {
return false; 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<byte[]> batch, int workerIndex) { private boolean sendUplinkBatchOverWebSocket(String relayUrl, String clusterId, String vpnConnectionId, List<byte[]> batch, int workerIndex) {
if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) { if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) {
return false; return false;