Network broadcast/multicast not sent by iPhone in personal hotspot mode

穿精又带淫゛_ 提交于 2019-12-03 13:32:57

I got broadcast working (on 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. 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!

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.

Full Sample Code

#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);
}

This sample method broadcasts a UDP message on port 16000.

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

Quick-and-dirty workaround

I also ran into the same problem when developing an iPhone app that uses a UDP multicast message to discover devices on the network. Apparently the iPhone blocks multicast messages in personal hotspot mode.

However, iPhone seems to use the 172.20.10.0/28 subnet for devices on the personal hotspot network. This means that there are just 16 possible addresses. Of these, 172.20.10.0 is apparently not used, 172.20.10.1 is the iPhone itself, and sending messages to 172.20.10.15 fails with a 'not permitted' error. This means that only the following 13 addresses can be used by clients: 172.20.10.2, 172.20.10.3, ..., 172.20.10.14.

So my work-around is pretty simple: instead of sending broadcast messages only to to 224.0.0.0, I also send them to all the other possible addresses in the subnet (172.20.10.2 - 172.20.10.14).

Of course, to be future-proof in a production app you should probably check the list of network interfaces, check the IP and subnet, etc., but for my personal use this method is sufficient.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!