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:
- TCP connection not working properly when using iPhone as hotspot
- 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?
Many thanks in advance.
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:
- use
getifaddrs(if)
to loop through all interfaces (do {} while (if = if->ifa_next)
)
- reject loopback interfaces (
if->ifa_flags & IFF_LOOPBACK
)
- filter only interfaces that support broadcast (
if->ifa_flags & IFF_BROADCAST
)
- create a socket with
int sock = socket()
- enable broadcast:
int yes = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes)
- connect to the broadcast address:
connect(sock, if->ifa_dstaddr, if->ifa_dstaddr->sa_len)
- 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.