Skip to content

Use nwbrowser for discovery on apple devices#50

Open
tim-alenus wants to merge 16 commits intoAppstractive:masterfrom
tim-alenus:feature/nw-browser
Open

Use nwbrowser for discovery on apple devices#50
tim-alenus wants to merge 16 commits intoAppstractive:masterfrom
tim-alenus:feature/nw-browser

Conversation

@tim-alenus
Copy link

#32

This pull requests adds a swift bridge to be able to use the nwbrowser object for discovery. NSNetService is deprecated and should not be used anymore. The bridge code is accessible through interop using SMP for KMP: https://spmforkmp.eu/

We are actively using this code in a (soon to be in production) KMP app.

@aschulz90
Copy link
Contributor

Thank you very much for the PR!

I have tested the implementation with the sample app, but found an issue with resolving service info:

2026-03-13 12:07:37.714644+0100 NsdKt[19161:1163155] [NWBrowserBridge] startBrowsing called - serviceType: _example._tcp., domain: local., triggerPermissionPrompt: true

2026-03-13 12:07:37.720537+0100 NsdKt[19161:1164350] [NWBrowserBridge] Permission trigger: listener ready - permission granted

2026-03-13 12:07:37.720592+0100 NsdKt[19161:1164350] [NWBrowserBridge] Permission trigger completed - success: true

2026-03-13 12:07:37.720660+0100 NsdKt[19161:1164350] NWBrowser permission state changed: GRANTED

2026-03-13 12:07:37.725179+0100 NsdKt[19161:1164350] [NWBrowserBridge] Browser state changed: ready

2026-03-13 12:07:37.725226+0100 NsdKt[19161:1164350] [NWBrowserBridge] Browser ready - permission granted

2026-03-13 12:07:37.725276+0100 NsdKt[19161:1164350] NWBrowser permission state changed: GRANTED

2026-03-13 12:07:37.736207+0100 NsdKt[19161:1164350] [NWBrowserBridge] Service added: iOS-23305697-c8ef-4b3a-a343-d71ac81cce4a._example._tcp.local.

2026-03-13 12:07:37.736294+0100 NsdKt[19161:1164350] NWBrowser found service: iOS-23305697-c8ef-4b3a-a343-d71ac81cce4a of type _example._tcp in domain local.

2026-03-13 12:07:37.736392+0100 NsdKt[19161:1164350] [NWBrowserBridge] Service added: android-2bd01c43-53dc-4df1-9870-4c5d693aa0a2._example._tcp.local.

2026-03-13 12:07:37.736460+0100 NsdKt[19161:1164350] NWBrowser found service: android-2bd01c43-53dc-4df1-9870-4c5d693aa0a2 of type _example._tcp in domain local.

2026-03-13 12:07:37.736482+0100 NsdKt[19161:1164350] [NWBrowserBridge] Service added: iOS-d0593df9-3023-440c-857c-390c2e199831._example._tcp.local.

2026-03-13 12:07:37.736515+0100 NsdKt[19161:1164350] NWBrowser found service: iOS-d0593df9-3023-440c-857c-390c2e199831 of type _example._tcp in domain local.

2026-03-13 12:07:37.736584+0100 NsdKt[19161:1163154] [NWBrowserBridge] Resolving service: iOS-23305697-c8ef-4b3a-a343-d71ac81cce4a._example._tcp.local.

2026-03-13 12:07:37.736715+0100 NsdKt[19161:1163154] [NWBrowserBridge] Resolving service: android-2bd01c43-53dc-4df1-9870-4c5d693aa0a2._example._tcp.local.

2026-03-13 12:07:37.736855+0100 NsdKt[19161:1163154] [NWBrowserBridge] Resolving service: iOS-d0593df9-3023-440c-857c-390c2e199831._example._tcp.local.

2026-03-13 12:07:37.760183+0100 NsdKt[19161:1164350] [connection] nw_socket_handle_socket_event [C5.1.1.1:1] Socket SO_ERROR [61: Connection refused]

2026-03-13 12:07:37.760300+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C5.1.1.1 192.168.178.113:8080 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.761208+0100 NsdKt[19161:1164350] [connection] nw_socket_handle_socket_event [C5.1.1.2:1] Socket SO_ERROR [61: Connection refused]

2026-03-13 12:07:37.761304+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C5.1.1.2 fe80::4e1:a2d8:4dd4:e7f7%en0.8080 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.761860+0100 NsdKt[19161:1164350] [connection] nw_socket_connect [C5.1.1.3:1] connectx(16 (guarded), [srcif=18, srcaddr=<NULL>, dstaddr=2003:e5:a74a:f300:8f9:3ad1:a029:bf16.8080], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [61: Connection refused]

2026-03-13 12:07:37.761969+0100 NsdKt[19161:1164350] [connection] nw_socket_connect [C5.1.1.3:1] connectx failed (fd 16) [61: Connection refused]

2026-03-13 12:07:37.761981+0100 NsdKt[19161:1164350] [] nw_socket_connect connectx failed [61: Connection refused]

2026-03-13 12:07:37.762037+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C5.1.1.3 2003:e5:a74a:f300:8f9:3ad1:a029:bf16.8080 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.762644+0100 NsdKt[19161:1164350] [connection] nw_socket_handle_socket_event [C5.1.1.4:1] Socket SO_ERROR [61: Connection refused]

2026-03-13 12:07:37.762714+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C5.1.1.4 fd7e:5f1e:3adc:0:109f:de85:37b1:53fb.8080 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: lo0)] already failing, returning

