Bonjour, also known as zero-configuration networking, enables automatic discovery of devices and services on a local network using industry standard.

Pinned Posts

Posts under Bonjour tag

49 Posts
Sort by:
Post not yet marked as solved
0 Replies
1.7k Views
Every now and then I talk to someone who’s trying to use Bonjour and just can’t get over the first hurdle. That happened today, and so I decided to share my write-up for the benefit of others. Questions or comments? Put them in a new thread here on DevForums, tagging it with Bonjour so that I see it. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Getting Started with Bonjour Bonjour is an Apple term for a variety of Internet standards [1]. Bonjour allows your app to browse for and connect to services on the network without infrastructure support. For example, Bonjour lets you find and connect to a printer even if the network has no DHCP server to hand out IP addresses. If you’re new to Bonjour, a good place to start is the Bonjour Overview. It’s in the documentation archive, so it hasn’t been updated in a while, but the fundamentals haven’t changed. There are, however, two things that have changed: Network framework has new Bonjour APIs, and the old ones are now deprecated. iOS 14 introduced local network privacy. This post shows how to get started with Bonjour, taking into account these new developments. [1] Specifically: RFC 3927 Dynamic Configuration of IPv4 Link-Local Addresses RFC 6762 Multicast DNS RFC 6763 DNS-Based Service Discovery Start Browsing Let’s start by implementing a service browser. To simplify things, this browses for SSH services. That way you can get started with the browser without first having to implement a server to register your service. If you don’t already have an SSH service registered on your network, start one by enabling System Settings > General > Sharing > Remote Login on your Mac. The SSH service type is, unsurprisingly, _ssh._tcp. First, on your Mac, run the dns-sd tool to confirm that you have an SSH service visible on your network: % dns-sd -B "_ssh._tcp" "local." % dns-sd -B "_ssh._tcp" "local." … Timestamp A-R Flags if Domain Service Type Instance Name … 11:54:43.315 Add 2 6 local. _ssh._tcp. Fluffy … 11:54:43.725 Add 2 6 local. _ssh._tcp. SAM the Robot 12 ^C This shows that I have two services, one called Fluffy and the other called SAM the Robot 12. Let’s write some iOS code to browse for those. To start, create an app from the iOS > App template and connect a button to the startStop() method of a class like this: import Foundation import Network class AppModel { var browserQ: NWBrowser? = nil func start() -> NWBrowser { print("browser will start") let descriptor = NWBrowser.Descriptor.bonjour(type: "_ssh._tcp", domain: "local.") let browser = NWBrowser(for: descriptor, using: .tcp) browser.stateUpdateHandler = { newState in print("browser did change state, new: \(newState)") } browser.browseResultsChangedHandler = { updated, changes in print("browser results did change:") for change in changes { switch change { case .added(let result): print("+ \(result.endpoint)") case .removed(let result): print("- \(result.endpoint)") case .changed(old: let old, new: let new, flags: _): print("± \(old.endpoint) \(new.endpoint)") case .identical: fallthrough @unknown default: print("?") } } } browser.start(queue: .main) return browser } func stop(browser: NWBrowser) { print("browser will stop") browser.stateUpdateHandler = nil browser.cancel() } func startStop() { if let browser = self.browserQ { self.browserQ = nil self.stop(browser: browser) } else { self.browserQ = self.start() } } } Note I’m using SwiftUI, but if you chose to use UIKit you could add this code directly to your view controller. Of course, whether you want to add networking code to your view controller is another question. The answer is, natch, “No”, except when creating a tiny test project like this one (-: Now build and run in the simulator and click your buton. It’ll print something like this: browser will start browser did change state, new: ready browser results did change: + SAM the Robot 12._ssh._tcp.local. + Fluffy._ssh._tcp.local. As you can see, it’s found our two SSH services. Yay! Run on the Device Now stop the app and run it on a real device. This time the Test button results in: browser will start … browser did change state, new: failed(-65555: NoAuth) This is local network privacy kicking in. There are two things you need to do: Add a NSBonjourServices property to your Info.plist to declare what service types you’re using. Add a NSLocalNetworkUsageDescription property to your Info.plist to explain what you’re doing with the local network. Do that and run your app again. On tapping the Test button you’ll see an alert asking you to grant your app access to the local network. Tap Allow and the browser will start generating results as before. Respond to Updates When working with Bonjour it’s important to keep your browser running to update your app’s state. To test this, start a Remote Login on a different machine and look for a new result being printed: browser results did change: + Slimey._ssh._tcplocal. And then turn it off: browser results did change: - Slimey._ssh._tcplocal. If you don’t have another Mac to test this with, start a dummy service using dns-sd: % dns-sd -R "Guy Smiley" "_ssh._tcp" "local." 12345 Registering Service Test._ssh._tcp.local. port 12345 … Press control-C to stop the dns-sd tool, which unregisters the service. Connect When the user choose a service, it’s time to connect. There are two ways to do this, depending on the networking API you use to run your connection. NWConnection can connect directly to a Bonjour service endpoint. For example, you might have code that connects to a DNS name and port: func makeConnection(host: String, port: UInt16) -> NWConnection { let host = NWEndpoint.Host(host) let port = NWEndpoint.Port(rawValue: port)! let endpoint = NWEndpoint.hostPort(host: host, port: port) return NWConnection(to: endpoint, using: .tcp) } Replace that with code that takes the endpoint you get back from the browser: func makeConnection(endpoint: NWEndpoint) -> NWConnection { return NWConnection(to: endpoint, using: .tcp) } If you’re using a legacy API, like BSD Sockets, you’ll need to resolve the Bonjour service endpoint to a DNS name and then pass that DNS name into your connection code. Network framework does not support resolving Bonjour service endpoints out of the box, so you’ll have to do that yourself. For an example of how you might do this, see this post. IMPORTANT For this to work reliably, your BSD Sockets code must support Happy Eyeballs. See TN3151 Choosing the right networking API for specific advice on that front. Register a Service Now let’s look at the server side. To listen for connections with Network framework, you might write code like this: import Foundation import Network class AppModel { var listenerQ: NWListener? = nil func start() -> NWListener? { print("listener will start") guard let listener = try? NWListener(using: .tcp) else { return nil } listener.stateUpdateHandler = { newState in print("listener did change state, new: \(newState)") } listener.newConnectionHandler = { connection in connection.cancel() } listener.start(queue: .main) return listener } func stop(listener: NWListener) { print("listener will stop") listener.stateUpdateHandler = nil listener.cancel() } func startStop() { if let listener = self.listenerQ { self.listenerQ = nil self.stop(listener: listener) } else { self.listenerQ = self.start() } } } To register your service with Bonjour, add these lines before the call to start(queue:): listener.service = .init(type: "_ssh._tcp") listener.serviceRegistrationUpdateHandler = { change in print(change) } The listener calls your service registration update handler to tell you the name of the service. Typically you display this value somewhere in your UI. For more about this, see Showing Connection Information in an iOS Server. To confirm that your service is running, open Terminal and choose Shell > New Remote Command. Your service should show up in the Secure Shell (ssh) list. Alternatively, browse for SSH services using the dns-sd tool, as illustrated in the Start Browsing section above.
Posted
by eskimo.
Last updated
.
Post not yet marked as solved
1 Replies
733 Views
I am developing an app that is to be runnable on both Mac Studio, and iPhone 14. The app instance on the Mac communicates with the app's other instance in the iPhone 14. To debug the Bonjour communications I need to run this source code in the debugger in both the Mac Studio simulator, and at the iPhone, simultaneously. The ideal would be to able to step through source code, and set break points, in each app instance simultaneously. Is that possible? If so how?
Posted
by spflanze.
Last updated
.
Post not yet marked as solved
3 Replies
809 Views
DNSServiceNATPortMappingCreate returns external port 0 since macOS 13 b3 build 23A5286i. This was the case in macOS 12 as well (see FB9137313 and FB9139688). Hopefully this won't take months after macOS is released before it is fixed like last time. Latest radar: FB12579235 Also, see https://developer.apple.com/forums/thread/681836
Posted
by Lucky7.
Last updated
.
Post not yet marked as solved
1 Replies
445 Views
My app uses NWBrowser and NWListener to advertise a service and to discover other peer instances on the local network. It regenerates a serial number with each fresh launch. This serial number is included in TXTRecord. I can see that the serial number correctly changes after every restart. The problem that is bugging me right now is as follows. If I run the app with the lightning cable connected to my Macbook and then disconnect the cable, the app running on my IPhone would retain the information about my Macbook no matter how many times I restart the app or restart the other peer. It doesn't matter if I close the app before disconnecting the cable or the other way around. Restarting the app on my Macbook while in this state doesn't refresh the record on my IPhone. The app is supposed to replace the found instances for new and change cases but the old instance always prevails. I even went into airplane mode and turned off everything wireless on both my IPhone and my macbook. Nothing but services previously advertised by my Macbook still show up, which includes _airplay._tcp A third-party app for Bonjour discovery also confirms the phantom instances but it was of course unable to resolve these instances. It always takes tens of minutes for the phantom instances to be removed short of a reboot. I then discovered that if I start the app without the lightening cable connected, this issue wouldn't happen anymore. This suggests to me that this may have to do with the lightning port's network. The same app (via Mac Catalyst) running on the Macbook does not have this issue. Has anyone experienced anything similar?
Posted
by YFQ.
Last updated
.
Post not yet marked as solved
0 Replies
543 Views
I am trying to use BCT to test my iPhone. However, it just stuck in the initial probe stage though the BCT indeed outputs the mDNS packets. Most weird thing is that the target IP of packets BCT outputs is gateway instead of 224.0.0.251. Could somebody help me figure out? Thank you very much.
Posted
by Cychen.
Last updated
.
Post not yet marked as solved
3 Replies
634 Views
On the 4th question of: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/NetServices/Articles/faq.html I see: "you can ask the Bonjour to reconfirm the record" I am basing my code on the TicTacToe example at: https://developer.apple.com/videos/play/wwdc2022/110339/ I believe the DNS records which remains after the device that put them there shuts down is causing problems for me when again. Using the DeviceDiscoveryUI as in that TicTacToe example how are these records "reconfirmed" ?
Posted
by spflanze.
Last updated
.
Post not yet marked as solved
0 Replies
546 Views
These are download links to zip files that contain Xcode 14.3 project files which are my attempt to establish USB communications with an iPhone 14 connected by lightning cable to a MacStudio: https://www.mediafire.com/file/k3my6y94iyjobeq/Bonjour-Trial-iPhone.zip/file https://www.mediafire.com/file/cof3b3w9tru1jd0/Bonjour-Trial-Mac-Enumeration.zip/file I could not attach them here. This web interface's file browser had these files grayed out, so it was necessary to make them available on a file sharing site. The Bonjour-Trial-iPhone.zip project files run on the iPhone 14. The Bonjour-Trial-Mac-Enumeration.zip run on the MacStudio. I have the iPhone 14 project code working on the iPhone. I expect it should be advertising its presence. MacStudio project runs, but does not find the iPhone 14. When I trace execution in file Bonjour-Trial-Mac-Enumeration.swift the "results" array is empty on line 65. What is going wrong here? Is a type "_ssh._tcp" connection possible between a MacStudio, and an iPhone, over the lightning cable?
Posted
by spflanze.
Last updated
.
Post not yet marked as solved
3 Replies
784 Views
The function: browseResultsChangedHandler: ((_ newResults: Set<NWBrowser.Result>, _ changes: Set<NWBrowser.Result.Change>) -> Void)? returns two Set type parameters. There is data in each element of these sets I need to display in a view. What is the best way to convert these sets to an array that can be iterated in a "ForEach" statement so they can be displayed in a View? When I attempt to do it on a set directly I get the error: Cannot convert value of type 'Set<NWBrowser.Result>' to expected argument type 'Range<Int>'
Posted
by spflanze.
Last updated
.
Post not yet marked as solved
2 Replies
568 Views
In the example code in this thread: https://stackoverflow.com/questions/61235935/understanding-some-nwbrowser-i-managed-to-get-working there is this line: print("result ", result ) The second argument in the print statement, the "result" variable, is not a String type. It is instead a struct whose definition begins in line 10731 in the "Network" import file. Somehow the print function knows how to convert this into a string for printing to the terminal. I conclude, with some uncertainty, that the instructions for doing this conversion have to be defined in this struct. Where in this struct is the conversion defined? If not defined there, how is the conversion done?
Posted
by spflanze.
Last updated
.