ubiety / Ubiety.Dns.Core

Flexible DNS library for .NET Core, and .NET Framework
Apache License 2.0
8 stars 6 forks source link

Dns resolver only supports AddressFamily.InterNetworkV6 #9

Closed robertgins closed 3 years ago

robertgins commented 4 years ago

The dns resolver no longers works with IPV4

Provide an IPV4 dns server to the resolver or by system configuration

DNS should work

problem in all versions of .NET core on windows, Linux or mac

The CTOR's for the tcp client now specify an address family of InterNetworkV6 instead of respecting the address family of the server they are targetin

coder2000 commented 4 years ago

It should work with both as DualMode is enabled. Do you have a specific error when using an IPv4 address?

robertgins commented 4 years ago

Sorry, that was not the best bug report. The host is on Linux (running in AWS Lambda) the specified error is "Address family not supported by protocol family". we have manually seeded the resolver builder with IPV4 addresses. In the code below the WorkingDNSServers is an array of IPEndPoints with IPV4 addresses.

 public static string GetCNameOfHostOrNull(string hostName)
    {
        Ubiety.Logging.Core.UbietyLogger.Initialize(new NullUbietyLogManager());
        ResolverBuilder resBuild = ResolverBuilder.Begin();
        resBuild.EnableLogging(new NullUbietyLogManager());
        resBuild.SetTimeout(1000).SetRetries(3).UseRecursion();
        if (WorkingDNSServers.Length > 0)
        {
            foreach (IPEndPoint dnsServer in WorkingDNSServers)
            {
                resBuild.AddDnsServer(dnsServer);
            }
        }

        Resolver dnsLookup = resBuild.Build();
        string returnValue = null;

        Response dnsResponse = dnsLookup.Query(hostName, Ubiety.Dns.Core.Common.QuestionType.CNAME, Ubiety.Dns.Core.Common.QuestionClass.IN);
        List<Ubiety.Dns.Core.Records.General.RecordCname> cnameRecords = dnsResponse.GetRecords<Ubiety.Dns.Core.Records.General.RecordCname>();
        if (cnameRecords.Count > 0)
        {
            returnValue = cnameRecords[0].Cname;
        }

        return returnValue;
    }
coder2000 commented 4 years ago

First, it seems you are using the builder in a way that it may not work, it should be more like:

  public static string GetCNameOfHostOrNull(string hostName)
     {
         Ubiety.Logging.Core.UbietyLogger.Initialize(new NullUbietyLogManager());
         Resolver dnsLookup = ResolverBuilder.Begin()
             .EnableLogging(new NullUbietyLogManager())
             .SetTimeout(1000).SetRetries(3).UseRecursion()
             .AddDnsServers(WorkingDNSServers).Build();

         string returnValue = null;

         Response dnsResponse = dnsLookup.Query(hostName, Ubiety.Dns.Core.Common.QuestionType.CNAME, Ubiety.Dns.Core.Common.QuestionClass.IN);
         List<Ubiety.Dns.Core.Records.General.RecordCname> cnameRecords = dnsResponse.GetRecords<Ubiety.Dns.Core.Records.General.RecordCname>();
         if (cnameRecords.Count > 0)
         {
             returnValue = cnameRecords[0].Cname;
         }

         return returnValue;
     }

You will also have to update to the new version, 4.2.0, as the AddDnsServers method is only available there.

robertgins commented 4 years ago

The "AddDnsServers" is not functionally different than the loop, one of the other developers in our team did a more in-depth analysis of the issue and it appears that the root cause is the use of DualMode = true for the in environments where both stacks are not installed. So the resolver will fail following the rules below: Lots of code to follow. I tested the following variations (in order): • IPv4 address o InterNetworkV6 (DualMode = true)  fail o InterNetwork (DualMode = true)  fail o InterNetwork (DualMode not specified => false)  success o default constructor (DualMode = true)  fail o default constructor (DualMode not specified => false)  success • IPv6 address o InterNetworkV6 (DualMode = true)  fail o InterNetworkV6 (DualMode not specified => false)  fail o default constructor (DualMode = true)  fail o default constructor (DualMode not specified => false)  fail

What this tells me is that a system (Linux in this case, maybe Windows if extremely configured since it always has IPv6 addresses these days) that is not running on IPv6 cannot use an IPv6 address and cannot use the DualMode option. Solution: don't use InternetworkV6. Let the system figure it out via the default constructor or maybe via the constructor that takes in the IPEndPoint or IPAddress. IPEndPoint objects (via the IPAddress) that are IPv6 will always be parsable separately from IPv4 due to the ':' vs. '.' delimiter in the address. TcpClient should be able to figure this out easily per IPEndpoint in the foreach iterator.

Here's the code if you care (again not color coded):

