iPhone在个人热点模式下未发送网络广播/多播

问题描述:

基于最近的经验发现,并基于网络上的各种帖子,似乎在启用了个人热点的iPhone上运行的应用程序无法将广播和/或多播发送到个人热点的网络上.谁能阐明这个问题的原因?

Based on recent empirical findings, and based on various posts on the web, it seems that an application running on an iPhone with personal hotspot enabled cannot send broadcasts and/or multicasts out onto the personal hotspot's network. Can anyone shed light on the cause of this problem?

应用程序

我有一个使用跨平台C ++代码构建的IOS应用程序,该应用程序将其存在广播并组播到正在运行的网络上.当iPhone连接到Wi-Fi网络时,该应用程序可以完美运行.在这种情况下,网络上的其他设备会接收到广播/多播,并且一切正常.通过将运行WireShark的计算机连接到网络,可以轻松地验证这一点-在数据包跟踪中可以看到广播/多播数据包.

I have an IOS application, built with cross-platform C++ code, that broadcasts and multicasts its presence onto the network it is running on. The application works flawlessly when the iPhone is connected to a Wi-Fi network. In this case, other devices on the network receive the broadcasts/multicasts, and everything functions correctly. This can be verified easily by connecting a computer running WireShark to the network -- the broadcast/multicast packets can be seen in the packet trace.

不用说,该应用程序在连接到本地Wi-Fi的iPhone上运行良好.

Needless to say, the application works well on an iPhone connected to a local Wi-Fi.

问题

当我在启用了个人热点的iPhone上运行该应用程序时,没有任何广播/多播发布到该热点网络上.可以使用WireShark对此进行验证,它在其跟踪中没有显示此类数据包.

When I run the application on an iPhone that has its personal hotspot enabled, no broadcasts/multicasts are released onto the hotspot network. This can be verified using WireShark, which shows no such packets in its trace.

使用个人热点作为能够处理广播和多播的网络路由器是否有任何限制?

Is there any constraint regarding using a personal hotspot as a network router capable of handling broadcasts and multicasts?

当我使用浏览器在"WireSharking"设备上请求网页时,个人热点会正确响应所有数据包,并返回网页内容.

When I requested a web page on my "WireSharking" device using a browser, the personal hotspot responds correctly to all packets, returning the web contents.

附带信息

我遇到了其他Stack Overflow帖子,这些帖子报告了相同或相似的问题:

I have come across other Stack Overflow posts that report the same, or similar, problems:

  1. 将iPhone用作iPhone时,TCP连接无法正常工作热点
  2. 无法通过个人热点发送ssdp广播

迈克尔·泰森(Michael Tyson)的"

A good tutorial for writing such a broadcasting/multicasting application on iPhone is Michael Tyson's "The Making of Talkie: Multi-interface broadcasting and multicast". Suffice it to say that my application conforms with all requirements (e.g., setting socket options SO_BROADCAST, SO_DONTROUTE, and IP_MULTICAST_IF where appropriate).

上面对参考文献(1)的答复中写道:"是否是因为个人热点引入了网络地址转换?".我过滤了WireShark跟踪,以仅显示连接到热点IP的数据包,并且没有证据表明个人热点向NAT地址发送了任何内容.

A reply to reference (1) above writes "Could it be because the personal hotspot introduces Network Address Translation?". I filtered the WireShark traces to show only packets connected to the hotspot IP, and there is no evidence of the personal hotspot sending anything to a NAT address.

总结

任何人都可以解释为什么运行个人热点的iPhone不广播/多播数据包以及如何解决该问题吗?

Can anyone explain why an iPhone running a personal hotspot does not broadcast/multicast packets, and how to solve the problem?

非常感谢.

我在iOS 10上广播正常工作

我要发布第二个答案,因为我找到了一种在个人热点模式下在iOS 10上进行广播的方法.解决方案有点复杂,但是这就是我的工作方式:

I'm posting a second answer, because I found a way to get broadcast working on iOS 10 in personal hotspot mode. the solution is a bit complex, but here is how I got it to work:

  1. 使用 getifaddrs(if)遍历所有接口( do {} while(if = if-> ifa_next))
  2. 拒绝回送接口( if-> ifa_flags和IFF_LOOPBACK )
  3. 仅过滤支持广播的接口( if-> ifa_flags和IFF_BROADCAST )
  4. 使用 int sock = socket()
  5. 创建一个套接字
  6. 启用广播: int yes = 1;setsockopt(袜子,SOL_SOCKET,SO_BROADCAST,&是,sizeof是)
  7. 连接到广播地址: connect(sock,if-> ifa_dstaddr,if-> ifa_dstaddr-> sa_len)
  8. 现在使用 send()发送您的消息!
  1. use getifaddrs(if) to loop through all interfaces (do {} while (if = if->ifa_next))
  2. reject loopback interfaces (if->ifa_flags & IFF_LOOPBACK)
  3. filter only interfaces that support broadcast (if->ifa_flags & IFF_BROADCAST)
  4. create a socket with int sock = socket()
  5. enable broadcast: int yes = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)
  6. connect to the broadcast address: connect(sock, if->ifa_dstaddr, if->ifa_dstaddr->sa_len)
  7. now use send() to send your message!

