Bonjour for discovering a specific device's ip

Hi, I'm new to swift programming and right now writing an app for esp8266-controlled lamp device. My lamp is broadcasting it's own IP through bonjour. So all I want is to discover any lamps in my network (http.tcp) and to read name and value. Is there any example of such implementation? All I found so far is old or a lit bit complicated for such simple question. Thanks in advance!
Answered by DTS Engineer in 662482022

Just adding .local to the host name solved the problem.

No it doesn’t. The Bonjour service name and the local DNS name do not need to be related and, even when they are, it’s common for them to be significantly different. For some fun examples of this, see this post.

The only way to get the DNS name of a service is to resolve it.

But it still freezing my app just after service found

Hmmm, I can’t spot the error. Rather than spend time debugging that I created a small test program that illustrates one way to do this. If you paste the code below into a new command-line tool project, it should be able to resolve any service you give it.

Share and Enjoy

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

import Foundation

final class BonjourResolver: NSObject, NetServiceDelegate {

    typealias CompletionHandler = (Result<(String, Int), Error>) -> Void

    @discardableResult
    static func resolve(service: NetService, completionHandler: @escaping CompletionHandler) -> BonjourResolver {
        precondition(Thread.isMainThread)
        let resolver = BonjourResolver(service: service, completionHandler: completionHandler)
        resolver.start()
        return resolver
    }
    
    private init(service: NetService, completionHandler: @escaping CompletionHandler) {
        // We want our own copy of the service because we’re going to set a
        // delegate on it but `NetService` does not conform to `NSCopying` so
        // instead we create a copy by copying each property.
        let copy = NetService(domain: service.domain, type: service.type, name: service.name)
        self.service = copy
        self.completionHandler = completionHandler
    }
    
    deinit {
        // If these fire the last reference to us was released while the resolve
        // was still in flight.  That should never happen because we retain
        // ourselves on `start`.
        assert(self.service == nil)
        assert(self.completionHandler == nil)
        assert(self.selfRetain == nil)
    }
    
    private var service: NetService? = nil
    private var completionHandler: (CompletionHandler)? = nil
    private var selfRetain: BonjourResolver? = nil
    
    private func start() {
        precondition(Thread.isMainThread)

        guard let service = self.service else { fatalError() }
        service.delegate = self
        service.resolve(withTimeout: 5.0)
        // Form a temporary retain loop to prevent us from being deinitialised
        // while the resolve is in flight.  We break this loop in `stop(with:)`.
        selfRetain = self
    }
    
    func stop() {
        self.stop(with: .failure(CocoaError(.userCancelled)))
    }
    
    private func stop(with result: Result<(String, Int), Error>) {
        precondition(Thread.isMainThread)

        self.service?.delegate = nil
        self.service?.stop()
        self.service = nil

        let completionHandler = self.completionHandler
        self.completionHandler = nil
        completionHandler?(result)
        
        selfRetain = nil
    }
    
    func netServiceDidResolveAddress(_ sender: NetService) {
        let hostName = sender.hostName!
        let port = sender.port
        self.stop(with: .success((hostName, port)))
    }

    func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
        let code = (errorDict[NetService.errorCode]?.intValue)
            .flatMap { NetService.ErrorCode.init(rawValue: $0) }
            ?? .unknownError
        let error = NSError(domain: NetService.errorDomain, code: code.rawValue, userInfo: nil)
        self.stop(with: .failure(error))
    }
}

func main() {
    let service = NetService(domain: "local.", type: "_ssh._tcp", name: "Fluffy")
    print("will resolve, service: \(service)")
    BonjourResolver.resolve(service: service) { result in
        switch result {
        case .success(let hostName):
            print("did resolve, host: \(hostName)")
            exit(EXIT_SUCCESS)
        case .failure(let error):
            print("did not resolve, error: \(error)")
            exit(EXIT_FAILURE)
        }
    }
    RunLoop.current.run()
}

main()

Thanks Quinn, this compiles and functions in Swift5, would appreciate a Swift6 compatible version. Currently I receive errors when attempting to use logger as below:

browser.stateUpdateHandler = { newState in
            logger.info("Browser did change state, new state: \(newState)")
        }

This produces error "Type of expression is ambiguous without a type annotation". ChatGPT 4 was unable to assist.

Hi there,

I struggle to get the mDNS name using _services._dns-sd._udp on iOS 26.1.

I tried two methods:

NetServiceBrowser always gives me

["NSNetServicesErrorCode": -72008 (missingRequiredConfigurationError), "NSNetServicesErrorDomain": 10]

DNSServiceBrowser fails with

NoAuth

I also tried NWBrowser, but learned that it does not work with _services._dns-sd._udp.

My Info.plist:

<key>NSBonjourServices</key>
<array>
	<string>_services._dns-sd._udp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>mDNS_Browser needs to have network access to search for Multicast services</string>

I tested it with a physical device, none of the above methods work. The strange thing is that NSBonjourServices works in the simulator the first time I use it but then fails, DNSServiceBrowser does not work. But with macOS 17 DNSServiceBrowser works but NSBonjourServices does not.

This is so random, I am out of ideas.

Here is an example project that can be used to test both methods: GitHub

@DTS Engineer :)

maybe this should be a new thread.

Yes, this should definitely be a new thread (-:

If you do start a thread, use the subtopic and tags as this thread; that way I’ll be sure to see it go by.

Before you do that, however, make sure to review TN3179 Understanding local network privacy. I suspect this is as simple as you not signing your app with the com.apple.developer.networking.multicast entitlement, a requirement that’s documented in that technote.

Share and Enjoy

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

Bonjour for discovering a specific device's ip
 
 
Q