Zeroconf: mDNS resolution not working for iOS devices with app built with Xcode 12.5
mDNS resolution is always returning 0 results and throwing an UnobservedTaskException silently for iOS devices (w/ iOS 14.5), when the app is built with Xcode 12.5. This happens on both local debugging session and on production released version. The exception message is: Cannot access a disposed object. Object name: 'System.Net.Sockets.UdpClient'.
Everything was/is working fine before the app was built with Xcode 12.5
I’m attaching a sample project that can be run. In the iOS project, there’s a method that catches unobserved exceptions and it is logging the inner messages.
Please note that the app must be run at least once, so that the new iOS 14 local network scanning permissions can be granted. Also, if the scan is being run only once, there are no results again but there is no error message. The silent exception is appearing in case we have consequitive scans.
Devices info: Tested on both iPhone X w/ iOS 14.5 & iPhone 12 Max Pro w/ iOS 14.5
Demo project: TestMdns.zip
About this issue
- Original URL
- State: open
- Created 3 years ago
- Comments: 26
Testing a few things mentioned here, I can also confirm that Xcode 12.5 is the issue. It seems like we may have to come up with a fix by switching over to use the NWConnectionGroup API, that sounds like a decent long-term solution.
Unless someone has an intuitive quick fix to this issue outside of a downgrade to 12.4, I will go ahead and make a test branch and try to get something running over this next week using the NWConnectionGroup API in replacement of the legacy Socket.
@cosinekitty This surely looks like it should fix the problem. This doesn’t seem to be related to the new Xcode version, but the strange thing is that I started experiencing it as soon as Xcode updated. Nevertheless, I’ll come back as soon as I’m able to test the fix, in case a new version isn’t released in the meantime.
@SpencerBurgess , @clairernovotny I have built a working Bonjour-based prototype.
This is its location: https://github.com/rcinge/Zeroconf/tree/bonjour_prototype
Currently it supports ResolveAsync() only; I have hardcoded the protocol “_audioplayer-discovery._tcp.local.” in the test Xamarin app, simply because that’s what I am working on. You should change this to a service that responds on your network. Note the required addition in project ZeroconfTest.Xam.iOS, Info.plist, and note that the NSBonjourServices string(s) should not include the .local:
The protocol used for the query is hardcoded in project ZeroconfTest.Xam, App.cs: ResolveOnClicked().
The scaffold entry point is in project Zeroconf, BonjourScaffold.cs. The integration is in BonjourBrowser.cs.
Note 1:
Apple’s API wants the caller to search using ServiceType and Domain. So there’s some logic in BonjourBrowser.StartDiscovery() to parse these from the protocol argument of ResolveAsync().
From an extremely rapid skim of the RFC, it looks like ServiceTypes must end in “._tcp.” or “._udp.” (I am calling these the “delimiters”) If there are more “delimiters” or I just plain missed the point, let me know what’s required and I’ll fix it…
Up to and including the “delimiters” is the service type; what follows the “delimiters” is the domain. If nothing follows those “delimiters,” the domain should be assumed to be “.local.”
Apple’s SearchForServices() documentation encourages developers to leave the domain parameter empty.
Note 2:
If someone knows where the real Xamarin definition of sockaddr, sockaddr_in, and sockaddr_in6 live, then the Sockaddr cruft class can go away.
Note 3:
A lot of the dictionary and Sockaddr stuff was brute force; for instance, all IPAddresses reported by a service are used as part of the key of the discoveredService dictionary and the entire zeroconfHost dictionary. Why? Just because you’re paranoid doesn’t mean they’re not out to get you.
Known bugs/issues:
For multiple protocols, the current code issues a SearchForServices(), but does not pay attention to the SearchStarted/SearchStopped events. It should do this and wait until all SearchStopped events (or errors) are seen?
None of the ResolverListener() is implemented.
Certainly many more.
@mduchev @clairernovotny Here’s Apple’s response to me:
After thinking about this, this is my guess/story about what is going on:
The reason this issue appeared with Xcode 12.5 is that the new multicast restriction policy is implemented in Apple-shipped library code (the NWConnectionGroup API), and an updated version of that library would ship with the new Xcode. Likewise, the library wrappers for all system calls would also ship with the new release of Xcode.
The core bit: there’s some undocumented socket option or system call argument that signals to the macOS kernel that multicast traffic is allowed on this specific socket. The NWConnectionGroup API knows this secret and will enable multicast traffic on a socket if: (1) the desired mDNS protocols and description are coded in Info.plist (2) the protocols specified do not violate Apple’s restrictions (3) the device user consents to allow the app to examine network traffic. Finally, as results from the multicast query are returned by the Receive() system call, the NWConnectionGroup API then gets to filter the data it returns to the caller.
The legacy BSD Sockets system call wrappers also know the secret. Before Xcode 12.5, they would enable multicast traffic unconditionally, but starting with Xcode 12.5, they don’t enable any multicast traffic.
Final bit: In the macOS kernel, when the network receive system call is executed, if the secret option has been set and the user consent flag is set, all is well. If the secret option is not set, the kernel looks for the multicast entitlement; if the entitlement exists, the receive call proceeds (legacy mode), otherwise it returns the “No route to host” error.
Apple denied my request for the multicast entitlement because it gives away the keys to the kingdom: the BSD Socket API is basically a set of kernel system calls-- it’s just too low level for all the policy enforcement. Since I am still developing my app, their logic of “well, use this API instead” is sound.
If the story is true, to make Zeroconf function again on iOS requires code that calls the NWConnectionGroup API.
Unfortunately, I can also confirm that the PR doesn’t fix the issues.
Without my deeply investigating, #190 is likely to be a fix for this issue. Perhaps you could try that code change and let us know if the problem goes away.