【问题标题】:Need IPv6 Multicast C code that works on iOS 9需要适用于 iOS 9 的 IPv6 多播 C 代码
【发布时间】:2015-12-30 22:50:22
【问题描述】:

Apple 现在要求 iOS 9 应用兼容 IPv6。我们基本上没问题,除了一些发送 UDP 广播的代码 - 现在这在 iOS 9 中失败了。

我读到的所有内容都告诉我,UDP 多播是在 IPv6 中执行此操作的正确方法。我找到了一些示例代码,但它不适用于我尝试过的任何版本的 iOS 或 Mac OS X。

这段代码是从我们程序中的 C/C++ 库调用的 - 很难回调到 Swift、Obj-C、Java 等。这段代码将由我们的 Mac OS X 和 Android 版本共享应用程序。有人会认为可以在任何 POSIX 环境中用 C 语言进行 IPv6 多播!

在下面的示例中,执行成功到最后的 sendto() 调用,它实际上发送了 UDP 消息。 sendto() 失败,失败后 errno 设置为 EBROKENPIPE (22)。

我最好的猜测是我错过了一些必需的 setsockopt() 调用,或者使用了错误的多播地址。现在,我被难住了。

这是我正在进行的函数调用(在 UDP 端口 4031 上多播“有人在外面吗?”):

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

这是被调用的代码:

// 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;
}

欢迎提出想法!

-蒂姆

【问题讨论】:

  • 要考虑的一件事是 IPv6 多播使用您真正需要正确处理的标志、范围和范围。例如,FF01::/112 范围是节点本地范围,但我猜您确实在寻找链接本地范围,例如 FF02::/112
  • 我试过 FF02::1 和 FF02:1111。结果相同:sendto() 返回 -1,errno 设置为 22 (EBROKENPIPE)。我们都只是在这里猜测。还有其他人在 iOS 9 上实现了多播吗?
  • 我只是说你需要小心你选择的多播地址。我不知道链接本地范围对于您要执行的操作是否正确(该范围不会离开链接,就像节点本地范围不会离开节点一样)。如果你看,FF02::1 是所有节点的地址。您确实需要在尝试测试的主机和网络上配置 IPv6(能够通过 IPv6 单播地址 ping)。然后,您需要研究 IPv6 多播 RFC 以进行智能组选择,而不是您似乎正在尝试的命中或未命中。

标签: c ios9 ipv6 multicast


【解决方案1】:

经过与 Apple 的反复讨论,以及一些额外的背景,我们有了答案。但这不是我原来问题的答案。首先,这是 Apple 的上下文线程:

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

事实证明,IPv6 多播实际上并不是我们解决手头真正问题所需要的——即在本地 Wi-Fi 网络上查找传统嵌入式设备。我们真的不得不使用 IPv4 UDP 广播来做到这一点。我们的嵌入式设备会忽略 IPv6 多播数据包,就像地球忽略飞过它的中微子一样。

Apple 为我们提供了一个 setsockopt() 调用,它使 IPv4 UDP 广播能够在 iOS 9 的基础设施 Wi-Fi 网络上工作。这是此功能的预期用例。当广播无法在 Ad Hoc Wi-Fi 网络中工作时,Apple 还向我们提供了一个可能的失败原因(这似乎是一个已知的 iOS 9 问题)。

所以,虽然这里没有回答我最初的问题,但根本问题已经解决。

【讨论】:

    猜你喜欢
    • 2020-06-15
    • 1970-01-01
    • 2013-07-05
    • 2017-05-04
    • 2023-03-30
    • 2017-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多