Search code examples
c++iossocketsdnsunreal-engine4

IPv6 Sockets with NAT64 network


I'm trying to implement IPv6 socket connections for UE3 using BSD Sockets. I've successfully ported the code from UE4 that uses all sockaddr_in6 and other relative to IPv6 structs and variables.

The connection works fine when connected to a regular network or 4G, but when connected to a NAT64 network (Required from Apple Approvals) through a Mac sharing feature, Connect() always returns ENETUNREACH. I've tested it with an iPad4 and iPhone6S with many code modifications (removing setIPv6Only function, looking with AF_UNSPEC, etc) not having luck with any of them.

Debugging some data found that getaddrinfo() is returning an IPv4 from the DNS, even thought I've set up the AAAA config in my DNS table, pointing to a Amazon AWS server that allows IPv6 (I've added this last part recently as I previously thought that an IPv6 should be also necessary from Server Side)

Here is the DNS look up:

FScopeLock ScopeLock(&HostByNameSynch);
addrinfo* AddrInfo = NULL;

// We are only interested in IPv6 addresses.
addrinfo HintAddrInfo;
appMemzero(&HintAddrInfo, sizeof(HintAddrInfo));

/* We only care about IPV6 results */
HintAddrInfo.ai_family = AF_INET6;//AF_UNSPEC;
HintAddrInfo.ai_socktype = SOCK_STREAM;
HintAddrInfo.ai_flags = AI_DEFAULT;

INT ErrorCode = SE_HOST_NOT_FOUND;
ErrorCode = getaddrinfo(HostName, NULL, &HintAddrInfo, &AddrInfo);
//ESocketErrors SocketError = TranslateGAIErrorCode(ErrorCode);
if (ErrorCode == SE_NO_ERROR)
{
    for (; AddrInfo != nullptr; AddrInfo = AddrInfo->ai_next)
    {
        if (AddrInfo->ai_family == AF_INET6)
        {
            /*sockaddr_in6* IPv6SockAddr = reinterpret_cast<sockaddr_in6*>(AddrInfo->ai_addr);
            if (IPv6SockAddr != nullptr)
            {
                static_cast<FInternetAddrBSDIPv6&>(OutAddr).SetIp(IPv6SockAddr->sin6_addr);
                return SE_NO_ERROR;
            }*/

            struct sockaddr_in6 input_socket6;
            memset (&input_socket6, 0, sizeof(struct sockaddr_in6));
            memcpy (&input_socket6, AddrInfo->ai_addr, AddrInfo->ai_addrlen);

            Addr.SetIp(input_socket6.sin6_addr);
            ErrorCode = 0;  // good to go.
            break;
        }
    }
}

freeaddrinfo(AddrInfo);

return ErrorCode;

Here is the Connect part:

UBOOL FSocketBSD::Connect(const FInternetAddrBSDIPv6& Addr)
{
INT Err = 0;

Err = connect(Socket, (sockaddr*)(FInternetAddrBSDIPv6&)Addr, sizeof(sockaddr_in6));//connect(Socket,Addr,sizeof(struct sockaddr_in));

debugf(TEXT("TCP Connect to with V6 %s"), *Addr.ToString(TRUE) );

if (Err == 0)
{
    return TRUE;
}
else 
{
    debugf(TEXT("TCP ERROR Connect with result: %s"), GSocketSubsystem->GetSocketError(Err) );
}

Err = GSocketSubsystem->GetLastErrorCode();
INT Return = FALSE;
switch (Err)
{
    case 0:
    case EAGAIN:
    case EINPROGRESS:
    case EINTR:
        Return = TRUE;
        break;
}
return Return;
}

Here is the socket creation:

SOCKET Socket = INVALID_SOCKET;
FSocketBSD* NewSocket = NULL;

// Creates a stream (TCP) socket
Socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
NewSocket = (Socket != INVALID_SOCKET) ? new FSocketBSD(Socket,SOCKTYPE_Streaming,SocketDescription) : NULL;

if (!NewSocket)
{
    debugf(NAME_Init, TEXT("Failed to create IPv6 socket %s [%s]"), *SocketDescription);
}
else 
{
    NewSocket->SetIPv6Only(false);

    // disable the SIGPIPE exception 
    int bAllow = 1;
    setsockopt(Socket, SOL_SOCKET, SO_NOSIGPIPE, &bAllow, sizeof(bAllow)); 
}

return NewSocket;


