Closed Daniel15 closed 4 years ago
So NameResolutionPal.TryGetNameInfo
returns the right hostname, but Dns.GetHostEntryAsync
doesn't. My guess is that it's something to do with this logic that does a forward lookup right after performing the reverse lookup:
Is there some way to avoid this? I just want the PTR record, I don't care about if the forward lookup matches.
NameResolutionPal.TryGetNameInfo
is perfect for my use case, but unfortunately it's internal-only 😕 I was testing by copying and pasting the code into my app, but obviously that's not really maintainable.
Here's my example code where I copied and pasted the NameResolutionPal
internals to directly test TryGetNameInfo
:
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace DnsTest
{
class Program
{
static async Task Main(string[] args)
{
await Task.WhenAll(
Lookup("118.127.7.26"),
Lookup("203.26.198.8"),
Lookup("141.0.104.145")
);
}
static async Task Lookup(string ip)
{
var result = await Dns.GetHostEntryAsync(ip);
Console.WriteLine($"{ip} Dns.GetHostEntryAsync = {result.HostName}");
var result2 = TryGetNameInfo(IPAddress.Parse(ip), out var socketError, out var nativeErrorCode);
Console.WriteLine($"{ip} TryGetNameInfo = {result2}");
}
public static unsafe string TryGetNameInfo(IPAddress addr, out SocketError socketError, out int nativeErrorCode)
{
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.PtrToStringAnsi((IntPtr)buffer) : null;
}
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();
default:
Debug.Fail("Unexpected error: " + error.ToString());
return SocketError.SocketError;
}
}
}
internal static partial class Interop
{
internal static partial class Sys
{
internal const int NI_MAXHOST = 1025;
[Flags]
internal enum GetNameInfoFlags : int
{
NI_NAMEREQD = 0x1,
NI_NUMERICHOST = 0x2,
}
internal enum GetAddrInfoErrorFlags : int
{
EAI_AGAIN = 1, // Temporary failure in name resolution.
EAI_BADFLAGS = 2, // Invalid value for `ai_flags' field.
EAI_FAIL = 3, // Non-recoverable failure in name resolution.
EAI_FAMILY = 4, // 'ai_family' not supported.
EAI_NONAME = 5, // NAME or SERVICE is unknown.
EAI_BADARG = 6, // One or more input arguments were invalid.
EAI_NOMORE = 7, // No more entries are present in the list.
EAI_MEMORY = 8, // Out of memory.
}
[DllImport("System.Native", EntryPoint = "SystemNative_GetNameInfo")] // Libraries.SystemNative
internal static extern unsafe int GetNameInfo(
byte* address,
uint addressLength,
byte isIpv6,
byte* host,
uint hostLength,
byte* service,
uint serviceLength,
GetNameInfoFlags flags);
}
}
internal static class IPAddressParserStatics
{
public const int IPv4AddressBytes = 4;
public const int IPv6AddressBytes = 16;
public const int IPv6AddressShorts = IPv6AddressBytes / 2;
}
}
Output when I run it:
% ./DnsTest
118.127.7.26 Dns.GetHostEntryAsync = d.sb
118.127.7.26 TryGetNameInfo = rtif-118.127.7.26-31.cs-1.as45671.net.au
203.26.198.8 Dns.GetHostEntryAsync = d.sb
203.26.198.8 TryGetNameInfo = ten2-2.br-1-1.as45671.net.au
141.0.104.145 Dns.GetHostEntryAsync = d.sb
141.0.104.145 TryGetNameInfo = 141.0.104.145.static.lyse.net
So GetHostEntryAsync
is definitely acting weird here. I might just use DnsClient.NET instead of the framework DNS code since it seems more reliable for this use case.
The Dns
class is a bit of a misnomer. It is not just DNS, but rather a name resolver -- it can look at more things than just DNS (e.g. /etc/hosts, NetBIOS names, etc.). So the result might not be incorrect here -- is it returning just your local domain name and stopping there, or also returning DNS-based domains?
Turns out the issue is that I have a wildcard subdomain (so [anything].d.sb
returns a valid IP), which was causing this behaviour. ping
actually exhibited the same behaviour:
% ping asdfasdfasdfasdf
PING d.sb (209.141.56.29) 56(84) bytes of data.
64 bytes from d.sb (209.141.56.29): icmp_seq=1 ttl=64 time=0.063 ms
^C
I found this interesting section in the resolv.conf
manpage (http://man7.org/linux/man-pages/man5/resolv.conf.5.html):
search Search list for host-name lookup. By default, the search list contains one entry, the local domain name. It is determined from the local hostname returned by gethostname(2); the local domain name is taken to be everything after the first '.'. Finally, if the hostname does not contain a '.', the root domain is assumed as the local domain name.
So what the Linux DNS resolver was doing was concatenating .d.sb
to the end of the requested hostname.
Adding
search .
to /etc/resolv.conf
fixed it, as it prevents the behaviour.
Dns.GetHostEntryAsync
is returning weird results for me. Calling it on several IPs such as118.127.7.26
,203.26.198.8
and141.0.104.145
are returning my server's domain name as theHostName
value, rather than the proper reverse DNS of the IP address. Thehost
command on the server does return the correct values though:How can I debug this? How does the DNS resolution in the framework work?