Get IP & Port from NWBrowser

Hi,


I wanted to try using the new NWBrowser available in iOS 13 to replace my old Bonjour browsing code, problem is I'm unabe to get the IP and Port of the service I'm looking for.


My code :


let params = NWParameters()
params.includePeerToPeer = true
_bonjourBrowser = NWBrowser(for: .bonjour(type: "_mpd._tcp.", domain: nil), using: params)

_bonjourBrowser.browseResultsChangedHandler = { results, changes in
  for change in changes {
    switch change {
    case .added(let browseResult):
      switch browseResult.endpoint {
      case .hostPort(let host, let port):
        print("added hostPort \(host) \(port)")
      case .service(let name, let type, let domain, let interface):
        print("added service \(name) \(type) \(domain) \(String(describing: interface))")
      default:
        print("fail")
      }
    case .removed(let browseResult):
      print("removed \(browseResult.endpoint)")
    case .changed(_, let browseResult, let flags):
      if flags.contains(.interfaceAdded) {
        print("\(browseResult.endpoint) added interfaces")
      }
      if flags.contains(.interfaceRemoved) {
        print("\(browseResult.endpoint) removed interfaces")
      }
    default:
      print("no change")
    }
  }
}

_bonjourBrowser.start(queue: DispatchQueue.global())


What I get is :

added service MPD.PI _mpd._tcp local. nil

added service MPD.MBP _mpd._tcp local. nil


How can I get an IP and Port out of this ?

Accepted Reply

AFAIK Network framework does not have an API that does the Bonjour resolve operation without also connecting. To solve this, you’ll have to go either up or down the stack:

  • Construct an

    NSNetService
    and then call
    -resolveWithTimeout:
    on it.
  • Use

    DNSServiceResolve
    .

Both of these have their challenges. Specifically, in the

NSNetService
case, you have to deal with run loops.

In both cases, make sure to take the resulting DNS name and pass that to your third-party library. That allows it to connect by name, which should improve the chances of it connecting successfully [1].

Oh, before I go, I want to stress that the fact that

NWBrowser
does not resolve is a feature, not a bug. Resolving every service you encounter when browsing is a major Bonjour faux pas. The goal is to only resolve the service you’re connecting to, and then only at the point when you do the connection. Network framework does that as part of
NWConnection
, so the issue here is about whether there’s a way to do that without actually connecting.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] This assumes that it uses a system connect-by-name API, or contains all the necessary smarts to connect in a wide variety of environments. If this is a cross-platform code base it may not, in which case you may encounter connectivity problems in oddball network environments. If so, you might want to do a dummy connection yourself (using

NWConnection
), get the IP address it used, and pass that to your library.

Replies

In Bonjour parlance, a browser does not return address information (local DNS name, IP address list, port). Rather, it returns a list of services (name, type, domain). To get the former from the latter, you have to resolve the service.

Most high-level networking APIs let you pass in a service and the networking API will resolve it for you. Thus, it’s relatively unusual to want to manually resolve it. Why are you doing that in this case?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi,


I'm working on an iOS app to control a MPD server (http://musicpd.org), the server advertise itself via Bonjour/Zeroconf.

To connect to it, the api (plain C) expect a couple Hostname/IP & port.


Until now I was using the old NetServiceBrowser but it's not very swifty and kinda ugly.

AFAIK Network framework does not have an API that does the Bonjour resolve operation without also connecting. To solve this, you’ll have to go either up or down the stack:

  • Construct an

    NSNetService
    and then call
    -resolveWithTimeout:
    on it.
  • Use

    DNSServiceResolve
    .

Both of these have their challenges. Specifically, in the

NSNetService
case, you have to deal with run loops.

In both cases, make sure to take the resulting DNS name and pass that to your third-party library. That allows it to connect by name, which should improve the chances of it connecting successfully [1].

Oh, before I go, I want to stress that the fact that