int port = 53; System.Net.IPAddress ipv4 = System.Net.IPAddress.Parse("8.8.8.8"); System.Net.IPEndPoint epv4 = new System.Net.IPEndPoint(ipv4, port); System.Net.IPAddress ipv6 = System.Net.IPAddress.Parse("2001:4860:4860::8888"); System.Net.IPEndPoint epv6 = new System.Net.IPEndPoint(ipv6, port); try { IPEndPoint ep = epv4; base.LogInformation("Start IPv4 address with InterNetworkV6 (DualMode = true)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(System.Net.Sockets.AddressFamily.InterNetworkV6) { ReceiveTimeout = 60, Client = { DualMode = true } }){ await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv4 address with InterNetworkV6 (DualMode = true) - success"); } catch(Exception ex) { base.LogInformation("IPv4 address with InterNetworkV6 (DualMode = true) - error: " + ex.ToString()); } try { IPEndPoint ep = epv4; base.LogInformation("Start IPv4 address with InterNetwork (DualMode = true)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(System.Net.Sockets.AddressFamily.InterNetwork) { ReceiveTimeout = 60, Client = { DualMode = true } }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv4 address with InterNetwork (DualMode = true) - success"); } catch (Exception ex) { base.LogInformation("IPv4 address with InterNetwork (DualMode = true) - error: " + ex.ToString()); } try { IPEndPoint ep = epv4; base.LogInformation("Start IPv4 address with InterNetwork (DualMode not specified => false)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(System.Net.Sockets.AddressFamily.InterNetwork) { ReceiveTimeout = 60 }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv4 address with InterNetwork (DualMode not specified => false) - success"); } catch (Exception ex) { base.LogInformation("IPv4 address with InterNetwork (DualMode not specified => false) - error: " + ex.ToString()); } try { IPEndPoint ep = epv4; base.LogInformation("Start IPv4 address with default constructor (DualMode = true)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient() { ReceiveTimeout = 60, Client = { DualMode = true } }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv4 address with default constructor (DualMode = true) - success"); } catch (Exception ex) { base.LogInformation("IPv4 address with default constructor (DualMode = true) - error: " + ex.ToString()); } try { IPEndPoint ep = epv4; base.LogInformation("Start IPv4 address with default constructor (DualMode not specified => false)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient() { ReceiveTimeout = 60 }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv4 address with default constructor (DualMode not specified => false) - success"); } catch (Exception ex) { base.LogInformation("IPv4 address with default constructor (DualMode not specified => false) - error: " + ex.ToString()); } try { IPEndPoint ep = epv6; base.LogInformation("Start IPv6 address with InterNetworkV6 (DualMode = true)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(System.Net.Sockets.AddressFamily.InterNetworkV6) { ReceiveTimeout = 60, Client = { DualMode = true } }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv6 address with InterNetworkV6 (DualMode = true) - success"); } catch (Exception ex) { base.LogInformation("IPv6 address with InterNetworkV6 (DualMode = true) - error: " + ex.ToString()); } try { IPEndPoint ep = epv6; base.LogInformation("Start IPv6 address with InterNetworkV6 (DualMode not specified => false)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(System.Net.Sockets.AddressFamily.InterNetworkV6) { ReceiveTimeout = 60 }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv6 address with InterNetworkV6 (DualMode not specified => false) - success"); } catch (Exception ex) { base.LogInformation("IPv6 address with InterNetworkV6 (DualMode not specified => false) - error: " + ex.ToString()); } try { IPEndPoint ep = epv6; base.LogInformation("Start IPv6 address with default constructor (DualMode = true)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient() { ReceiveTimeout = 60, Client = { DualMode = true } }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv6 address with default constructor (DualMode = true) - success"); } catch (Exception ex) { base.LogInformation("IPv6 address with default constructor (DualMode = true) - error: " + ex.ToString()); } try { IPEndPoint ep = epv6; base.LogInformation("Start IPv6 address with default constructor (DualMode not specified => false)"); using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient() { ReceiveTimeout = 60 }) { await client.ConnectAsync(ep.Address, ep.Port).ConfigureAwait(false); } base.LogInformation("IPv6 address with default constructor (DualMode not specified => false) - success"); } catch (Exception ex) { base.LogInformation("IPv6 address with default constructor (DualMode not specified => false) - error: " + ex.ToString()); }

Here's the log information:

Start IPv4 address with InterNetworkV6 (DualMode = true) IPv4 address with InterNetworkV6 (DualMode = true) - error: System.Net.Sockets.SocketException (97): Address family not supported by protocol at System.Net.Sockets.Socket..ctor(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) at System.Net.Sockets.TcpClient..ctor(AddressFamily family) at Test.Main()

