Improve Android VPN mixed-load QoS

This commit is contained in:
2026-05-15 21:28:56 +03:00
parent 54ce203c67
commit d2cd83a2be
3 changed files with 96 additions and 8 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 207 versionCode 208
versionName "0.2.207" versionName "0.2.208"
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)}\""
@@ -21,6 +21,7 @@ import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.widget.Toast; import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -472,6 +473,8 @@ public class RapDiagnosticService extends Service {
result = runVPNDeepTest(client, clusterId, params); result = runVPNDeepTest(client, clusterId, params);
} else if ("vpn_download_test".equals(type)) { } else if ("vpn_download_test".equals(type)) {
result = runVPNDownloadTest(params.optString("url", "http://192.168.200.61:18080/downloads/rap-android-rdp-vpn-build.json")); result = runVPNDownloadTest(params.optString("url", "http://192.168.200.61:18080/downloads/rap-android-rdp-vpn-build.json"));
} else if ("vpn_mixed_load_test".equals(type) || "vpn_parallel_http_get".equals(type)) {
result = runVPNMixedLoadTest(params);
} else if ("launch_telegram".equals(type)) { } else if ("launch_telegram".equals(type)) {
result = openExternalURL(params.optString("url", "tg://resolve?domain=telegram")); result = openExternalURL(params.optString("url", "tg://resolve?domain=telegram"));
} else if ("remote_assist_start".equals(type)) { } else if ("remote_assist_start".equals(type)) {
@@ -1222,6 +1225,92 @@ public class RapDiagnosticService extends Service {
} }
} }
private String runVPNMixedLoadTest(JSONObject payload) {
int parallel = payload.optInt("parallel", 12);
if (parallel < 1) {
parallel = 1;
}
if (parallel > 20) {
parallel = 20;
}
int timeoutMs = payload.optInt("timeout_ms", 20000);
if (timeoutMs < 3000) {
timeoutMs = 3000;
}
if (timeoutMs > 45000) {
timeoutMs = 45000;
}
String rdpHost = payload.optString("rdp_host", "192.168.200.95");
int rdpPort = payload.optInt("rdp_port", 3389);
String[] defaults = new String[] {
"http://2ip.ru/",
"http://example.com/",
"http://neverssl.com/",
"http://192.168.200.61:18080/downloads/rap-android-rdp-vpn-build.json",
"http://192.168.200.61:18080/"
};
List<String> urls = new ArrayList<>();
JSONArray payloadUrls = payload.optJSONArray("urls");
if (payloadUrls != null) {
for (int i = 0; i < payloadUrls.length(); i++) {
String url = payloadUrls.optString(i, "");
if (url != null && !url.trim().isEmpty()) {
urls.add(url.trim());
}
}
}
if (urls.isEmpty()) {
for (String url : defaults) {
urls.add(url);
}
}
String rdpBefore = runVPNTCPConnect(rdpHost, rdpPort, Math.min(timeoutMs, 10000));
String[] results = new String[parallel];
Thread[] threads = new Thread[parallel];
final int requestTimeoutMs = timeoutMs;
long started = System.currentTimeMillis();
for (int i = 0; i < parallel; i++) {
final int index = i;
final String target = urls.get(i % urls.size());
threads[i] = new Thread(() -> results[index] = runVPNHttpGet(target, requestTimeoutMs), "rap-vpn-load-test-" + index);
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join(timeoutMs + 5000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
long elapsed = System.currentTimeMillis() - started;
int ok = 0;
int failed = 0;
StringBuilder sample = new StringBuilder();
for (int i = 0; i < results.length; i++) {
String item = results[i];
if (item != null && item.contains("-> HTTP ")) {
ok++;
} else {
failed++;
}
if (i < 5) {
if (sample.length() > 0) {
sample.append("; ");
}
sample.append(item == null ? "timeout/no_result" : item);
}
}
String rdpAfter = runVPNTCPConnect(rdpHost, rdpPort, Math.min(timeoutMs, 10000));
return compact("vpn_mixed_load_test parallel=" + parallel
+ " ok=" + ok
+ " failed=" + failed
+ " elapsed_ms=" + elapsed
+ " rdp_before={" + rdpBefore + "}"
+ " rdp_after={" + rdpAfter + "}"
+ " sample={" + sample + "}", 2500);
}
private String openExternalURL(String target) { private String openExternalURL(String target) {
try { try {
Intent open = new Intent(Intent.ACTION_VIEW, Uri.parse(target)); Intent open = new Intent(Intent.ACTION_VIEW, Uri.parse(target));
@@ -56,7 +56,8 @@ public class RapVpnService extends VpnService {
private static final boolean PACKET_WEBSOCKET_DATAPLANE_ENABLED = true; private static final boolean PACKET_WEBSOCKET_DATAPLANE_ENABLED = true;
private static final int VPN_BATCH_MAX_PACKETS = 512; private static final int VPN_BATCH_MAX_PACKETS = 512;
private static final int VPN_BATCH_MAX_BYTES = 1024 * 1024; private static final int VPN_BATCH_MAX_BYTES = 1024 * 1024;
private static final int UPLINK_WORKER_MAX_COUNT = 1; private static final int UPLINK_WORKER_MIN_COUNT = 4;
private static final int UPLINK_WORKER_MAX_COUNT = 4;
private static final int UPLINK_QUEUE_CAPACITY = 32768; private static final int UPLINK_QUEUE_CAPACITY = 32768;
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;
@@ -1028,10 +1029,7 @@ public class RapVpnService extends VpnService {
running = true; running = true;
long runtimeId = runtimeGeneration.incrementAndGet(); 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(UPLINK_WORKER_MIN_COUNT, Math.min(UPLINK_WORKER_MAX_COUNT, Math.max(1, Runtime.getRuntime().availableProcessors())));
if (uplinkWorkerCount < 2) {
uplinkWorkerCount = 1;
}
uplinkQueueOffersByWorker = createAtomicCounters(uplinkWorkerCount); uplinkQueueOffersByWorker = createAtomicCounters(uplinkWorkerCount);
uplinkQueueDropsByWorker = createAtomicCounters(uplinkWorkerCount); uplinkQueueDropsByWorker = createAtomicCounters(uplinkWorkerCount);
uplinkSenderPacketsByWorker = createAtomicCounters(uplinkWorkerCount); uplinkSenderPacketsByWorker = createAtomicCounters(uplinkWorkerCount);
@@ -2290,7 +2288,8 @@ public class RapVpnService extends VpnService {
int ihl = (packet[0] & 0x0f) * 4; int ihl = (packet[0] & 0x0f) * 4;
int tcpHeaderLength = ((packet[ihl + 12] >> 4) & 0x0f) * 4; int tcpHeaderLength = ((packet[ihl + 12] >> 4) & 0x0f) * 4;
int tcpPayloadLength = Math.max(0, ipTotalLength - ihl - tcpHeaderLength); int tcpPayloadLength = Math.max(0, ipTotalLength - ihl - tcpHeaderLength);
return syn || fin || rst || (ack && tcpPayloadLength == 0) || (psh && tcpPayloadLength <= 96); boolean rdp = flow.srcPort == 3389 || flow.dstPort == 3389;
return rdp || syn || fin || rst || (ack && tcpPayloadLength == 0) || (psh && tcpPayloadLength <= 96);
} }
private boolean clampIPv4TCPMSS(byte[] packet, int length, int maxMss) { private boolean clampIPv4TCPMSS(byte[] packet, int length, int maxMss) {