UBOOL FSocketBSD::SetIPv6Only(UBOOL bIPv6Only)
{
    INT v6only = bIPv6Only ? 1 : 0;
    UBOOL bOk = setsockopt(Socket,IPPROTO_IPV6,IPV6_V6ONLY,(char*) &v6only,sizeof( v6only )) == 0;

    if(bOk == false)
    {
        //check(SocketSubsystem);
        debugf(NAME_Init, TEXT("Failed to set sock opt for socket (%s)"), GSocketSubsystem->GetSocketError());
    }

    return bOk;
}

Here is some debugging info:

[0009.49] Log: TCP SetIp TCHAR u101si.info
[0009.49] Log: TCP GetHostByNameFromCache with FInternetAddrBSDIPv6
[0009.49] Log: TCP GetHostByName starting async task
[0009.49] Log: TCP Performing DNS lookup for u101si.info
[0009.66] Log: TCP SetIp in6_addr
[0009.66] Log: TCP AddHostNameToCache with FInternetAddrBSDIPv6
[0009.66] Log: TCP Tick DNS lookup
[0009.66] Log: TCP AInternetLink Resolve success
[0009.66] ScriptLog: [TcpLinkClient] resolved to 54.169.208.63:4646
[0009.66] Log: Host addr is WIFI: 169.254.23.45
[0009.66] Log: TCP BindPort with NewSocket at 0.0.0.0
[0009.66] Log: TCP SetIp FInternetIpAddr for 0.0.0.0
[0009.66] Log: TCP SetIp in_addr
[0009.66] Log: TCP Using IPv4 address: 0.0.0.0  on an ipv6 socket
[0009.66] Log: TCP Bind for 0.0.0.0:0 result: 1
[0009.66] ScriptLog: [TcpLinkClient] Bound to port: 60885
[0009.66] Log: TCP Open with 54.169.208.63
[0009.66] Log: TCP SetIp with INT value: 917098559
[0009.66] Log: TCP SetIp in_addr
[0009.66] Log: TCP Using IPv4 address: 54.169.208.63  on an ipv6 socket
[0009.66] Log: TCP Connect to with V6 [::ffff:54.169.208.63]:4646
[0009.66] Log: TCP ERROR Connect with result: SE_ENETUNREACH

I have never worked with IPv6 protocols nor low level socket connections, so I'm at a lost here. Is this caused to client side issues? DNS lookup problems? Server not accepting IPv6 clients?

Thanks in advance!


Solution

  • After many test in the device, I made it work.

    The issue came the cache for DNS, that was returning the outdated IP and from the call to getaddrinfo(). When addding the port parameter, it triggered the right register in the DNS: A for IPv4 and AAAA for IPv6.

    This is the call that works for IPv4 and IPv6:

    FInternetAddrBSDIPv6& Addr;
    struct addrinfo *res, *res0;
    
    // We are only interested in IPv6 addresses.
    addrinfo HintAddrInfo;
    appMemzero(&HintAddrInfo, sizeof(HintAddrInfo));
    
    HintAddrInfo.ai_family = AF_UNSPEC;
    HintAddrInfo.ai_socktype = SOCK_STREAM;
    //HintAddrInfo.ai_protocol = IPPROTO_TCP;
    HintAddrInfo.ai_flags = AI_DEFAULT;
    
    INT ErrorCode = SE_HOST_NOT_FOUND;
    ErrorCode = getaddrinfo(HostName, "1212", &HintAddrInfo, &res0);
    
    if (ErrorCode == SE_NO_ERROR)
    {
        for (res = res0; res; res = res->ai_next) 
        {
            debugf(TEXT("TCP GetHostByName with DNS successful"));
    
            if (res->ai_addr != 0)
            {
                if( res->ai_family == AF_INET6 )
                {
                    struct sockaddr_in6 input_socket6;
                    memset (&input_socket6, 0, sizeof(struct sockaddr_in6));
                    memcpy (&input_socket6, res->ai_addr, res->ai_addrlen);
    
                    Addr.SetIp(input_socket6.sin6_addr);
                    ErrorCode = 0;  // good to go.
                    break;
                }
                else if( res->ai_family == AF_INET )
                {
                    const in_addr &IP = ((sockaddr_in *) res->ai_addr)->sin_addr;
                    Addr.SetIp(IP);
                    ErrorCode = 0;  // good to go.
                    break;
                }
            }
    
        }
    }
    
    freeaddrinfo(res0);
    
    return ErrorCode;