// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace EpicGames.Horde.Compute; /// /// Exception for external IP resolver /// public class ExternalIpResolverException : Exception { /// public ExternalIpResolverException(string? message) : base(message) { } /// public ExternalIpResolverException(string? message, Exception? innerException) : base(message, innerException) { } } /// /// Find public IP address of local machine by querying a third-party IP lookup service /// public class ExternalIpResolver { /// /// Cache of last resolved IP address. Once resolved, the address is cached for the lifetime of this class. /// private IPAddress? _resolvedIp; private readonly HttpClient _httpClient; private readonly List _ipLookupUrls = new() { "http://whatismyip.akamai.com", "http://checkip.amazonaws.com", "http://ifconfig.me/ip" }; /// /// Constructor /// /// public ExternalIpResolver(HttpClient httpClient) { _httpClient = httpClient; } /// /// Get the external, public-facing IP of local machine /// /// Cancellation token /// External IP address /// If unable to resolve public async Task GetExternalIpAddressAsync(CancellationToken cancellationToken = default) { if (_resolvedIp != null) { return _resolvedIp; } ExternalIpResolverException? lastException = null; foreach (string lookupUrl in _ipLookupUrls) { Uri url = new(lookupUrl); try { _resolvedIp = await GetExternalIpAddressAsync(url, cancellationToken); return _resolvedIp; } catch (ExternalIpResolverException e) { lastException = e; } } throw new ExternalIpResolverException("Exhausted list of all IP resolvers", lastException); } private async Task GetExternalIpAddressAsync(Uri url, CancellationToken cancellationToken = default) { HttpResponseMessage res = await _httpClient.GetAsync(url, cancellationToken); if (!res.IsSuccessStatusCode) { throw new ExternalIpResolverException($"Non-successful response code: {res.StatusCode}"); } string content = await res.Content.ReadAsStringAsync(cancellationToken); if (!IPAddress.TryParse(content, out IPAddress? ipAddress)) { throw new ExternalIpResolverException($"Failed parsing HTTP body as an IP address: {content}"); } return ipAddress; } }