Resolve IPv6 address from hostname

2019-09-16 08:13发布

问题:

We're updating our socket code to handle the new iOS requirements where you need to be able to connect in an IPv6-only environment.

We're currently using .NET sockets and TcpClient.

I'm trying to set up a solution where both IPv6 and IPv4 work, but so far nothing's happening.

If I use:

TcpClient client = new TcpClient();
client.BeginConnect( "server.hostname.com", 8182, this._onConnect, client );

in a IPv6 environment, I get no response; the _onConnect method will never get triggered.

If I use:

TcpClient client = new TcpClient( AddressFamily.InterNetworkV6 );
client.BeginConnect( "server.hostname.com", 8182,this._onConnect, client);

I get the SocketException:

An address incompatible with the requested protocol was used (errorCode: 10047, socketErrorCode: AddressFamilyNotSupported, nativeErrorCode: 10047)

Which is annoying, if understandable. If I use:

TcpClient client = new TcpClient( AddressFamily.InterNetworkV6 );
client.BeginConnect( "xxxx:xxxx:xxxx::xxxx:xxxx", 8182,this._onConnect, client);

Then I can connect, however:

  • I'm now putting in a raw IP address instead of a host name, which isn't good
  • This will no longer work in an IPv4 environment

So I figured I could get the supported IPs from the hostname and try to connect to one after the other.

With this code:

IPHostEntry hostInfo = Dns.GetHostEntry( "server.hostname.com" );
foreach(IPAddress ip in hostInfo.AddressList)
    Debug.Log( "Ip: " + ip.ToString() + ": address family: " + ip.AddressFamily );

It'll only print out the IPv4 IPs, which isn't super helpful.

What am I doing wrong here? How can I get the IPv6 IP from a string hostname? Or is there a better way to resolve this?

I was kind of under the assumption that this would auto-resolve on the DNS level, but I guess the version of Mono included with Unity isn't up-to-date enough.

Thanks

回答1:

It looks like in mono, passing the hostname to create or connect to server will fail. Using the direct IP always work. Unity has not updated mono socket stuff for a long time. There is nothing wrong with using the IP.

The missed the Apple deadline which was on the 1st. My suggestion to you is to first check if your app is running on iOS then then use IPv6 for everything. If IPv6 fails or returns null then use IPv4.

It'll only print out the IPv4 IPs, which isn't super helpful.

What am I doing wrong here? How can I get the IPv6 IP from a string hostname? Or is there a better way to resolve this?

It's printing out the IPv4 because you did not filter it. You have to manually check if the ip is a an IPv6 in each loop.

//IPv4
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
}

//IPv6
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
}

If IPv6 is not found even after using the solution above to filter it out then that means that the network you are on does not support IPv6. When I say that, I mean your ISP. If your ISP did not give you an IPv6 then you can't connect to another public server with IPv6. You will only connect to public servers with the IPv4 your ISP assigned to you.

To very this, visit here and here. It will tell you if you have IPv4, IPv6 or both.

The code below is a general function I use to retrieve local, public IP including version 4 and 6.

private string getIPAddress(string hostName, IPTYPE ipType, ADDRESSFAM Addfam)
    {
        //Return null if ADDRESSFAM is Ipv6 but Os does not support it
        if (Addfam == ADDRESSFAM.IPv6 && !System.Net.Sockets.Socket.OSSupportsIPv6)
        {
            return null;
        }

        //////////////HANDLE LOCAL IP(IPv4 and IPv6)//////////////
        if (ipType == IPTYPE.LOCAL_IP)
        {

            System.Net.IPHostEntry host;
            string localIP = "";
            host = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
            foreach (System.Net.IPAddress ip in host.AddressList)
            {
                //IPv4
                if (Addfam == ADDRESSFAM.IPv4)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        localIP = ip.ToString();
                    }
                }

                 //IPv6
                else if (Addfam == ADDRESSFAM.IPv6)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
                    {
                        localIP = ip.ToString();
                    }
                }
            }
            return localIP;

        }

        //////////////HANDLE PUBLIC IP(IPv4 and IPv6)//////////////
        if (ipType == IPTYPE.PUBLIC_IP)
        {
            //Return if hostName String is null
            if (string.IsNullOrEmpty(hostName))
            {
                return null;
            }

            System.Net.IPHostEntry host;
            string localIP = "";
            host = System.Net.Dns.GetHostEntry(hostName);
            foreach (System.Net.IPAddress ip in host.AddressList)
            {
                //IPv4
                if (Addfam == ADDRESSFAM.IPv4)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        localIP = ip.ToString();
                    }
                }

                //IPv6
                else if (Addfam == ADDRESSFAM.IPv6)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
                    {
                        localIP = ip.ToString();
                    }
                }

            }
            return localIP;
        }
        return null;
    }


    enum IPTYPE
    {
        LOCAL_IP, PUBLIC_IP
    }

    enum ADDRESSFAM
    {
        IPv4, IPv6
    }

Usage:

    Debug.Log("Local IPv4: " + getIPAddress("", IPTYPE.LOCAL_IP, ADDRESSFAM.IPv4));
    Debug.Log("Local IPv6: " + getIPAddress("", IPTYPE.LOCAL_IP, ADDRESSFAM.IPv6));

    Debug.Log("Public IPv4: " + getIPAddress("google.com", IPTYPE.PUBLIC_IP, ADDRESSFAM.IPv4));
    Debug.Log("Public IPv6: " + getIPAddress("google.com", IPTYPE.PUBLIC_IP, ADDRESSFAM.IPv6));


回答2:

@Programmer's code gives the IP address for a hostname, but I wanted to clarify 2 things.

1) The code:

TcpClient client = new TcpClient( AddressFamily.InterNetworkV6 );
client.BeginConnect( "server.hostname.com", 8182,this._onConnect, client);

DOES work, but only on iOS. In the editor and on Android, it gives an exception (as of Unity 5.3.5f1). This seems like a bug in the implementation, so I've sent in a bug report.

2) Despite System.Net.Sockets.Socket.SupportsIPv6 being marked as obsolete and Unity advising you to go with System.Net.Sockets.Socket.OSSupportsIPv6, OSSupportsIPv6 will throw an exception on Android



标签: c# unity3d mono