iOS NAT64 address synthesis not working as expected with Mac NAT64 proxy

Hello,


I am in the process of trying to make an application work correctly in an IPv6 only environment on iOS as required for App Store certification. It works by sending a request to some service which returns an IP address to establish a UDP connection with. Currently, this returns an IPv4 address to connect to (via a low level socket), and it's a large amount of work to get this to support IPv6 correctly.


For the time being, I am trying to use getaddrinfo to synthesize an IPv6 address using the code example provided in https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW23

however, it just returns the IPv4 address - rather than the synthesized IPv6 address - when running on an iPhone XS (running iOS 12.1.4) with no sim card, connected via WiFi to a MacBook Pro wifi hotspot (running macOS 10.14.3) with the NAT64 proxy enabled.

Looking at the wifi information on the iPhone itself, it currently has a link-local IPv4 address (169.254.0.0/24) and 2 IPv6 addresses (starting 2001:2::aab1:...), so I believe the setup is correct.


I created an Xcode iOS project with the following code in main.mm:

#import 
#import "AppDelegate.h"

#include 
#include 
#include 
#include 

#include 

void test_getaddrinfo(int family) {
    uint8_t ipv4[4] = {192, 0, 2, 1};
    struct addrinfo hints, *res, *res0;
    int error;
    
    char ipv4_str_buf[INET_ADDRSTRLEN] = { 0 };
    const char *ipv4_str = inet_ntop(AF_INET, &ipv4, ipv4_str_buf, sizeof(ipv4_str_buf));
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    error = getaddrinfo(ipv4_str, "http", &hints, &res0);
    if (error) {
        printf("%s", gai_strerror(error));
    }
    
    for (res = res0; res; res = res->ai_next) {
        printf("ai_flags:%d ai_family:%d ai_socktype:%d ai_protocol:%d ai_addrlen:%d ai_canonname:%s\n",
               res->ai_flags, res->ai_family, res->ai_socktype, res->ai_protocol, res->ai_addrlen,
               res->ai_canonname == NULL ? "" : res->ai_canonname);
        
        char* s = NULL;
        switch (res->ai_addr->sa_family) {
            case AF_INET: {
                struct sockaddr_in* addr_in = (struct sockaddr_in*)res->ai_addr;
                s = (char*)malloc(INET_ADDRSTRLEN);
                inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
                break;
            }
            case AF_INET6: {
                struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)res->ai_addr;
                s = (char*)malloc(INET6_ADDRSTRLEN);
                inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
                break;
            }
            default:
                break;
        }
        printf("IP address: %s\n", s);
        free(s);
    }
    
    freeaddrinfo(res0);
}

int main(int argc, char * argv[]) {
    
    printf("--unspec--\n");
    test_getaddrinfo(PF_UNSPEC);
    printf("--inet6--\n");
    test_getaddrinfo(PF_INET6);
    
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}


and this gives me the following output:


--unspec--

ai_flags:0 ai_family:2 ai_socktype:1 ai_protocol:6 ai_addrlen:16 ai_canonname:

IP address: 192.0.2.1

--inet6--

ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:

IP address: ::ffff:192.0.2.1


when I would expect this to print "64:ff9b::192.0.2.1" in both PF_UNSPEC and PF_INET6 cases, as specified both in the Apple Developer documentation and the manpage for getaddrinfo.


Can anybody advise as to what I may have done wrong during setup, or is this a regression in either macOS or iOS?


Thanks in advance.

I’d expect this to work, and I vaguely remember it having worked for me in the past. Alas, I’m not in a position to try it for myself today. Maybe when I get back into the office next week.

In the meantime, I do have one suggestion: Change your test IP address to something that’s not a private IP address. My favourite address for this sort of thing is 93.184.216.34, the IP address for

example.com
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your prompt response!
I tried both 93.184.216.34 and example.com (without and with a DNS lookup), and got the following output, which matches what I saw above with the other IP address, and the expected behaviour when doing a DNS lookup:

93.184.216.34:

--unspec--

ai_flags:0 ai_family:2 ai_socktype:1 ai_protocol:6 ai_addrlen:16 ai_canonname:

IP address: 93.184.216.34

--inet6--

ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:

IP address: ::ffff:93.184.216.34



example.com:

--unspec--

ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:

IP address: 2001:2:0:1baa::5db8:d822

--inet6--

ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:

IP address: 2001:2:0:1baa::5db8:d822

@eskimo
Any chance that you were able to try it out on your side this week?

Sadly no. I have the URL of this thread sitting on my desktop, waiting for me to get a free moment. Alas, I have a backlog of tricky DTS incidents to work through before I’ll get to that.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

OK, I cleared off my desk at home, making space for a ‘victim’ Mac to run Internet Sharing, and ran your test there. Specifically, I’m testing with iOS 12.1.4 on the client and macOS 10.14.3 on the Mac running Internet Sharing (MacNAT64).

