From 0638be614dadc0ef6364766f52ca4bf98e2af44a Mon Sep 17 00:00:00 2001 From: freekof Date: Mon, 2 Mar 2026 23:41:44 +0800 Subject: [PATCH] Switch speedtest IP fallback to Cloudflare trace first --- .../ServiceLib/Handler/ConnectionHandler.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConnectionHandler.cs b/v2rayN/ServiceLib/Handler/ConnectionHandler.cs index 7cef4641..6a253403 100644 --- a/v2rayN/ServiceLib/Handler/ConnectionHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConnectionHandler.cs @@ -5,8 +5,8 @@ public static class ConnectionHandler private static readonly string _tag = "ConnectionHandler"; private static readonly string[] _speedtestIpApiUrls = [ - "https://api.ipapi.is", - "https://api.ip.sb/geoip" + "https://www.cloudflare.com/cdn-cgi/trace", + "https://api.ipapi.is" ]; public static async Task RunAvailabilityCheck() @@ -70,6 +70,11 @@ public static class ConnectionHandler } var result = await response.Content.ReadAsStringAsync(cts.Token).ConfigureAwait(false); + if (url.Contains("/cdn-cgi/trace", StringComparison.OrdinalIgnoreCase)) + { + return ParseCloudflareTrace(result); + } + return JsonUtils.Deserialize(result); } catch @@ -78,6 +83,47 @@ public static class ConnectionHandler } } + private static IPAPIInfo? ParseCloudflareTrace(string? traceContent) + { + if (traceContent.IsNullOrEmpty()) + { + return null; + } + + var tracePairs = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var line in traceContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var idx = line.IndexOf('='); + if (idx <= 0) + { + continue; + } + + var key = line[..idx]; + var value = line[(idx + 1)..]; + tracePairs[key] = value; + } + + tracePairs.TryGetValue("ip", out var ip); + if (ip.IsNullOrEmpty()) + { + return null; + } + + tracePairs.TryGetValue("loc", out var loc); + + return new IPAPIInfo + { + ip = ip, + clientIp = ip, + ip_addr = ip, + query = ip, + country = loc, + country_code = loc, + countryCode = loc + }; + } + private static string? FormatCountryAndIp(IPAPIInfo? ipInfo, bool compact) { if (ipInfo == null)