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
This commit is contained in:
instant992 2026-06-11 04:13:10 +04:00
parent cf44ff32b9
commit a807fa9db6
2 changed files with 76 additions and 1 deletions

View file

@ -31,6 +31,28 @@ public class ProxyRotationController implements NotificationCenter.NotificationC
} }
startedCheck = true; startedCheck = true;
proxyInfo.checking = 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.proxyCheckPingId = ConnectionsManager.getInstance(currentAccount).checkProxy(proxyInfo.address, proxyInfo.port, proxyInfo.username, proxyInfo.password, proxyInfo.secret, time -> AndroidUtilities.runOnUIThread(() -> {
proxyInfo.availableCheckTime = SystemClock.elapsedRealtime(); proxyInfo.availableCheckTime = SystemClock.elapsedRealtime();
proxyInfo.checking = false; proxyInfo.checking = false;
@ -69,6 +91,22 @@ public class ProxyRotationController implements NotificationCenter.NotificationC
if (info == SharedConfig.currentProxy || info.checking || !info.available) { if (info == SharedConfig.currentProxy || info.checking || !info.available) {
continue; 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(); SharedPreferences.Editor editor = MessagesController.getGlobalMainSettings().edit();
editor.putString("proxy_ip", info.address); editor.putString("proxy_ip", info.address);
@ -116,7 +154,13 @@ public class ProxyRotationController implements NotificationCenter.NotificationC
int state = ConnectionsManager.getInstance(account).getConnectionState(); 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) { if (!isCurrentlyChecking) {
AndroidUtilities.runOnUIThread(checkProxyAndSwitchRunnable, ROTATION_TIMEOUTS.get(SharedConfig.proxyRotationTimeout) * 1000L); AndroidUtilities.runOnUIThread(checkProxyAndSwitchRunnable, ROTATION_TIMEOUTS.get(SharedConfig.proxyRotationTimeout) * 1000L);
} }

View file

@ -118,6 +118,37 @@ public class XrayController {
} }
} }
/** All built-in proxies that match the current network, in declaration order. */
public static java.util.List<SharedConfig.ProxyInfo> builtinProxiesForCurrentNetwork() {
java.util.List<SharedConfig.ProxyInfo> 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) { private static SharedConfig.ProxyInfo fromServer(XrayServers.Server s) {
if (s == null || s.address == null || s.address.isEmpty()) return null; if (s == null || s.address == null || s.address.isEmpty()) return null;
SharedConfig.ProxyInfo p = SharedConfig.ProxyInfo.createVless( SharedConfig.ProxyInfo p = SharedConfig.ProxyInfo.createVless(