3rdeye7 3rdeye7 - 4 months ago 38
iOS Question

iOS sockets IPv6 Support

I am getting an error when I try to connect to my ipv4 server. Currently the ios app users are required to enter their sever's an IP address, port, and account information.

The ios app then calls Connect on the SocketSender class (included in the header search path) which in turns calls the connect function of Socket.h and then checks the results.

Connect - SocketSender.cpp

bool SocketSender::Connect (const char *host, int port, CApiError &err)
{

errno = 0;
struct hostent *hostinfo;

hostinfo = gethostbyname (host);

if (!hostinfo) {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = SOCKET_ERRNO();
err.SetSystemError(m_nLastErrorNo);
#else
/* Linux stores the gethostbyname error in h_errno. */
m_nLastErrorNo = EINVAL; // h_errno value is incompatible with the "normal" error codes
err.SetError(FIX_SN(h_errno, hstrerror(h_errno)), CATEGORY_SYSTEM | ERR_TYPE_ERROR);
#endif
return false;
}

socket_fd = socket (AF_INET, SOCK_STREAM, 0);

if (socket_fd == -1) {
m_nLastErrorNo = SOCKET_ERRNO();
err.SetSystemError(m_nLastErrorNo);
return false;
}

struct sockaddr_in address;

address.sin_family = AF_INET;
address.sin_port = htons (port);
address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list;

int result;

SetSocketOptions();

result = connect (socket_fd, (struct sockaddr *) &address, sizeof (address));

if (result == -1) {
if (IS_IN_PROGRESS()) {
fd_set f1,f2,f3;
struct timeval tv;

/* configure the sets */
FD_ZERO(&f1);
FD_ZERO(&f2);
FD_ZERO(&f3);
FD_SET(socket_fd, &f2);
FD_SET(socket_fd, &f3);

/* we will have a timeout period */
tv.tv_sec = 5;
tv.tv_usec = 0;

int selrez = select(socket_fd + 1,&f1,&f2,&f3,&tv);

if (selrez == -1) { // socket error
m_nLastErrorNo = SOCKET_ERRNO();
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}

if (FD_ISSET(socket_fd, &f3)) { // failed to connect ..
int sockerr = 0;
#ifdef PLATFORM_WIN32
int sockerr_len = sizeof(sockerr);
#else
socklen_t sockerr_len = sizeof(sockerr);
#endif
getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
if (sockerr != 0) {
m_nLastErrorNo = sockerr;
} else {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok?
#else
m_nLastErrorNo = ETIMEDOUT;
#endif
}
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}

if (!FD_ISSET(socket_fd, &f2)) { // cannot read, so some (unknown) error occured (probably time-out)
int sockerr = 0;
#ifdef PLATFORM_WIN32
int sockerr_len = sizeof(sockerr);
#else
socklen_t sockerr_len = sizeof(sockerr);
#endif
getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
if (sockerr != 0) {
m_nLastErrorNo = sockerr;
} else {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok?
#else
m_nLastErrorNo = ETIMEDOUT;
#endif
}
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
#ifndef PLATFORM_WIN32 // FIXME: is the same needed for windows ?

// unix always marks socket as "success", however error code has to be double-checked
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
err.SetSystemError();
return false;
}
if(error != 0) {
m_nLastErrorNo = error;
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
#endif
} else {
m_nLastErrorNo = SOCKET_ERRNO();
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
}

m_nIP = ntohl(address.sin_addr.s_addr);

m_bServerSocket = false;
return true;
}


That is the original version that worked without any problems. When i changed the above to use AF_INET6 and in_addr6->sin6_addr, i kept getting errors and the application failed to connect. I tried using getaddrinfo but this still did not connect.

struct addrinfo hints, *res, *res0;
int error;
const char *cause = NULL;

memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(host, "PORT", &hints, &res0);
if (error) {
errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
}
socket_fd = -1;
printf("IP addresses for %s:\n\n", host);
int result;
void *addr;
char *ipver;
for (res = res0; res!=NULL; res = res->ai_next) {
socket_fd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (socket_fd < 0) {
cause = "socket";
continue;
}

if ((result = connect(socket_fd, res->ai_addr, res->ai_addrlen)) < 0) {
cause = "connect";
close(socket_fd);
socket_fd = -1;
continue;
}
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (res->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
SetSocketOptions();
break; /* okay we got one */
}


I need to make it backwards compatible with ipv6 and ipv4. Any help would be much appreciated as i have been stuck testing this for the past week. Also if anyone knows how to debug the SocketSender.cpp on XCode that would be alot of help.

Answer

So after two weeks of testing out different approaches and familiarizing myself with networking (POSIX) I finally got this to work mainly due to @user102008 suggestion.

This is relevant to Client-Server applications.My application is a client application that connects to a IPv4 server/system at a remote location. We have yet to support IPv6 for our products which include clients(iOS,android,windows,unix) and servers (windows & unix), but will support upon future releases. The reason for this support was solely due to Apple changing their apple review process environment.

Approach, Tips and Issues

  1. Apple has provided a way to test IPv6 compatibility with your app. This is sharing your connection from your Ethernet using NAT64/DNS64. This failed many times for me. After researching and resetting my SMC, I came across this article and realized i may have been messing with the configuration too much. So I reset my SMC, restarted and created the internet sharing host. Always remember to turn off WiFi before making any changes to internet sharing.
  2. Users were required to connect to the server with a IPv4 IP address. The Application ran perfectly on an IPv4 networking but failed in an IPv6 network. This was due to the app not resolving the IP address literal. The networking library my application uses was a cpp library that was included as a preprocess macro. One of the biggest annoyance was trying to debug, because you cant debug compile time code. So what I did was move over my cpp files with their headers to the project (luckily it was only 3 files).
  3. IMPORTANT TO AYONE PASSING PORT NUMBERS. This is tied to #2 and resolving the IPv4 literal. I used apples exact implementation on the networking overview (listing 10-1). Every time i tested, the connect function was returning -1, meaning it did not connect. Thanks to @user102008 providing me with this article, I realized apple implementation of getaddrinfo was broken when trying to pass a string literal for the port. Yes, they ask for a constant char, even when trying c_str() it would still return port number of 0. For this reason, an apple developer who Ive noticed answer and addresses countless networking problems provided a work around. This fixed my issue of the port continuously returning 0, code is posted below as well. What i did, was simply add this into my networking class (SocketSender.cpp) and instead of calling getaddrinfo in Connect, i called get getaddrinfo_compat. This allowed me to connect perfectly in IPv4 and IPv6 networks.

    static int getaddrinfo_compat(  
    const char * hostname,  
    const char * servname,  
    const struct addrinfo * hints,  
    struct addrinfo ** res  
    ) {  
       int    err;  
       int    numericPort;  
    
        // If we're given a service name and it's a numeric string, set `numericPort` to that,  
       // otherwise it ends up as 0.  
    
       numericPort = servname != NULL ? atoi(servname) : 0;  
    
       // Call `getaddrinfo` with our input parameters.  
    
       err = getaddrinfo(hostname, servname, hints, res);  
    
      // Post-process the results of `getaddrinfo` to work around   <rdar://problem/26365575>.  
    
    if ( (err == 0) && (numericPort != 0) ) {  
    for (const struct addrinfo * addr = *res; addr != NULL; addr = addr->ai_next) {  
        in_port_t *    portPtr;  
    
        switch (addr->ai_family) {  
            case AF_INET: {  
                portPtr = &((struct sockaddr_in *) addr->ai_addr)->sin_port;  
            } break;  
            case AF_INET6: {  
                portPtr = &((struct sockaddr_in6 *) addr->ai_addr)->sin6_port;  
            } break;  
            default: {  
                portPtr = NULL;  
            } break;  
        }  
        if ( (portPtr != NULL) && (*portPtr == 0) ) {  
            *portPtr = htons(numericPort);  
        }  
    }  
    }  
    return err;  
    } 
    
  4. I actually save IP (address.sin_addr.s_addr) in a long data type that is a private variable, m_nIP. problem was i didnt need the IPv6 as our entire product groups use IPv4. Solved this using the code is below.

    const uint8_t *bytes = ((const struct sockaddr_in6 *)addrPtr)->sin6_addr.s6_addr;
    bytes += 12;
    struct in_addr addr = { *(const in_addr_t *)bytes };
    m_nIP = ntohl(addr.s_addr);
    
  5. RELEVANT GUIDES Beej's Guide to Network Programming, UserLevel IPv6 Intro, Porting Applications to IPv6