How to do UDP broadcast in iOS 9?

We have an app which uses an IPv4-style UDP broadcast to "ping" the local network, so it can find the IP address of a specific custom embedded hardware device which responds to that broadcast. This all worked fine until iOS 9. Now, the sendto() call in our app fails with errno == EADDRESSUNAVAIL. Digging further, we believe that our UDP broadcast to 255.255.255.255 is just incompatible with the new iOS9, IPv6 way of doing things.


Everything I read tells me that IPv6 multicast is the "right" way to solve that problem, now. We've tried to modify our code to send our "is anybody out there?" UDP broadcast via IPv6 multicast, but that doesn't quite work yet. Before I go into details, though, my first question is: is this problem even fundamentally solvable? We cannot update the firmware on the embedded device our app is searching for; that device has an IPv4 stack, it listens for UDP broadcasts on 255.255.255.255 and we cannot change its code to do anything else. Given this limitation, is there any way at all an app running on iOS9 (with its IPv6-only stack) can even send a UDP packet that our device will hear? In other words, are we fundamenally screwed?


If this problem is even tractable at all, here is my stack overflow post - including code - that describes my attempt to solve it in IPv6.

http://stackoverflow.com/questions/32912042/need-ipv6-multicast-c-code-that-works-on-ios-9?noredirect=1#comment53744115_32912042

My one response from Stack Overflow suggests that I used a different IPv6 address. I tried that, but "no joy."


Really, I think we are going to need guidance from someone intimiately familiar with Apple's iOS 9 implementation of POSIX networking. I don't think we are going to get there by guessing.


Finally - if there truly is no way to send an IPv4-style UDP broadcast from an iOS 9 app to our vintage 2005 embedded device ... how should we solve this problem in the future? If we build a new embedded device, what is the "right" way to do network discovery protocol? Bonjour? Something else? If we have to start over from scratch, I'd at least like to make sure we start off on the right foot.


Thanks to all.


-Tim

Replies

Everything I read tells me that IPv6 multicast is the "right" way to solve that problem, now.

I disagree. The right way to solve this is with Bonjour. Bonjour solves this problem comprehensively and in a standards-compliant way. IMO it’s unlikely that any ad hoc solution will be as good.

Of course, a good Bonjour implementation will use IPv6 if the OS it’s running on supports it (-:

Digging further, we believe that our UDP broadcast to 255.255.255.255 is just incompatible with the new iOS9, IPv6 way of doing things.

No. At least, not if you’re talking about the requirements mentioned in our recent developer news post. Those are App Review requirements, not technical requirements.

As to how those requirements will be enforced, I don’t work for App Review and thus I can only point you to the information that they’ve published (that is, the above-mentioned developer news post). I expect that they will clarify things as the deadline gets nearer. For the moment, I strongly recommend that you test your entire product in a NAT64 environment, as described in the documents referenced by that post.

IMPORTANT A NAT64 network won’t hand out IPv4 addresses. iOS will cope with that just fine, using IPv6 for WAN access and both IPv6 and IPv4 for link-local stuff. This works because (amongst other things) iOS supports IPv4 link-local addressing (RFC 3927). You’ll have to make sure your embedded device either supports IPv6 or, if it’s IPv4 only, can work with IPv4 link-local addresses.

This all worked fine until iOS 9. Now, the sendto() call in our app fails with errno == EADDRESSUNAVAIL.

I’m not sure why your current code is failing but, in my experience, lots of UDP broadcast code works way too hard due to historical limitations of BSD Sockets. At the end of this email I’ve pasted in some code that shows how I do broadcasts (using

IP_BOUND_IF
). I tested it on iOS 9 and it works just fine.

The only gotcha is correctly identifying the Wi-Fi interface, which is hard now that

CNCopySupportedInterfaces
is deprecated. In my example I hard-coded
en0
, which is less than ideal.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
- (void)sendBroadcast {
    unsigned int        wifiInterface;
    int                fd;
    BOOL                success;
    struct sockaddr_in  destAddr;
    NSData *            payloadData;
    ssize_t            bytesSent;

    wifiInterface = if_nametoindex("en0");
    assert(wifiInterface != 0);

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(fd >= 0);

    static const int kOne = 1;
    success = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &kOne, sizeof(kOne)) == 0;
    assert(success);

    success = setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &wifiInterface, sizeof(wifiInterface)) == 0;
    assert(success);

    memset(&destAddr, 0, sizeof(destAddr));
    destAddr.sin_family = AF_INET;
    destAddr.sin_len = sizeof(destAddr);
    destAddr.sin_addr.s_addr = INADDR_BROADCAST;
    destAddr.sin_port = htons(12345);

    payloadData = [[NSString stringWithFormat:@"%@\r\n", [NSDate date]] dataUsingEncoding:NSUTF8StringEncoding];

    bytesSent = sendto(fd, payloadData.bytes, payloadData.length, 0, (const struct sockaddr *) &destAddr, sizeof(destAddr));
    if (bytesSent >= 0) {
        NSLog(@"success");
    } else {
        NSLog(@"error %d", errno);
    }

    success = close(fd) == 0;
    assert(success);
}