2026-03-13 12:07:37.971532+0100 NsdKt[19161:1164350] [tcp] tcp_input [C7.1.1.1:1] flags=[R.] seq=0, ack=1694816947, win=0 state=SYN_SENT rcv_nxt=0, snd_una=1694816946

2026-03-13 12:07:37.971802+0100 NsdKt[19161:1164350] [tcp] tcp_input [C6.1.1.1:1] flags=[R.] seq=0, ack=35045154, win=0 state=SYN_SENT rcv_nxt=0, snd_una=35045153

2026-03-13 12:07:37.972182+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C7.1.1.1 fe80::495:eaa2:1249:41c6%en0.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.972388+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C6.1.1.1 fe80::b46b:78ff:fe20:9703%en0.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.977454+0100 NsdKt[19161:1164350] [tcp] tcp_input [C7.1.1.2:1] flags=[R.] seq=0, ack=1083350805, win=0 state=SYN_SENT rcv_nxt=0, snd_una=1083350804

2026-03-13 12:07:37.977768+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C7.1.1.2 2003:e5:a74a:f300:1c84:5171:52a3:feb1.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.982225+0100 NsdKt[19161:1164350] [tcp] tcp_input [C6.1.1.2:1] flags=[R.] seq=0, ack=3199224272, win=0 state=SYN_SENT rcv_nxt=0, snd_una=3199224271

2026-03-13 12:07:37.982529+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C6.1.1.2 2003:e5:a74a:f300:b46b:78ff:fe20:9703.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.987992+0100 NsdKt[19161:1164350] [tcp] tcp_input [C7.1.1.3:1] flags=[R.] seq=0, ack=3775446529, win=0 state=SYN_SENT rcv_nxt=0, snd_una=3775446528

2026-03-13 12:07:37.988311+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C7.1.1.3 192.168.178.61:8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.994898+0100 NsdKt[19161:1164350] [tcp] tcp_input [C6.1.1.3:1] flags=[R.] seq=0, ack=740213971, win=0 state=SYN_SENT rcv_nxt=0, snd_una=740213970

2026-03-13 12:07:37.995205+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C6.1.1.3 192.168.178.44:8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:37.997362+0100 NsdKt[19161:1164350] [tcp] tcp_input [C7.1.1.4:1] flags=[R.] seq=0, ack=1991452425, win=0 state=SYN_SENT rcv_nxt=0, snd_una=1991452424

2026-03-13 12:07:37.997700+0100 NsdKt[19161:1164350] [connection] nw_endpoint_flow_failed_with_error [C7.1.1.4 fd7e:5f1e:3adc:0:1c57:ec4:6f45:59f3.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, uses wifi, LQM: good)] already failing, returning

2026-03-13 12:07:38.006266+0100 NsdKt[19161:1163015] [tcp] tcp_input [C6.1.1.4:1] flags=[R.] seq=0, ack=2235909935, win=0 state=SYN_SENT rcv_nxt=0, snd_una=2235909934

2026-03-13 12:07:38.006607+0100 NsdKt[19161:1163015] [connection] nw_endpoint_flow_failed_with_error [C6.1.1.4 fd7e:5f1e:3adc:0:b46b:78ff:fe20:9703.8080 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, uses wifi, LQM: good)] already failing, returning

The services are found as expected. But when resolving, a connection is tried to the advertised port (8080), which fails because the sample app isn't actually running anything on that port and the whole resolve of the service fails (no info is updated in the UI).
That works with the old implementation, so I would expect it to work with the new one as well.
Are you able to reproduce this issue with the sample app?

I can see that a connection is done here: https://github.com/tim-alenus/dns-sd-kt/blob/feature/nw-browser/lib/src/swift/nativeBridge/NWBrowserBridge.swift#L257
Might this be the cause?

@tim-alenus
Copy link
Author

Hi @aschulz90
Thanks for checking out the PR. I made some adaptations to the SwiftBridge code.

However the behavior on iOS using NWBrowser cannot fully match the behavior on Android when there is no actual service running on the host. Using NWBrowser, the hostname and IP addresses can only be resolved when a successful TCP (or UDP) connection can be setup. With Bonjour, when a service is discovered, the host the service is running on is not guaranteed. See also here on StackOverflow: https://stackoverflow.com/questions/60579798/how-to-resolve-addresses-and-port-information-from-an-nwendpoint-service-enum-ca

To handle this case I have added a timeout to this connection of 5 seconds. When the timeout occurs a onResolved is still called resulting in a DiscoveryEvent.Resolved still being emitted but it will contain empty host, port and ip addresses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants