|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Versioning;
namespace System.Net
{
/// <summary>Provides simple domain name resolution functionality.</summary>
public static class Dns
{
/// <summary>Gets the host name of the local machine.</summary>
public static string GetHostName()
{
NameResolutionActivity activity = NameResolutionTelemetry.Log.BeforeResolution(string.Empty);
string name;
try
{
name = NameResolutionPal.GetHostName();
}
catch (Exception ex) when (LogFailure(string.Empty, activity, ex))
{
Debug.Fail("LogFailure should return false");
throw;
}
NameResolutionTelemetry.Log.AfterResolution(string.Empty, activity, answer: name);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, name);
return name;
}
public static IPHostEntry GetHostEntry(IPAddress address)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(address);
if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"Invalid address '{address}'");
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(address));
}
IPHostEntry ipHostEntry = GetHostEntryCore(address, AddressFamily.Unspecified);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(address, $"{ipHostEntry} with {ipHostEntry.AddressList.Length} entries");
return ipHostEntry;
}
public static IPHostEntry GetHostEntry(string hostNameOrAddress) =>
GetHostEntry(hostNameOrAddress, AddressFamily.Unspecified);
/// <summary>
/// Resolves a host name or IP address to an <see cref="IPHostEntry"/> instance.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="family">The address family for which IPs should be retrieved. If <see cref="AddressFamily.Unspecified"/>, retrieve all IPs regardless of address family.</param>
/// <returns>
/// An <see cref="IPHostEntry"/> instance that contains the address information about the host specified in <paramref name="hostNameOrAddress"/>.
/// </returns>
public static IPHostEntry GetHostEntry(string hostNameOrAddress, AddressFamily family)
{
ArgumentNullException.ThrowIfNull(hostNameOrAddress);
// See if it's an IP Address.
IPHostEntry ipHostEntry;
if (NameResolutionPal.SupportsGetNameInfo && IPAddress.TryParse(hostNameOrAddress, out IPAddress? address))
{
if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"Invalid address '{address}'");
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostNameOrAddress));
}
ipHostEntry = GetHostEntryCore(address, family);
}
else
{
ipHostEntry = GetHostEntryCore(hostNameOrAddress, family);
}
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(hostNameOrAddress, $"{ipHostEntry} with {ipHostEntry.AddressList.Length} entries");
return ipHostEntry;
}
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress) =>
GetHostEntryAsync(hostNameOrAddress, AddressFamily.Unspecified, CancellationToken.None);
/// <summary>
/// Resolves a host name or IP address to an <see cref="IPHostEntry"/> instance as an asynchronous operation.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="cancellationToken">A cancellation token that can be used to signal the asynchronous operation should be canceled.</param>
/// <returns>
/// The task object representing the asynchronous operation. The <see cref="Task{TResult}.Result"/> property on the task object returns
/// an <see cref="IPHostEntry"/> instance that contains the address information about the host specified in <paramref name="hostNameOrAddress"/>.
/// </returns>
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress, CancellationToken cancellationToken) =>
GetHostEntryAsync(hostNameOrAddress, AddressFamily.Unspecified, cancellationToken);
/// <summary>
/// Resolves a host name or IP address to an <see cref="IPHostEntry"/> instance as an asynchronous operation.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="family">The address family for which IPs should be retrieved. If <see cref="AddressFamily.Unspecified"/>, retrieve all IPs regardless of address family.</param>
/// <param name="cancellationToken">A cancellation token that can be used to signal the asynchronous operation should be canceled.</param>
/// <returns>
/// The task object representing the asynchronous operation. The <see cref="Task{TResult}.Result"/> property on the task object returns
/// an <see cref="IPHostEntry"/> instance that contains the address information about the host specified in <paramref name="hostNameOrAddress"/>.
/// </returns>
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress, AddressFamily family, CancellationToken cancellationToken = default)
{
if (NetEventSource.Log.IsEnabled())
{
Task<IPHostEntry> t = GetHostEntryCoreAsync(hostNameOrAddress, justReturnParsedIp: false, throwOnIIPAny: true, family, cancellationToken);
t.ContinueWith(static (t, s) =>
{
string hostNameOrAddress = (string)s!;
if (t.Status == TaskStatus.RanToCompletion)
{
NetEventSource.Info(hostNameOrAddress, $"{t.Result} with {t.Result.AddressList.Length} entries");
}
Exception? ex = t.Exception?.InnerException;
if (ex is SocketException soex)
{
NetEventSource.Error(hostNameOrAddress, $"{hostNameOrAddress} DNS lookup failed with {soex.ErrorCode}");
}
else if (ex is OperationCanceledException)
{
NetEventSource.Error(hostNameOrAddress, $"{hostNameOrAddress} DNS lookup was canceled");
}
}, hostNameOrAddress, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
return t;
}
else
{
return GetHostEntryCoreAsync(hostNameOrAddress, justReturnParsedIp: false, throwOnIIPAny: true, family, cancellationToken);
}
}
public static Task<IPHostEntry> GetHostEntryAsync(IPAddress address)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(address);
if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"Invalid address '{address}'");
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(address));
}
return RunAsync(static (s, activity) => {
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
IPHostEntry ipHostEntry = GetHostEntryCore((IPAddress)s, AddressFamily.Unspecified, activity);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info((IPAddress)s, $"{ipHostEntry} with {ipHostEntry.AddressList.Length} entries");
return ipHostEntry;
}, address, CancellationToken.None);
}
public static IAsyncResult BeginGetHostEntry(IPAddress address, AsyncCallback? requestCallback, object? stateObject) =>
TaskToAsyncResult.Begin(GetHostEntryAsync(address), requestCallback, stateObject);
public static IAsyncResult BeginGetHostEntry(string hostNameOrAddress, AsyncCallback? requestCallback, object? stateObject) =>
TaskToAsyncResult.Begin(GetHostEntryAsync(hostNameOrAddress), requestCallback, stateObject);
public static IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(asyncResult);
return TaskToAsyncResult.End<IPHostEntry>(asyncResult);
}
public static IPAddress[] GetHostAddresses(string hostNameOrAddress)
=> GetHostAddresses(hostNameOrAddress, AddressFamily.Unspecified);
/// <summary>
/// Returns the Internet Protocol (IP) addresses for the specified host.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="family">The address family for which IPs should be retrieved. If <see cref="AddressFamily.Unspecified"/>, retrieve all IPs regardless of address family.</param>
/// <returns>
/// An array of type <see cref="IPAddress"/> that holds the IP addresses for the host that is specified by the <paramref name="hostNameOrAddress"/> parameter.
/// </returns>
public static IPAddress[] GetHostAddresses(string hostNameOrAddress, AddressFamily family)
{
ArgumentNullException.ThrowIfNull(hostNameOrAddress);
// See if it's an IP Address.
IPAddress[] addresses;
if (IPAddress.TryParse(hostNameOrAddress, out IPAddress? address))
{
if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"Invalid address '{address}'");
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostNameOrAddress));
}
addresses = (family == AddressFamily.Unspecified || address.AddressFamily == family) ? new IPAddress[] { address } : Array.Empty<IPAddress>();
}
else
{
addresses = GetHostAddressesCore(hostNameOrAddress, family);
}
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(hostNameOrAddress, addresses);
return addresses;
}
public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress) =>
(Task<IPAddress[]>)GetHostEntryOrAddressesCoreAsync(hostNameOrAddress, justReturnParsedIp: true, throwOnIIPAny: true, justAddresses: true, AddressFamily.Unspecified, CancellationToken.None);
/// <summary>
/// Returns the Internet Protocol (IP) addresses for the specified host as an asynchronous operation.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="cancellationToken">A cancellation token that can be used to signal the asynchronous operation should be canceled.</param>
/// <returns>
/// The task object representing the asynchronous operation. The <see cref="Task{TResult}.Result"/> property on the task object returns an array of
/// type <see cref="IPAddress"/> that holds the IP addresses for the host that is specified by the <paramref name="hostNameOrAddress"/> parameter.
/// </returns>
public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress, CancellationToken cancellationToken) =>
(Task<IPAddress[]>)GetHostEntryOrAddressesCoreAsync(hostNameOrAddress, justReturnParsedIp: true, throwOnIIPAny: true, justAddresses: true, AddressFamily.Unspecified, cancellationToken);
/// <summary>
/// Returns the Internet Protocol (IP) addresses for the specified host as an asynchronous operation.
/// </summary>
/// <param name="hostNameOrAddress">The host name or IP address to resolve.</param>
/// <param name="family">The address family for which IPs should be retrieved. If <see cref="AddressFamily.Unspecified"/>, retrieve all IPs regardless of address family.</param>
/// <param name="cancellationToken">A cancellation token that can be used to signal the asynchronous operation should be canceled.</param>
/// <returns>
/// The task object representing the asynchronous operation. The <see cref="Task{TResult}.Result"/> property on the task object returns an array of
/// type <see cref="IPAddress"/> that holds the IP addresses for the host that is specified by the <paramref name="hostNameOrAddress"/> parameter.
/// </returns>
public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress, AddressFamily family, CancellationToken cancellationToken = default) =>
(Task<IPAddress[]>)GetHostEntryOrAddressesCoreAsync(hostNameOrAddress, justReturnParsedIp: true, throwOnIIPAny: true, justAddresses: true, family, cancellationToken);
public static IAsyncResult BeginGetHostAddresses(string hostNameOrAddress, AsyncCallback? requestCallback, object? state) =>
TaskToAsyncResult.Begin(GetHostAddressesAsync(hostNameOrAddress), requestCallback, state);
public static IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(asyncResult);
return TaskToAsyncResult.End<IPAddress[]>(asyncResult);
}
[Obsolete("GetHostByName has been deprecated. Use GetHostEntry instead.")]
public static IPHostEntry GetHostByName(string hostName)
{
ArgumentNullException.ThrowIfNull(hostName);
if (IPAddress.TryParse(hostName, out IPAddress? address))
{
return CreateHostEntryForAddress(address);
}
return GetHostEntryCore(hostName, AddressFamily.Unspecified);
}
[Obsolete("BeginGetHostByName has been deprecated. Use BeginGetHostEntry instead.")]
public static IAsyncResult BeginGetHostByName(string hostName, AsyncCallback? requestCallback, object? stateObject) =>
TaskToAsyncResult.Begin(GetHostEntryCoreAsync(hostName, justReturnParsedIp: true, throwOnIIPAny: true, AddressFamily.Unspecified, CancellationToken.None), requestCallback, stateObject);
[Obsolete("EndGetHostByName has been deprecated. Use EndGetHostEntry instead.")]
public static IPHostEntry EndGetHostByName(IAsyncResult asyncResult)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(asyncResult);
return TaskToAsyncResult.End<IPHostEntry>(asyncResult);
}
[Obsolete("GetHostByAddress has been deprecated. Use GetHostEntry instead.")]
public static IPHostEntry GetHostByAddress(string address)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(address);
IPHostEntry ipHostEntry = GetHostEntryCore(IPAddress.Parse(address), AddressFamily.Unspecified);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(address, ipHostEntry);
return ipHostEntry;
}
[Obsolete("GetHostByAddress has been deprecated. Use GetHostEntry instead.")]
public static IPHostEntry GetHostByAddress(IPAddress address)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
ArgumentNullException.ThrowIfNull(address);
IPHostEntry ipHostEntry = GetHostEntryCore(address, AddressFamily.Unspecified);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(address, ipHostEntry);
return ipHostEntry;
}
[Obsolete("Resolve has been deprecated. Use GetHostEntry instead.")]
public static IPHostEntry Resolve(string hostName)
{
ArgumentNullException.ThrowIfNull(hostName);
// See if it's an IP Address.
IPHostEntry ipHostEntry;
if (NameResolutionPal.SupportsGetNameInfo && IPAddress.TryParse(hostName, out IPAddress? address) &&
(address.AddressFamily != AddressFamily.InterNetworkV6 || SocketProtocolSupportPal.OSSupportsIPv6))
{
try
{
ipHostEntry = GetHostEntryCore(address, AddressFamily.Unspecified);
}
catch (SocketException ex)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(hostName, ex);
ipHostEntry = CreateHostEntryForAddress(address);
}
}
else
{
ipHostEntry = GetHostEntryCore(hostName, AddressFamily.Unspecified);
}
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(hostName, ipHostEntry);
return ipHostEntry;
}
[Obsolete("BeginResolve has been deprecated. Use BeginGetHostEntry instead.")]
public static IAsyncResult BeginResolve(string hostName, AsyncCallback? requestCallback, object? stateObject) =>
TaskToAsyncResult.Begin(GetHostEntryCoreAsync(hostName, justReturnParsedIp: false, throwOnIIPAny: false, AddressFamily.Unspecified, CancellationToken.None), requestCallback, stateObject);
[Obsolete("EndResolve has been deprecated. Use EndGetHostEntry instead.")]
public static IPHostEntry EndResolve(IAsyncResult asyncResult)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
IPHostEntry ipHostEntry;
try
{
ipHostEntry = TaskToAsyncResult.End<IPHostEntry>(asyncResult);
}
catch (SocketException ex)
{
object? asyncState = TaskToAsyncResult.Unwrap(asyncResult).AsyncState;
IPAddress? address = asyncState switch
{
IPAddress a => a,
KeyValuePair<IPAddress, AddressFamily> t => t.Key,
_ => null
};
if (address is null)
throw; // BeginResolve was called with a HostName, not an IPAddress
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex);
ipHostEntry = CreateHostEntryForAddress(address);
}
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, ipHostEntry);
return ipHostEntry;
}
private static IPHostEntry GetHostEntryCore(string hostName, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default) =>
(IPHostEntry)GetHostEntryOrAddressesCore(hostName, justAddresses: false, addressFamily, activityOrDefault);
private static IPAddress[] GetHostAddressesCore(string hostName, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default) =>
(IPAddress[])GetHostEntryOrAddressesCore(hostName, justAddresses: true, addressFamily, activityOrDefault);
private static object GetHostEntryOrAddressesCore(string hostName, bool justAddresses, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default)
{
ValidateHostName(hostName);
// NameResolutionActivity may have already been set if we're being called from RunAsync.
NameResolutionActivity activity = activityOrDefault ?? NameResolutionTelemetry.Log.BeforeResolution(hostName);
object result;
try
{
SocketError errorCode = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses, addressFamily, out string? newHostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode);
if (errorCode != SocketError.Success)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(hostName, $"{hostName} DNS lookup failed with {errorCode}");
throw CreateException(errorCode, nativeErrorCode);
}
result = justAddresses ? (object)
addresses :
new IPHostEntry
{
AddressList = addresses,
HostName = newHostName!,
Aliases = aliases
};
}
catch (Exception ex) when (LogFailure(hostName, activity, ex))
{
Debug.Fail("LogFailure should return false");
throw;
}
NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: result);
return result;
}
private static IPHostEntry GetHostEntryCore(IPAddress address, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default) =>
(IPHostEntry)GetHostEntryOrAddressesCore(address, justAddresses: false, addressFamily, activityOrDefault);
private static IPAddress[] GetHostAddressesCore(IPAddress address, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default) =>
(IPAddress[])GetHostEntryOrAddressesCore(address, justAddresses: true, addressFamily, activityOrDefault);
// Does internal IPAddress reverse and then forward lookups (for Legacy and current public methods).
private static object GetHostEntryOrAddressesCore(IPAddress address, bool justAddresses, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
// Try to get the data for the host from its address.
// We need to call getnameinfo first, because getaddrinfo w/ the ipaddress string
// will only return that address and not the full list.
// Do a reverse lookup to get the host name.
// NameResolutionActivity may have already been set if we're being called from RunAsync.
NameResolutionActivity activity = activityOrDefault ?? NameResolutionTelemetry.Log.BeforeResolution(address);
SocketError errorCode;
string? name;
try
{
name = NameResolutionPal.TryGetNameInfo(address, out errorCode, out int nativeErrorCode);
if (errorCode != SocketError.Success)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"{address} DNS lookup failed with {errorCode}");
throw CreateException(errorCode, nativeErrorCode);
}
Debug.Assert(name != null);
}
catch (Exception ex) when (LogFailure(address, activity, ex))
{
Debug.Fail("LogFailure should return false");
throw;
}
NameResolutionTelemetry.Log.AfterResolution(address, activity, answer: name);
// Do the forward lookup to get the IPs for that host name
activity = NameResolutionTelemetry.Log.BeforeResolution(name);
object result;
try
{
errorCode = NameResolutionPal.TryGetAddrInfo(name, justAddresses, addressFamily, out string? hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode);
if (errorCode != SocketError.Success)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(address, $"forward lookup for '{name}' failed with {errorCode}");
}
result = justAddresses ?
(object)addresses :
new IPHostEntry
{
HostName = hostName!,
Aliases = aliases,
AddressList = addresses
};
}
catch (Exception ex) when (LogFailure(name, activity, ex))
{
Debug.Fail("LogFailure should return false");
throw;
}
NameResolutionTelemetry.Log.AfterResolution(name, activity, answer: result);
// One of three things happened:
// 1. Success.
// 2. There was a ptr record in dns, but not a corollary A/AAA record.
// 3. The IP was a local (non-loopback) IP that resolved to a connection specific dns suffix.
// - Workaround, Check "Use this connection's dns suffix in dns registration" on that network
// adapter's advanced dns settings.
// Return whatever we got.
return result;
}
private static Task<IPHostEntry> GetHostEntryCoreAsync(string hostName, bool justReturnParsedIp, bool throwOnIIPAny, AddressFamily family, CancellationToken cancellationToken) =>
(Task<IPHostEntry>)GetHostEntryOrAddressesCoreAsync(hostName, justReturnParsedIp, throwOnIIPAny, justAddresses: false, family, cancellationToken);
// If hostName is an IPString and justReturnParsedIP==true then no reverse lookup will be attempted, but the original address is returned.
private static Task GetHostEntryOrAddressesCoreAsync(string hostName, bool justReturnParsedIp, bool throwOnIIPAny, bool justAddresses, AddressFamily family, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(hostName);
if (cancellationToken.IsCancellationRequested)
{
return justAddresses ? (Task)
Task.FromCanceled<IPAddress[]>(cancellationToken) :
Task.FromCanceled<IPHostEntry>(cancellationToken);
}
object asyncState;
// See if it's an IP Address.
if (NameResolutionPal.SupportsGetNameInfo && IPAddress.TryParse(hostName, out IPAddress? ipAddress))
{
if (throwOnIIPAny && (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.IPv6Any)))
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(hostName, $"Invalid address '{ipAddress}'");
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostName));
}
if (justReturnParsedIp)
{
return justAddresses ? (Task)
Task.FromResult(family == AddressFamily.Unspecified || ipAddress.AddressFamily == family ? new[] { ipAddress } : Array.Empty<IPAddress>()) :
Task.FromResult(CreateHostEntryForAddress(ipAddress));
}
asyncState = family == AddressFamily.Unspecified ? (object)ipAddress : new KeyValuePair<IPAddress, AddressFamily>(ipAddress, family);
}
else
{
if (NameResolutionPal.SupportsGetAddrInfoAsync)
{
#pragma warning disable CS0162 // Unreachable code detected -- SupportsGetAddrInfoAsync is a constant on *nix.
// If the OS supports it and 'hostName' is not an IP Address, resolve the name asynchronously
// instead of calling the synchronous version in the ThreadPool.
// If it fails, we will fall back to ThreadPool as well.
ValidateHostName(hostName);
Task? t;
if (NameResolutionTelemetry.AnyDiagnosticsEnabled())
{
t = justAddresses
? GetAddrInfoWithTelemetryAsync<IPAddress[]>(hostName, justAddresses, family, cancellationToken)
: GetAddrInfoWithTelemetryAsync<IPHostEntry>(hostName, justAddresses, family, cancellationToken);
}
else
{
t = NameResolutionPal.GetAddrInfoAsync(hostName, justAddresses, family, cancellationToken);
}
// If async resolution started, return task to user, otherwise fall back to sync API on threadpool.
if (t != null)
{
return t;
}
#pragma warning restore CS0162
}
asyncState = family == AddressFamily.Unspecified ? (object)hostName : new KeyValuePair<string, AddressFamily>(hostName, family);
}
if (justAddresses)
{
return RunAsync(static (s, activity) => s switch
{
string h => GetHostAddressesCore(h, AddressFamily.Unspecified, activity),
KeyValuePair<string, AddressFamily> t => GetHostAddressesCore(t.Key, t.Value, activity),
IPAddress a => GetHostAddressesCore(a, AddressFamily.Unspecified, activity),
KeyValuePair<IPAddress, AddressFamily> t => GetHostAddressesCore(t.Key, t.Value, activity),
_ => null
}, asyncState, cancellationToken);
}
else
{
return RunAsync(static (s, activity) => s switch
{
string h => GetHostEntryCore(h, AddressFamily.Unspecified, activity),
KeyValuePair<string, AddressFamily> t => GetHostEntryCore(t.Key, t.Value, activity),
IPAddress a => GetHostEntryCore(a, AddressFamily.Unspecified, activity),
KeyValuePair<IPAddress, AddressFamily> t => GetHostEntryCore(t.Key, t.Value, activity),
_ => null
}, asyncState, cancellationToken);
}
}
private static Task<T>? GetAddrInfoWithTelemetryAsync<T>(string hostName, bool justAddresses, AddressFamily addressFamily, CancellationToken cancellationToken)
where T : class
{
long startingTimestamp = Stopwatch.GetTimestamp();
Task? task = NameResolutionPal.GetAddrInfoAsync(hostName, justAddresses, addressFamily, cancellationToken);
if (task != null)
{
return CompleteAsync(task, hostName, startingTimestamp);
}
// If resolution even did not start don't bother with telemetry.
// We will retry on thread-pool.
return null;
static async Task<T> CompleteAsync(Task task, string hostName, long startingTimeStamp)
{
NameResolutionActivity activity = NameResolutionTelemetry.Log.BeforeResolution(hostName, startingTimeStamp);
Exception? exception = null;
T? result = null;
try
{
result = await ((Task<T>)task).ConfigureAwait(false);
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: result, exception: exception);
}
}
}
private static IPHostEntry CreateHostEntryForAddress(IPAddress address) =>
new IPHostEntry
{
HostName = address.ToString(),
Aliases = Array.Empty<string>(),
AddressList = new IPAddress[] { address }
};
private static void ValidateHostName(string hostName)
{
const int MaxHostName = 255;
if (hostName.Length > MaxHostName ||
(hostName.Length == MaxHostName && hostName[MaxHostName - 1] != '.')) // If 255 chars, the last one must be a dot.
{
throw new ArgumentOutOfRangeException(nameof(hostName),
SR.Format(SR.net_toolong, nameof(hostName), MaxHostName.ToString(NumberFormatInfo.CurrentInfo)));
}
}
private static bool LogFailure(object hostNameOrAddress, in NameResolutionActivity activity, Exception exception)
{
NameResolutionTelemetry.Log.AfterResolution(hostNameOrAddress, activity, answer: null, exception: exception);
return false;
}
/// <summary>Mapping from key to current task in flight for that key.</summary>
private static readonly Dictionary<object, Task> s_tasks = new Dictionary<object, Task>();
/// <summary>Queue the function to be invoked asynchronously.</summary>
/// <remarks>
/// Since this is doing synchronous work on a thread pool thread, we want to limit how many threads end up being
/// blocked. We could employ a semaphore to limit overall usage, but a common case is that DNS requests are made
/// for only a handful of endpoints, and a reasonable compromise is to ensure that requests for a given host are
/// serialized. Once the data for that host is cached locally by the OS, the subsequent requests should all complete
/// very quickly, and if the head-of-line request is taking a long time due to the connection to the server, we won't
/// block lots of threads all getting data for that one host. We also still want to issue the request to the OS, rather
/// than having all concurrent requests for the same host share the exact same task, so that any shuffling of the results
/// by the OS to enable round robin is still perceived.
/// </remarks>
private static Task<TResult> RunAsync<TResult>(Func<object, NameResolutionActivity, TResult> func, object key, CancellationToken cancellationToken)
{
bool tracingEnabled = NameResolutionActivity.IsTracingEnabled();
Activity? activityToRestore = tracingEnabled ? Activity.Current : null;
NameResolutionActivity activity = NameResolutionTelemetry.Log.BeforeResolution(key);
if (tracingEnabled)
{
// Do not overwrite Activity.Current in the caller's ExecutionContext.
Activity.Current = activityToRestore;
}
Task<TResult>? task = null;
lock (s_tasks)
{
// Get the previous task for this key, if there is one.
s_tasks.TryGetValue(key, out Task? prevTask);
prevTask ??= Task.CompletedTask;
// Invoke the function in a queued work item when the previous task completes. Note that some callers expect the
// returned task to have the key as the task's AsyncState.
task = prevTask.ContinueWith(delegate
{
Debug.Assert(!Monitor.IsEntered(s_tasks));
try
{
return func(key, activity);
}
finally
{
// When the work is done, remove this key/task pair from the dictionary if this is still the current task.
// Because the work item is created and stored into both the local and the dictionary while the lock is
// held, and since we take the same lock here, inside this lock it's guaranteed to see the changes
// made by the call site.
lock (s_tasks)
{
((ICollection<KeyValuePair<object, Task>>)s_tasks).Remove(new KeyValuePair<object, Task>(key!, task!));
}
}
}, key, cancellationToken, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
// If it's possible the task may end up getting canceled, it won't have a chance to remove itself from
// the dictionary if it is canceled, so use a separate continuation to do so.
if (cancellationToken.CanBeCanceled)
{
task.ContinueWith((task, key) =>
{
lock (s_tasks)
{
((ICollection<KeyValuePair<object, Task>>)s_tasks).Remove(new KeyValuePair<object, Task>(key!, task));
}
// Since it was canceled, func(..) had not executed and call AfterResolution it needs to be called here.
NameResolutionTelemetry.Log.AfterResolution(key!, activity, new OperationCanceledException());
}, key, CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
// Finally, store the task into the dictionary as the current task for this key.
s_tasks[key] = task;
}
return task;
}
private static SocketException CreateException(SocketError error, int nativeError) =>
new SocketException((int)error) { HResult = nativeError };
}
}
|