namespace ServiceLib.Handler; public static class ConnectionHandler { private static readonly string _tag = "ConnectionHandler"; /// /// Runs ping and IP checks. /// public static async Task RunAvailabilityCheck() { var time = await GetRealPingTimeInfo(); var ip = time > 0 ? await GetIPInfo() : Global.None; return new AvailabilityCheckResult(time, ip); } /// /// Gets IP information using the default local proxy. /// private static async Task GetIPInfo() { var webProxy = await GetWebProxy(); var ipInfo = await GetIPInfo(webProxy); return ipInfo?.ToString() ?? Global.None; } /// /// Measures real ping time using configured test URL. /// private static async Task GetRealPingTimeInfo() { var responseTime = -1; try { var webProxy = await GetWebProxy(); for (var i = 0; i < 2; i++) { responseTime = await GetRealPingTime(webProxy); if (responseTime > 0) { break; } await Task.Delay(500); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); return -1; } return responseTime; } /// /// Creates local SOCKS proxy instance. /// private static async Task GetWebProxy() { var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); return new WebProxy($"socks5://{Global.Loopback}:{port}"); } /// /// Measures response time by sending HTTP requests through proxy. /// public static async Task GetRealPingTime(IWebProxy? webProxy, CancellationToken cancellationToken = default) { var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl; var responseTime = -1; try { using var timeoutCts = new CancellationTokenSource(); timeoutCts.CancelAfter(Global.LocalFetch); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var linkedToken = linkedCts.Token; using var client = new HttpClient(new SocketsHttpHandler() { Proxy = webProxy, UseProxy = webProxy != null, ConnectTimeout = Global.LocalFetch, }); List oneTime = []; for (var i = 0; i < 2; i++) { var timer = Stopwatch.StartNew(); await client.GetAsync(url, linkedToken).ConfigureAwait(false); timer.Stop(); oneTime.Add((int)timer.Elapsed.TotalMilliseconds); await Task.Delay(100, linkedToken); } responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault(); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch { // Ignore } return responseTime; } /// /// Gets IP and country information through specified proxy. /// public static async Task GetIPInfo(IWebProxy? webProxy, CancellationToken cancellationToken = default) { try { var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; if (url.IsNullOrEmpty()) { return null; } var downloadHandle = new DownloadService(); var result = await downloadHandle.TryDownloadString(url, webProxy, "", cancellationToken); if (result == null) { return null; } var ipInfo = JsonUtils.Deserialize(result); if (ipInfo == null) { return null; } var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query; var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code ?? "unknown"; return new IpInfoResult(country, ip); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch { return null; } } }