|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Versioning;
namespace System.Net
{
internal static partial class NameResolutionPal
{
public const bool SupportsGetAddrInfoAsync = false;
[UnsupportedOSPlatformGuard("wasi")]
public static bool SupportsGetNameInfo => !OperatingSystem.IsWasi();
#pragma warning disable IDE0060
internal static Task? GetAddrInfoAsync(string hostName, bool justAddresses, AddressFamily family, CancellationToken cancellationToken) =>
throw new NotSupportedException();
#pragma warning restore IDE0060
private static SocketError GetSocketErrorForNativeError(int error)
{
switch (error)
{
case 0:
return SocketError.Success;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_AGAIN:
return SocketError.TryAgain;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_BADFLAGS:
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_BADARG:
return SocketError.InvalidArgument;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_FAIL:
return SocketError.NoRecovery;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_FAMILY:
return SocketError.AddressFamilyNotSupported;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_NONAME:
return SocketError.HostNotFound;
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_MEMORY:
throw new OutOfMemoryException();
case (int)Interop.Sys.GetAddrInfoErrorFlags.EAI_SYSTEM:
Debug.Fail($"Unexpected error: {error} errno: {Interop.Sys.GetErrNo()}");
return SocketError.SocketError;
default:
Debug.Fail($"Unexpected error: {error}");
return SocketError.SocketError;
}
}
private static unsafe void ParseHostEntry(Interop.Sys.HostEntry hostEntry, bool justAddresses, out string? hostName, out string[] aliases, out IPAddress[] addresses)
{
try
{
hostName = !justAddresses && hostEntry.CanonicalName != null
? Marshal.PtrToStringUTF8((IntPtr)hostEntry.CanonicalName)
: null;
IPAddress[] localAddresses;
if (hostEntry.IPAddressCount == 0)
{
localAddresses = Array.Empty<IPAddress>();
}
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];
int nativeAddressCount = 0;
Interop.Sys.IPAddress* addressHandle = hostEntry.IPAddressList;
for (int i = 0; i < hostEntry.IPAddressCount; i++)
{
Interop.Sys.IPAddress nativeAddr = addressHandle[i];
if (Array.IndexOf(nativeAddresses, nativeAddr, 0, nativeAddressCount) == -1 &&
(!nativeAddr.IsIPv6 || SocketProtocolSupportPal.OSSupportsIPv6)) // Do not include IPv6 addresses if IPV6 support is force-disabled
{
nativeAddresses[nativeAddressCount++] = nativeAddr;
}
}
localAddresses = new IPAddress[nativeAddressCount];
for (int i = 0; i < nativeAddressCount; i++)
{
localAddresses[i] = nativeAddresses[i].GetIPAddress();
}
}
string[] localAliases = Array.Empty<string>();
if (!justAddresses && hostEntry.Aliases != null)
{
int numAliases = 0;
while (hostEntry.Aliases[numAliases] != null)
{
numAliases++;
}
if (numAliases > 0)
{
localAliases = new string[numAliases];
for (int i = 0; i < localAliases.Length; i++)
{
localAliases[i] = Marshal.PtrToStringUTF8((IntPtr)hostEntry.Aliases[i])!;
}
}
}
aliases = localAliases;
addresses = localAddresses;
}
finally
{
Interop.Sys.FreeHostEntry(&hostEntry);
}
}
public static unsafe SocketError TryGetAddrInfo(string name, bool justAddresses, AddressFamily addressFamily, out string? hostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode)
{
if (name == "")
{
// To match documented behavior on Windows, if an empty string is passed in, use the local host's name.
name = Dns.GetHostName();
}
Interop.Sys.HostEntry entry;
int result = Interop.Sys.GetHostEntryForName(name, addressFamily, &entry);
if (result != 0)
{
nativeErrorCode = result;
hostName = name;
aliases = Array.Empty<string>();
addresses = Array.Empty<IPAddress>();
return GetSocketErrorForNativeError(result);
}
ParseHostEntry(entry, justAddresses, out hostName, out aliases, out addresses);
nativeErrorCode = 0;
return SocketError.Success;
}
public static unsafe string? TryGetNameInfo(IPAddress addr, out SocketError socketError, out int nativeErrorCode)
{
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
byte* buffer = stackalloc byte[Interop.Sys.NI_MAXHOST + 1 /*for null*/];
byte isIPv6;
int rawAddressLength;
if (addr.AddressFamily == AddressFamily.InterNetwork)
{
isIPv6 = 0;
rawAddressLength = IPAddressParserStatics.IPv4AddressBytes;
}
else
{
isIPv6 = 1;
rawAddressLength = IPAddressParserStatics.IPv6AddressBytes;
}
byte* rawAddress = stackalloc byte[rawAddressLength];
addr.TryWriteBytes(new Span<byte>(rawAddress, rawAddressLength), out int bytesWritten);
Debug.Assert(bytesWritten == rawAddressLength);
int error = Interop.Sys.GetNameInfo(
rawAddress,
(uint)rawAddressLength,
isIPv6,
buffer,
Interop.Sys.NI_MAXHOST,
null,
0,
Interop.Sys.GetNameInfoFlags.NI_NAMEREQD);
socketError = GetSocketErrorForNativeError(error);
nativeErrorCode = error;
return socketError == SocketError.Success ? Marshal.PtrToStringUTF8((IntPtr)buffer) : null;
}
public static string GetHostName() => Interop.Sys.GetHostName();
}
}
|