NWBrowser
does not resolve is a feature, not a bug. Resolving every service you encounter when browsing is a major Bonjour faux pas. The goal is to only resolve the service you’re connecting to, and then only at the point when you do the connection. Network framework does that as part of
NWConnection
, so the issue here is about whether there’s a way to do that without actually connecting.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] This assumes that it uses a system connect-by-name API, or contains all the necessary smarts to connect in a wide variety of environments. If this is a cross-platform code base it may not, in which case you may encounter connectivity problems in oddball network environments. If so, you might want to do a dummy connection yourself (using

NWConnection
), get the IP address it used, and pass that to your library.

Ok, thank you for the explanation

Earlier I wrote:

AFAIK Network framework does not have an API that does the Bonjour resolve operation without also connecting.

I had a quick chat with the Network framework team and they confirmed this. Exposing a ‘NWResolver’ API is feasible, but handling all the edge cases is tricky. For example, earlier I wrote:

make sure to take the resulting DNS name and pass that to your third-party library.

It turns out that, in the general case, you might get multiple DNS names back for a service. This is why Network framework has such an emphasis on resolving as part of connecting. Handling all the edge cases otherwise is challenging.

Still, if you’d like to see this sort of thing added in the future, I encourage you to file an enhancement request describing your requirements. Be as specific as possible here, for example, explaining why it’s not feasible to resolve while connecting, details of the connection process used by the underlying library you’re using, and so on.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Most high-level networking APIs let you pass in a service and the networking API will resolve it for you. Thus, it’s relatively unusual to want to manually resolve it. Why are you doing that in this case?

So OK, I agree that mandatory requirement in NetService API to run resolve just even to take TXT record is pain. And great to have ability to take it from NWBrowser.Result right when Result is found. But what if I discover http.tcp service type in order to talk to host by means of REST using URLSession API? just having name, type, domain trio does not allow me to create well formed url for URLSession. And I dont see any way to create URLSession from NWBrowser.Result... Also my attempts to switch to NWConnection for handling REST via http or WebSockets are just even more pain as it lays down much more lower in stack than I need for my tasks. What I missed?

Another important feature for me is that basically Bonjour services allow me to not hardcode or rely on "standard ports" which obviously does not work in variety of hosts - i.e even in very simple example with http.tcp I see hosts in my network that use from 80 to 5000 for this service type. So point to avoid to deal with port at all allowing discovered service type to provide it to me or even better to construct url with port used by published service. Or maybe to allow URLSession to be constructed from NWBrowser.Result?
Does it sound reasonable enough to be submitted as feature request into RADAR?




Does it sound reasonable enough to be submitted as feature request
into RADAR?

Yes.

Building a URL from a Bonjour service is one of the few cases where I find myself needing the Bonjour resolve operation. It’s annoying that Network framework doesn’t make this easy but there’s nothing stopping you from using the older APIs I mentioned earlier (-[NSNetService resolveWithTimeout:] or DNSServiceResolve).

Share and Enjoy

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

Building a URL from a Bonjour service is one of the few cases where I find myself needing the Bonjour resolve operation. It’s annoying that Network framework doesn’t make this easy but there’s nothing stopping you from using the older APIs I mentioned earlier (-[NSNetService resolveWithTimeout:] or DNSServiceResolve).

That is what I tried to do - reworked all my networking stack to old good NSNetService. But. After that I've found that NSNetService does not allow handle local network permission arrived with iOS 14, while Network framework does it via
Code Block
case waiting(NWError)

bummer. And it sounds as any future features will go to Network framework as well. So I returned back to Network.framework (waisted time not counted :)) and just want to bring your attention to the issue of building url using port.

Code Block
Does it sound reasonable enough to be submitted as feature request
into RADAR?
Yes.

radar id (or now its feedback assistant id?;)): FB8971991

How can I boost the signal on this, because this is the exact use case I am trying to solve right now. I like the idea of a URLRequest with a path relative to a browser result, or something along those lines. I'm thinking something like this:

let browserResult = <some NWBrowser.Result>
let request = URLRequest(path: "/do/something", relativeTo: browserResult)

How can I boost the signal on this

I don’t have any advice on that front but I posted a workaround you can use (in most situations) to this thread.

Share and Enjoy

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