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

2

8

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?

The Application

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.

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

The Problem

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?

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.

Collateral Information

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

  1. TCP connection not working properly when using iPhone as hotspot
  2. Fail to send ssdp broadcast by personal hotspot

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).

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.

In summary

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

Enolaenormity answered 1/3, 2015 at 16:13 Comment(4)
Interesting - but not surprising. Apple're very fond of locking stuff right down, and it sounds like what's going on here (although I've no personal experience with messaging during a Personal Hotspot session). You may find there's nothing that can be done. My only suggestion would be to see if Bonjour functions correctly, which may shed light on what's happening. If it works, perhaps you could use Bonjour + unicast for that case, if it's particularly important to you.Blakemore
@Michael: Many thanks for your response. I was afraid I might have to code up an IOS-specific fix (i.e., using Bonjour) and this for a cross-platform product that is currently using shared C++ code. I will certainly investigate that angle. Your point is well made -- if Bonjour works correctly, Apple is limiting hotspot functionality for non-Bonjour apps.Enolaenormity
No worries - here's a little bonjour test app you could use to try it out, too. Just run one instance on the phone running Personal Hotspot, and another on a different device. dl.dropboxusercontent.com/u/6956432/BonjourTest.zipBlakemore
@user1495323 In my specific case I wanted the iPhone to connect to another particular device on the LAN via broadcasts/multicasts between them. Since it was a hotspot, the iPhone didn't broadcast/multicast, thus thwarting my original plan. I therefore had the other device put up a QR code which, when read by the hotspot iPhone, gave all the information needed. More generically, however, there are two solutions you can use: (a) implement Bonjour on your iPhone to enable socket connections, or (b) use unicasts, rather than broadcast/multicast -- a hotspot iPhone does support unicasting.Enolaenormity
P
7

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
Pallaton answered 10/11, 2016 at 9:53 Comment(0)
P
3

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.

Pallaton answered 10/11, 2016 at 8:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.