Guard Android VPN runtime generations

This commit is contained in:
2026-05-15 15:13:37 +03:00
parent 769bb3176c
commit 59afc6bcc7
2 changed files with 33 additions and 26 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 200 versionCode 201
versionName "0.2.200" versionName "0.2.201"
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)}\""
@@ -195,6 +195,7 @@ public class RapVpnService extends VpnService {
private final AtomicLong runtimeWatchdogRecoveries = new AtomicLong(); private final AtomicLong runtimeWatchdogRecoveries = new AtomicLong();
private final AtomicLong tcpHandshakeStalls = new AtomicLong(); private final AtomicLong tcpHandshakeStalls = new AtomicLong();
private final AtomicLong runtimeWatchdogHardRestarts = new AtomicLong(); private final AtomicLong runtimeWatchdogHardRestarts = new AtomicLong();
private final AtomicLong runtimeGeneration = new AtomicLong();
private final AtomicLong diagnosticEnsureRequests = new AtomicLong(); private final AtomicLong diagnosticEnsureRequests = new AtomicLong();
private final AtomicLong diagnosticRestartRequests = new AtomicLong(); private final AtomicLong diagnosticRestartRequests = new AtomicLong();
private final AtomicBoolean hardRuntimeRestartInProgress = new AtomicBoolean(); private final AtomicBoolean hardRuntimeRestartInProgress = new AtomicBoolean();
@@ -1025,6 +1026,7 @@ public class RapVpnService extends VpnService {
writeActivePacketRelayConfig(selectedRelayUrl, relayUrls); writeActivePacketRelayConfig(selectedRelayUrl, relayUrls);
stopPacketRelay(); stopPacketRelay();
running = true; running = true;
long runtimeId = runtimeGeneration.incrementAndGet();
runtimeStartedAt = System.currentTimeMillis(); runtimeStartedAt = System.currentTimeMillis();
uplinkWorkerCount = Math.max(1, Math.min(UPLINK_WORKER_MAX_COUNT, Math.max(1, Runtime.getRuntime().availableProcessors() - 1))); uplinkWorkerCount = Math.max(1, Math.min(UPLINK_WORKER_MAX_COUNT, Math.max(1, Runtime.getRuntime().availableProcessors() - 1)));
if (uplinkWorkerCount < 2) { if (uplinkWorkerCount < 2) {
@@ -1073,15 +1075,15 @@ public class RapVpnService extends VpnService {
writeRuntimeStatus("relay_reset_warning", "queue reset failed: " + e.getMessage(), 0, 0, 0, 1); writeRuntimeStatus("relay_reset_warning", "queue reset failed: " + e.getMessage(), 0, 0, 0, 1);
} }
}, "rap-vpn-relay-reset"); }, "rap-vpn-relay-reset");
uplinkThread = new Thread(() -> pumpTunToRelay(clusterId, vpnConnectionId), "rap-vpn-uplink"); uplinkThread = new Thread(() -> pumpTunToRelay(runtimeId, clusterId, vpnConnectionId), "rap-vpn-uplink");
uplinkSenderThreads = new Thread[uplinkWorkerCount]; uplinkSenderThreads = new Thread[uplinkWorkerCount];
for (int i = 0; i < uplinkWorkerCount; i++) { for (int i = 0; i < uplinkWorkerCount; i++) {
final int workerIndex = i; final int workerIndex = i;
uplinkSenderThreads[i] = new Thread(() -> pumpUplinkQueueToRelay(workerIndex, clusterId, vpnConnectionId), "rap-vpn-uplink-sender-" + workerIndex); uplinkSenderThreads[i] = new Thread(() -> pumpUplinkQueueToRelay(runtimeId, workerIndex, clusterId, vpnConnectionId), "rap-vpn-uplink-sender-" + workerIndex);
} }
downlinkThread = new Thread(() -> runDownlinkWithRestart(clusterId, vpnConnectionId), "rap-vpn-downlink-receiver"); downlinkThread = new Thread(() -> runDownlinkWithRestart(runtimeId, clusterId, vpnConnectionId), "rap-vpn-downlink-receiver");
downlinkWriterThread = new Thread(this::pumpDownlinkQueueToTun, "rap-vpn-downlink-writer"); downlinkWriterThread = new Thread(() -> pumpDownlinkQueueToTun(runtimeId), "rap-vpn-downlink-writer");
runtimeWatchdogThread = new Thread(() -> runRuntimeWatchdog(clusterId, vpnConnectionId), "rap-vpn-runtime-watchdog"); runtimeWatchdogThread = new Thread(() -> runRuntimeWatchdog(runtimeId, clusterId, vpnConnectionId), "rap-vpn-runtime-watchdog");
diagnosticWatchdogThread = new Thread(this::runDiagnosticServiceWatchdog, "rap-vpn-diagnostic-watchdog"); diagnosticWatchdogThread = new Thread(this::runDiagnosticServiceWatchdog, "rap-vpn-diagnostic-watchdog");
if (resetThread != null) { if (resetThread != null) {
resetThread.start(); resetThread.start();
@@ -1308,6 +1310,7 @@ public class RapVpnService extends VpnService {
} }
private void stopPacketRelay() { private void stopPacketRelay() {
runtimeGeneration.incrementAndGet();
running = false; running = false;
VpnPacketWebSocketRelay relay = packetWebSocketRelay; VpnPacketWebSocketRelay relay = packetWebSocketRelay;
packetWebSocketRelay = null; packetWebSocketRelay = null;
@@ -1564,13 +1567,17 @@ public class RapVpnService extends VpnService {
} }
} }
private void runDownlinkWithRestart(String clusterId, String vpnConnectionId) { private boolean isRuntimeActive(long runtimeId) {
return running && runtimeGeneration.get() == runtimeId;
}
private void runDownlinkWithRestart(long runtimeId, String clusterId, String vpnConnectionId) {
long restarts = 0; long restarts = 0;
while (running) { while (isRuntimeActive(runtimeId)) {
downlinkRestarts = restarts; downlinkRestarts = restarts;
writeRuntimeDetail("starting", "downlink loop starting", "downlink", 0, restarts, ""); writeRuntimeDetail("starting", "downlink loop starting", "downlink", 0, restarts, "");
pumpRelayToTun(clusterId, vpnConnectionId); pumpRelayToTun(clusterId, vpnConnectionId);
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
restarts++; restarts++;
@@ -1580,18 +1587,18 @@ public class RapVpnService extends VpnService {
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
} }
} }
} }
private void runRuntimeWatchdog(String clusterId, String vpnConnectionId) { private void runRuntimeWatchdog(long runtimeId, String clusterId, String vpnConnectionId) {
while (running) { while (isRuntimeActive(runtimeId)) {
try { try {
Thread.sleep(RUNTIME_WATCHDOG_INTERVAL_MS); Thread.sleep(RUNTIME_WATCHDOG_INTERVAL_MS);
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@@ -1645,7 +1652,7 @@ public class RapVpnService extends VpnService {
recoverPacketRelayRuntime(clusterId, vpnConnectionId, "tcp_handshake_stall stale=" + stale); recoverPacketRelayRuntime(clusterId, vpnConnectionId, "tcp_handshake_stall stale=" + stale);
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
} catch (Exception e) { } catch (Exception e) {
@@ -1821,7 +1828,7 @@ public class RapVpnService extends VpnService {
return value == null || value.isEmpty() ? "missing" : "present"; return value == null || value.isEmpty() ? "missing" : "present";
} }
private void pumpTunToRelay(String clusterId, String vpnConnectionId) { private void pumpTunToRelay(long runtimeId, String clusterId, String vpnConnectionId) {
byte[] packet = new byte[32767]; byte[] packet = new byte[32767];
long readPackets = 0; long readPackets = 0;
FileDescriptor fd = null; FileDescriptor fd = null;
@@ -1831,7 +1838,7 @@ public class RapVpnService extends VpnService {
input = new FileInputStream(fd); input = new FileInputStream(fd);
uplinkTunFd = fd; uplinkTunFd = fd;
uplinkTunInput = input; uplinkTunInput = input;
while (running) { while (isRuntimeActive(runtimeId)) {
int n = input.read(packet); int n = input.read(packet);
if (n > 0) { if (n > 0) {
readPackets++; readPackets++;
@@ -1849,7 +1856,7 @@ public class RapVpnService extends VpnService {
} }
} }
} catch (Exception e) { } catch (Exception e) {
if (running) { if (isRuntimeActive(runtimeId)) {
Log.e(TAG, "vpn uplink stopped", e); Log.e(TAG, "vpn uplink stopped", e);
writeRuntimeStatus("error", "uplink stopped: " + e.getMessage(), readPackets, 0, 0, 0); writeRuntimeStatus("error", "uplink stopped: " + e.getMessage(), readPackets, 0, 0, 0);
writeRuntimeDetail("stopped", "uplink stopped: " + e.getMessage(), "uplink", readPackets, 0, e.getClass().getSimpleName()); writeRuntimeDetail("stopped", "uplink stopped: " + e.getMessage(), "uplink", readPackets, 0, e.getClass().getSimpleName());
@@ -2474,11 +2481,11 @@ public class RapVpnService extends VpnService {
} }
} }
private void pumpUplinkQueueToRelay(int workerIndex, String clusterId, String vpnConnectionId) { private void pumpUplinkQueueToRelay(long runtimeId, int workerIndex, String clusterId, String vpnConnectionId) {
long sentPackets = 0; long sentPackets = 0;
long errors = 0; long errors = 0;
List<byte[]> batch = new ArrayList<>(VPN_BATCH_MAX_PACKETS); List<byte[]> batch = new ArrayList<>(VPN_BATCH_MAX_PACKETS);
while (running) { while (isRuntimeActive(runtimeId)) {
try { try {
BlockingQueue<byte[]>[] queues = uplinkQueues; BlockingQueue<byte[]>[] queues = uplinkQueues;
if (queues == null || workerIndex < 0 || workerIndex >= queues.length) { if (queues == null || workerIndex < 0 || workerIndex >= queues.length) {
@@ -2556,12 +2563,12 @@ public class RapVpnService extends VpnService {
writeRuntimeStatus("uplink_sent", "sent batch=" + batch.size(), 0, sentPackets, 0, errors); writeRuntimeStatus("uplink_sent", "sent batch=" + batch.size(), 0, sentPackets, 0, errors);
writeRuntimeDetail("sent", "worker=" + workerIndex + " sent batch=" + batch.size(), "uplink_sender", sentPackets, errors, "", workerIndex); writeRuntimeDetail("sent", "worker=" + workerIndex + " sent batch=" + batch.size(), "uplink_sender", sentPackets, errors, "", workerIndex);
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
writeRuntimeDetail("read_wait", "uplink queue wait interrupted", "uplink_sender", sentPackets, errors, e.getClass().getSimpleName(), workerIndex); writeRuntimeDetail("read_wait", "uplink queue wait interrupted", "uplink_sender", sentPackets, errors, e.getClass().getSimpleName(), workerIndex);
} catch (Exception e) { } catch (Exception e) {
if (running) { if (isRuntimeActive(runtimeId)) {
Log.w(TAG, "vpn uplink batch send failed; continuing", e); Log.w(TAG, "vpn uplink batch send failed; continuing", e);
errors++; errors++;
AtomicLong[] senderErrors = uplinkSenderErrorsByWorker; AtomicLong[] senderErrors = uplinkSenderErrorsByWorker;
@@ -2573,7 +2580,7 @@ public class RapVpnService extends VpnService {
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException interrupted) { } catch (InterruptedException interrupted) {
if (!running) { if (!isRuntimeActive(runtimeId)) {
return; return;
} }
} }
@@ -3191,7 +3198,7 @@ public class RapVpnService extends VpnService {
return false; return false;
} }
private void pumpDownlinkQueueToTun() { private void pumpDownlinkQueueToTun(long runtimeId) {
long writtenPackets = 0; long writtenPackets = 0;
long errors = 0; long errors = 0;
FileDescriptor fd = null; FileDescriptor fd = null;
@@ -3199,7 +3206,7 @@ public class RapVpnService extends VpnService {
try { try {
fd = Os.dup(tunnel.getFileDescriptor()); fd = Os.dup(tunnel.getFileDescriptor());
downlinkTunFd = fd; downlinkTunFd = fd;
while (running) { while (isRuntimeActive(runtimeId)) {
BlockingQueue<byte[]>[] queues = downlinkQueues; BlockingQueue<byte[]>[] queues = downlinkQueues;
if (queues == null || queues.length == 0) { if (queues == null || queues.length == 0) {
return; return;
@@ -3228,7 +3235,7 @@ public class RapVpnService extends VpnService {
} }
} }
} catch (Exception e) { } catch (Exception e) {
if (running) { if (isRuntimeActive(runtimeId)) {
Log.e(TAG, "vpn downlink writer stopped", e); Log.e(TAG, "vpn downlink writer stopped", e);
writeRuntimeStatus("error", "downlink writer stopped: " + e.getMessage(), 0, 0, writtenPackets, errors); writeRuntimeStatus("error", "downlink writer stopped: " + e.getMessage(), 0, 0, writtenPackets, errors);
writeRuntimeDetail("stopped", "downlink writer stopped: " + e.getMessage(), "downlink_writer", writtenPackets, errors, e.getClass().getSimpleName()); writeRuntimeDetail("stopped", "downlink writer stopped: " + e.getMessage(), "downlink_writer", writtenPackets, errors, e.getClass().getSimpleName());