From: Stephen Toub Date: Tue, 17 Sep 2019 12:20:14 +0000 (-0400) Subject: Remove allocations from Dns.* (dotnet/corefx#41061) X-Git-Tag: submit/tizen/20210909.063632~11031^2~453 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0b5abf840284890db2f0d1f19c95b95e43e175cf;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Remove allocations from Dns.* (dotnet/corefx#41061) This started as an effort to reduce the size of System.Net.NameResolution.dll when publishing a trimmed app. It's not that big to begin with, but it's carrying around a copy of all of the IAsyncResult helper types, because the Get*Async methods are currently wrappers for the Begin/End* methods. This PR inverts that, wrapping the Begin/End* methods instead around the Get*Async methods, using the same TaskToApm helper we use in other places in corefx for the same purpose. This makes the Get*Async methods faster and lighterweight, but it does increase the number/amount of allocation in the Begin/End* APIs. Since these are considered legacy, I normally would consider that a good trade, however we still use these Begin/End methods in a few places in System.Net.Sockets, and I didn't want to regress those use cases. So, this also then trims some additional fat, which helps the Get*Async cases even further, and gets the Begin/End* to be even better than before the change. This includes not allocating an IPHostEntry when we're just going to unwrap it and return its addresses, computing the exact IPAddress[] size we need rather than using a List<> to grow it and ToArray to create the actual array, avoiding creating the HostName if we don't need it, and avoiding an unnecessary SafeHandle allocation. As part of this, I also noticed that we had some bugs in how some of our interop structures on Windows were defined. In particular, fields that in the native types were size_t were defined as int rather than IntPtr in the managed code. It appears we were saved by padding, but I fixed it regardless. And as long as I was changing pretty much everything else, where I was touching code I also cleaned up some legacy style stuff. Commit migrated from https://github.com/dotnet/corefx/commit/a55e95cbd9fca7ce5b3ad34c583642a35f42cd63 --- diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs index e3b330b..e2d8474 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs @@ -5,8 +5,6 @@ using System; using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - internal partial class Interop { internal partial class Kernel32 diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfo.cs b/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfo.cs deleted file mode 100644 index 93f500c..0000000 --- a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Net.Sockets; -using System.Runtime.InteropServices; -#if !SYSTEM_NET_SOCKETS_DLL -using SocketType = System.Net.Internals.SocketType; -using ProtocolFamily = System.Net.Internals.ProtocolFamily; -#endif - -namespace System.Net.Sockets -{ - // data structures and types needed for getaddrinfo calls. - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - internal unsafe struct AddressInfo - { - internal AddressInfoHints ai_flags; - internal AddressFamily ai_family; - internal SocketType ai_socktype; - internal ProtocolFamily ai_protocol; - internal int ai_addrlen; - internal sbyte* ai_canonname; // Ptr to the canonical name - check for NULL - internal byte* ai_addr; // Ptr to the sockaddr structure - internal AddressInfo* ai_next; // Ptr to the next AddressInfo structure - } -} diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoEx.cs b/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoEx.cs deleted file mode 100644 index 0972101..0000000 --- a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoEx.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Net.Internals; -using System.Runtime.InteropServices; - -namespace System.Net.Sockets -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct AddressInfoEx - { - internal AddressInfoHints ai_flags; - internal AddressFamily ai_family; - internal SocketType ai_socktype; - internal ProtocolFamily ai_protocol; - internal int ai_addrlen; - internal IntPtr ai_canonname; // Ptr to the canonical name - check for NULL - internal byte* ai_addr; // Ptr to the sockaddr structure - internal IntPtr ai_blob; // Unused ptr to blob data about provider - internal int ai_bloblen; - internal IntPtr ai_provider; // Unused ptr to the namespace provider guid - internal AddressInfoEx* ai_next; // Next structure in linked list - } -} diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoHints.cs b/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoHints.cs index 62ea626..232d4ec 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoHints.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/AddressInfoHints.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Runtime.InteropServices; - namespace System.Net.Sockets { [Flags] diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoExW.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoExW.cs index d64cc8c..c0a48996 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoExW.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoExW.cs @@ -13,6 +13,8 @@ internal static partial class Interop { internal const string GetAddrInfoExCancelFunctionName = "GetAddrInfoExCancel"; + internal const int NS_ALL = 0; + internal unsafe delegate void LPLOOKUPSERVICE_COMPLETION_ROUTINE([In] int dwError, [In] int dwBytes, [In] NativeOverlapped* lpOverlapped); [DllImport(Libraries.Ws2_32, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] @@ -21,15 +23,30 @@ internal static partial class Interop [In] string pServiceName, [In] int dwNamespace, [In] IntPtr lpNspId, - [In] ref AddressInfoEx pHints, - [Out] out AddressInfoEx* ppResult, + [In] AddressInfoEx* pHints, + [Out] AddressInfoEx** ppResult, [In] IntPtr timeout, - [In] ref NativeOverlapped lpOverlapped, + [In] NativeOverlapped* lpOverlapped, [In] LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, - [Out] out IntPtr lpNameHandle - ); + [Out] IntPtr* lpNameHandle); [DllImport(Libraries.Ws2_32, ExactSpelling = true)] - internal static extern unsafe void FreeAddrInfoExW([In] AddressInfoEx* pAddrInfo); + internal static extern unsafe void FreeAddrInfoExW(AddressInfoEx* pAddrInfo); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct AddressInfoEx + { + internal AddressInfoHints ai_flags; + internal AddressFamily ai_family; + internal int ai_socktype; + internal int ai_protocol; + internal IntPtr ai_addrlen; + internal IntPtr ai_canonname; // Ptr to the canonical name - check for NULL + internal byte* ai_addr; // Ptr to the sockaddr structure + internal IntPtr ai_blob; // Unused ptr to blob data about provider + internal IntPtr ai_bloblen; + internal IntPtr ai_provider; // Unused ptr to the namespace provider guid + internal AddressInfoEx* ai_next; // Next structure in linked list + } } } diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoW.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoW.cs index 91ff6b5..54fb797 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoW.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.GetAddrInfoW.cs @@ -5,18 +5,32 @@ using System; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; internal static partial class Interop { internal static partial class Winsock { [DllImport(Interop.Libraries.Ws2_32, ExactSpelling = true, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] - internal static extern int GetAddrInfoW( - [In] string nodename, - [In] string servicename, - [In] ref AddressInfo hints, - [Out] out SafeFreeAddrInfo handle - ); + internal static extern unsafe int GetAddrInfoW( + [In] string pNameName, + [In] string pServiceName, + [In] AddressInfo* pHints, + [Out] AddressInfo** ppResult); + + [DllImport(Interop.Libraries.Ws2_32, ExactSpelling = true, SetLastError = true)] + internal static extern unsafe void FreeAddrInfoW(AddressInfo* info); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal unsafe struct AddressInfo + { + internal AddressInfoHints ai_flags; + internal AddressFamily ai_family; + internal int ai_socktype; + internal int ai_protocol; + internal IntPtr ai_addrlen; + internal sbyte* ai_canonname; // Ptr to the canonical name - check for NULL + internal byte* ai_addr; // Ptr to the sockaddr structure + internal AddressInfo* ai_next; // Ptr to the next AddressInfo structure + } } } diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.SocketConstructorFlags.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.SocketConstructorFlags.cs index f3b61aa..4a2e892 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.SocketConstructorFlags.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.SocketConstructorFlags.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Net.Sockets; -using System.Runtime.InteropServices; internal static partial class Interop { diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.freeaddrinfo.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.freeaddrinfo.cs deleted file mode 100644 index adeb5c2..0000000 --- a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.freeaddrinfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; - -internal static partial class Interop -{ - internal static partial class Winsock - { - [DllImport(Interop.Libraries.Ws2_32, ExactSpelling = true, SetLastError = true)] - internal static extern void freeaddrinfo([In] IntPtr info); - } -} diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/SafeFreeAddrInfo.cs b/src/libraries/Common/src/Interop/Windows/WinSock/SafeFreeAddrInfo.cs deleted file mode 100644 index 1d26467..0000000 --- a/src/libraries/Common/src/Interop/Windows/WinSock/SafeFreeAddrInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Threading; -using Microsoft.Win32.SafeHandles; -using System.Diagnostics; - -namespace System.Net.Sockets -{ -#if DEBUG - internal sealed class SafeFreeAddrInfo : DebugSafeHandle - { -#else - internal sealed class SafeFreeAddrInfo : SafeHandleZeroOrMinusOneIsInvalid { -#endif - private SafeFreeAddrInfo() : base(true) { } - - internal static int GetAddrInfo(string nodename, string servicename, ref AddressInfo hints, out SafeFreeAddrInfo outAddrInfo) - { - return Interop.Winsock.GetAddrInfoW(nodename, servicename, ref hints, out outAddrInfo); - } - - protected override bool ReleaseHandle() - { - Interop.Winsock.freeaddrinfo(handle); - return true; - } - } -} diff --git a/src/libraries/Common/src/System/Net/DebugSafeHandle.cs b/src/libraries/Common/src/System/Net/DebugSafeHandle.cs index e2e7c46..ad41a64 100644 --- a/src/libraries/Common/src/System/Net/DebugSafeHandle.cs +++ b/src/libraries/Common/src/System/Net/DebugSafeHandle.cs @@ -4,12 +4,6 @@ using Microsoft.Win32.SafeHandles; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - namespace System.Net { #if DEBUG @@ -36,8 +30,7 @@ namespace System.Net { _trace = "WARNING! GC-ed >>" + this.GetType().ToString() + "<< (should be explicitly closed) \r\n"; #if TRACE_VERBOSE - string stacktrace = Environment.StackTrace; - _trace += stacktrace; + _trace += Environment.StackTrace; #endif } diff --git a/src/libraries/Common/tests/System/Net/Configuration.Http.cs b/src/libraries/Common/tests/System/Net/Configuration.Http.cs index a648108..aa37c69 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Http.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Http.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj index 8c54b79..3ee88c6 100644 --- a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj +++ b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj @@ -5,9 +5,8 @@ netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release - + - @@ -19,13 +18,10 @@ Common\System\Net\InternalException.cs - - - Common\System\Net\ContextAwareResult.cs - - - Common\System\Net\LazyAsyncResult.cs + + Common\CoreLib\System\Threading\Tasks\TaskToApm.cs + Common\System\Net\Sockets\ProtocolType.cs @@ -41,15 +37,11 @@ Common\System\Net\ByteOrder.cs - - - Common\System\Net\ContextAwareResult.Windows.cs - Common\System\Net\DebugSafeHandle.cs @@ -71,9 +63,6 @@ Interop\Windows\Interop.Libraries.cs - - Interop\Windows\WinSock\AddressInfo.cs - Interop\Windows\WinSock\AddressInfoHints.cs @@ -92,9 +81,6 @@ Interop\Windows\WinSock\Interop.GetAddrInfoW.cs - - Interop\Windows\Winsock\Interop.freeaddinfo.cs - Interop\Windows\WinSock\Interop.WSAStartup.cs @@ -107,12 +93,6 @@ Common\System\Net\Sockets\ProtocolFamily.cs - - Interop\Windows\WinSock\SafeFreeAddrInfo.cs - - - Interop\Windows\WinSock\AddressInfoEx.cs - Interop\Windows\WinSock\Interop.GetAddrInfoExW.cs @@ -131,9 +111,6 @@ - - Common\System\Net\ContextAwareResult.Unix.cs - Common\System\Net\InteropIPAddressExtensions.Unix.cs @@ -198,5 +175,6 @@ + diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/DNS.cs b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs similarity index 51% rename from src/libraries/System.Net.NameResolution/src/System/Net/DNS.cs rename to src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs index 4f0403d..b6a4909 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/DNS.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs @@ -5,360 +5,194 @@ using System.Globalization; using System.Net.Internals; using System.Net.Sockets; -using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; namespace System.Net { - /// - /// Provides simple - /// domain name resolution functionality. - /// - + /// Provides simple domain name resolution functionality. public static class Dns { - // Host names any longer than this automatically fail at the winsock level. - // If the host name is 255 chars, the last char must be a dot. - private const int MaxHostName = 255; - - [Obsolete("GetHostByName is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IPHostEntry GetHostByName(string hostName) + /// Gets the host name of the local machine. + public static string GetHostName() { + if (NetEventSource.IsEnabled) NetEventSource.Info(null, null); NameResolutionPal.EnsureSocketsAreInitialized(); - if (hostName == null) - { - throw new ArgumentNullException(nameof(hostName)); - } - - // See if it's an IP Address. - IPAddress address; - if (IPAddress.TryParse(hostName, out address)) - { - return NameResolutionUtilities.GetUnresolvedAnswer(address); - } - return InternalGetHostByName(hostName); + return NameResolutionPal.GetHostName(); } - private static void ValidateHostName(string hostName) + public static IPHostEntry GetHostEntry(IPAddress address) { - if (hostName.Length > MaxHostName // If 255 chars, the last one must be a dot. - || hostName.Length == MaxHostName && hostName[MaxHostName - 1] != '.') + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); + NameResolutionPal.EnsureSocketsAreInitialized(); + + if (address is null) { - throw new ArgumentOutOfRangeException(nameof(hostName), SR.Format(SR.net_toolong, - nameof(hostName), MaxHostName.ToString(NumberFormatInfo.CurrentInfo))); + throw new ArgumentNullException(nameof(address)); } - } - - private static IPHostEntry InternalGetHostByName(string hostName) - { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); - IPHostEntry ipHostEntry = null; - ValidateHostName(hostName); - - int nativeErrorCode; - SocketError errorCode = NameResolutionPal.TryGetAddrInfo(hostName, out ipHostEntry, out nativeErrorCode); - if (errorCode != SocketError.Success) + if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) { - throw SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode); + throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(address))); } + IPHostEntry ipHostEntry = GetHostEntryCore(address); + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; - } // GetHostByName + } - [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IPHostEntry GetHostByAddress(string address) + public static IPHostEntry GetHostEntry(string hostNameOrAddress) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); NameResolutionPal.EnsureSocketsAreInitialized(); - if (address == null) + if (hostNameOrAddress is null) { - throw new ArgumentNullException(nameof(address)); + throw new ArgumentNullException(nameof(hostNameOrAddress)); } - IPHostEntry ipHostEntry = InternalGetHostByAddress(IPAddress.Parse(address)); - - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); - return ipHostEntry; - } // GetHostByAddress - - [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IPHostEntry GetHostByAddress(IPAddress address) - { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); - NameResolutionPal.EnsureSocketsAreInitialized(); + // See if it's an IP Address. + IPHostEntry ipHostEntry; + if (IPAddress.TryParse(hostNameOrAddress, out IPAddress address)) + { + if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) + { + throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(hostNameOrAddress))); + } - if (address == null) + ipHostEntry = GetHostEntryCore(address); + } + else { - throw new ArgumentNullException(nameof(address)); + ipHostEntry = GetHostEntryCore(hostNameOrAddress); } - IPHostEntry ipHostEntry = InternalGetHostByAddress(address); - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; - } // GetHostByAddress + } - // Does internal IPAddress reverse and then forward lookups (for Legacy and current public methods). - private static IPHostEntry InternalGetHostByAddress(IPAddress address) + public static Task GetHostEntryAsync(string hostNameOrAddress) => + GetHostEntryCoreAsync(hostNameOrAddress, justReturnParsedIp: false, throwOnIIPAny: true); + + public static Task GetHostEntryAsync(IPAddress address) { if (NetEventSource.IsEnabled) NetEventSource.Info(null, address); + NameResolutionPal.EnsureSocketsAreInitialized(); - // - // Try to get the data for the host from it's 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. - SocketError errorCode; - int nativeErrorCode; - string name = NameResolutionPal.TryGetNameInfo(address, out errorCode, out nativeErrorCode); - if (errorCode == SocketError.Success) + if (address is null) { - // Do the forward lookup to get the IPs for that host name - IPHostEntry hostEntry; - errorCode = NameResolutionPal.TryGetAddrInfo(name, out hostEntry, out nativeErrorCode); - if (errorCode == SocketError.Success) - { - return hostEntry; - } - - if (NetEventSource.IsEnabled) NetEventSource.Error(null, SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode)); - - // One of two things happened: - // 1. There was a ptr record in dns, but not a corollary A/AAA record. - // 2. 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. - - // Just return the resolved host name and no IPs. - return hostEntry; + throw new ArgumentNullException(nameof(address)); } - throw SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode); - - } // InternalGetHostByAddress - - /***************************************************************************** - Function : gethostname - - Abstract: Queries the hostname from DNS - - Input Parameters: + if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) + { + throw new ArgumentException(SR.net_invalid_ip_addr, nameof(address)); + } - Returns: String - ******************************************************************************/ + return RunAsync(s => GetHostEntryCore((IPAddress)s), address); + } - /// - /// Gets the host name of the local machine. - /// - public static string GetHostName() + public static IAsyncResult BeginGetHostEntry(IPAddress address, AsyncCallback requestCallback, object stateObject) { - if (NetEventSource.IsEnabled) NetEventSource.Info(null, null); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); - NameResolutionPal.EnsureSocketsAreInitialized(); + IAsyncResult asyncResult = TaskToApm.Begin(GetHostEntryAsync(address), requestCallback, stateObject); - return NameResolutionPal.GetHostName(); + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); + return asyncResult; } - [Obsolete("Resolve is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IPHostEntry Resolve(string hostName) + public static IAsyncResult BeginGetHostEntry(string hostNameOrAddress, AsyncCallback requestCallback, object stateObject) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); - NameResolutionPal.EnsureSocketsAreInitialized(); + IAsyncResult asyncResult = TaskToApm.Begin(GetHostEntryAsync(hostNameOrAddress), requestCallback, stateObject); - if (hostName == null) - { - throw new ArgumentNullException(nameof(hostName)); - } + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); + return asyncResult; + } - // See if it's an IP Address. - IPAddress address; - IPHostEntry ipHostEntry; + public static IPHostEntry EndGetHostEntry(IAsyncResult asyncResult) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - if (IPAddress.TryParse(hostName, out address) && (address.AddressFamily != AddressFamily.InterNetworkV6 || SocketProtocolSupportPal.OSSupportsIPv6)) - { - try - { - ipHostEntry = InternalGetHostByAddress(address); - } - catch (SocketException ex) - { - if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); - ipHostEntry = NameResolutionUtilities.GetUnresolvedAnswer(address); - } - } - else - { - ipHostEntry = InternalGetHostByName(hostName); - } + IPHostEntry ipHostEntry = TaskToApm.End(asyncResult ?? throw new ArgumentNullException(nameof(asyncResult))); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; } - private static void ResolveCallback(object context) + public static IPAddress[] GetHostAddresses(string hostNameOrAddress) { - DnsResolveAsyncResult result = (DnsResolveAsyncResult)context; - IPHostEntry hostEntry; - try - { - if (result.IpAddress != null) - { - hostEntry = InternalGetHostByAddress(result.IpAddress); - } - else - { - hostEntry = InternalGetHostByName(result.HostName); - } - } - catch (OutOfMemoryException) - { - throw; - } - catch (Exception exception) - { - result.InvokeCallback(exception); - return; - } - - result.InvokeCallback(hostEntry); - } + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); + NameResolutionPal.EnsureSocketsAreInitialized(); - // Helpers for async GetHostByName, ResolveToAddresses, and Resolve - they're almost identical - // If hostName is an IPString and justReturnParsedIP==true then no reverse lookup will be attempted, but the original address is returned. - private static IAsyncResult HostResolutionBeginHelper(string hostName, bool justReturnParsedIp, bool throwOnIIPAny, AsyncCallback requestCallback, object state) - { - if (hostName == null) + if (hostNameOrAddress is null) { - throw new ArgumentNullException(nameof(hostName)); + throw new ArgumentNullException(nameof(hostNameOrAddress)); } - if (NetEventSource.IsEnabled) NetEventSource.Info(null, hostName); - // See if it's an IP Address. - IPAddress ipAddress; - DnsResolveAsyncResult asyncResult; - if (IPAddress.TryParse(hostName, out ipAddress)) + IPAddress[] addresses; + if (IPAddress.TryParse(hostNameOrAddress, out IPAddress address)) { - if (throwOnIIPAny && (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.IPv6Any))) - { - throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostName)); - } - - asyncResult = new DnsResolveAsyncResult(ipAddress, null, state, requestCallback); - - if (justReturnParsedIp) + if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) { - IPHostEntry hostEntry = NameResolutionUtilities.GetUnresolvedAnswer(ipAddress); - asyncResult.StartPostingAsyncOp(false); - asyncResult.InvokeCallback(hostEntry); - asyncResult.FinishPostingAsyncOp(); - return asyncResult; + throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(hostNameOrAddress))); } - } - else - { - asyncResult = new DnsResolveAsyncResult(hostName, null, state, requestCallback); - } - // Set up the context, possibly flow. - asyncResult.StartPostingAsyncOp(false); - - // 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 (NameResolutionPal.SupportsGetAddrInfoAsync && ipAddress == null) - { - ValidateHostName(hostName); - NameResolutionPal.GetAddrInfoAsync(asyncResult); + addresses = new IPAddress[] { address }; } else { - // Start the resolve. - Task.Factory.StartNew( - s => ResolveCallback(s), - asyncResult, - CancellationToken.None, - TaskCreationOptions.DenyChildAttach, - TaskScheduler.Default); + addresses = GetHostAddressesCore(hostNameOrAddress); } - // Finish the flowing, maybe it completed? This does nothing if we didn't initiate the flowing above. - asyncResult.FinishPostingAsyncOp(); - return asyncResult; + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, addresses); + return addresses; } - private static IAsyncResult HostResolutionBeginHelper(IPAddress address, bool flowContext, AsyncCallback requestCallback, object state) + public static Task GetHostAddressesAsync(string hostNameOrAddress) => + (Task)GetHostEntryOrAddressesCoreAsync(hostNameOrAddress, justReturnParsedIp: true, throwOnIIPAny: true, justAddresses: true); + + public static IAsyncResult BeginGetHostAddresses(string hostNameOrAddress, AsyncCallback requestCallback, object state) { - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); - if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) - { - throw new ArgumentException(SR.net_invalid_ip_addr, nameof(address)); - } + IAsyncResult asyncResult = TaskToApm.Begin(GetHostAddressesAsync(hostNameOrAddress), requestCallback, state); - if (NetEventSource.IsEnabled) NetEventSource.Info(null, address); + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); + return asyncResult; + } - // Set up the context, possibly flow. - DnsResolveAsyncResult asyncResult = new DnsResolveAsyncResult(address, null, state, requestCallback); - if (flowContext) - { - asyncResult.StartPostingAsyncOp(false); - } + public static IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult) + { + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - // Start the resolve. - Task.Factory.StartNew( - s => ResolveCallback(s), - asyncResult, - CancellationToken.None, - TaskCreationOptions.DenyChildAttach, - TaskScheduler.Default); + IPAddress[] addresses = TaskToApm.End(asyncResult ?? throw new ArgumentNullException(nameof(asyncResult))); - // Finish the flowing, maybe it completed? This does nothing if we didn't initiate the flowing above. - asyncResult.FinishPostingAsyncOp(); - return asyncResult; + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, addresses); + return addresses; } - private static IPHostEntry HostResolutionEndHelper(IAsyncResult asyncResult) + [Obsolete("GetHostByName is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IPHostEntry GetHostByName(string hostName) { - // - // parameter validation - // - if (asyncResult == null) - { - throw new ArgumentNullException(nameof(asyncResult)); - } - DnsResolveAsyncResult castedResult = asyncResult as DnsResolveAsyncResult; - if (castedResult == null) - { - throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult)); - } - if (castedResult.EndCalled) + NameResolutionPal.EnsureSocketsAreInitialized(); + + if (hostName is null) { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndResolve))); + throw new ArgumentNullException(nameof(hostName)); } - if (NetEventSource.IsEnabled) NetEventSource.Info(null); - - castedResult.InternalWaitForCompletion(); - castedResult.EndCalled = true; - - Exception exception = castedResult.Result as Exception; - if (exception != null) + if (IPAddress.TryParse(hostName, out IPAddress address)) { - ExceptionDispatchInfo.Throw(exception); + return CreateHostEntryForAddress(address); } - return (IPHostEntry)castedResult.Result; + return GetHostEntryCore(hostName); } [Obsolete("BeginGetHostByName is obsoleted for this type, please use BeginGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] @@ -366,230 +200,279 @@ namespace System.Net { if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); - NameResolutionPal.EnsureSocketsAreInitialized(); - - IAsyncResult asyncResult = HostResolutionBeginHelper(hostName, true, true, requestCallback, stateObject); + IAsyncResult asyncResult = TaskToApm.Begin(GetHostEntryCoreAsync(hostName, justReturnParsedIp: true, throwOnIIPAny: true), requestCallback, stateObject); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); return asyncResult; - } // BeginGetHostByName + } [Obsolete("EndGetHostByName is obsoleted for this type, please use EndGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] public static IPHostEntry EndGetHostByName(IAsyncResult asyncResult) { if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - NameResolutionPal.EnsureSocketsAreInitialized(); - IPHostEntry ipHostEntry = HostResolutionEndHelper(asyncResult); + IPHostEntry ipHostEntry = TaskToApm.End(asyncResult ?? throw new ArgumentNullException(nameof(asyncResult))); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; - } // EndGetHostByName() + } - public static IPHostEntry GetHostEntry(string hostNameOrAddress) + [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IPHostEntry GetHostByAddress(string address) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); NameResolutionPal.EnsureSocketsAreInitialized(); - if (hostNameOrAddress == null) + if (address is null) { - throw new ArgumentNullException(nameof(hostNameOrAddress)); + throw new ArgumentNullException(nameof(address)); } - // See if it's an IP Address. - IPAddress address; - IPHostEntry ipHostEntry; - if (IPAddress.TryParse(hostNameOrAddress, out address)) - { - if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) - { - throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(hostNameOrAddress))); - } - - ipHostEntry = InternalGetHostByAddress(address); - } - else - { - ipHostEntry = InternalGetHostByName(hostNameOrAddress); - } + IPHostEntry ipHostEntry = GetHostEntryCore(IPAddress.Parse(address)); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; } - - public static IPHostEntry GetHostEntry(IPAddress address) + [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IPHostEntry GetHostByAddress(IPAddress address) { if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); NameResolutionPal.EnsureSocketsAreInitialized(); - if (address == null) + if (address is null) { throw new ArgumentNullException(nameof(address)); } - if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) - { - throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(address))); - } - - IPHostEntry ipHostEntry = InternalGetHostByAddress(address); + IPHostEntry ipHostEntry = GetHostEntryCore(address); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; - } // GetHostEntry + } - public static IPAddress[] GetHostAddresses(string hostNameOrAddress) + [Obsolete("Resolve is obsoleted for this type, please use GetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IPHostEntry Resolve(string hostName) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); NameResolutionPal.EnsureSocketsAreInitialized(); - if (hostNameOrAddress == null) + if (hostName is null) { - throw new ArgumentNullException(nameof(hostNameOrAddress)); + throw new ArgumentNullException(nameof(hostName)); } // See if it's an IP Address. - IPAddress address; - IPAddress[] addresses; - if (IPAddress.TryParse(hostNameOrAddress, out address)) + IPHostEntry ipHostEntry; + if (IPAddress.TryParse(hostName, out IPAddress address) && + (address.AddressFamily != AddressFamily.InterNetworkV6 || SocketProtocolSupportPal.OSSupportsIPv6)) { - if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)) + try { - throw new ArgumentException(SR.Format(SR.net_invalid_ip_addr, nameof(hostNameOrAddress))); + ipHostEntry = GetHostEntryCore(address); + } + catch (SocketException ex) + { + if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); + ipHostEntry = CreateHostEntryForAddress(address); } - addresses = new IPAddress[] { address }; } else { - // InternalGetHostByName works with IP addresses (and avoids a reverse-lookup), but we need - // explicit handling in order to do the ArgumentException and guarantee the behavior. - addresses = InternalGetHostByName(hostNameOrAddress).AddressList; + ipHostEntry = GetHostEntryCore(hostName); } - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, addresses); - return addresses; + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); + return ipHostEntry; } - public static IAsyncResult BeginGetHostEntry(string hostNameOrAddress, AsyncCallback requestCallback, object stateObject) + [Obsolete("BeginResolve is obsoleted for this type, please use BeginGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IAsyncResult BeginResolve(string hostName, AsyncCallback requestCallback, object stateObject) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); - NameResolutionPal.EnsureSocketsAreInitialized(); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); - IAsyncResult asyncResult = HostResolutionBeginHelper(hostNameOrAddress, false, true, requestCallback, stateObject); + IAsyncResult asyncResult = TaskToApm.Begin(GetHostEntryCoreAsync(hostName, justReturnParsedIp: false, throwOnIIPAny: false), requestCallback, stateObject); if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); return asyncResult; - } // BeginResolve + } - public static IAsyncResult BeginGetHostEntry(IPAddress address, AsyncCallback requestCallback, object stateObject) + [Obsolete("EndResolve is obsoleted for this type, please use EndGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] + public static IPHostEntry EndResolve(IAsyncResult asyncResult) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, address); - - NameResolutionPal.EnsureSocketsAreInitialized(); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); + IPHostEntry ipHostEntry; - IAsyncResult asyncResult = HostResolutionBeginHelper(address, true, requestCallback, stateObject); + try + { + ipHostEntry = TaskToApm.End(asyncResult); + } + catch (SocketException ex) + { + IPAddress address = asyncResult switch + { + Task t => t.AsyncState as IPAddress, + TaskToApm.TaskAsyncResult twar => twar._task.AsyncState as IPAddress, + _ => null + }; - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); - return asyncResult; - } // BeginResolve + if (address is null) + throw; // BeginResolve was called with a HostName, not an IPAddress - public static IPHostEntry EndGetHostEntry(IAsyncResult asyncResult) - { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - IPHostEntry ipHostEntry = HostResolutionEndHelper(asyncResult); + if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); + ipHostEntry = CreateHostEntryForAddress(address); + } if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); return ipHostEntry; - } // EndResolve() + } - public static IAsyncResult BeginGetHostAddresses(string hostNameOrAddress, AsyncCallback requestCallback, object state) + private static IPHostEntry GetHostEntryCore(string hostName) => + (IPHostEntry)GetHostEntryOrAddressesCore(hostName, justAddresses: false); + + private static IPAddress[] GetHostAddressesCore(string hostName) => + (IPAddress[])GetHostEntryOrAddressesCore(hostName, justAddresses: true); + + private static object GetHostEntryOrAddressesCore(string hostName, bool justAddresses) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostNameOrAddress); - NameResolutionPal.EnsureSocketsAreInitialized(); + if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); + ValidateHostName(hostName); - IAsyncResult asyncResult = HostResolutionBeginHelper(hostNameOrAddress, true, true, requestCallback, state); + SocketError errorCode = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses, out hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); + if (errorCode != SocketError.Success) + { + throw SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode); + } - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); - return asyncResult; - } // BeginResolve + object result = justAddresses ? (object) + addresses : + new IPHostEntry + { + AddressList = addresses, + HostName = hostName, + Aliases = aliases + }; - public static IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult) - { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - IPHostEntry ipHostEntry = HostResolutionEndHelper(asyncResult); + if (NetEventSource.IsEnabled) NetEventSource.Exit(null, result); + return result; + } - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); - return ipHostEntry.AddressList; - } // EndResolveToAddresses + private static IPHostEntry GetHostEntryCore(IPAddress address) => + (IPHostEntry)GetHostEntryOrAddressesCore(address, justAddresses: false); - [Obsolete("BeginResolve is obsoleted for this type, please use BeginGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IAsyncResult BeginResolve(string hostName, AsyncCallback requestCallback, object stateObject) + private static IPAddress[] GetHostAddressesCore(IPAddress address) => + (IPAddress[])GetHostEntryOrAddressesCore(address, justAddresses: true); + + // Does internal IPAddress reverse and then forward lookups (for Legacy and current public methods). + private static object GetHostEntryOrAddressesCore(IPAddress address, bool justAddresses) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName); + if (NetEventSource.IsEnabled) NetEventSource.Info(null, address); - NameResolutionPal.EnsureSocketsAreInitialized(); + // 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. - IAsyncResult asyncResult = HostResolutionBeginHelper(hostName, false, false, requestCallback, stateObject); + // Do a reverse lookup to get the host name. + string name = NameResolutionPal.TryGetNameInfo(address, out SocketError errorCode, out int nativeErrorCode); + if (errorCode != SocketError.Success) + { + throw SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode); + } - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, asyncResult); - return asyncResult; - } // BeginResolve + // Do the forward lookup to get the IPs for that host name + errorCode = NameResolutionPal.TryGetAddrInfo(name, justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out nativeErrorCode); + if (errorCode != SocketError.Success) + { + if (NetEventSource.IsEnabled) NetEventSource.Error(null, SocketExceptionFactory.CreateSocketException(errorCode, nativeErrorCode)); + } - [Obsolete("EndResolve is obsoleted for this type, please use EndGetHostEntry instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static IPHostEntry EndResolve(IAsyncResult asyncResult) + // 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 justAddresses ? + (object)addresses : + new IPHostEntry + { + HostName = hostName, + Aliases = aliases, + AddressList = addresses + }; + } + + private static Task GetHostEntryCoreAsync(string hostName, bool justReturnParsedIp, bool throwOnIIPAny) => + (Task)GetHostEntryOrAddressesCoreAsync(hostName, justReturnParsedIp, throwOnIIPAny, justAddresses: false); + + // 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) { - if (NetEventSource.IsEnabled) NetEventSource.Enter(null, asyncResult); - IPHostEntry ipHostEntry; + if (NetEventSource.IsEnabled) NetEventSource.Info(null, hostName); + NameResolutionPal.EnsureSocketsAreInitialized(); - try + if (hostName is null) { - ipHostEntry = HostResolutionEndHelper(asyncResult); + throw new ArgumentNullException(nameof(hostName)); } - catch (SocketException ex) + + // See if it's an IP Address. + if (IPAddress.TryParse(hostName, out IPAddress ipAddress)) { - IPAddress address = ((DnsResolveAsyncResult)asyncResult).IpAddress; - if (address == null) - throw; // BeginResolve was called with a HostName, not an IPAddress + if (throwOnIIPAny && (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.IPv6Any))) + { + throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostName)); + } - if (NetEventSource.IsEnabled) NetEventSource.Error(null, ex); - ipHostEntry = NameResolutionUtilities.GetUnresolvedAnswer(address); + if (justReturnParsedIp) + { + return justAddresses ? (Task) + Task.FromResult(new[] { ipAddress }) : + Task.FromResult(CreateHostEntryForAddress(ipAddress)); + } + + return justAddresses ? (Task) + RunAsync(s => GetHostAddressesCore((IPAddress)s), ipAddress) : + RunAsync(s => GetHostEntryCore((IPAddress)s), ipAddress); } - if (NetEventSource.IsEnabled) NetEventSource.Exit(null, ipHostEntry); - return ipHostEntry; - } // EndResolve() + // 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 (NameResolutionPal.SupportsGetAddrInfoAsync && ipAddress is null) + { + ValidateHostName(hostName); + return NameResolutionPal.GetAddrInfoAsync(hostName, justAddresses); + } - //************* Task-based async public methods ************************* - public static Task GetHostAddressesAsync(string hostNameOrAddress) - { - NameResolutionPal.EnsureSocketsAreInitialized(); - return Task.Factory.FromAsync( - (arg, requestCallback, stateObject) => BeginGetHostAddresses(arg, requestCallback, stateObject), - asyncResult => EndGetHostAddresses(asyncResult), - hostNameOrAddress, - null); + return justAddresses ? (Task) + RunAsync(s => GetHostAddressesCore((string)s), hostName) : + RunAsync(s => GetHostEntryCore((string)s), hostName); } - public static Task GetHostEntryAsync(IPAddress address) - { - NameResolutionPal.EnsureSocketsAreInitialized(); - return Task.Factory.FromAsync( - (arg, requestCallback, stateObject) => BeginGetHostEntry(arg, requestCallback, stateObject), - asyncResult => EndGetHostEntry(asyncResult), - address, - null); - } + private static Task RunAsync(Func func, object arg) => + Task.Factory.StartNew(func, arg, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + private static IPHostEntry CreateHostEntryForAddress(IPAddress address) => + new IPHostEntry + { + HostName = address.ToString(), + Aliases = Array.Empty(), + AddressList = new IPAddress[] { address } + }; - public static Task GetHostEntryAsync(string hostNameOrAddress) + private static void ValidateHostName(string hostName) { - NameResolutionPal.EnsureSocketsAreInitialized(); - return Task.Factory.FromAsync( - (arg, requestCallback, stateObject) => BeginGetHostEntry(arg, requestCallback, stateObject), - asyncResult => EndGetHostEntry(asyncResult), - hostNameOrAddress, - null); + 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))); + } } } } diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/DnsResolveAsyncResult.cs b/src/libraries/System.Net.NameResolution/src/System/Net/DnsResolveAsyncResult.cs deleted file mode 100644 index 2edf0d4..0000000 --- a/src/libraries/System.Net.NameResolution/src/System/Net/DnsResolveAsyncResult.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Net -{ - internal sealed class DnsResolveAsyncResult : ContextAwareResult - { - internal string HostName { get; } - internal IPAddress IpAddress { get; } - - // Forward lookup - internal DnsResolveAsyncResult(string hostName, object myObject, object myState, AsyncCallback myCallBack) - : base(myObject, myState, myCallBack) - { - HostName = hostName; - } - - // Reverse lookup - internal DnsResolveAsyncResult(IPAddress ipAddress, object myObject, object myState, AsyncCallback myCallBack) - : base(myObject, myState, myCallBack) - { - IpAddress = ipAddress; - } - } -} diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/IPHostEntry.cs b/src/libraries/System.Net.NameResolution/src/System/Net/IPHostEntry.cs index 43b3ec8..e9029df 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/IPHostEntry.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/IPHostEntry.cs @@ -4,78 +4,16 @@ namespace System.Net { - // Host information - /// - /// Provides a container class for Internet host address information. - /// + /// Provides a container class for Internet host address information. public class IPHostEntry { - private string _hostName; - private string[] _aliases; - private IPAddress[] _addressList; - // CBT: When doing a DNS resolve, can the resulting host name trusted as an SPN? - // Only used on Win7Sp1+. Assume trusted by default. - internal bool isTrustedHost = true; + /// Gets or sets the DNS name of the host. + public string HostName { get; set; } - /// - /// - /// Contains the DNS - /// name of the host. - /// - /// - /// - /// - public string HostName - { - get - { - return _hostName; - } - set - { - _hostName = value; - } - } + /// Gets or sets a list of aliases that are associated with a host. + public string[] Aliases { get; set; } - /// - /// - /// Provides an - /// array of strings containing other DNS names that resolve to the IP addresses - /// in . - /// - /// - /// - /// - public string[] Aliases - { - get - { - return _aliases; - } - set - { - _aliases = value; - } - } - - /// - /// - /// Provides an - /// array of objects. - /// - /// - /// - /// - public IPAddress[] AddressList - { - get - { - return _addressList; - } - set - { - _addressList = value; - } - } - } // class IPHostEntry -} // namespace System.Net + /// Gets or sets a list of IP addresses that are associated with a host. + public IPAddress[] AddressList { get; set; } + } +} diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs index 6434fbd..47a6a20 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs @@ -8,6 +8,7 @@ using System.Net.Internals; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; namespace System.Net { @@ -15,6 +16,11 @@ namespace System.Net { public const bool SupportsGetAddrInfoAsync = false; + public static void EnsureSocketsAreInitialized() { } // No-op for Unix + + internal static Task GetAddrInfoAsync(string hostName, bool justAddresses) => + throw new NotSupportedException(); + private static SocketError GetSocketErrorForNativeError(int error) { switch (error) @@ -38,90 +44,84 @@ namespace System.Net } } - private static unsafe IPHostEntry CreateIPHostEntry(Interop.Sys.HostEntry hostEntry) + private static unsafe void ParseHostEntry(Interop.Sys.HostEntry hostEntry, bool justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses) { - string hostName = null; - if (hostEntry.CanonicalName != null) + try { - hostName = Marshal.PtrToStringAnsi((IntPtr)hostEntry.CanonicalName); - } - - int numAddresses = hostEntry.IPAddressCount; + hostName = !justAddresses && hostEntry.CanonicalName != null ? + Marshal.PtrToStringAnsi((IntPtr)hostEntry.CanonicalName) : + null; - IPAddress[] ipAddresses; - if (numAddresses == 0) - { - ipAddresses = Array.Empty(); - } - else - { - // - // getaddrinfo returns multiple entries per address, for each socket type (datagram, stream, etc.). - // Our callers expect just one entry for each address. So we need to deduplicate the results. - // It's important to keep the addresses in order, since they are returned in the order in which - // connections should be attempted. - // - // We assume that the list returned by getaddrinfo is relatively short; after all, the intent is that - // the caller may need to attempt to contact every address in the list before giving up on a connection - // attempt. So an O(N^2) algorithm should be fine here. Keep in mind that any "better" algorithm - // is likely to involve extra allocations, hashing, etc., and so will probably be more expensive than - // this one in the typical (short list) case. - // - var nativeAddresses = new Interop.Sys.IPAddress[hostEntry.IPAddressCount]; - var nativeAddressCount = 0; - - var addressListHandle = hostEntry.AddressListHandle; - for (int i = 0; i < hostEntry.IPAddressCount; i++) + IPAddress[] localAddresses; + if (hostEntry.IPAddressCount == 0) + { + localAddresses = Array.Empty(); + } + else { - var nativeIPAddress = default(Interop.Sys.IPAddress); - int err = Interop.Sys.GetNextIPAddress(&hostEntry, &addressListHandle, &nativeIPAddress); - Debug.Assert(err == 0); + // getaddrinfo returns multiple entries per address, for each socket type (datagram, stream, etc.). + // Our callers expect just one entry for each address. So we need to deduplicate the results. + // It's important to keep the addresses in order, since they are returned in the order in which + // connections should be attempted. + // + // We assume that the list returned by getaddrinfo is relatively short; after all, the intent is that + // the caller may need to attempt to contact every address in the list before giving up on a connection + // attempt. So an O(N^2) algorithm should be fine here. Keep in mind that any "better" algorithm + // is likely to involve extra allocations, hashing, etc., and so will probably be more expensive than + // this one in the typical (short list) case. + + var nativeAddresses = new Interop.Sys.IPAddress[hostEntry.IPAddressCount]; + int nativeAddressCount = 0; + + Interop.Sys.addrinfo* addressListHandle = hostEntry.AddressListHandle; + for (int i = 0; i < hostEntry.IPAddressCount; i++) + { + Interop.Sys.IPAddress nativeIPAddress = default; + int err = Interop.Sys.GetNextIPAddress(&hostEntry, &addressListHandle, &nativeIPAddress); + Debug.Assert(err == 0); + + if (Array.IndexOf(nativeAddresses, nativeIPAddress, 0, nativeAddressCount) == -1) + { + nativeAddresses[nativeAddressCount++] = nativeIPAddress; + } + } - if (Array.IndexOf(nativeAddresses, nativeIPAddress, 0, nativeAddressCount) == -1) + localAddresses = new IPAddress[nativeAddressCount]; + for (int i = 0; i < nativeAddressCount; i++) { - nativeAddresses[nativeAddressCount] = nativeIPAddress; - nativeAddressCount++; + localAddresses[i] = nativeAddresses[i].GetIPAddress(); } } - ipAddresses = new IPAddress[nativeAddressCount]; - for (int i = 0; i < nativeAddressCount; i++) + string[] localAliases = Array.Empty(); + if (!justAddresses && hostEntry.Aliases != null) { - ipAddresses[i] = nativeAddresses[i].GetIPAddress(); - } - } + int numAliases = 0; + while (hostEntry.Aliases[numAliases] != null) + { + numAliases++; + } - int numAliases; - for (numAliases = 0; hostEntry.Aliases != null && hostEntry.Aliases[numAliases] != null; numAliases++) - { - } + if (numAliases > 0) + { + localAliases = new string[numAliases]; + for (int i = 0; i < localAliases.Length; i++) + { + localAliases[i] = Marshal.PtrToStringAnsi((IntPtr)hostEntry.Aliases[i]); + } + } + } - string[] aliases; - if (numAliases == 0) - { - aliases = Array.Empty(); + aliases = localAliases; + addresses = localAddresses; } - else + finally { - aliases = new string[numAliases]; - for (int i = 0; i < numAliases; i++) - { - Debug.Assert(hostEntry.Aliases[i] != null); - aliases[i] = Marshal.PtrToStringAnsi((IntPtr)hostEntry.Aliases[i]); - } + Interop.Sys.FreeHostEntry(&hostEntry); } - - Interop.Sys.FreeHostEntry(&hostEntry); - - return new IPHostEntry - { - HostName = hostName, - AddressList = ipAddresses, - Aliases = aliases - }; } - public static unsafe SocketError TryGetAddrInfo(string name, out IPHostEntry hostinfo, out int nativeErrorCode) + public static unsafe SocketError TryGetAddrInfo(string name, bool justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode) { if (name == "") { @@ -133,22 +133,18 @@ namespace System.Net int result = Interop.Sys.GetHostEntryForName(name, &entry); if (result != 0) { - hostinfo = NameResolutionUtilities.GetUnresolvedAnswer(name); nativeErrorCode = result; + hostName = name; + aliases = Array.Empty(); + addresses = Array.Empty(); return GetSocketErrorForNativeError(result); } - hostinfo = CreateIPHostEntry(entry); - + ParseHostEntry(entry, justAddresses, out hostName, out aliases, out addresses); nativeErrorCode = 0; return SocketError.Success; } - internal static void GetAddrInfoAsync(DnsResolveAsyncResult asyncResult) - { - throw new NotSupportedException(); - } - public static unsafe string TryGetNameInfo(IPAddress addr, out SocketError socketError, out int nativeErrorCode) { byte* buffer = stackalloc byte[Interop.Sys.NI_MAXHOST + 1 /*for null*/]; @@ -182,17 +178,9 @@ namespace System.Net socketError = GetSocketErrorForNativeError(error); nativeErrorCode = error; - return socketError != SocketError.Success ? null : Marshal.PtrToStringAnsi((IntPtr)buffer); + return socketError == SocketError.Success ? Marshal.PtrToStringAnsi((IntPtr)buffer) : null; } - public static string GetHostName() - { - return Interop.Sys.GetHostName(); - } - - public static void EnsureSocketsAreInitialized() - { - // No-op for Unix. - } + public static string GetHostName() => Interop.Sys.GetHostName(); } } diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs index 5f70982..576043d 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs @@ -2,245 +2,92 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; -using ProtocolFamily = System.Net.Internals.ProtocolFamily; +using System.Diagnostics; namespace System.Net { internal static partial class NameResolutionPal { - // - // used by GetHostName() to preallocate a buffer for the call to gethostname. - // - private const int HostNameBufferLength = 256; - - private static bool s_initialized; + private static volatile bool s_initialized; private static readonly object s_initializedLock = new object(); private static readonly unsafe Interop.Winsock.LPLOOKUPSERVICE_COMPLETION_ROUTINE s_getAddrInfoExCallback = GetAddressInfoExCallback; private static bool s_getAddrInfoExSupported; - public static bool SupportsGetAddrInfoAsync + public static void EnsureSocketsAreInitialized() { - get + if (!s_initialized) { - EnsureSocketsAreInitialized(); - return s_getAddrInfoExSupported; + InitializeSockets(); } - } - - /*++ - - Routine Description: - - Takes a native pointer (expressed as an int) to a hostent structure, - and converts the information in their to an IPHostEntry class. This - involves walking through an array of native pointers, and a temporary - ArrayList object is used in doing this. - Arguments: - - nativePointer - Native pointer to hostent structure. - - - - Return Value: - - An IPHostEntry structure. - - --*/ - private static IPHostEntry NativeToHostEntry(IntPtr nativePointer) - { - // - // marshal pointer to struct - // - - hostent Host = Marshal.PtrToStructure(nativePointer); - IPHostEntry HostEntry = new IPHostEntry(); - - if (Host.h_name != IntPtr.Zero) + static void InitializeSockets() { - HostEntry.HostName = Marshal.PtrToStringAnsi(Host.h_name); - if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"HostEntry.HostName: {HostEntry.HostName}"); - } - - // decode h_addr_list to ArrayList of IP addresses. - // The h_addr_list field is really a pointer to an array of pointers - // to IP addresses. Loop through the array, and while the pointer - // isn't NULL read the IP address, convert it to an IPAddress class, - // and add it to the list. - - var TempIPAddressList = new List(); - int IPAddressToAdd; - string AliasToAdd; - IntPtr currentArrayElement; + lock (s_initializedLock) + { + if (!s_initialized) + { + SocketError errorCode = Interop.Winsock.WSAStartup(); + if (errorCode != SocketError.Success) + { + // WSAStartup does not set LastWin32Error + throw new SocketException((int)errorCode); + } - // - // get the first pointer in the array - // - currentArrayElement = Host.h_addr_list; - nativePointer = Marshal.ReadIntPtr(currentArrayElement); + s_getAddrInfoExSupported = GetAddrInfoExSupportsOverlapped(); + s_initialized = true; + } + } + } + } - while (nativePointer != IntPtr.Zero) + public static bool SupportsGetAddrInfoAsync + { + get { - // - // if it's not null it points to an IPAddress, - // read it... - // - IPAddressToAdd = Marshal.ReadInt32(nativePointer); -#if BIGENDIAN - // IP addresses from native code are always a byte array - // converted to int. We need to convert the address into - // a uniform integer value. - IPAddressToAdd = (int)(((uint)IPAddressToAdd << 24) | - (((uint)IPAddressToAdd & 0x0000FF00) << 8) | - (((uint)IPAddressToAdd >> 8) & 0x0000FF00) | - ((uint)IPAddressToAdd >> 24)); -#endif - - if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"currentArrayElement:{currentArrayElement} nativePointer:{nativePointer} IPAddressToAdd:{IPAddressToAdd}"); - - // - // ...and add it to the list - // - TempIPAddressList.Add(new IPAddress((long)IPAddressToAdd & 0x0FFFFFFFF)); - - // - // now get the next pointer in the array and start over - // - currentArrayElement = currentArrayElement + IntPtr.Size; - nativePointer = Marshal.ReadIntPtr(currentArrayElement); + EnsureSocketsAreInitialized(); + return s_getAddrInfoExSupported; } + } - HostEntry.AddressList = TempIPAddressList.ToArray(); - - // - // Now do the same thing for the aliases. - // - - var TempAliasList = new List(); - - currentArrayElement = Host.h_aliases; - nativePointer = Marshal.ReadIntPtr(currentArrayElement); + public static unsafe SocketError TryGetAddrInfo(string name, bool justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode) + { + aliases = Array.Empty(); - while (nativePointer != IntPtr.Zero) + var hints = new Interop.Winsock.AddressInfo { ai_family = AddressFamily.Unspecified }; // Gets all address families + if (!justAddresses) { - if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"currentArrayElement:{currentArrayElement} nativePointer:{nativePointer}"); - - // - // if it's not null it points to an Alias, - // read it... - // - AliasToAdd = Marshal.PtrToStringAnsi(nativePointer); - - // - // ...and add it to the list - // - TempAliasList.Add(AliasToAdd); - - // - // now get the next pointer in the array and start over - // - currentArrayElement = currentArrayElement + IntPtr.Size; - nativePointer = Marshal.ReadIntPtr(currentArrayElement); + hints.ai_flags = AddressInfoHints.AI_CANONNAME; } - HostEntry.Aliases = TempAliasList.ToArray(); - - return HostEntry; - } // NativeToHostEntry - - public static unsafe SocketError TryGetAddrInfo(string name, out IPHostEntry hostinfo, out int nativeErrorCode) - { - // - // Use SocketException here to show operation not supported - // if, by some nefarious means, this method is called on an - // unsupported platform. - // - SafeFreeAddrInfo root = null; - var addresses = new List(); - string canonicalname = null; - - AddressInfo hints = new AddressInfo(); - hints.ai_flags = AddressInfoHints.AI_CANONNAME; - hints.ai_family = AddressFamily.Unspecified; // gets all address families - - nativeErrorCode = 0; - - // - // Use try / finally so we always get a shot at freeaddrinfo - // + Interop.Winsock.AddressInfo* result = null; try { - SocketError errorCode = (SocketError)SafeFreeAddrInfo.GetAddrInfo(name, null, ref hints, out root); + SocketError errorCode = (SocketError)Interop.Winsock.GetAddrInfoW(name, null, &hints, &result); if (errorCode != SocketError.Success) - { // Should not throw, return mostly blank hostentry - hostinfo = NameResolutionUtilities.GetUnresolvedAnswer(name); + { + nativeErrorCode = (int)errorCode; + hostName = name; + addresses = Array.Empty(); return errorCode; } - AddressInfo* pAddressInfo = (AddressInfo*)root.DangerousGetHandle(); - // - // Process the results - // - while (pAddressInfo != null) - { - // - // Retrieve the canonical name for the host - only appears in the first AddressInfo - // entry in the returned array. - // - if (canonicalname == null && pAddressInfo->ai_canonname != null) - { - canonicalname = Marshal.PtrToStringUni((IntPtr)pAddressInfo->ai_canonname); - } - // - // Only process IPv4 or IPv6 Addresses. Note that it's unlikely that we'll - // ever get any other address families, but better to be safe than sorry. - // We also filter based on whether IPv6 is supported on the current - // platform / machine. - // - var socketAddress = new ReadOnlySpan(pAddressInfo->ai_addr, pAddressInfo->ai_addrlen); - - if (pAddressInfo->ai_family == AddressFamily.InterNetwork) - { - if (socketAddress.Length == SocketAddressPal.IPv4AddressSize) - addresses.Add(CreateIPv4Address(socketAddress)); - } - else if (pAddressInfo->ai_family == AddressFamily.InterNetworkV6 && SocketProtocolSupportPal.OSSupportsIPv6) - { - if (socketAddress.Length == SocketAddressPal.IPv6AddressSize) - addresses.Add(CreateIPv6Address(socketAddress)); - } - // - // Next addressinfo entry - // - pAddressInfo = pAddressInfo->ai_next; - } + addresses = ParseAddressInfo(result, justAddresses, out hostName); + nativeErrorCode = 0; + return SocketError.Success; } finally { - if (root != null) + if (result != null) { - root.Dispose(); + Interop.Winsock.FreeAddrInfoW(result); } } - - // - // Finally, put together the IPHostEntry - // - hostinfo = new IPHostEntry(); - - hostinfo.HostName = canonicalname != null ? canonicalname : name; - hostinfo.Aliases = Array.Empty(); - hostinfo.AddressList = addresses.ToArray(); - - return SocketError.Success; } public static unsafe string TryGetNameInfo(IPAddress addr, out SocketError errorCode, out int nativeErrorCode) @@ -255,7 +102,6 @@ namespace System.Net const int NI_MAXHOST = 1025; char* hostname = stackalloc char[NI_MAXHOST]; - nativeErrorCode = 0; fixed (byte* addressBufferPtr = addressBuffer) { errorCode = Interop.Winsock.GetNameInfoW( @@ -268,60 +114,38 @@ namespace System.Net (int)Interop.Winsock.NameInfoFlags.NI_NAMEREQD); } - return errorCode == SocketError.Success ? new string(hostname) : null; + if (errorCode == SocketError.Success) + { + nativeErrorCode = 0; + return new string(hostname); + } + + nativeErrorCode = (int)errorCode; + return null; } public static unsafe string GetHostName() { - // - // note that we could cache the result ourselves since you - // wouldn't expect the hostname of the machine to change during - // execution, but this might still happen and we would want to - // react to that change. - // + // We do not cache the result in case the hostname changes. + const int HostNameBufferLength = 256; byte* buffer = stackalloc byte[HostNameBufferLength]; if (Interop.Winsock.gethostname(buffer, HostNameBufferLength) != SocketError.Success) { throw new SocketException(); } - return new string((sbyte*)buffer); - } - public static void EnsureSocketsAreInitialized() - { - if (!Volatile.Read(ref s_initialized)) - { - lock (s_initializedLock) - { - if (!s_initialized) - { - SocketError errorCode = Interop.Winsock.WSAStartup(); - - if (errorCode != SocketError.Success) - { - // - // failed to initialize, throw - // - // WSAStartup does not set LastWin32Error - throw new SocketException((int)errorCode); - } - - s_getAddrInfoExSupported = GetAddrInfoExSupportsOverlapped(); - - Volatile.Write(ref s_initialized, true); - } - } - } + return new string((sbyte*)buffer); } - public static unsafe void GetAddrInfoAsync(DnsResolveAsyncResult asyncResult) + public static unsafe Task GetAddrInfoAsync(string hostName, bool justAddresses) { GetAddrInfoExContext* context = GetAddrInfoExContext.AllocateContext(); + GetAddrInfoExState state; try { - var state = new GetAddrInfoExState(asyncResult); + state = new GetAddrInfoExState(hostName, justAddresses); context->QueryStateHandle = state.CreateHandle(); } catch @@ -330,18 +154,24 @@ namespace System.Net throw; } - AddressInfoEx hints = new AddressInfoEx(); - hints.ai_flags = AddressInfoHints.AI_CANONNAME; - hints.ai_family = AddressFamily.Unspecified; // Gets all address families + var hints = new Interop.Winsock.AddressInfoEx { ai_family = AddressFamily.Unspecified }; // Gets all address families + if (!justAddresses) + { + hints.ai_flags = AddressInfoHints.AI_CANONNAME; + } - SocketError errorCode = - (SocketError)Interop.Winsock.GetAddrInfoExW(asyncResult.HostName, null, 0 /* NS_ALL*/, IntPtr.Zero, ref hints, out context->Result, IntPtr.Zero, ref context->Overlapped, s_getAddrInfoExCallback, out context->CancelHandle); + SocketError errorCode = (SocketError)Interop.Winsock.GetAddrInfoExW( + hostName, null, Interop.Winsock.NS_ALL, IntPtr.Zero, &hints, &context->Result, IntPtr.Zero, &context->Overlapped, s_getAddrInfoExCallback, &context->CancelHandle); if (errorCode != SocketError.IOPending) + { ProcessResult(errorCode, context); + } + + return state.Task; } - private static unsafe void GetAddressInfoExCallback([In] int error, [In] int bytes, [In] NativeOverlapped* overlapped) + private static unsafe void GetAddressInfoExCallback(int error, int bytes, NativeOverlapped* overlapped) { // Can be casted directly to GetAddrInfoExContext* because the overlapped is its first field GetAddrInfoExContext* context = (GetAddrInfoExContext*)overlapped; @@ -355,52 +185,148 @@ namespace System.Net { GetAddrInfoExState state = GetAddrInfoExState.FromHandleAndFree(context->QueryStateHandle); - if (errorCode != SocketError.Success) + if (errorCode == SocketError.Success) + { + IPAddress[] addresses = ParseAddressInfoEx(context->Result, state.JustAddresses, out string hostName); + state.SetResult(state.JustAddresses ? (object) + addresses : + new IPHostEntry + { + HostName = hostName ?? state.HostName, + Aliases = Array.Empty(), + AddressList = addresses + }); + } + else { - state.CompleteAsyncResult(new SocketException((int)errorCode)); - return; + state.SetResult(new SocketException((int)errorCode)); } + } + finally + { + GetAddrInfoExContext.FreeContext(context); + } + } + + private static unsafe IPAddress[] ParseAddressInfo(Interop.Winsock.AddressInfo* addressInfoPtr, bool justAddresses, out string hostName) + { + Debug.Assert(addressInfoPtr != null); - AddressInfoEx* result = context->Result; - string canonicalName = null; + // Count how many results we have. + int addressCount = 0; + for (Interop.Winsock.AddressInfo* result = addressInfoPtr; result != null; result = result->ai_next) + { + int addressLength = (int)result->ai_addrlen; - List addresses = new List(); + if (result->ai_family == AddressFamily.InterNetwork) + { + if (addressLength == SocketAddressPal.IPv4AddressSize) + { + addressCount++; + } + } + else if (SocketProtocolSupportPal.OSSupportsIPv6 && result->ai_family == AddressFamily.InterNetworkV6) + { + if (addressLength == SocketAddressPal.IPv6AddressSize) + { + addressCount++; + } + } + } - while (result != null) + // Store them into the array. + var addresses = new IPAddress[addressCount]; + addressCount = 0; + string canonicalName = justAddresses ? "NONNULLSENTINEL" : null; + for (Interop.Winsock.AddressInfo* result = addressInfoPtr; result != null; result = result->ai_next) + { + if (canonicalName == null && result->ai_canonname != null) { - if (canonicalName == null && result->ai_canonname != IntPtr.Zero) - canonicalName = Marshal.PtrToStringUni(result->ai_canonname); + canonicalName = Marshal.PtrToStringUni((IntPtr)result->ai_canonname); + } - var socketAddress = new ReadOnlySpan(result->ai_addr, result->ai_addrlen); + int addressLength = (int)result->ai_addrlen; + var socketAddress = new ReadOnlySpan(result->ai_addr, addressLength); - if (result->ai_family == AddressFamily.InterNetwork) + if (result->ai_family == AddressFamily.InterNetwork) + { + if (addressLength == SocketAddressPal.IPv4AddressSize) { - if (socketAddress.Length == SocketAddressPal.IPv4AddressSize) - addresses.Add(CreateIPv4Address(socketAddress)); + addresses[addressCount++] = CreateIPv4Address(socketAddress); } - else if (SocketProtocolSupportPal.OSSupportsIPv6 && result->ai_family == AddressFamily.InterNetworkV6) + } + else if (SocketProtocolSupportPal.OSSupportsIPv6 && result->ai_family == AddressFamily.InterNetworkV6) + { + if (addressLength == SocketAddressPal.IPv6AddressSize) { - if (socketAddress.Length == SocketAddressPal.IPv6AddressSize) - addresses.Add(CreateIPv6Address(socketAddress)); + addresses[addressCount++] = CreateIPv6Address(socketAddress); } - - result = result->ai_next; } + } - if (canonicalName == null) - canonicalName = state.HostName; + hostName = justAddresses ? null : canonicalName; + return addresses; + } - state.CompleteAsyncResult(new IPHostEntry + private static unsafe IPAddress[] ParseAddressInfoEx(Interop.Winsock.AddressInfoEx* addressInfoExPtr, bool justAddresses, out string hostName) + { + Debug.Assert(addressInfoExPtr != null); + + // First count how many address results we have. + int addressCount = 0; + for (Interop.Winsock.AddressInfoEx* result = addressInfoExPtr; result != null; result = result->ai_next) + { + int addressLength = (int)result->ai_addrlen; + + if (result->ai_family == AddressFamily.InterNetwork) { - HostName = canonicalName, - Aliases = Array.Empty(), - AddressList = addresses.ToArray() - }); + if (addressLength == SocketAddressPal.IPv4AddressSize) + { + addressCount++; + } + } + else if (SocketProtocolSupportPal.OSSupportsIPv6 && result->ai_family == AddressFamily.InterNetworkV6) + { + if (addressLength == SocketAddressPal.IPv6AddressSize) + { + addressCount++; + } + } } - finally + + // Then store them into an array. + var addresses = new IPAddress[addressCount]; + addressCount = 0; + string canonicalName = justAddresses ? "NONNULLSENTINEL" : null; + for (Interop.Winsock.AddressInfoEx* result = addressInfoExPtr; result != null; result = result->ai_next) { - GetAddrInfoExContext.FreeContext(context); + if (canonicalName == null && result->ai_canonname != IntPtr.Zero) + { + canonicalName = Marshal.PtrToStringUni(result->ai_canonname); + } + + int addressLength = (int)result->ai_addrlen; + var socketAddress = new ReadOnlySpan(result->ai_addr, addressLength); + + if (result->ai_family == AddressFamily.InterNetwork) + { + if (addressLength == SocketAddressPal.IPv4AddressSize) + { + addresses[addressCount++] = CreateIPv4Address(socketAddress); + } + } + else if (SocketProtocolSupportPal.OSSupportsIPv6 && result->ai_family == AddressFamily.InterNetworkV6) + { + if (addressLength == SocketAddressPal.IPv6AddressSize) + { + addresses[addressCount++] = CreateIPv6Address(socketAddress); + } + } } + + // Return the parsed host name (if we got one) and addresses. + hostName = justAddresses ? null : canonicalName; + return addresses; } private static unsafe IPAddress CreateIPv4Address(ReadOnlySpan socketAddress) @@ -412,57 +338,83 @@ namespace System.Net private static unsafe IPAddress CreateIPv6Address(ReadOnlySpan socketAddress) { Span address = stackalloc byte[IPAddressParserStatics.IPv6AddressBytes]; - uint scope; - SocketAddressPal.GetIPv6Address(socketAddress, address, out scope); - - return new IPAddress(address, (long)scope); + SocketAddressPal.GetIPv6Address(socketAddress, address, out uint scope); + return new IPAddress(address, scope); } - #region GetAddrInfoAsync Helper Classes - - // - // Warning: If this ever ported to NETFX, AppDomain unloads needs to be handled - // to protect against AppDomainUnloadException if there are pending operations. - // - - private sealed class GetAddrInfoExState + private sealed class GetAddrInfoExState : IThreadPoolWorkItem { - private readonly DnsResolveAsyncResult _asyncResult; + private AsyncTaskMethodBuilder IPHostEntryBuilder; + private AsyncTaskMethodBuilder IPAddressArrayBuilder; private object _result; - public string HostName => _asyncResult.HostName; - - public GetAddrInfoExState(DnsResolveAsyncResult asyncResult) + public GetAddrInfoExState(string hostName, bool justAddresses) { - _asyncResult = asyncResult; + HostName = hostName; + JustAddresses = justAddresses; + if (justAddresses) + { + IPAddressArrayBuilder = AsyncTaskMethodBuilder.Create(); + _ = IPAddressArrayBuilder.Task; // force initialization + } + else + { + IPHostEntryBuilder = AsyncTaskMethodBuilder.Create(); + _ = IPHostEntryBuilder.Task; // force initialization + } } - public void CompleteAsyncResult(object o) - { - // We don't want to expose the GetAddrInfoEx callback thread to user code. - // The callback occurs in a native windows thread pool. + public string HostName { get; } - _result = o; + public bool JustAddresses { get; } - Task.Factory.StartNew(s => - { - var self = (GetAddrInfoExState)s; - self._asyncResult.InvokeCallback(self._result); - }, this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + public Task Task => JustAddresses ? (Task)IPAddressArrayBuilder.Task : IPHostEntryBuilder.Task; + + public void SetResult(object result) + { + // Store the result and then queue this object to the thread pool to actually complete the Tasks, as we + // want to avoid invoking continuations on the Windows callback thread. Effectively we're manually + // implementing TaskCreationOptions.RunContinuationsAsynchronously, which we can't use because we're + // using AsyncTaskMethodBuilder, which we're using in order to create either a strongly-typed Task + // or Task without allocating additional objects. + Debug.Assert(result is Exception || result is IPAddress[] || result is IPHostEntry); + _result = result; + ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } - public IntPtr CreateHandle() + void IThreadPoolWorkItem.Execute() { - GCHandle handle = GCHandle.Alloc(this, GCHandleType.Normal); - return GCHandle.ToIntPtr(handle); + if (JustAddresses) + { + if (_result is Exception e) + { + IPAddressArrayBuilder.SetException(e); + } + else + { + IPAddressArrayBuilder.SetResult((IPAddress[])_result); + } + } + else + { + if (_result is Exception e) + { + IPHostEntryBuilder.SetException(e); + } + else + { + IPHostEntryBuilder.SetResult((IPHostEntry)_result); + } + } } + public IntPtr CreateHandle() => GCHandle.ToIntPtr(GCHandle.Alloc(this, GCHandleType.Normal)); + public static GetAddrInfoExState FromHandleAndFree(IntPtr handle) { GCHandle gcHandle = GCHandle.FromIntPtr(handle); var state = (GetAddrInfoExState)gcHandle.Target; gcHandle.Free(); - return state; } } @@ -471,7 +423,7 @@ namespace System.Net private unsafe struct GetAddrInfoExContext { public NativeOverlapped Overlapped; - public AddressInfoEx* Result; + public Interop.Winsock.AddressInfoEx* Result; public IntPtr CancelHandle; public IntPtr QueryStateHandle; @@ -479,19 +431,18 @@ namespace System.Net { var context = (GetAddrInfoExContext*)Marshal.AllocHGlobal(sizeof(GetAddrInfoExContext)); *context = default; - return context; } public static void FreeContext(GetAddrInfoExContext* context) { if (context->Result != null) + { Interop.Winsock.FreeAddrInfoExW(context->Result); + } Marshal.FreeHGlobal((IntPtr)context); } } - - #endregion } } diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionUtilities.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionUtilities.cs deleted file mode 100644 index 4be4755..0000000 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionUtilities.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Net -{ - internal static class NameResolutionUtilities - { - public static IPHostEntry GetUnresolvedAnswer(IPAddress address) - { - return new IPHostEntry - { - HostName = address.ToString(), - Aliases = Array.Empty(), - AddressList = new IPAddress[] { address } - }; - } - - public static IPHostEntry GetUnresolvedAnswer(string name) - { - return new IPHostEntry - { - HostName = name, - Aliases = Array.Empty(), - AddressList = Array.Empty() - }; - } - } -} diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByAddressTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByAddressTest.cs index 3a89a36..b54874d 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByAddressTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostByAddressTest.cs @@ -12,7 +12,6 @@ namespace System.Net.NameResolution.Tests public class GetHostByAddressTest { [Fact] - public void DnsObsoleteGetHostByAddress_BadIPString_Throws() { Assert.Throws(() => Dns.GetHostByAddress("badIPString")); diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index da8716f..e3d015d 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -8,7 +8,6 @@ using System.Net.Sockets; using System.Threading.Tasks; using Xunit; -using Xunit.Sdk; namespace System.Net.NameResolution.Tests { @@ -74,10 +73,12 @@ namespace System.Net.NameResolution.Tests [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue(32797)] [InlineData("")] [InlineData(TestSettings.LocalHost)] - public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) => await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(hostName)); + public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) => + await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(hostName)); [Fact] - public async Task Dns_GetHostEntryAsync_IPString_Ok() => await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(TestSettings.LocalIPString)); + public async Task Dns_GetHostEntryAsync_IPString_Ok() => + await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(TestSettings.LocalIPString)); private static async Task TestGetHostEntryAsync(Func> getHostEntryFunc) { @@ -95,10 +96,20 @@ namespace System.Net.NameResolution.Tests } [Fact] - public async Task Dns_GetHostEntryAsync_NullStringHost_Fail() => await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync((string)null)); + public async Task Dns_GetHostEntry_NullStringHost_Fail() + { + Assert.Throws(() => Dns.GetHostEntry((string)null)); + await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync((string)null)); + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, (string)null, null)); + } [Fact] - public async Task Dns_GetHostEntryAsync_NullIPAddressHost_Fail() => await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync((IPAddress)null)); + public async Task Dns_GetHostEntryAsync_NullIPAddressHost_Fail() + { + Assert.Throws(() => Dns.GetHostEntry((IPAddress)null)); + await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync((IPAddress)null)); + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, (IPAddress)null, null)); + } public static IEnumerable GetInvalidAddresses() { @@ -109,125 +120,114 @@ namespace System.Net.NameResolution.Tests [Theory] [MemberData(nameof(GetInvalidAddresses))] - public async Task Dns_GetHostEntryAsync_AnyIPAddress_Fail(IPAddress address) + public async Task Dns_GetHostEntry_AnyIPAddress_Fail(IPAddress address) { - string addressString = address.ToString(); + Assert.Throws(() => Dns.GetHostEntry(address)); + Assert.Throws(() => Dns.GetHostEntry(address.ToString())); await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync(address)); - await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync(addressString)); - } + await Assert.ThrowsAsync(() => Dns.GetHostEntryAsync(address.ToString())); - [Fact] - public void DnsBeginGetHostEntry_BadName_Throws() - { - IAsyncResult asyncObject = Dns.BeginGetHostEntry("BadName", null, null); - Assert.ThrowsAny(() => Dns.EndGetHostEntry(asyncObject)); + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, address, null)); + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, address.ToString(), null)); } [Fact] - public void DnsBeginGetHostEntry_BadIpString_Throws() + public async Task DnsGetHostEntry_MachineName_AllVariationsMatch() { - IAsyncResult asyncObject = Dns.BeginGetHostEntry("0.0.1.1", null, null); - Assert.ThrowsAny(() => Dns.EndGetHostEntry(asyncObject)); - } + IPHostEntry syncResult = Dns.GetHostEntry(TestSettings.LocalHost); + IPHostEntry apmResult = Dns.EndGetHostEntry(Dns.BeginGetHostEntry(TestSettings.LocalHost, null, null)); + IPHostEntry asyncResult = await Dns.GetHostEntryAsync(TestSettings.LocalHost); - [Fact] - public void DnsBeginGetHostEntry_MachineName_MatchesGetHostEntry() - { - IAsyncResult asyncObject = Dns.BeginGetHostEntry(TestSettings.LocalHost, null, null); - IPHostEntry results = Dns.EndGetHostEntry(asyncObject); - IPHostEntry entry = Dns.GetHostEntry(TestSettings.LocalHost); + Assert.Equal(syncResult.HostName, apmResult.HostName); + Assert.Equal(syncResult.HostName, asyncResult.HostName); - Assert.Equal(entry.HostName, results.HostName); - Assert.Equal(entry.AddressList, results.AddressList); + Assert.Equal(syncResult.AddressList, apmResult.AddressList); + Assert.Equal(syncResult.AddressList, asyncResult.AddressList); } [Fact] - public void DnsBeginGetHostEntry_Loopback_MatchesGetHostEntry() + public async Task DnsGetHostEntry_Loopback_AllVariationsMatch() { - IAsyncResult asyncObject = Dns.BeginGetHostEntry(IPAddress.Loopback, null, null); - IPHostEntry results = Dns.EndGetHostEntry(asyncObject); - IPHostEntry entry = Dns.GetHostEntry(IPAddress.Loopback); + IPHostEntry syncResult = Dns.GetHostEntry(IPAddress.Loopback); + IPHostEntry apmResult = Dns.EndGetHostEntry(Dns.BeginGetHostEntry(IPAddress.Loopback, null, null)); + IPHostEntry asyncResult = await Dns.GetHostEntryAsync(IPAddress.Loopback); - Assert.Equal(entry.HostName, results.HostName); + Assert.Equal(syncResult.HostName, apmResult.HostName); + Assert.Equal(syncResult.HostName, asyncResult.HostName); - Assert.Equal(entry.AddressList, results.AddressList); + Assert.Equal(syncResult.AddressList, apmResult.AddressList); + Assert.Equal(syncResult.AddressList, asyncResult.AddressList); } - [Fact] - public void DnsGetHostEntry_BadName_Throws() - { - Assert.ThrowsAny(() => Dns.GetHostEntry("BadName")); - } - - [Fact] - public void DnsGetHostEntry_BadIpString_Throws() - { - Assert.ThrowsAny(() => Dns.GetHostEntry("0.0.1.1")); - } - - [Fact] - public void DnsGetHostEntry_HostAlmostTooLong254Chars_Throws() - { - Assert.ThrowsAny(() => Dns.GetHostEntry( - "Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualllllllly.I.Will.Get.To.The.Eeeee" + [Theory] + [InlineData("BadName")] // unknown name + [InlineData("0.0.1.1")] // unknown address + [InlineData("Test-\u65B0-Unicode")] // unknown unicode name + [InlineData("xn--test--unicode-0b01a")] // unknown punicode name + [InlineData("Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualllllllly.I.Will.Get.To.The.Eeeee" + "eeeeend.Almost.There.Are.We.Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualll" - + "llllly.I.Will.Get.To.The.Eeeeeeeeeend.Almost.There.Are")); - } - - [Fact] - public void DnsGetHostEntry_HostAlmostTooLong254CharsAndDot_Throws() + + "llllly.I.Will.Get.To.The.Eeeeeeeeeend.Almost.There.Are")] // very long name but not too long + public async Task DnsGetHostEntry_BadName_ThrowsSocketException(string hostNameOrAddress) { - Assert.ThrowsAny(() => Dns.GetHostEntry( - "Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualllllllly.I.Will.Get.To.The.Eeeee" - + "eeeeend.Almost.There.Are.We.Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualll" - + "llllly.I.Will.Get.To.The.Eeeeeeeeeend.Almost.There.Are.")); + Assert.ThrowsAny(() => Dns.GetHostEntry(hostNameOrAddress)); + await Assert.ThrowsAnyAsync(() => Dns.GetHostEntryAsync(hostNameOrAddress)); + await Assert.ThrowsAnyAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, hostNameOrAddress, null)); } - [Fact] - public void DnsGetHostEntry_HostTooLong255Chars_Throws() - { - Assert.ThrowsAny(() => Dns.GetHostEntry( - "Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualllllllly.I.Will.Get.To.The.Eeeee" + [Theory] + [InlineData("Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualllllllly.I.Will.Get.To.The.Eeeee" + "eeeeend.Almost.There.Are.We.Really.Long.Name.Over.One.Hundred.And.Twenty.Six.Chars.Eeeeeeeventualll" - + "llllly.I.Will.Get.To.The.Eeeeeeeeeend.Almost.There.Aret")); + + "llllly.I.Will.Get.To.The.Eeeeeeeeeend.Almost.There.Aret")] + public async Task DnsGetHostEntry_BadName_ThrowsArgumentOutOfRangeException(string hostNameOrAddress) + { + Assert.ThrowsAny(() => Dns.GetHostEntry(hostNameOrAddress)); + await Assert.ThrowsAnyAsync(() => Dns.GetHostEntryAsync(hostNameOrAddress)); + await Assert.ThrowsAnyAsync(() => Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, hostNameOrAddress, null)); } - [Fact] - public void DnsGetHostEntry_LocalHost_ReturnsFqdnAndLoopbackIPs() + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public async Task DnsGetHostEntry_LocalHost_ReturnsFqdnAndLoopbackIPs(int mode) { - IPHostEntry entry = Dns.GetHostEntry("localhost"); + IPHostEntry entry = mode switch + { + 0 => Dns.GetHostEntry("localhost"), + 1 => await Dns.GetHostEntryAsync("localhost"), + _ => await Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, "localhost", null) + }; Assert.NotNull(entry.HostName); Assert.True(entry.HostName.Length > 0, "Empty host name"); - Assert.True(entry.AddressList.Length >= 1, "No local IPs"); - foreach (IPAddress addr in entry.AddressList) - Assert.True(IPAddress.IsLoopback(addr), "Not a loopback address: " + addr); + Assert.All(entry.AddressList, addr => Assert.True(IPAddress.IsLoopback(addr), "Not a loopback address: " + addr)); } - [Fact] - public void DnsGetHostEntry_LoopbackIP_MatchesGetHostEntryLoopbackString() + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public async Task DnsGetHostEntry_LoopbackIP_MatchesGetHostEntryLoopbackString(int mode) { - IPHostEntry ipEntry = Dns.GetHostEntry(IPAddress.Loopback); - IPHostEntry stringEntry = Dns.GetHostEntry(IPAddress.Loopback.ToString()); + IPAddress address = IPAddress.Loopback; + + IPHostEntry ipEntry = mode switch + { + 0 => Dns.GetHostEntry(address), + 1 => await Dns.GetHostEntryAsync(address), + _ => await Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, address, null) + }; + IPHostEntry stringEntry = mode switch + { + 0 => Dns.GetHostEntry(address.ToString()), + 1 => await Dns.GetHostEntryAsync(address.ToString()), + _ => await Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, address.ToString(), null) + }; Assert.Equal(ipEntry.HostName, stringEntry.HostName); Assert.Equal(ipEntry.AddressList, stringEntry.AddressList); } - - [Fact] - public void DnsGetHostEntry_UnknownUnicodeHost_HostNotFound() - { - // This would succeed if the name was registered in DNS - Assert.ThrowsAny(() => Dns.GetHostEntry("Test-\u65B0-Unicode")); - } - - [Fact] - public void DnsGetHostEntry_UnknownPunicodeHost_HostNotFound() - { - // This would succeed if the name was registered in DNS - Assert.ThrowsAny(() => Dns.GetHostEntry("xn--test--unicode-0b01a")); - } } } diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/TestSettings.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/TestSettings.cs index 0a76e96..5741d1a 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/TestSettings.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/TestSettings.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Net; using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; namespace System.Net.NameResolution.Tests diff --git a/src/libraries/System.Net.NameResolution/tests/PalTests/Fakes/IPAddressFakeExtensions.cs b/src/libraries/System.Net.NameResolution/tests/PalTests/Fakes/IPAddressFakeExtensions.cs index e6777d4..96e5ad4 100644 --- a/src/libraries/System.Net.NameResolution/tests/PalTests/Fakes/IPAddressFakeExtensions.cs +++ b/src/libraries/System.Net.NameResolution/tests/PalTests/Fakes/IPAddressFakeExtensions.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; - namespace System.Net { internal static class IPAddressFakeExtensions diff --git a/src/libraries/System.Net.NameResolution/tests/PalTests/NameResolutionPalTests.cs b/src/libraries/System.Net.NameResolution/tests/PalTests/NameResolutionPalTests.cs index d0dab1c..ed10320 100644 --- a/src/libraries/System.Net.NameResolution/tests/PalTests/NameResolutionPalTests.cs +++ b/src/libraries/System.Net.NameResolution/tests/PalTests/NameResolutionPalTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Net.Sockets; -using System.Net.Test.Common; using System.Runtime.InteropServices; using Xunit; using Xunit.Abstractions; @@ -36,29 +35,31 @@ namespace System.Net.NameResolution.PalTests Assert.NotNull(NameResolutionPal.GetHostName()); } - [Fact] - public void TryGetAddrInfo_LocalHost() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TryGetAddrInfo_LocalHost(bool justAddresses) { - IPHostEntry hostEntry; - int nativeErrorCode; - SocketError error = NameResolutionPal.TryGetAddrInfo("localhost", out hostEntry, out nativeErrorCode); + SocketError error = NameResolutionPal.TryGetAddrInfo("localhost", justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); - Assert.NotNull(hostEntry.HostName); - Assert.NotNull(hostEntry.AddressList); - Assert.NotNull(hostEntry.Aliases); + if (!justAddresses) + { + Assert.NotNull(hostName); + } + Assert.NotNull(aliases); + Assert.NotNull(addresses); } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [OuterLoop("Uses external server")] - public void TryGetAddrInfo_HostName() + public void TryGetAddrInfo_HostName(bool justAddresses) { string hostName = NameResolutionPal.GetHostName(); Assert.NotNull(hostName); - IPHostEntry hostEntry; - int nativeErrorCode; - SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, out hostEntry, out nativeErrorCode); + SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses, out hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); if (error == SocketError.HostNotFound && (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))) { // On Unix, we are not guaranteed to be able to resove the local host. The ability to do so depends on the @@ -67,10 +68,12 @@ namespace System.Net.NameResolution.PalTests } Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); - Assert.NotNull(hostEntry.HostName); - Assert.NotNull(hostEntry.AddressList); - Assert.NotNull(hostEntry.Aliases); + if (!justAddresses) + { + Assert.NotNull(hostName); + } + Assert.NotNull(aliases); + Assert.NotNull(addresses); } [Fact] @@ -101,13 +104,13 @@ namespace System.Net.NameResolution.PalTests [Fact] public void TryGetAddrInfo_LocalHost_TryGetNameInfo() { - IPHostEntry hostEntry; - int nativeErrorCode; - SocketError error = NameResolutionPal.TryGetAddrInfo("localhost", out hostEntry, out nativeErrorCode); + SocketError error = NameResolutionPal.TryGetAddrInfo("localhost", justAddresses: false, out string hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); + Assert.NotNull(hostName); + Assert.NotNull(aliases); + Assert.NotNull(addresses); - string name = NameResolutionPal.TryGetNameInfo(hostEntry.AddressList[0], out error, out nativeErrorCode); + string name = NameResolutionPal.TryGetNameInfo(addresses[0], out error, out nativeErrorCode); Assert.Equal(SocketError.Success, error); Assert.NotNull(name); } @@ -119,9 +122,7 @@ namespace System.Net.NameResolution.PalTests string hostName = NameResolutionPal.GetHostName(); Assert.NotNull(hostName); - IPHostEntry hostEntry; - int nativeErrorCode; - SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, out hostEntry, out nativeErrorCode); + SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses: false, out hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); if (error == SocketError.HostNotFound) { // On Unix, getaddrinfo returns host not found, if all the machine discovery settings on the local network @@ -131,9 +132,11 @@ namespace System.Net.NameResolution.PalTests } Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); + Assert.NotNull(hostName); + Assert.NotNull(aliases); + Assert.NotNull(addresses); - string name = NameResolutionPal.TryGetNameInfo(hostEntry.AddressList[0], out error, out nativeErrorCode); + string name = NameResolutionPal.TryGetNameInfo(addresses[0], out error, out nativeErrorCode); if (error == SocketError.HostNotFound) { // On Unix, getaddrinfo returns private ipv4 address for hostname. If the OS doesn't have the @@ -146,35 +149,38 @@ namespace System.Net.NameResolution.PalTests Assert.NotNull(name); } - [Fact] - public void TryGetAddrInfo_ExternalHost() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TryGetAddrInfo_ExternalHost(bool justAddresses) { string hostName = "microsoft.com"; - IPHostEntry hostEntry; - int nativeErrorCode; - SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, out hostEntry, out nativeErrorCode); + SocketError error = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses, out hostName, out string[] aliases, out IPAddress[] addresses, out _); Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); + Assert.NotNull(aliases); + Assert.NotNull(addresses); } - [Fact] - public void TryGetNameInfo_LocalHost_IPv4_TryGetAddrInfo() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TryGetNameInfo_LocalHost_IPv4_TryGetAddrInfo(bool justAddresses) { - SocketError error; - int nativeErrorCode; - string name = NameResolutionPal.TryGetNameInfo(new IPAddress(new byte[] { 127, 0, 0, 1 }), out error, out nativeErrorCode); + string name = NameResolutionPal.TryGetNameInfo(new IPAddress(new byte[] { 127, 0, 0, 1 }), out SocketError error, out _); Assert.Equal(SocketError.Success, error); Assert.NotNull(name); - IPHostEntry hostEntry; - error = NameResolutionPal.TryGetAddrInfo(name, out hostEntry, out nativeErrorCode); + error = NameResolutionPal.TryGetAddrInfo(name, justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out _); Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); + Assert.NotNull(aliases); + Assert.NotNull(addresses); } - [Fact] - public void TryGetNameInfo_LocalHost_IPv6_TryGetAddrInfo() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TryGetNameInfo_LocalHost_IPv6_TryGetAddrInfo(bool justAddresses) { SocketError error; int nativeErrorCode; @@ -187,15 +193,15 @@ namespace System.Net.NameResolution.PalTests Assert.Equal(SocketError.Success, error); Assert.NotNull(name); - IPHostEntry hostEntry; - error = NameResolutionPal.TryGetAddrInfo(name, out hostEntry, out nativeErrorCode); + error = NameResolutionPal.TryGetAddrInfo(name, justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out _); if (SocketError.Success != error && Environment.OSVersion.Platform == PlatformID.Unix) { LogUnixInfo(); } Assert.Equal(SocketError.Success, error); - Assert.NotNull(hostEntry); + Assert.NotNull(aliases); + Assert.NotNull(addresses); } [Fact] diff --git a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj index 1679260..174a698 100644 --- a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj +++ b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj @@ -22,12 +22,6 @@ Common\System\Net\Logging\NetEventSource.cs - - ProductionCode\System\Net\NameResolutionUtilities.cs - - - ProductionCode\System\Net\DnsResolveAsyncResult.cs - Common\System\Net\Sockets\ProtocolType.cs @@ -77,9 +71,6 @@ Interop\Windows\Interop.Libraries.cs - - Interop\Windows\WinSock\AddressInfo.cs - Interop\Windows\WinSock\AddressInfoHints.cs @@ -98,9 +89,6 @@ Interop\Windows\WinSock\Interop.GetAddrInfoW.cs - - Interop\Windows\Winsock\Interop.freeaddinfo.cs - Interop\Windows\WinSock\Interop.WSAStartup.cs @@ -113,12 +101,6 @@ Common\System\Net\Sockets\ProtocolFamily.cs - - Interop\Windows\WinSock\SafeFreeAddrInfo.cs - - - Interop\Windows\WinSock\AddressInfoEx.cs - Interop\Windows\WinSock\Interop.GetAddrInfoExW.cs diff --git a/src/libraries/System.Net.NameResolution/tests/UnitTests/Fakes/FakeNameResolutionPal.cs b/src/libraries/System.Net.NameResolution/tests/UnitTests/Fakes/FakeNameResolutionPal.cs index 69d1bc9..e66b3e0 100644 --- a/src/libraries/System.Net.NameResolution/tests/UnitTests/Fakes/FakeNameResolutionPal.cs +++ b/src/libraries/System.Net.NameResolution/tests/UnitTests/Fakes/FakeNameResolutionPal.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; using System.Net.Sockets; -using System.Threading; +using System.Threading.Tasks; namespace System.Net { @@ -35,7 +34,7 @@ namespace System.Net FakesEnsureSocketsAreInitializedCallCount++; } - internal static SocketError TryGetAddrInfo(string hostName, out IPHostEntry ipHostEntry, out int nativeErrorCode) + internal static SocketError TryGetAddrInfo(string name, bool justAddresses, out string hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode) { throw new NotImplementedException(); } @@ -51,7 +50,7 @@ namespace System.Net throw new NotImplementedException(); } - internal static void GetAddrInfoAsync(DnsResolveAsyncResult asyncResult) + internal static Task GetAddrInfoAsync(string hostName, bool justAddresses) { throw new NotImplementedException(); } diff --git a/src/libraries/System.Net.NameResolution/tests/UnitTests/System.Net.NameResolution.Unit.Tests.csproj b/src/libraries/System.Net.NameResolution/tests/UnitTests/System.Net.NameResolution.Unit.Tests.csproj index f8eb745..7a6818e 100644 --- a/src/libraries/System.Net.NameResolution/tests/UnitTests/System.Net.NameResolution.Unit.Tests.csproj +++ b/src/libraries/System.Net.NameResolution/tests/UnitTests/System.Net.NameResolution.Unit.Tests.csproj @@ -14,11 +14,8 @@ ProductionCode\System\Net\IPHostEntry.cs - - ProductionCode\System\Net\DNS.cs - - - ProductionCode\System\Net\DnsResolveAsyncResult.cs + + ProductionCode\System\Net\Dns.cs @@ -43,5 +40,8 @@ Common\System\Net\InternalException.cs + + Common\CoreLib\System\Threading\Tasks\TaskToApm.cs + \ No newline at end of file