UDP broadcasts with expected replies

In this question https://developer.apple.com/forums/thread/660472 ,
I thought there was a problem on Big Sur beta with getifaddrs() or with UDP broadcasts.

But the problem turns out to be something else. UDP broadcasts seem to work, The problem occurs, not with the UDP broadcasts, but with the UDP responses to the broadcasts, and not just on Big Sur (and iOS 14), but with the most current release of Catalina 10.15.7.

And, the problem did *not* occur on previous releases of Catalina (from several months ago, maybe 10.15.4? and earlier).

getifaddrs() is returning the IPV4 address 127.0.0.1, and that is the address being used for the UDP broadcast.

On macOS 10.15.7, with code compiled with Xcode 12, the UDP broadcasts to 127.0.0.1 succeed. e.g. they are going out on the local network, as seen by a UDP sniffer running on a Raspberry Pi 4 on the local network.

But, the new binaries never receives a UDP reply from the target responding device that is trying to respond to the UDP broadcast.

However, on the same Mac and macOS 10.15.7, running a binary that was compiled months ago with Xcode 11 from the same source code, that old binary does receive UPD responses to the UDP broadcasts. Reliably. And the code also works just fine on Mohave.

What could cause this difference? Is it intended? (new firewall rules?). Does it require a bug report? (If so, to where?) Or is there a way to turn it on/off, so that UDP responses to broadcast UDP packets can be received?

Note that this UDP (broadcast and response) protocol is an open source protocol being used by likely multiple thousands of existing SDR radios from multiple hardware vendors, and dozens of open source software projects on multiple platforms. e.g. something not likely to change.

And my iOS beta test SDR app did work a few months ago using this protocol, but can't be built with the current Xcode release and still work.

Replies

getifaddrs is returning the IPv4 address 127.0.0.1, and that is the
address being used for the UDP broadcast.

Can you clarify this statement? 127.0.0.1 is the IPv4 localhost address. How are you getting that back from getifaddrs? And what are you doing with that address?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Eskimo asks:

Can you clarify this statement? 127.0.0.1 is the IPv4 localhost address.
How are you getting that back from getifaddrs? And what are you doing with that address?

A call to getifaddrs(&p) on macOS 10.15.7 is returning exactly 2 IPV4 addresses,
where (p->ifaaddr->safamily == AFINET)

Those 2 IPV4 addresses are:
  • > 0.0.0.0

  • > 127.0.0.1

If I try a UDP broadcast to those 2 addresses, one at a time:

my
socket = socket(PFINET, SOCKDGRAM, 0);
setsockopt(mysocket, SOLSOCKET, SOBROADCAST,
               (char *)&i, sizeof(i));