我已经确认这既可以在iPhone连接到WiFi网络时使用,也可以在个人热点模式下使用.

I've confirmed that this works both when the iPhone is connected to a WiFi network, and also when it's in personal hotspot mode.

完整示例代码

#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>

-(IBAction)sayHello:(id)sender {

    // fetch a linked list of all network interfaces
    struct ifaddrs *interfaces;
    if (getifaddrs(&interfaces) == -1) {
        NSLog(@"getifaddrs() failed: %s", strerror(errno));
        return;
    }

    // loop through the linked list
    for(struct ifaddrs *interface=interfaces; interface; interface=interface->ifa_next) {

        // ignore loopback interfaces
        if (interface->ifa_flags & IFF_LOOPBACK) continue;

        // ignore interfaces that don't have a broadcast address
        if (!(interface->ifa_flags & IFF_BROADCAST) || interface->ifa_dstaddr == NULL) continue;

        // check the type of the address (IPv4, IPv6)
        int protocol_family;
        struct sockaddr_in ipv4_addr = {0};
        struct sockaddr_in6 ipv6_addr = {0};
        struct sockaddr *addr;
        if (interface->ifa_dstaddr->sa_family == AF_INET) {
            if (interface->ifa_dstaddr->sa_len > sizeof ipv4_addr) {
                NSLog(@"Address too big");
                continue;
            }
            protocol_family = PF_INET;
            memcpy(&ipv4_addr, interface->ifa_dstaddr, interface->ifa_dstaddr->sa_len);
            ipv4_addr.sin_port = htons(16000);
            addr = (struct sockaddr *)&ipv4_addr;

            char text_addr[255] = {0};
            inet_ntop(AF_INET, &ipv4_addr.sin_addr, text_addr, sizeof text_addr);
            NSLog(@"Sending message to %s:%d", text_addr, ntohs(ipv4_addr.sin_port));
        }
        else if (interface->ifa_dstaddr->sa_family == AF_INET6) {
            if (interface->ifa_dstaddr->sa_len > sizeof ipv6_addr) {
                NSLog(@"Address too big");
                continue;
            }
            protocol_family = PF_INET6;
            memcpy(&ipv6_addr, interface->ifa_dstaddr, interface->ifa_dstaddr->sa_len);
            ipv6_addr.sin6_port = htons(16000);
            addr = (struct sockaddr *)&ipv6_addr;

            char text_addr[255] = {0};
            inet_ntop(AF_INET6, &ipv6_addr.sin6_addr, text_addr, sizeof text_addr);
            NSLog(@"Sending message to %s:%d", text_addr, ntohs(ipv6_addr.sin6_port));
        }
        else {
            NSLog(@"Unsupported protocol: %d", interface->ifa_dstaddr->sa_family);
            continue;
        }

        // create a socket
        int sock = socket(protocol_family, SOCK_DGRAM, IPPROTO_UDP);
        if (sock == -1) {
            NSLog(@"socket() failed: %s", strerror(errno));
            continue;
        }

        // configure the socket for broadcast mode
        int yes = 1;
        if (-1 == setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)) {
            NSLog(@"setsockopt() failed: %s", strerror(errno));
        }

        // create some bytes to send
        NSString *message = @"Hello world!\n";
        NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];

        if (-1 == connect(sock, addr, addr->sa_len)) {
            NSLog(@"connect() failed: %s", strerror(errno));
        }

        // send the message
        ssize_t sent_bytes = send(sock, data.bytes, data.length, 0);
        if (sent_bytes == -1) {
            NSLog(@"send() failed: %s", strerror(errno));
        }
        else if (sent_bytes<data.length) {
            NSLog(@"send() sent only %d of %d bytes", (int)sent_bytes, (int)data.length);
        }

        // close the socket! important! there is only a finite number of file descriptors available!
        if (-1 == close(sock)) {
            NSLog(@"close() failed: %s", strerror(errno));
        }
    }

    freeifaddrs(interfaces);
}

此示例方法在端口16000上广播UDP消息.

This sample method broadcasts a UDP message on port 16000.

要进行调试,可以使用工具 socat .只需在您的计算机上运行以下命令(该命令应该与电话位于同一网络上)即可:

For debugging, you can use the tool socat. Just run the following command on your computer (which should be on the same network as the phone):

socat UDP-RECV:16000 STDOUT