diff --git a/.github/workflows/TestHostName.yml b/.github/workflows/TestHostName.yml new file mode 100644 index 0000000000..e463f8ea1a --- /dev/null +++ b/.github/workflows/TestHostName.yml @@ -0,0 +1,50 @@ +name: TestHostName + +on: + workflow_dispatch: + push: + branches: + - main + - '[0-9]+.x' + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: true + +jobs: + testHostName: + name: Test hostname on ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: ubuntu-latest + - os: windows-latest + - os: macos-latest + runs-on: ${{ matrix.os }} + continue-on-error: true + + steps: + - name: resolve1 + if: always() + shell: pwsh + run: | + $hostName = [System.Net.Dns]::GetHostName() + [System.Net.Dns]::GetHostEntry($hostName) + - name: resolve2 + if: always() + shell: pwsh + run: | + $hostName = [System.Net.Dns]::GetHostName() + [System.Net.Dns]::GetHostEntry($hostName + ".local") diff --git a/src/Common/src/Common/Net/DomainNameResolver.cs b/src/Common/src/Common/Net/DomainNameResolver.cs index 0b1da12ddd..879e3548ec 100644 --- a/src/Common/src/Common/Net/DomainNameResolver.cs +++ b/src/Common/src/Common/Net/DomainNameResolver.cs @@ -42,28 +42,49 @@ private DomainNameResolver() public string? ResolveHostName(bool throwOnError = false) { + string? resultingHostName = null; + string? resultingHostEntryHostName = null; + bool? workaroundApplied = null; + try { string hostName = Dns.GetHostName(); + resultingHostName = hostName; if (string.IsNullOrEmpty(hostName)) { // Workaround for failure when running on macOS. // See https://github.com/actions/runner-images/issues/1335 and https://github.com/dotnet/runtime/issues/36849. + hostName = "localhost"; + workaroundApplied = true; } IPHostEntry hostEntry = Dns.GetHostEntry(hostName); - return hostEntry.HostName; + resultingHostEntryHostName = hostEntry.HostName; + + if (string.IsNullOrEmpty(resultingHostEntryHostName)) + { + throw new InvalidOperationException($"IPHostEntry.HostName is {GetTextFor(resultingHostEntryHostName)}."); + } + + return resultingHostEntryHostName; } - catch (Exception) + catch (Exception exception) { if (throwOnError) { - throw; + throw new InvalidOperationException( + $"Failed to resolve hostname. First={GetTextFor(resultingHostName)}, Second={GetTextFor(resultingHostEntryHostName)}, WorkaroundApplied={workaroundApplied}", + exception); } return null; } } + + private static string GetTextFor(string? value) + { + return value == null ? "null" : "empty"; + } } diff --git a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs index 6ad1371335..aa6f48820c 100644 --- a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs @@ -43,7 +43,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options) options.ServiceName = GetServiceName(options); HostInfo? hostInfo = options.UseNetworkInterfaces ? _inetUtils.FindFirstNonLoopbackHostInfo() : null; - options.HostName ??= hostInfo != null ? hostInfo.Hostname : _domainNameResolver.ResolveHostName(); + options.HostName ??= hostInfo != null ? hostInfo.Hostname : _domainNameResolver.ResolveHostName(true); if (string.IsNullOrWhiteSpace(options.IPAddress)) { diff --git a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs index c8861fd4d4..81bd573afc 100644 --- a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs +++ b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs @@ -35,7 +35,7 @@ internal sealed class ConsulRegistration : IServiceInstance public bool IsSecure => _optionsMonitor.CurrentValue.EffectiveScheme == "https"; /// - public Uri Uri => new($"{_optionsMonitor.CurrentValue.EffectiveScheme}://{Host}:{Port}"); + public Uri Uri => FormatUri(); /// public Uri? NonSecureUri => IsSecure ? null : Uri; @@ -73,6 +73,20 @@ internal ConsulRegistration(AgentServiceRegistration innerRegistration, IOptions Metadata = innerRegistration.Meta.AsReadOnly(); } + private Uri FormatUri() + { + string scheme = _optionsMonitor.CurrentValue.EffectiveScheme; + + try + { + return new Uri($"{scheme}://{Host}:{Port}"); + } + catch (UriFormatException exception) + { + throw new UriFormatException($"Failed to build URI from components. Scheme={scheme}, Host={Host},Port={Port}.", exception); + } + } + /// /// Creates a registration for the currently running app, to be submitted to the Consul server. ///