From a807fa9db6205b3e2b1b3981e7eddcc1b61eb9cd Mon Sep 17 00:00:00 2001 From: instant992 Date: Thu, 11 Jun 2026 04:13:10 +0400 Subject: [PATCH] Rotate to another server on stuck proxy connection - ProxyRotationController now probes VLESS+Reality servers with a real TCP latency check (the MTProto check always failed for them), so rotation can actually find a working server - Switching to a built-in VLESS server now starts Xray and points Telegram at the local SOCKS5 listener via XrayController.switchToBuiltinProxy, instead of pointing at the raw server address - Skip built-in servers that don't match the current network when rotating - Trigger rotation not only on ConnectingToProxy but also on prolonged Connecting/WaitingForNetwork states, covering the infinite-connecting case --- .../messenger/ProxyRotationController.java | 46 ++++++++++++++++++- .../telegram/messenger/XrayController.java | 31 +++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ProxyRotationController.java b/TMessagesProj/src/main/java/org/telegram/messenger/ProxyRotationController.java index 9b5fb771..bb169dfd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ProxyRotationController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ProxyRotationController.java @@ -31,6 +31,28 @@ public class ProxyRotationController implements NotificationCenter.NotificationC } startedCheck = true; proxyInfo.checking = true; + if (proxyInfo.isVless()) { + // VLESS+Reality servers don't speak the MTProto proxy protocol, + // so Telegram's checkProxy always fails. Use a real TCP-connect + // latency probe to the server endpoint instead. + final SharedConfig.ProxyInfo info = proxyInfo; + new Thread(() -> { + long latency = XrayController.measureTcpLatency(info.address, info.port, 4000); + AndroidUtilities.runOnUIThread(() -> { + info.availableCheckTime = SystemClock.elapsedRealtime(); + info.checking = false; + if (latency < 0) { + info.available = false; + info.ping = 0; + } else { + info.ping = latency; + info.available = true; + } + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyCheckDone, info); + }); + }, "vless-rotation-ping").start(); + continue; + } proxyInfo.proxyCheckPingId = ConnectionsManager.getInstance(currentAccount).checkProxy(proxyInfo.address, proxyInfo.port, proxyInfo.username, proxyInfo.password, proxyInfo.secret, time -> AndroidUtilities.runOnUIThread(() -> { proxyInfo.availableCheckTime = SystemClock.elapsedRealtime(); proxyInfo.checking = false; @@ -69,6 +91,22 @@ public class ProxyRotationController implements NotificationCenter.NotificationC if (info == SharedConfig.currentProxy || info.checking || !info.available) { continue; } + // Don't switch to a built-in server that doesn't match the current + // network (e.g. a Wi-Fi server while we're on mobile data). + if (info.builtin && !XrayController.matchesCurrentNetwork(info)) { + continue; + } + + if (info.builtin && info.isVless()) { + // Built-in VLESS proxy: start Xray and point Telegram at the + // local SOCKS5 listener. switchToBuiltinProxy persists the + // selection and applies the proxy settings itself. + SharedConfig.currentProxy = info; + XrayController.switchToBuiltinProxy(info); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged); + NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyChangedByRotation); + break; + } SharedPreferences.Editor editor = MessagesController.getGlobalMainSettings().edit(); editor.putString("proxy_ip", info.address); @@ -116,7 +154,13 @@ public class ProxyRotationController implements NotificationCenter.NotificationC int state = ConnectionsManager.getInstance(account).getConnectionState(); - if (state == ConnectionsManager.ConnectionStateConnectingToProxy) { + // Treat any prolonged "not connected" state as a stuck connection + // that should trigger a rotation to another server after the + // configured timeout. This covers the "infinite connecting" case + // where Telegram keeps trying a dead proxy without ever failing. + if (state == ConnectionsManager.ConnectionStateConnectingToProxy + || state == ConnectionsManager.ConnectionStateConnecting + || state == ConnectionsManager.ConnectionStateWaitingForNetwork) { if (!isCurrentlyChecking) { AndroidUtilities.runOnUIThread(checkProxyAndSwitchRunnable, ROTATION_TIMEOUTS.get(SharedConfig.proxyRotationTimeout) * 1000L); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/XrayController.java b/TMessagesProj/src/main/java/org/telegram/messenger/XrayController.java index 66586ddb..485dfdb6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/XrayController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/XrayController.java @@ -118,6 +118,37 @@ public class XrayController { } } + /** All built-in proxies that match the current network, in declaration order. */ + public static java.util.List builtinProxiesForCurrentNetwork() { + java.util.List list = new java.util.ArrayList<>(); + for (XrayServers.Server s : XrayServers.SERVERS) { + SharedConfig.ProxyInfo p = fromServer(s); + if (p != null && matchesCurrentNetwork(p)) list.add(p); + } + return list; + } + + /** + * Switch the active built-in proxy: (re)start Xray with the given server, + * persist the selection and point Telegram at the local SOCKS5 listener. + * Runs the heavy work on a background thread. Safe to call from any thread. + */ + public static void switchToBuiltinProxy(SharedConfig.ProxyInfo proxy) { + if (proxy == null || !proxy.isVless()) return; + final VlessConfig cfg = proxy.toVlessConfig(); + new Thread(() -> { + boolean ok = start(cfg); + if (ok) { + SharedConfig.currentProxy = proxy; + persistBuiltinProxySelection(proxy); + try { Thread.sleep(300); } catch (InterruptedException ignored) {} + ConnectionsManager.setProxySettings(true, "127.0.0.1", cfg.localPort, "", "", ""); + } else { + Log.e(TAG, "switchToBuiltinProxy: Xray failed to start for " + proxy.builtinName); + } + }, "xray-switch").start(); + } + private static SharedConfig.ProxyInfo fromServer(XrayServers.Server s) { if (s == null || s.address == null || s.address.isEmpty()) return null; SharedConfig.ProxyInfo p = SharedConfig.ProxyInfo.createVless(