Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need IPv6 Multicast C code that works on iOS 9

Apple now requires iOS 9 apps to be IPv6 compliant. We're mostly OK, except for a bit of code which sends a UDP broadcast - this now fails in iOS 9.

Everything I read tells me that UDP multicast is the right way to do this in IPv6. I've found some example code, but it doesn't work on any version of iOS or Mac OS X I've tried.

This code is being called from a C/C++ lib inside our program - difficult to make a callback into Swift, Obj-C, Java, etc. And this code will be shared by a Mac OS X and Android version of our app. One would think it's possible to do IPv6 multicast in C in any POSIX environment!

In the sample below, execution succeeds up to the final sendto() call, which actually sends the UDP message. That sendto() fails, with errno set to EBROKENPIPE (22) after the failure.

My best guess is that I'm missing some required setsockopt() call, or am using the wrong multicast address. Right now, I'm stumped.

Here's the function call I'm making (to multicast "Is anybody out there?" on UDP port 4031):

char *msg = "Is anybody out there?";
err = multicast_udp_msg ( "FF01::1111", 4031, msg, strlen(msg) );

Here's the code that is being called:

// Multicasts a message on a specific UDP port.
// myhost - IPv6 address on which to multicast the message (i.e., ourself)
// port - UDP port on which to broadcast the mssage
// msg - message contents to broadcast
// msgsize - length of message in bytes
// Return value is zero if successful, or nonzero on error.

int multicast_udp_msg ( char *myhost, short port, char *msg, size_t msgsize )
{
    int        sockfd, n;
    char    service[16] = { 0 };
    int        err = 0;
    struct addrinfo hints = { 0 }, *res, *ressave;
    struct sockaddr_storage addr = { 0 };

    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_DGRAM;

    sprintf ( service, "%hd", port );
    n = getaddrinfo ( myhost, service, &hints, &res );
    if ( n < 0 )
    {
        fprintf(stderr, "getaddrinfo error:: [%s]\n", gai_strerror(n));
        return -1;
    }

    ressave = res;

    sockfd = socket ( res->ai_family, res->ai_socktype, res->ai_protocol );
    if ( sockfd >= 0 )
    {
        memcpy ( &addr, res->ai_addr, sizeof ( addr ) );
        if ( joinGroup ( sockfd, 0, 8, &addr ) == 0 )
            if ( bind ( sockfd, res->ai_addr, res->ai_addrlen ) == 0 )
                if ( sendto ( sockfd, msg, msgsize, 0, (struct sockaddr *) &addr, sizeof ( addr ) ) < 0 )
                    err = errno;

        close ( sockfd );

        res = res->ai_next;
    }

    freeaddrinfo ( ressave );
    return err;
}

int
joinGroup(int sockfd, int loopBack, int mcastTTL,
         struct sockaddr_storage *addr)
{
    int r1, r2, r3, retval;

    retval=-1;

    switch (addr->ss_family) {
        case AF_INET: {
            struct ip_mreq      mreq;

            mreq.imr_multiaddr.s_addr=
            ((struct sockaddr_in *)addr)->sin_addr.s_addr;
            mreq.imr_interface.s_addr= INADDR_ANY;

            r1= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,
                           &loopBack, sizeof(loopBack));
            if (r1<0)
                perror("joinGroup:: IP_MULTICAST_LOOP:: ");

            r2= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
                           &mcastTTL, sizeof(mcastTTL));
            if (r2<0)
                perror("joinGroup:: IP_MULTICAST_TTL:: ");

            r3= setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                           (const void *)&mreq, sizeof(mreq));
            if (r3<0)
                perror("joinGroup:: IP_ADD_MEMBERSHIP:: ");

        } break;

        case AF_INET6: {
            struct ipv6_mreq    mreq6;

            memcpy(&mreq6.ipv6mr_multiaddr,
                   &(((struct sockaddr_in6 *)addr)->sin6_addr),
                   sizeof(struct in6_addr));

            mreq6.ipv6mr_interface= 0; // cualquier interfaz

            r1= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
                           &loopBack, sizeof(loopBack));
            if (r1<0)
                perror("joinGroup:: IPV6_MULTICAST_LOOP:: ");

            r2= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
                           &mcastTTL, sizeof(mcastTTL));
            if (r2<0)
                perror("joinGroup:: IPV6_MULTICAST_HOPS::  ");

            r3= setsockopt(sockfd, IPPROTO_IPV6,
                           IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6));
            if (r3<0)
                perror("joinGroup:: IPV6_ADD_MEMBERSHIP:: ");

        } break;

        default:
            r1=r2=r3=-1;
    }

    if ((r1>=0) && (r2>=0) && (r3>=0))
        retval=0;

    return retval;
}

Thoughts welcome!

-Tim

like image 555
Tim DeBenedictis Avatar asked Oct 02 '15 16:10

Tim DeBenedictis


1 Answers

After some back-and-forth with Apple, and some additional context, we have an answer. But it's not the answer to my original question. First, here's the Apple thread for context:

https://forums.developer.apple.com/message/71107

It turns out that IPv6 multicast was not actually what we needed to solve the real problem at hand - namely, finding a legacy embedded device on a local Wi-Fi network. We really had to use IPv4 UDP broadcast to do that. Our embedded device ignores IPv6 multicast packets like the Earth ignores neutrinos flying through it.

Apple gave us a setsockopt() call which enabled IPv4 UDP broadcast to work in iOS 9 on an infrastructure Wi-Fi network. That is the intended use case for this feature. And Apple also gave us a likely cause of failure when that broadcast failed to work in an Ad Hoc Wi-Fi network (that appears to be a known iOS 9 issue).

So, although my original question is not answered here, the underlying issue has been resolved.

like image 139
Tim DeBenedictis Avatar answered Nov 11 '22 07:11

Tim DeBenedictis