Bonjour: Search for a service by name

3.1k views Asked by At

I have an application that needs to search and resolve a Bonjour-advertised service whose name is known in advance. Most Bonjour examples I have found related to service discovery are structured more or less like this:

  1. Call browse to detect all services of a given type (for example, this could be _http._tcp)
  2. For each service found, serviceFound is called. Service names are reported here
  3. Call resolve on each service found
  4. For each service resolved, serviceResolved is called

Is it possible with Bonjour to skip the "discovery" stage, since I know in advance the name of the service I want to resolve? Can I just detect and resolve a service with a known name?

1

There are 1 answers

4
Alexandre Fenyo On BEST ANSWER

1- Answer

Yes, you can start with the 3rd step if you already know the name of the service. This is because this step is performed through a DNS lookup for a SRV record with the name of the service sent to a well-known multicast address. So, no previous information is needed to make this call, and the mDNS responder must be stateless, since the underlying DNS protocol is stateless (each response is bound to a unique request - no state maintained between several requests).

2- Example

Here is an example I've just written with Swift, that has passed tests running on my iPad to find a service running on my Mac Mini. So, we suppose the domain is local, the service type is _http._tcpand the name of the service is myservice, running on host Mac-mini-de-Alexandre.local and listening to TCP port 8080.

To track informations about the service, for instance its hostname and TCP port, we define a class that implements the NetServiceDelegate protocol:

class MyNetServiceDelegate : NSObject, NetServiceDelegate {
     public func netServiceDidResolveAddress(_ sender: NetService) {
        print(sender.hostName!, sender.port)
    }
}

This new class will be used to instantiate the delegate for a NetService instance.

So, we create a NetService instance corresponding to the service we already know about, that we store in the long-term with a static constant property of some main class:

static let ns = NetService(domain: "local.", type: "_http._tcp.", name: "myservice")

It is stored in the long term because it must not be deallocated before we find our service.

Note that the delegate property in class NetService is declared unowned(unsafe). So, we also need to create a reference to the delegate instance:

static let ns_deleg = MyNetServiceDelegate()

When we want to resolve the service, we may write:

ns.delegate = ns_deleg
ns.resolve(withTimeout: TimeInterval(10))

The delegate instance will be called later (resolve() is a non-blocking method) if the service is found, and in this case, it will print the hostname and port.

Here is the output I got in my Xcode output window:

Mac-mini-de-Alexandre.local. 8080

Finally, note that because of the unowned reference, it would be a mistake to write the following code (the delegate instance would be shortly deallocated):

// bad code -- do not write that -- only here to show a common mistake
ns.delegate = MyNetServiceDelegate()
ns.resolve(withTimeout: TimeInterval(10))

3- Trick to help debugging

Here is a little trick to debug such a mDNS resolution: on a Unix shell (macOS for instance), just run the following line:

dig -p 5353 @224.0.0.251 myservice._http._tcp.local. SRV +short

If an http service with name myservice is running, you will get the host name and port. With my example, you will get the following:

0 0 8080 Mac-mini-de-Alexandre.local.

So, before trying to use the Swift code I've written here, just check that your service is correctly announced with this shell command.

Finally, note that this dig based command only makes one IPv4 mDNS query on each IPv4 network interface, but using the Apple Bonjour API, two groups of mDNS requests are done automatically: one with IPv4 to the multicast destination 224.0.0.251 on each network interface that supports IPv4, and another with IPv6 to the multicast destination ff02::fb on each interface that supports IPv6.