The result was what I expected. I started out with your code verbatim. Putting it into a test project it produced results like this:

--unspec--
ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:
IP address: 2001:2:0:1baa::5db8:d822
--inet6--
ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:
IP address: 2001:2:0:1baa::5db8:d822

Note that 0x5d is 93, 0xb8 is 184, and so on, meaning that the IPv6 address has the IPv4 address (93.184.216.34) embedded within it, using a custom prefix (2001:2:0:1baa) chosen at random by the MacNAT64.

I then tweaked the code to make it simpler. My tweaked version is below. Hint: Any time you’re tempted to write code that’s specific to an IP version, reach for

getaddrinfo
or
getnameinfo
(-:

This tweaked code produces exactly the same results.

I’m not sure what’s going at your end, but this is working for me.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
void test_getaddrinfo(int family) {
    const char *ipv4_str = "93.184.216.34";
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    struct addrinfo * addrList;
    int error = getaddrinfo(ipv4_str, "http", &hints, &addrList);
    if (error) {
        printf("%s", gai_strerror(error));
        return;
    }

    for (struct addrinfo * addr = addrList; addr; addr = addr->ai_next) {
        printf("ai_flags:%d ai_family:%d ai_socktype:%d ai_protocol:%d ai_addrlen:%d ai_canonname:%s\n",
               addr->ai_flags, addr->ai_family, addr->ai_socktype, addr->ai_protocol, addr->ai_addrlen,
               addr->ai_canonname == NULL ? "" : addr->ai_canonname);

        char host[NI_MAXHOST];
        int niErr = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV);
        if (niErr != 0) {
            snprintf(host, sizeof(host), "error: %d", niErr);
        }
        printf("IP address: %s\n", host);
    }

    freeaddrinfo(addrList);
}

Hi,


Thank you very much for testing this out and sorry for the super late response. Only now have I gotten time to look into this again.


Really strange that this works for you. Unfortunately, it seems to be an issue across many devices on our side. I tried connecting my iPhone XS to 3 different Macs (2 x 2017 MBPs and a 2015 MBP) with Internet Sharing enabled, all 3 of them had the same result in the Xcode window:

--unspec--

ai_flags:0 ai_family:2 ai_socktype:1 ai_protocol:6 ai_addrlen:16 ai_canonname:

IP address: 93.184.216.34

--inet6--

ai_flags:0 ai_family:30 ai_socktype:1 ai_protocol:6 ai_addrlen:28 ai_canonname:

IP address: ::ffff:93.184.216.34


I also tried running this on a different iPhone 7 in case there's a configuration error on their side, and again, it came up with the same output in Xcode.


Unfortunately, this is blocking us from getting IPv6 to work correctly with some internal networking code. We made sure to follow https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html

to the letter to set up the the MacNAT64.
Looking at the man pages for getaddrinfo, I noticed the following sentence:
"On non-qualifying interfaces, getaddrinfo() is guaranteed to return immediately without attempting any resolution, and will return the IPv4 address if ai_family is PF_UNSPEC or PF_INET."
This appears to be what is happening on my side. Is there a syscall that we can use (or some logs we can look at) to investigate why the interface may be non-qualifying (or check whether that's the case)?
I'm beginning to think that perhaps my Xcode project is misconfigured somehow? I'm not doing anything except using the Xcode managed provisioning profile with my developer account, with default settings created from the "Single view app" template, and using the code in this thread.
Do you have any further insight into debugging this, or maybe seeinfg whether the MacNAT64 is doing the right thing? Could it be caused by our internal network? I'll try the same test again at using a different network (my home wifi) as well to see if that changes anything.
EDIT (5/6/2019): I tried on a different network, and my colleague also tried building an Xcode project on a different laptop (rather than just running the NAT64 gateway on a different laptop), and still no change. I'm suspecting at this point that perhaps the Xcode project setup is to blame, as I can't think of any other variables which are different between our setup and yours @eskimo.


Thanks again

Note that seems to be the same issue as https://forums.developer.apple.com/message/189470#189470 which never appeared to have a resolution.

I'm beginning to think that perhaps my Xcode project is misconfigured somehow?

That seems unlikely. I can’t think of any Xcode settings that would affect this behaviour.

This appears to be what is happening on my side. Is there a syscall that we can use (or some logs we can look at) to investigate why the interface may be non-qualifying (or check whether that's the case)?

I don’t know for sure, and don’t have time to research this during WWDC )-: One thing you can do is install the Network Diagnostic configuration profile (see our Bug Reporting > Profiles and Logs page) and then look at the system log to see if anything interesting pops up.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
iOS NAT64 address synthesis not working as expected with Mac NAT64 proxy
 
 
Q