How to resolve addresses and port information from an NWEndpoint.service enum case (if possible)
Asked Answered
G

1

15

Currently I'm using NetServiceBrowser to find Bonjour services and to resolve corresponding addresses and port.

Looking to de-complicate my code I stumbled upon NWBrowser which seems to provide a very simple interface to deal with the Bonjour discovering.

However, the browseResultsChangedHandler sends back results and changes which contain an endpoint of enum case service. I'm trying to get address and port information from the results, but is seems the NWEndpoint would have to be of enum type .hostPort.

Ideally I would use the endpoint to connect to servers using NWConnection, however, I'm using another library which doesn't handle the NWEndpoint directly.

Are there (simple) ways of getting addresses and port information from an NWEndpoint.service result?

import Foundation
import Network

let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: ""), using: NWParameters())

browser.browseResultsChangedHandler = { (results, changes) in
    print("Results:")

    for result in results
    {
        if case .service(let service) = result.endpoint
        {
            debugPrint(service)
        }
        else
        {
            assert(false, "This nevers gets executed")
        }
    }

    print("Changes:")

    for change in changes
    {
        if case .added(let added) = change
        {
            if case .service(let service) = added.endpoint
            {
                debugPrint(service)
            }
            else
            {
                assert(false, "This nevers gets executed")
            }
        }
    }
}

browser.start(queue: DispatchQueue.main)

sleep(3)
Generatrix answered 7/3, 2020 at 16:34 Comment(2)
Did you ever figure this out?Eva
Unfortunately no.Generatrix
C
10

This is possible.

First, you should not attempt to get a service's host and port until the user has actually chosen to connect to it - in most of your application, you should only store a reference to the service object, since Bonjour allows the service's host and port to change. From Bonjour Concepts:

Additionally, services are not tied to specific IP addresses or even host names. […] If clients store the host name (as in most cases they now do), they will not be able to connect if the service moves to a different host.

Bonjour takes the service-oriented view. Queries are made according to the type of service needed, not the hosts providing them. Applications store service instance names, not addresses, so if the IP address, port number, or even host name has changed, the application can still connect. By concentrating on services rather than devices, the user’s browsing experience is made more useful and trouble-free.

This reduces DNS noise within your network and is core to the design of Bonjour. This means all my next suggestions should not happen in your browseResultsChangedHandler.

The officially recommended way to use NWBrowser appears to be to open a NWConnection to the service and use that instead of extracting the address and port and connecting manually. You can open the connection and the Network framework will handle resolving the actual host/port and connecting to the service.

let connection = NWConnection(to: service.endpoint, using: .tcp)

connection.stateUpdateHandler = { state in
    switch state {
    case .ready:
        if let innerEndpoint = connection.currentPath?.remoteEndpoint,
           case .hostPort(let host, let port) = innerEndpoint {
            print("Connected to", "\(host):\(port)") // Here, I have the host/port information
        }
    default:
        break
    }
}
connection.start(queue: .global())

If you can't use this Network framework connection, I'd guess you could then close the connection (maybe need to wait for TCP to stop using the port) and use it for your own purposes.

You can also use the deprecated NetService api to resolve a service (although I haven't personally gotten this to work with a manually constructed instance) or DNSServiceResolve.

There's a lot more context from an official Apple representative in this Apple Developer Forum thread.

Columbuscolumbyne answered 10/8, 2021 at 7:10 Comment(2)
Thanks for your answer, appreciate the effort. While strictly speaking the answer to my question still seems to be 'no' (no address/port info from a service result) I think you've provided a neat workaround. It's a pity that we have to go through the cost of creating a network connection to get address and port info, but nevertheless this solution definitely works!Generatrix
This whole answer is really nice and helped me dig through some stuff with NsNetService in Xamarin.Forms. One hint from my side for anyone having issues with NsNetService.Resolve (resolveWithTimeout in iOS) function: In the NWBrowser CompleteChangesDelegate create a new service on the MainThread. This will trigger something (yeah I'm not really sure too that exactly) to be available on the MainThread that is needed for the Resolve call later on and it should work.Survive

© 2022 - 2024 — McMap. All rights reserved.