Start IPv4 address with InterNetwork (DualMode = true) IPv4 address with InterNetwork (DualMode = true) - error: System.NotSupportedException: This protocol version is not supported. at System.Net.Sockets.Socket.set_DualMode(Boolean value) at Test.Main()

Start IPv4 address with InterNetwork (DualMode not specified => false) IPv4 address with InterNetwork (DualMode not specified => false) - success

Start IPv4 address with default constructor (DualMode = true) IPv4 address with default constructor (DualMode = true) - error: System.NotSupportedException: This protocol version is not supported. at System.Net.Sockets.Socket.set_DualMode(Boolean value) at Test.Main()

Start IPv4 address with default constructor (DualMode not specified => false) IPv4 address with default constructor (DualMode not specified => false) - success

Start IPv6 address with InterNetworkV6 (DualMode = true) IPv6 address with InterNetworkV6 (DualMode = true) - error: System.Net.Sockets.SocketException (97): Address family not supported by protocol at System.Net.Sockets.Socket..ctor(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) at System.Net.Sockets.TcpClient..ctor(AddressFamily family) at Test.Main()

Start IPv6 address with InterNetworkV6 (DualMode not specified => false) IPv6 address with InterNetworkV6 (DualMode not specified => false) - error: System.Net.Sockets.SocketException (97): Address family not supported by protocol at System.Net.Sockets.Socket..ctor(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) at System.Net.Sockets.TcpClient..ctor(AddressFamily family) at Test.Main()

Start IPv6 address with default constructor (DualMode = true) IPv6 address with default constructor (DualMode = true) - error: System.NotSupportedException: This protocol version is not supported. at System.Net.Sockets.Socket.set_DualMode(Boolean value) at Test.Main()

Start IPv6 address with default constructor (DualMode not specified => false) IPv6 address with default constructor (DualMode not specified => false) - error: System.NotSupportedException: This protocol version is not supported. at System.Net.Sockets.Socket.BeginConnect(IPAddress address, Int32 port, AsyncCallback requestCallback, Object state) at System.Net.Sockets.TcpClient.BeginConnect(IPAddress address, Int32 port, AsyncCallback requestCallback, Object state) at System.Net.Sockets.TcpClient.<>c.b__27_0(IPAddress targetAddess, Int32 targetPort, AsyncCallback callback, Object state) at System.Threading.Tasks.TaskFactory1.FromAsyncImpl[TArg1,TArg2](Func5 beginMethod, Func2 endFunction, Action1 endAction, TArg1 arg1, TArg2 arg2, Object state, TaskCreationOptions creationOptions) at System.Threading.Tasks.TaskFactory.FromAsync[TArg1,TArg2](Func5 beginMethod, Action1 endMethod, TArg1 arg1, TArg2 arg2, Object state) at System.Net.Sockets.TcpClient.ConnectAsync(IPAddress address, Int32 port) at Test.Main()

coder2000 commented 4 years ago

Can you provide a full runnable repo of your example so I can take a look? I am not getting any issues running the dual-mode sockets on Windows or Linux with IPv4 or IPv6 addresses.

robertgins commented 4 years ago

I think the key is the "dual mode", we are actually running in AWS Lambda (https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) which is not necessarily dual stack. In our configuration, it does not appear to have an IPV6 stack in it.
This code fails in the lambda Linux environment that only has IPV4 networking (version 2.5 of ubiety works fine)

try { System.Net.IPAddress ipAddress = System.Net.IPAddress.Parse("8.8.8.8"); System.Net.IPEndPoint ipEndPoint = new System.Net.IPEndPoint(ipAddress, 53);

            ResolverBuilder resBuild = ResolverBuilder.Begin();
            resBuild.SetTimeout(1000).SetRetries(3).UseRecursion();
            resBuild.AddDnsServer(ipEndPoint);
            Resolver dnsLookup = resBuild.Build();
            string returnValue = null;

            Response dnsResponse = dnsLookup.Query("www.amazon.com", Ubiety.Dns.Core.Common.QuestionType.CNAME, Ubiety.Dns.Core.Common.QuestionClass.IN);
            List<Ubiety.Dns.Core.Records.General.RecordCname> cnameRecords = dnsResponse.GetRecords<Ubiety.Dns.Core.Records.General.RecordCname>();
            if (cnameRecords.Count > 0)
            {
                returnValue = cnameRecords[0].Cname;
            }
            if (returnValue == null || String.IsNullOrWhiteSpace(returnValue))
            {
                Console.WriteLine("Null returnValue");
            }
            else
            {
                Console.WriteLine("www.amazon.com => " + returnValue);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Lookup Failed: " + e.ToString());
        }
coder2000 commented 3 years ago

Is this still an issue? I am still looking into how to support single stack platforms while maintaining dual stack.