Quinn-


Thanks very much for taking the time to look into this. Appreciate the suggestion for Bonjour for our future embedded device - we'll do that way with the next rev of our hardware device.


However, we are still seeing the same failure on iOS 9 with the code snippet you've sampled us. We made minor changes to make your code compile in our C++ environment, but don't think these are significant. Just in case, I've included our version at the end of this email.


Specifically, we see sendto() return -1 with errno set to 51 (ENETUNREACH). Yet the same iOS device running your code can communicate with an HTTP server living on the same embedded device that we are trying to send the UDP broadcast to. And the sendto() call in your code succeeds when we run it on an iOS 7 device joined to the same network. We don't think there's anything "wrong" with the IPv4 network that our iOS 7&9 test devices are joined to.

Our feeling, at this point, is that we've simply hit an iOS 9 bug. For clarification, we are running iOS 9.0.2 on an iPad Air. We are building your code with Xcode 6.4.

Finally, here is our slightly modified version of your sample code. Again, thanks very much for the assistance here. Can you suggest next steps?

Best regards,

-Tim


int broadcast_udp_msg ( char addr, short port, char msg, size_t msgsize )
{


  unsigned int        wifiInterface;
  int                fd;
  bool                success;
  struct sockaddr_in  destAddr;
  //NSData *            payloadData;
  ssize_t            bytesSent;

  wifiInterface = if_nametoindex("en0");
  //assert(wifiInterface != 0);  // we see wifiInterface == 6

  fd = socket(AF_INET, SOCK_DGRAM, 0);
  //assert(fd >= 0);

  static const int kOne = 1;
  success = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &kOne, sizeof(kOne)) == 0;
  //assert(success);  we see success == 1

  success = setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &wifiInterface, sizeof(wifiInterface)) == 0;
  //assert(success);  we see success == 1

  memset(&destAddr, 0, sizeof(destAddr));
  destAddr.sin_family = AF_INET;
  destAddr.sin_len = sizeof(destAddr);
  destAddr.sin_addr.s_addr = INADDR_BROADCAST;
  destAddr.sin_port = htons(12345);

  char *payloadData = "Hello";//[[NSString stringWithFormat:@"%@\r\n", [NSDate date]] dataUsingEncoding:NSUTF8StringEncoding];

  bytesSent = sendto(fd, payloadData, strlen(payloadData), 0, (const struct sockaddr *) &destAddr, sizeof(destAddr));
  if (bytesSent >= 0) {
  printf("success\n");
  } else {
  printf("error %d\n", errno);     // we see this output in console with errno == 51
  }
  success = close(fd) == 0;
  //assert(success);

  return success;

}

Like I said, I actually tested that code on iOS 9 and it worked as expected, which implies that there’s some sort of environment difference in place. I was testing on my home infrastructure Wi-Fi, with 192.16/16 IP addresses provided by DHCP, along with default gateway and a DNS server. I tested with a WWAN capable device with both WWAN off and WWAN on.

How about you?

Oh, there’s one other thing I’d like you to test. Use

getifaddrs
to get the IPv4 address for the interface and then bind (using
bind
) the socket’s source address to a port on that interface. Let me know if that changes things.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Quinn,


Sorry for belated reply. We have some new information for you.


1) Our Wi-Fi configuration is a bit different. Our embedded hardware device generates an Ad Hoc Wi-Fi network. The iOS 9.0.2 iPad and our device are both in Ad Hoc Wi-Fi mode. Your code fails to send a UDP broadcast from an iOS 9 phone on the Ad-Hoc Wi-Fi network. That may be significant.


2) We also have an AirPort Extreme router. When we join the iPhone to our home AirPort Extreme Wi-Fi router (i.e. infrastructure mode Wi-Fi), your code succeeds in sending the UDP broadcast. In order words, we can replicate your result. Even better, when our emedded device is joined to the same AirPort Extreme Wi-Fi network as the iOS 9 device, it recieves the UDP broadcast and responds. Cool!


3) We added calls to getifaddrs() and bind(), as you suggested. It does not change our results. UDP broadcast fails on the Ad Hoc Wi-Fi network, but succeeds on the AirPort Extreme (infrastructure mode) Wi-Fi network.


So, really, the iOS 9 UDP broadcast failure only happens when the iOS device is on an Ad Hoc Wi-Fi network. What do you suggest next?


-Tim

Ad hoc means different things to different people. If you’re talking about IBSS then I doubt that’s relevant because Wi-Fi parameters like that generally don’t affect IP-level routing decisions.

The other thread in flight right now indicates that the problem is most likely related to whether the interface has a gateway.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

What do you suggest next?

You should definitely file a bug about this, with as much detail as you can collect. Please post your bug number, just for the record.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Try calculating your broadcast address based on the adapters ip and subnet address.

ALso, I'm not sure I understand the statement, "it listens on 255.255.255.255". I'm assuming you meant to say that it listens over a specific udp port required by firmware Instead of a broadcast address.

HTH


greg