Oh, the packets are fragmented. This explains the issue.
So, in the above description I omitted something important. Regarding datagramVectorReadMessageCount I said this:
macOS is not one of those platforms! Indeed, on macOS we have an exploding shim for the function. So on macOS, under the hood we're seeing you set the vector read size to 30 and just ignoring it:
case _ as ChannelOptions.Types.DatagramVectorReadMessageCountOption:
// We only support vector reads on these OSes. Let us know if there's another OS with this syscall!
#if os(Linux) || os(FreeBSD) || os(Android)
self.vectorReadManager.updateMessageCount(value as! Int)
#else
break
#endif
However, we don't ignore the update to the RecvByteBufferAllocator: that still applies. This is the clue.
The secret here is that the UDP packet in question is actually really large: much larger than the MTU. You can see this in the Wireshark reassembly (occurring in packet 4) where Wireshark synthesises a fake UDP packet with Data (4028 bytes). This is a 4028-byte UDP datagram. But your network has a 1500-byte MTU. This forces the datagram to be spread across a number of IP fragments.
macOS is reassembling this UDP datagram into a single, 4028-byte datagram, and delivering that in a single shot to user space. But by default we only have a 2kB buffer, so this packet gets truncated. When you increased the size of the allocator to 60kB, there was now space for this datagram! Turns out the vector reads are a red herring here, but the allocator size is not.
I didn't propose this theory earlier because, frankly, IP fragmentation is fairly unusual. Most protocols try to perform MTU discovery and then fragment at the application layer because it's quite common for fragmented packets to fail to traverse network boundaries.
In this case, you can remove the vector read change and just set the buffer size to something somewhat reasonable. UDP has a 16-bit packet length so the absolute fallback is 64kB, which will definitely hold any UDP packet irregardless of how big it gets. In this case you're likely to use very few channels so it's probably fine to have slightly excessive memory use here.