Skip to content

Commit 34218ec

Browse files
JeffCyrstephentoub
authored andcommitted
Modified Dns.GetHostAddressesAsync to be truly async (dotnet/corefx#26850)
* Modified Dns.GetHostAddressesAsync to be truly async * Applied code review recommendations * Unix build fix * Unix build fix dotnet/corefx#2 * Unix build fix dotnet/corefx#3 * NETFX build fix * Fixed useGetHostByName logic * Simplified ProcessResult code * Cleaned up cancel code * cleanup Commit migrated from dotnet/corefx@d3ff31e
1 parent 2cbd921 commit 34218ec

15 files changed

+523
-88
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Net.Internals;
6+
using System.Runtime.InteropServices;
7+
8+
namespace System.Net.Sockets
9+
{
10+
[StructLayout(LayoutKind.Sequential)]
11+
internal unsafe struct AddressInfoEx
12+
{
13+
internal AddressInfoHints ai_flags;
14+
internal AddressFamily ai_family;
15+
internal SocketType ai_socktype;
16+
internal ProtocolFamily ai_protocol;
17+
internal int ai_addrlen;
18+
internal IntPtr ai_canonname; // Ptr to the canonical name - check for NULL
19+
internal byte* ai_addr; // Ptr to the sockaddr structure
20+
internal IntPtr ai_blob; // Unused ptr to blob data about provider
21+
internal int ai_bloblen;
22+
internal IntPtr ai_provider; // Unused ptr to the namespace provider guid
23+
internal AddressInfoEx* ai_next; // Next structure in linked list
24+
}
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Net.Sockets;
7+
using System.Runtime.InteropServices;
8+
using System.Threading;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class Winsock
13+
{
14+
internal const string GetAddrInfoExCancelFunctionName = "GetAddrInfoExCancel";
15+
16+
internal unsafe delegate void LPLOOKUPSERVICE_COMPLETION_ROUTINE([In] int dwError, [In] int dwBytes, [In] NativeOverlapped* lpOverlapped);
17+
18+
[DllImport(Interop.Libraries.Ws2_32, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
19+
internal static extern unsafe int GetAddrInfoExW(
20+
[In] string pName,
21+
[In] string pServiceName,
22+
[In] int dwNamespace,
23+
[In] IntPtr lpNspId,
24+
[In] ref AddressInfoEx pHints,
25+
[Out] out AddressInfoEx* ppResult,
26+
[In] IntPtr timeout,
27+
[In] ref NativeOverlapped lpOverlapped,
28+
[In] LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine,
29+
[Out] out IntPtr lpNameHandle
30+
);
31+
32+
[DllImport("ws2_32.dll", ExactSpelling = true, SetLastError = true)]
33+
internal static extern unsafe void FreeAddrInfoEx([In] AddressInfoEx* pAddrInfo);
34+
}
35+
}
36+

src/libraries/Common/src/Interop/Windows/kernel32/Interop.LoadLibraryEx.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal partial class Interop
1212
internal partial class Kernel32
1313
{
1414
public const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
15+
public const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800;
1516

1617
[DllImport(Libraries.Kernel32, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
1718
public static extern SafeLibraryHandle LoadLibraryExW([In] string lpwLibFileName, [In] IntPtr hFile, [In] uint dwFlags);

src/libraries/Common/src/System/Net/SocketAddressPal.Unix.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ public static unsafe void SetPort(byte[] buffer, ushort port)
102102
ThrowOnFailure(err);
103103
}
104104

105-
public static unsafe uint GetIPv4Address(byte[] buffer)
105+
public static unsafe uint GetIPv4Address(ReadOnlySpan<byte> buffer)
106106
{
107107
uint ipAddress;
108108
Interop.Error err;
109-
fixed (byte* rawAddress = buffer)
109+
fixed (byte* rawAddress = &MemoryMarshal.GetReference(buffer))
110110
{
111111
err = Interop.Sys.GetIPv4Address(rawAddress, buffer.Length, &ipAddress);
112112
}
@@ -115,11 +115,11 @@ public static unsafe uint GetIPv4Address(byte[] buffer)
115115
return ipAddress;
116116
}
117117

118-
public static unsafe void GetIPv6Address(byte[] buffer, Span<byte> address, out uint scope)
118+
public static unsafe void GetIPv6Address(ReadOnlySpan<byte> buffer, Span<byte> address, out uint scope)
119119
{
120120
uint localScope;
121121
Interop.Error err;
122-
fixed (byte* rawAddress = buffer)
122+
fixed (byte* rawAddress = &MemoryMarshal.GetReference(buffer))
123123
fixed (byte* ipAddress = &MemoryMarshal.GetReference(address))
124124
{
125125
err = Interop.Sys.GetIPv6Address(rawAddress, buffer.Length, ipAddress, address.Length, &localScope);

src/libraries/Common/src/System/Net/SocketAddressPal.Windows.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static unsafe void SetPort(byte[] buffer, ushort port)
3838
port.HostToNetworkBytes(buffer, 2);
3939
}
4040

41-
public static unsafe uint GetIPv4Address(byte[] buffer)
41+
public static unsafe uint GetIPv4Address(ReadOnlySpan<byte> buffer)
4242
{
4343
unchecked
4444
{
@@ -49,7 +49,7 @@ public static unsafe uint GetIPv4Address(byte[] buffer)
4949
}
5050
}
5151

52-
public static unsafe void GetIPv6Address(byte[] buffer, Span<byte> address, out uint scope)
52+
public static unsafe void GetIPv6Address(ReadOnlySpan<byte> buffer, Span<byte> address, out uint scope)
5353
{
5454
for (int i = 0; i < address.Length; i++)
5555
{

src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<AssemblyName>System.Net.NameResolution</AssemblyName>
66
<ProjectGuid>{1714448C-211E-48C1-8B7E-4EE667D336A1}</ProjectGuid>
77
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8-
</PropertyGroup>
8+
</PropertyGroup>
99
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='uap-Windows_NT-Debug|AnyCPU'" />
1010
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='uap-Windows_NT-Release|AnyCPU'" />
1111
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='netcoreapp-Windows_NT-Debug|AnyCPU'" />
@@ -46,6 +46,10 @@
4646
<Compile Include="$(CommonPath)\System\Net\IPEndPointStatics.cs">
4747
<Link>Common\System\Net\IPEndPointStatics.cs</Link>
4848
</Compile>
49+
<Compile Include="$(CommonPath)\System\Net\ByteOrder.cs">
50+
<Link>Common\System\Net\ByteOrder.cs</Link>
51+
</Compile>
52+
<Compile Include="System\Net\DnsResolveAsyncResult.cs" />
4953
</ItemGroup>
5054
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
5155
<Compile Include="System\Net\NameResolutionPal.Windows.cs" />
@@ -72,6 +76,9 @@
7276
<Compile Include="$(CommonPath)\System\Net\SocketProtocolSupportPal.Windows.cs">
7377
<Link>Common\System\Net\SocketProtocolSupportPal.Windows</Link>
7478
</Compile>
79+
<Compile Include="$(CommonPath)\System\Net\SocketAddressPal.Windows.cs">
80+
<Link>Common\System\Net\SocketAddressPal.Windows</Link>
81+
</Compile>
7582
<!-- Interop -->
7683
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
7784
<Link>Interop\Windows\Interop.Libraries.cs</Link>
@@ -118,12 +125,27 @@
118125
<Compile Include="$(CommonPath)\Interop\Windows\Winsock\SafeFreeAddrInfo.cs">
119126
<Link>Interop\Windows\Winsock\SafeFreeAddrInfo.cs</Link>
120127
</Compile>
128+
<Compile Include="$(CommonPath)\Interop\Windows\Winsock\AddressInfoEx.cs">
129+
<Link>Interop\Windows\Winsock\AddressInfoEx.cs</Link>
130+
</Compile>
131+
<Compile Include="$(CommonPath)\Interop\Windows\Winsock\Interop.GetAddrInfoExW.cs">
132+
<Link>Interop\Windows\Winsock\Interop.GetAddrInfoExW.cs</Link>
133+
</Compile>
134+
<Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeLibraryHandle.cs">
135+
<Link>Common\Microsoft\Win32\SafeHandles\SafeLibraryHandle.cs</Link>
136+
</Compile>
137+
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.GetProcAddress.cs">
138+
<Link>Interop\Windows\Kernel32\Interop.GetProcAddress.cs</Link>
139+
</Compile>
140+
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.LoadLibraryEx.cs">
141+
<Link>Interop\Windows\Kernel32\Interop.LoadLibraryEx.cs</Link>
142+
</Compile>
143+
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.FreeLibrary.cs">
144+
<Link>Interop\Windows\Kernel32\Interop.FreeLibrary.cs</Link>
145+
</Compile>
121146
</ItemGroup>
122147
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
123148
<Compile Include="System\Net\NameResolutionPal.Unix.cs" />
124-
<Compile Include="$(CommonPath)\System\Net\ByteOrder.cs">
125-
<Link>Common\System\Net\Internals\ByteOrder.cs</Link>
126-
</Compile>
127149
<Compile Include="$(CommonPath)\System\Net\ContextAwareResult.Unix.cs">
128150
<Link>Common\System\Net\ContextAwareResult.Unix.cs</Link>
129151
</Compile>
@@ -189,7 +211,8 @@
189211
<Reference Include="System.Security.Claims" />
190212
<Reference Include="System.Security.Principal.Windows" />
191213
<Reference Include="System.Threading" />
214+
<Reference Include="System.Threading.Overlapped" />
192215
<Reference Include="System.Threading.Tasks" />
193216
</ItemGroup>
194217
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
195-
</Project>
218+
</Project>

src/libraries/System.Net.NameResolution/src/System/Net/DNS.cs

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,22 @@ public static IPHostEntry GetHostByName(string hostName)
4141
return InternalGetHostByName(hostName, false);
4242
}
4343

44-
private static IPHostEntry InternalGetHostByName(string hostName, bool includeIPv6)
44+
private static void ValidateHostName(string hostName)
4545
{
46-
if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName);
47-
IPHostEntry ipHostEntry = null;
48-
4946
if (hostName.Length > MaxHostName // If 255 chars, the last one must be a dot.
5047
|| hostName.Length == MaxHostName && hostName[MaxHostName - 1] != '.')
5148
{
5249
throw new ArgumentOutOfRangeException(nameof(hostName), SR.Format(SR.net_toolong,
5350
nameof(hostName), MaxHostName.ToString(NumberFormatInfo.CurrentInfo)));
5451
}
52+
}
53+
54+
private static IPHostEntry InternalGetHostByName(string hostName, bool includeIPv6)
55+
{
56+
if (NetEventSource.IsEnabled) NetEventSource.Enter(null, hostName);
57+
IPHostEntry ipHostEntry = null;
58+
59+
ValidateHostName(hostName);
5560

5661
//
5762
// IPv6 Changes: IPv6 requires the use of getaddrinfo() rather
@@ -252,42 +257,19 @@ public static IPHostEntry Resolve(string hostName)
252257
return ipHostEntry;
253258
}
254259

255-
private class ResolveAsyncResult : ContextAwareResult
256-
{
257-
// Forward lookup
258-
internal ResolveAsyncResult(string hostName, object myObject, bool includeIPv6, object myState, AsyncCallback myCallBack) :
259-
base(myObject, myState, myCallBack)
260-
{
261-
this.hostName = hostName;
262-
this.includeIPv6 = includeIPv6;
263-
}
264-
265-
// Reverse lookup
266-
internal ResolveAsyncResult(IPAddress address, object myObject, bool includeIPv6, object myState, AsyncCallback myCallBack) :
267-
base(myObject, myState, myCallBack)
268-
{
269-
this.includeIPv6 = includeIPv6;
270-
this.address = address;
271-
}
272-
273-
internal readonly string hostName;
274-
internal bool includeIPv6;
275-
internal IPAddress address;
276-
}
277-
278260
private static void ResolveCallback(object context)
279261
{
280-
ResolveAsyncResult result = (ResolveAsyncResult)context;
262+
DnsResolveAsyncResult result = (DnsResolveAsyncResult)context;
281263
IPHostEntry hostEntry;
282264
try
283265
{
284-
if (result.address != null)
266+
if (result.IpAddress != null)
285267
{
286-
hostEntry = InternalGetHostByAddress(result.address, result.includeIPv6);
268+
hostEntry = InternalGetHostByAddress(result.IpAddress, result.IncludeIPv6);
287269
}
288270
else
289271
{
290-
hostEntry = InternalGetHostByName(result.hostName, result.includeIPv6);
272+
hostEntry = InternalGetHostByName(result.HostName, result.IncludeIPv6);
291273
}
292274
}
293275
catch (OutOfMemoryException)
@@ -315,20 +297,20 @@ private static IAsyncResult HostResolutionBeginHelper(string hostName, bool just
315297
if (NetEventSource.IsEnabled) NetEventSource.Info(null, hostName);
316298

317299
// See if it's an IP Address.
318-
IPAddress address;
319-
ResolveAsyncResult asyncResult;
320-
if (IPAddress.TryParse(hostName, out address))
300+
IPAddress ipAddress;
301+
DnsResolveAsyncResult asyncResult;
302+
if (IPAddress.TryParse(hostName, out ipAddress))
321303
{
322-
if (throwOnIIPAny && (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any)))
304+
if (throwOnIIPAny && (ipAddress.Equals(IPAddress.Any) || ipAddress.Equals(IPAddress.IPv6Any)))
323305
{
324306
throw new ArgumentException(SR.net_invalid_ip_addr, nameof(hostName));
325307
}
326308

327-
asyncResult = new ResolveAsyncResult(address, null, includeIPv6, state, requestCallback);
309+
asyncResult = new DnsResolveAsyncResult(ipAddress, null, includeIPv6, state, requestCallback);
328310

329311
if (justReturnParsedIp)
330312
{
331-
IPHostEntry hostEntry = NameResolutionUtilities.GetUnresolvedAnswer(address);
313+
IPHostEntry hostEntry = NameResolutionUtilities.GetUnresolvedAnswer(ipAddress);
332314
asyncResult.StartPostingAsyncOp(false);
333315
asyncResult.InvokeCallback(hostEntry);
334316
asyncResult.FinishPostingAsyncOp();
@@ -337,19 +319,29 @@ private static IAsyncResult HostResolutionBeginHelper(string hostName, bool just
337319
}
338320
else
339321
{
340-
asyncResult = new ResolveAsyncResult(hostName, null, includeIPv6, state, requestCallback);
322+
asyncResult = new DnsResolveAsyncResult(hostName, null, includeIPv6, state, requestCallback);
341323
}
342324

343325
// Set up the context, possibly flow.
344326
asyncResult.StartPostingAsyncOp(false);
345327

346-
// Start the resolve.
347-
Task.Factory.StartNew(
348-
s => ResolveCallback(s),
349-
asyncResult,
350-
CancellationToken.None,
351-
TaskCreationOptions.DenyChildAttach,
352-
TaskScheduler.Default);
328+
// If the OS supports it and 'hostName' is not an IP Address, resolve the name asynchronously
329+
// instead of calling the synchronous version in the ThreadPool.
330+
if (NameResolutionPal.SupportsGetAddrInfoAsync && ipAddress == null)
331+
{
332+
ValidateHostName(hostName);
333+
NameResolutionPal.GetAddrInfoAsync(asyncResult);
334+
}
335+
else
336+
{
337+
// Start the resolve.
338+
Task.Factory.StartNew(
339+
s => ResolveCallback(s),
340+
asyncResult,
341+
CancellationToken.None,
342+
TaskCreationOptions.DenyChildAttach,
343+
TaskScheduler.Default);
344+
}
353345

354346
// Finish the flowing, maybe it completed? This does nothing if we didn't initiate the flowing above.
355347
asyncResult.FinishPostingAsyncOp();
@@ -371,7 +363,7 @@ private static IAsyncResult HostResolutionBeginHelper(IPAddress address, bool fl
371363
if (NetEventSource.IsEnabled) NetEventSource.Info(null, address);
372364

373365
// Set up the context, possibly flow.
374-
ResolveAsyncResult asyncResult = new ResolveAsyncResult(address, null, includeIPv6, state, requestCallback);
366+
DnsResolveAsyncResult asyncResult = new DnsResolveAsyncResult(address, null, includeIPv6, state, requestCallback);
375367
if (flowContext)
376368
{
377369
asyncResult.StartPostingAsyncOp(false);
@@ -399,7 +391,7 @@ private static IPHostEntry HostResolutionEndHelper(IAsyncResult asyncResult)
399391
{
400392
throw new ArgumentNullException(nameof(asyncResult));
401393
}
402-
ResolveAsyncResult castedResult = asyncResult as ResolveAsyncResult;
394+
DnsResolveAsyncResult castedResult = asyncResult as DnsResolveAsyncResult;
403395
if (castedResult == null)
404396
{
405397
throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
@@ -611,7 +603,7 @@ public static IPHostEntry EndResolve(IAsyncResult asyncResult)
611603
}
612604
catch (SocketException ex)
613605
{
614-
IPAddress address = ((ResolveAsyncResult)asyncResult).address;
606+
IPAddress address = ((DnsResolveAsyncResult)asyncResult).IpAddress;
615607
if (address == null)
616608
throw; // BeginResolve was called with a HostName, not an IPAddress
617609

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace System.Net
6+
{
7+
internal sealed class DnsResolveAsyncResult : ContextAwareResult
8+
{
9+
internal string HostName { get; }
10+
internal bool IncludeIPv6 { get; }
11+
internal IPAddress IpAddress { get; }
12+
13+
// Forward lookup
14+
internal DnsResolveAsyncResult(string hostName, object myObject, bool includeIPv6, object myState, AsyncCallback myCallBack)
15+
: base(myObject, myState, myCallBack)
16+
{
17+
HostName = hostName;
18+
IncludeIPv6 = includeIPv6;
19+
}
20+
21+
// Reverse lookup
22+
internal DnsResolveAsyncResult(IPAddress ipAddress, object myObject, bool includeIPv6, object myState, AsyncCallback myCallBack)
23+
: base(myObject, myState, myCallBack)
24+
{
25+
IncludeIPv6 = includeIPv6;
26+
IpAddress = ipAddress;
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)