if ((p->ifa
addr) && p->ifaaddr->safamily == AFINET) {
                strncpy(ip
str, inetntoa(bcastAddr.sinaddr), ADRSIZE);
bcastAddr.sinfamily = AFINET;
    bcast
Addr.sinport = htons(1024);
                bcast
Addr.sinaddr
                        = ((struct sockaddr
in *)(p->ifabroadaddr))->sinaddr;
sendto(mysocket, (char *)data, 63, 0,
                           (const struct sockaddr *)&bcast
Addr,
                           sizeof(bcastAddr));

The UDP broadcast to 127.0.0.1 is the one I see on my local network (UDP sniffer on a Raspberry Pi).

There is a device on the local network that responds to that UDP broadcast by replying with a UDP packet.

This code looks for the response after the above UDP broadcast:

b = recvfrom(my
socket, (char *)data, 1500, 0,
                 (struct sockaddr *)&recvAddr, &addrLen);

This code compiled using older versions (3 to 6 months ago) of Xcode receives a UDP response from that broadcast.
This code when compiled and run on older versions (maybe 6 months ago) of Catalina or Mohave receives a UDP response from that broadcast.
This code runs an a Raspberry Pi 4 running Buster, and receives a UDP response from that broadcast.

e.g. recvfrom() returns a positive number, and a valid recv
Addr IP address for the responder.

The same code compiled with Xcode 12.0.1 and run on the current version of macOS 10.15.7 never receives any UDP data on that socket and port (1024).

The above is all clang compiled command-line C code on macOS.
But an identical new failure occurs inside an iOS Objective C app built for the current iOS with the latest Xcode SDK releases.
The UDP code used to work (receive a response) 6 months ago, both on the macOS Terminal command-line and inside an iOS app.

What changed? Is the UDP response firewalled? Or am I missing some new Network permission requirement?
Is the change in macOS and iOS (at the same time), or inside the Xcode SDK being used (stdlib, etc.)?
First up, administrivia: If you use code syntax for your code that’ll make it much easier to read. For inline code, surround it by single backticks. For code blocks, press the <> button. Without this DevForums eats your underscores [1], which is kinda problematic when discussing BSD Sockets code.

A call to getifaddrs(&p) on macOS 10.15.7 is returning exactly 2
IPv4 addresses, where (p->ifa_addr->sa_family == AF_INET)

Those 2 IPv4 addresses are:

  • 0.0.0.0

  • 127.0.0.1

OK, this makes no sense on two different fronts:
  • If your Mac has a broadcast-capable interface then you should get back an IPv4 address for that interface. This will either be a real address (DHCP, manual, and so on) or, if not, it’ll be a link-local address in the 169.254/16 subnet (for more on this, see RFC 3927). If you’re not getting this then there’s something wrong with your getifaddrs code.

  • However, even if you do get that back that’s not a broadcast address. Valid broadcast addresses are 255.255.255.255 or the maximum address on the subnet (for example, the broadcast address for 169.254/16 is 169.254.255.255).

Historically BSD-based systems would treat 0.0.0.0 as a broadcast address but that’s not something you should be relying on.



I’d like to clear up the first point first. Below is some code that prints out the interface list. Please run it on your Mac and let me know what it prints.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

[1] To be clear, DevForums should not treat embedded underscores, like the one within AFINET, as introducing italics (r. 64929174). In standard Markdown an underscore is only significant at the edge of a word.



Code Block
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <netdb.h>
static void printIPv4Addresses(void) {
struct ifaddrs * addrList = NULL;
int err = getifaddrs(&addrList);
if (err != 0) {
printf("getifaddrs failed, error: %d\n", err);
return;
}
struct ifaddrs * cursor = addrList;
while (cursor != NULL) {
if ( (cursor->ifa_addr != NULL) && (cursor->ifa_addr->sa_family == AF_INET) ) {
char ifAddr[NI_MAXHOST];
err = getnameinfo(
cursor->ifa_addr, cursor->ifa_addr->sa_len,
ifAddr, sizeof(ifAddr),
NULL, 0,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (err != 0) {
strlcpy(ifAddr, "?", sizeof(ifAddr));
}
printf("%8s %s\n", cursor->ifa_name, ifAddr);
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrList);
}
int main(int argc, char **argv) {
#pragma unused(argc)
#pragma unused(argv)
printIPv4Addresses();
return EXIT_SUCCESS;
}

Hmmm... your code
Code Block
err = getnameinfo( cursor->ifa_addr , ...

is printing out :

     lo0 127.0.0.1
     en0 10.0.1.128

but my code is using and printing out :
Code Block
cursor->ifa_broadaddr , ...


Oops, I had my print pipelined in the wrong order. Here's the correct print out

Code Block
bcast_Addr.sin_addr = ((struct sockaddr_in *)(p->ifa_broadaddr))->sin_addr;
strncpy(ip_printout_string, inet_ntoa(bcast_Addr.sin_addr), ADR_SIZE);

0 try ip:      lo0 127.0.0.1 63 
1 try ip:      en0 10.0.1.255 63 

So 10.0.1.255 is the broadcast address that used to work and now doesn't.

Isn't ifa_broadaddr the proper IP address to be used for UDP broadcasts?
In any case, using 10.0.1.255 causes the UDP broadcast to be seen on the local network,
and that broadcast used to get a UDP response, and after updating Catalina and Xcode, no longer does.


Oops, I had my print pipelined in the wrong order. Here's the correct print out

Code Block
bcast_Addr.sin_addr = ((struct sockaddr_in *)(p->ifa_broadaddr))->sin_addr;
strncpy(ip_printout_string, inet_ntoa(bcast_Addr.sin_addr), ADR_SIZE);

0 try ip:      lo0 127.0.0.1 63 
1 try ip:      en0 10.0.1.255 63 

So 10.0.1.255 is the broadcast address that used to work and now doesn't.

Isn't ifa_broadaddr the proper IP address to be used for UDP
broadcasts?

Well, that kinda depends. 10.0.1.255 is the broadcast address for the 10.0.1/24 network. Most folks use the local broadcast address, that is, 255.255.255.255. For more on this distinction, read up on broadcast addresses.

I’d like to confirm the on-the-wire behaviour here. If you look at a packet trace (see Recording a Packet Trace), do you see the 10.0.1.255 get sent? If so, what’s its source and destination IP? And does your accessory send a response? And, if so, what does its destination and source IP look like?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
I have a UDP packet sniffer on the same LAN (a Raspberry Pi 4) at 10.0.1.125 . It reliably sees the UDP broadcast packet from 10.0.1.128 (the Mac) when I run the UDP broadcast code on the Mac.

There is a UDP responder on the network at 10.0.1.122 (hidden inside an FPGA). Pinging 10.0.1.122 from the Mac (10.0.1.128) works. Pinging the Mac (10.0.1.128) from the Pi (10.0.1.125) also works. The Pi and an older Mac running identical UDP broadcast code sees the UDP response from 10.0.1.122 reliably, in response to identical UDP broadcast packets (as seen by the Pi 4 sniffer). The sender address in the UDP response packet is 10.0.1.122 (as received on the older Mac and Pi).

Sending UDP packets directly (not broadcast) from a newer updated Mac (10.0.1.128) to the responder (10.0.1.122) and receiving UDP replies works great. But the newer Mac after updating to the latest macOS 10.15.7 with Xcode 12.0.1 never sees any UDP replies to UDP broadcast packets. So the broadcast code used to work (as in receive replies), but now does not, after macOS was updated to 10.15.7 (or maybe after .5 or .6, not when the issue first started). Similar issue with recent updates to iOS. Code that used to work 4 to 6 months ago, now does not.

Here's the strange thing on the Mac updated to 10.15.7 :
These 2 binaries were built from the same source code version (C code in a git repo):
Code Block
-rwxr-xr-x  1 rhn  staff    33980 Jun 29 10:46 hl2_tcp*
-rwxr-xr-x  1 rhn  staff  57344 Oct  7 15:14 hl2_tcp*

The June binary works (gets a UDP response from 10.0.1.122).
Todays build does not.
On the same Mac.

Oh. I forgot. The source code has been on GitHub:
Code Block
github.com hotpaw2 hl2_tcp hl2_tcp.c

But the newer Mac after updating to the latest macOS 10.15.7 with
Xcode 12.0.1 never sees any UDP replies to UDP broadcast packets.

So presumably the source address of these broadcasts is 10.0.1.122. What’s the destination address?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
When the Raspberry Pi and the older Mac do the UDP broadcast, they see their own IP address as the destination address in the UDP reply.

Here's the trace route. from the working build on the new Mac (similar to the traceroute on an older Mac):

Code Block
08:02:46.609781 IP 10.0.1.128.55446 > 10.0.1.255.1024: UDP, length 63
08:02:46.619310 IP 10.0.1.122.1024 > 10.0.1.128.55446: UDP, length 60

b = recvfrom() sees this reply. (10.0.1.128 is the new Mac, 10.0.1.122 is the FPGA).
b returns 60.

On the newly updated Mac, here's the trace route for the freshly compiled build:
Code Block
08:06:03.297044 IP 10.0.1.128.51366 > 10.0.1.255.1024: UDP, length 63
08:06:03.309021 IP 10.0.1.122.1024 > 10.0.1.128.51366: UDP, length 60

b = recvfrom() times out. (identical C source code!)
b returns -1 after multiple retries for many mS.

So the getifaddr(), broadcast, device, and network behavior are the same. Used to work 4 months ago on Catalina and iOS.
But the newly compiled code behaves differently on the freshly updated new Mac(s) (Catalina, Big Sur beta, and iOS 14.0.1).

Is this firewalling of the UDP response from the application intentional, due to a missing new compiler or build configuration option, or a bug?
So here's an update:
getifadders() and UDP broadcast and UDP receive of the reply works...
BUT only from an admin account on macOS Catalina 10.15.7 and Big Sur beta.

From user accounts, Big Sur seems to completely block the UDP broadcast (sendto() returns -1),
and Catalina appears to allow the UPD broadcast but blocks (firewalls) receiving any UDP replies.
Even when the Firewall is turned completely off in System Preferences : Privacy.
iOS probably recently inherited the same behavior to identical code (stuffed inside a test iOS app).

Source code for the broadcast app is here: https://github.com/hotpaw2/hl2_tcp
Source code for a fake emulator of the hardware/FPGA UDP reply (runs on a Raspberry Pi) is here: https://gist.github.com/hotpaw2/5e75e19ecd1fb2d43d6f8f6f44bd2611

Older macOS releases (and older iOS releases) allowed this UDP broadcast and UDP receive to work, even from non-admin User accounts. Even with the Firewall turned on.

Intentional (new firewall privacy/security feature)? Or a bug?