Every now and then I talk to someone who’s trying to use Bonjour and just can’t get over the first hurdle. That happened today, and so I decided to share my write-up for the benefit of others.
Questions or comments? Put them in a new thread here on DevForums, tagging it with Bonjour so that I see it.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Getting Started with Bonjour
Bonjour is an Apple term for a variety of Internet standards [1]. Bonjour allows your app to browse for and connect to services on the network without infrastructure support. For example, Bonjour lets you find and connect to a printer even if the network has no DHCP server to hand out IP addresses.
If you’re new to Bonjour, a good place to start is the Bonjour Overview. It’s in the documentation archive, so it hasn’t been updated in a while, but the fundamentals haven’t changed.
There are, however, two things that have changed:
Network framework has new Bonjour APIs, and the old ones are now deprecated.
iOS 14 introduced local network privacy.
This post shows how to get started with Bonjour, taking into account these new developments.
[1] Specifically:
RFC 3927 Dynamic Configuration of IPv4 Link-Local Addresses
RFC 6762 Multicast DNS
RFC 6763 DNS-Based Service Discovery
Start Browsing
Let’s start by implementing a service browser. To simplify things, this browses for SSH services. That way you can get started with the browser without first having to implement a server to register your service. If you don’t already have an SSH service registered on your network, start one by enabling System Settings > General > Sharing > Remote Login on your Mac.
The SSH service type is, unsurprisingly, _ssh._tcp. First, on your Mac, run the dns-sd tool to confirm that you have an SSH service visible on your network:
% dns-sd -B "_ssh._tcp" "local."
% dns-sd -B "_ssh._tcp" "local."
…
Timestamp A-R Flags if Domain Service Type Instance Name
…
11:54:43.315 Add 2 6 local. _ssh._tcp. Fluffy
…
11:54:43.725 Add 2 6 local. _ssh._tcp. SAM the Robot 12
^C
This shows that I have two services, one called Fluffy and the other called SAM the Robot 12. Let’s write some iOS code to browse for those. To start, create an app from the iOS > App template and connect a button to the startStop() method of a class like this:
import Foundation
import Network
class AppModel {
var browserQ: NWBrowser? = nil
func start() -> NWBrowser {
print("browser will start")
let descriptor = NWBrowser.Descriptor.bonjour(type: "_ssh._tcp", domain: "local.")
let browser = NWBrowser(for: descriptor, using: .tcp)
browser.stateUpdateHandler = { newState in
print("browser did change state, new: \(newState)")
}
browser.browseResultsChangedHandler = { updated, changes in
print("browser results did change:")
for change in changes {
switch change {
case .added(let result):
print("+ \(result.endpoint)")
case .removed(let result):
print("- \(result.endpoint)")
case .changed(old: let old, new: let new, flags: _):
print("± \(old.endpoint) \(new.endpoint)")
case .identical:
fallthrough
@unknown default:
print("?")
}
}
}
browser.start(queue: .main)
return browser
}
func stop(browser: NWBrowser) {
print("browser will stop")
browser.stateUpdateHandler = nil
browser.cancel()
}
func startStop() {
if let browser = self.browserQ {
self.browserQ = nil
self.stop(browser: browser)
} else {
self.browserQ = self.start()
}
}
}
Note I’m using SwiftUI, but if you chose to use UIKit you could add this code directly to your view controller. Of course, whether you want to add networking code to your view controller is another question. The answer is, natch, “No”, except when creating a tiny test project like this one (-:
Now build and run in the simulator and click your buton. It’ll print something like this:
browser will start
browser did change state, new: ready
browser results did change:
+ SAM the Robot 12._ssh._tcp.local.
+ Fluffy._ssh._tcp.local.
As you can see, it’s found our two SSH services. Yay!
Run on the Device
Now stop the app and run it on a real device. This time the Test button results in:
browser will start
…
browser did change state, new: failed(-65555: NoAuth)
This is local network privacy kicking in. There are two things you need to do:
Add a NSBonjourServices property to your Info.plist to declare what service types you’re using.
Add a NSLocalNetworkUsageDescription property to your Info.plist to explain what you’re doing with the local network.
Do that and run your app again. On tapping the Test button you’ll see an alert asking you to grant your app access to the local network. Tap Allow and the browser will start generating results as before.
Respond to Updates
When working with Bonjour it’s important to keep your browser running to update your app’s state. To test this, start a Remote Login on a different machine and look for a new result being printed:
browser results did change:
+ Slimey._ssh._tcplocal.
And then turn it off:
browser results did change:
- Slimey._ssh._tcplocal.
If you don’t have another Mac to test this with, start a dummy service using dns-sd:
% dns-sd -R "Guy Smiley" "_ssh._tcp" "local." 12345
Registering Service Test._ssh._tcp.local. port 12345
…
Press control-C to stop the dns-sd tool, which unregisters the service.
Connect
When the user choose a service, it’s time to connect. There are two ways to do this, depending on the networking API you use to run your connection.
NWConnection can connect directly to a Bonjour service endpoint. For example, you might have code that connects to a DNS name and port:
func makeConnection(host: String, port: UInt16) -> NWConnection {
let host = NWEndpoint.Host(host)
let port = NWEndpoint.Port(rawValue: port)!
let endpoint = NWEndpoint.hostPort(host: host, port: port)
return NWConnection(to: endpoint, using: .tcp)
}
Replace that with code that takes the endpoint you get back from the browser:
func makeConnection(endpoint: NWEndpoint) -> NWConnection {
return NWConnection(to: endpoint, using: .tcp)
}
If you’re using a legacy API, like BSD Sockets, you’ll need to resolve the Bonjour service endpoint to a DNS name and then pass that DNS name into your connection code. Network framework does not support resolving Bonjour service endpoints out of the box, so you’ll have to do that yourself. For an example of how you might do this, see this post.
IMPORTANT For this to work reliably, your BSD Sockets code must support Happy Eyeballs. See TN3151 Choosing the right networking API for specific advice on that front.
Register a Service
Now let’s look at the server side. To listen for connections with Network framework, you might write code like this:
import Foundation
import Network
class AppModel {
var listenerQ: NWListener? = nil
func start() -> NWListener? {
print("listener will start")
guard let listener = try? NWListener(using: .tcp) else { return nil }
listener.stateUpdateHandler = { newState in
print("listener did change state, new: \(newState)")
}
listener.newConnectionHandler = { connection in
connection.cancel()
}
listener.start(queue: .main)
return listener
}
func stop(listener: NWListener) {
print("listener will stop")
listener.stateUpdateHandler = nil
listener.cancel()
}
func startStop() {
if let listener = self.listenerQ {
self.listenerQ = nil
self.stop(listener: listener)
} else {
self.listenerQ = self.start()
}
}
}
To register your service with Bonjour, add these lines before the call to start(queue:):
listener.service = .init(type: "_ssh._tcp")
listener.serviceRegistrationUpdateHandler = { change in
print(change)
}
The listener calls your service registration update handler to tell you the name of the service. Typically you display this value somewhere in your UI. For more about this, see Showing Connection Information in an iOS Server.
To confirm that your service is running, open Terminal and choose Shell > New Remote Command. Your service should show up in the Secure Shell (ssh) list.
Alternatively, browse for SSH services using the dns-sd tool, as illustrated in the Start Browsing section above.
Bonjour
RSS for tagBonjour, also known as zero-configuration networking, enables automatic discovery of devices and services on a local network using industry standard.
Posts under Bonjour tag
43 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Our product (rockhawk.ca) uses the Multipeer Connectivity framework for peer-to-peer communication between multiple iOS/iPadOS devices. My understanding is that MC framework communicates via three methods: 1) infrastructure wifi (i.e. multiple iOS/iPadOS devices are connected to the same wifi network), 2) peer-to-peer wifi, or 3) Bluetooth. In my experience, I don't believe I've seen MC use Bluetooth. With wifi turned off on the devices, and Bluetooth turned on, no connection is established. With wifi on and Bluetooth off, MC works and I presume either infrastructure wifi (if available) or peer-to-peer wifi are used.
I'm trying to overcome two issues:
Over time (since iOS 9.x), the radio transmit strength for MC over peer-to-peer wifi has decreased to the point that range is unacceptable for our use case. We need at least 150 feet range.
We would like to extend this support to watchOS and the MC framework is not available.
Regarding #1, I'd like to confirm that if infrastructure wifi is available, MC uses it. If infrastructure wifi is not available, MC uses peer-to-peer wifi. If this is true, then we can assure our customers that if infrastructure wifi is available at the venue, then with all devices connected to it, range will be adequate.
If infrastructure wifi is not available at the venue, perhaps a mobile wifi router (battery operated) could be set up, devices connected to it, then range would be adequate. We are about to test this. Reasonable?
Can we be assured that if infrastructure wifi is available, MC uses it?
Regarding #2, given we are targeting minimum watchOS 7.0, would the available networking APIs and frameworks be adequate to implement our own equivalent of the MC framework so our app on iOS/iPadOS and watchOS devices could communicate? How much work? Where would I start? I'm new to implementing networking but experienced in using the MC framework. I'm assuming that I would write the networking code to use infrastructure wifi to achieve acceptable range.
Many thanks!
Tim
Hello,
I was able to use the TicTackToe code base and modify it such that I have a toggle at the top of the screen that allows me to start / stop the NWBrowser and NWListener. I have it setup so when the browser finds another device it attempts to connect to it. I support N devices / connections. I am able to use the NWParameters extension that is in the TickTackToe game that uses a passcode and TLS. I am able to send messages between devices just fine. Here is what I used
extension NWParameters {
// Create parameters for use in PeerConnection and PeerListener.
convenience init(passcode: String) {
// Customize TCP options to enable keepalives.
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 2
// Create parameters with custom TLS and TCP options.
self.init(tls: NWParameters.tlsOptions(passcode: passcode), tcp: tcpOptions)
// Enable using a peer-to-peer link.
self.includePeerToPeer = true
}
// Create TLS options using a passcode to derive a preshared key.
private static func tlsOptions(passcode: String) -> NWProtocolTLS.Options {
let tlsOptions = NWProtocolTLS.Options()
let authenticationKey = SymmetricKey(data: passcode.data(using: .utf8)!)
let authenticationCode = HMAC<SHA256>.authenticationCode(for: "HI".data(using: .utf8)!, using: authenticationKey)
let authenticationDispatchData = authenticationCode.withUnsafeBytes {
DispatchData(bytes: $0)
}
sec_protocol_options_add_pre_shared_key(tlsOptions.securityProtocolOptions,
authenticationDispatchData as __DispatchData,
stringToDispatchData("HI")! as __DispatchData)
sec_protocol_options_append_tls_ciphersuite(tlsOptions.securityProtocolOptions,
tls_ciphersuite_t(rawValue: TLS_PSK_WITH_AES_128_GCM_SHA256)!)
return tlsOptions
}
// Create a utility function to encode strings as preshared key data.
private static func stringToDispatchData(_ string: String) -> DispatchData? {
guard let stringData = string.data(using: .utf8) else {
return nil
}
let dispatchData = stringData.withUnsafeBytes {
DispatchData(bytes: $0)
}
return dispatchData
}
}
When I try to modify it to use QUIC and TLS 1.3 like so
extension NWParameters {
// Create parameters for use in PeerConnection and PeerListener.
convenience init(psk: String) {
self.init(quic: NWParameters.quicOptions(psk: psk))
self.includePeerToPeer = true
}
private static func quicOptions(psk: String) -> NWProtocolQUIC.Options {
let quicOptions = NWProtocolQUIC.Options(alpn: ["h3"])
let authenticationKey = SymmetricKey(data: psk.data(using: .utf8)!)
let authenticationCode = HMAC<SHA256>.authenticationCode(for: "hello".data(using: .utf8)!, using: authenticationKey)
let authenticationDispatchData = authenticationCode.withUnsafeBytes {
DispatchData(bytes: $0)
}
sec_protocol_options_set_min_tls_protocol_version(quicOptions.securityProtocolOptions, .TLSv13)
sec_protocol_options_set_max_tls_protocol_version(quicOptions.securityProtocolOptions, .TLSv13)
sec_protocol_options_add_pre_shared_key(quicOptions.securityProtocolOptions,
authenticationDispatchData as __DispatchData,
stringToDispatchData("hello")! as __DispatchData)
sec_protocol_options_append_tls_ciphersuite(quicOptions.securityProtocolOptions,
tls_ciphersuite_t(rawValue: TLS_AES_128_GCM_SHA256)!)
sec_protocol_options_set_verify_block(quicOptions.securityProtocolOptions, { _, _, sec_protocol_verify_complete in
sec_protocol_verify_complete(true)
}, .main)
return quicOptions
}
// Create a utility function to encode strings as preshared key data.
private static func stringToDispatchData(_ string: String) -> DispatchData? {
guard let stringData = string.data(using: .utf8) else {
return nil
}
let dispatchData = stringData.withUnsafeBytes {
DispatchData(bytes: $0)
}
return dispatchData
}
}
I get the following errors in the console
boringssl_session_handshake_incomplete(241) [C3:1][0x109d0c600] SSL library error
boringssl_session_handshake_error_print(44) [C3:1][0x109d0c600] Error: 4459057536:error:100000ae:SSL routines:OPENSSL_internal:NO_CERTIFICATE_SET:/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls13_server.cc:882:
boringssl_session_handshake_incomplete(241) [C4:1][0x109d0d200] SSL library error
boringssl_session_handshake_error_print(44) [C4:1][0x109d0d200] Error: 4459057536:error:100000ae:SSL routines:OPENSSL_internal:NO_CERTIFICATE_SET:/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls13_server.cc:882:
nw_endpoint_flow_failed_with_error [C3 fe80::1884:2662:90ca:b011%en0.65328 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, dns, uses wifi)] already failing, returning
nw_endpoint_flow_failed_with_error [C4 192.168.0.98:65396 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], scoped, ipv4, dns, uses wifi)] already failing, returning
quic_crypto_connection_state_handler [C1:1] [2ae0263d7dc186c7-] TLS error -9858 (state failed)
nw_connection_copy_connected_local_endpoint_block_invoke [C3] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C3] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C3] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
quic_crypto_connection_state_handler [C2:1] [84fdc1e910f59f0a-] TLS error -9858 (state failed)
nw_connection_copy_connected_local_endpoint_block_invoke [C4] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
nw_connection_copy_connected_remote_endpoint_block_invoke [C4] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
nw_connection_copy_protocol_metadata_internal_block_invoke [C4] Client called nw_connection_copy_protocol_metadata_internal on unconnected nw_connection
Am I missing some configuration? I noticed with the working code that uses TCP and TLS that there is an NWParameters initializer that accepts tls options and tcp option but there isnt one that accepts tls and quic.
Thank you for any help :)
Hi,
I'm using the Network framework to browse for devices on the local network.
Unfortunately, I get many crash reports that crash in nw_browser_cancel, of which two are attached.
This discussion seems to have a similar issue, but it was never resolved: https://forums.developer.apple.com/forums/thread/696037
Contrary to the situation in the linked thread, my implementation uses DispatchQueue.main as the queue for the browser, so I don't think over-releasing the queue is the problem.
I am unable to reproduce this problem myself, but one of my users can reproduce it reliably it seems.
How can I resolve this crash?
2024-11-10_14-24-35.3886_+0100-4fdbdb8e944a4b655d60df53da3aa8c759f4fd1f.crash
2024-11-08_08-54-31.6366_+0100-303cabefb74bf89cdea3127b1cad122ee46016f2.crash
In my iOS app I am currently using Bonjour (via Network.framework) to have two local devices find each other and then establish a single bidirectional QUIC connection between them.
I am now trying to transition from a single QUIC connection to a QUIC multiplex group (NWMultiplexGroup) with multiple QUIC streams sharing a single tunnel.
However I am hitting an error when trying to establish the NWConnectionGroup tunnel to the endpoint discovered via Bonjour.
I am using the same "_aircam._udp" Bonjour service name I used before (for the single connection) and am getting the following error:
nw_group_descriptor_allows_endpoint Endpoint iPhone15Pro._aircam._udp.local. is of invalid type for multiplex group
Does NWConnectionGroup not support connecting to Bonjour endpoints? Or do I need a different service name string? Or is there something else I could be doing wrong?
If connecting to Bonjour endpoints isn't supported, I assume I'll have to work around this by first resolving the discovered endpoint using Quinn's code from this thread?
And I guess I would then have to have two NWListeners, one just for Bonjour discovery and one listening on a port of my choice for the multiplex tunnel connection?
In the FAQ about Local Network, a lot of topics are covered but, unless I missed something, I didn't see the topic of MDMs being covered.
[Q] Could the FAQ be updated to cover whether it is possible to grant this Local Network permission through a configuration profile?
The answer, based on google searches and different forums, seems to be a negative. It seems a bit strange considering that this feature has been available on iOS for at least 3 years.
Anyway, even if it is not possible, it would be useful to add in the FAQ that this is not possible.
Hello everyone I'm new to swift and I can't quite figure it out yet:(
I am developing a simple online game for mac os that involves two players connected to the same WIFI. I need to constantly receive information from the server and I don't understand how to implement it. If I call the receive function indefinitely, then my program freezes. I realized that this should happen asynchronously, but that's just how my program understands when a package came from the server. I understand that I need a delegate or handler, but I don't understand how to do it. Please help me to add the receive function and everything that is necessary for it
import Foundation
import Network
enum CustomErrors: Error {
case DataError
case NetworkError
case DecoderError
case InvalidAddress
}
class TapperConnection: ObservableObject {
private var _serverAlive = false
private var connection: NWConnection!
private var serverPort: UInt16 = 20001
private var serverIp: String = "127.0.0.1"
private var _myDeviceName = Host.current().localizedName ?? ""
@Published var messageDc: [HostData] = []
@Published var messageLobby: [HostData] = []
@Published var messageState: GameData = GameData()
private var buffer = 2048
private var _inputData = ""
private var _outputData = ""
private var _myIp = ""
private var isServer = false
private var isClient = false
var myIp: String {
return _myIp
}
var myDeviceName: String {
return _myDeviceName
}
private func getMyIp() -> String? {
var address: String?
var ifaddr: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return nil }
guard let firstAddr = ifaddr else { return nil }
for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let interface = ifptr.pointee
let addrFamily = interface.ifa_addr.pointee.sa_family
if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
let name = String(cString: interface.ifa_name)
if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
&hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST)
address = String(cString: hostname)
}
}
}
freeifaddrs(ifaddr)
return address
}
private func isValidIP(_ ip: String) -> Bool {
let regex = try! NSRegularExpression(pattern: "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
return regex.firstMatch(in: ip, range: NSRange(location: 0, length: ip.utf16.count)) != nil
}
@Sendable
private func updateServerState(to state: NWConnection.State) {
switch state {
case .setup:
_serverAlive = true
case .waiting:
_serverAlive = true
case .ready:
_serverAlive = true
case .failed:
_serverAlive = false
case .cancelled:
_serverAlive = false
case .preparing:
_serverAlive = false
default:
_serverAlive = false
}
}
func createConnection() throws {
let ip = getMyIp()
if ip != nil {
serverIp = ip!
_myIp = ip!
} else {
throw CustomErrors.NetworkError
}
isServer = true
do {
try connectToServer()
} catch {
throw CustomErrors.NetworkError
}
}
func createConnection(ip: String) throws {
if isValidIP(ip) {
serverIp = ip
} else {
throw CustomErrors.InvalidAddress
}
let _ip = getMyIp()
if _ip != nil {
_myIp = _ip!
} else {
throw CustomErrors.NetworkError
}
isClient = true
do {
try connectToServer()
} catch {
throw CustomErrors.NetworkError
}
}
private func connectToServer() throws {
if isServer {
// ...............
// run server exec
// ...............
}
let _params = NWParameters(dtls: nil, udp: .init())
_params.requiredLocalEndpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(_myIp), port: 20002)
connection = NWConnection(host: NWEndpoint.Host(serverIp), port: NWEndpoint.Port(rawValue: serverPort)!, using: _params)
connection.stateUpdateHandler = updateServerState(to:)
connection.start(queue: .global())
while !_serverAlive {}
do {
try send(message: "im:\(_myDeviceName)")
receive()
} catch {
print("Error sending disconnect message: \(error)")
}
}
func closeConnection() {
do {
try send(message: "dc:\(_myDeviceName)")
} catch {
print("Error sending disconnect message: \(error)")
}
_serverAlive = false
connection.cancel()
}
func send(message: String) throws {
var error = false
connection.send(content: message.data(using: String.Encoding.utf8), completion: NWConnection.SendCompletion.contentProcessed(({ NWError in
if NWError == nil {
print("Data was sent!")
} else {
error = true
}
})))
if error {
throw CustomErrors.NetworkError
}
}
func receive() {
self.connection.receive(minimumIncompleteLength: 1, maximumLength: 65535) { data, _, isComplete, _ in
if isComplete {
if data != nil {
let response: String = String(decoding: data!, as: UTF8.self)
var decodeData: Any
var messageType: MessageType
(decodeData, messageType) = try! Decoder.decodeMessage(response)
switch messageType {
case MessageType.lobby:
self.messageLobby = decodeData as! [HostData]
case MessageType.state:
self.messageState = decodeData as! GameData
case MessageType.dc:
self.messageDc = decodeData as! [HostData]
}
}
self.receive()
}
}
}
}
I am developing an application that allows you to interact with people on your local network.
I have a view called ProfileView() which has has identifiers inside of it such as that build up the profile for each person.
Essentially, what I want to do is discover people who are on this app on your local network, or who are nearby to you based on bluetooth. I do not want to use a server, as I would like this to be an application that does not require internet access to function. Also if possible, I would like a toggle to allow yourself to be discovered in the background, even if not using the app.
Any ideas how to do this? Also, is there any better way to do this instead of Bluetooth and Local Network?
Thank you
Possible code chunks needed:
Discover nearby bluetooth users
Discover nearby network users
Toggle for discovery
Toggle for background discovery (while not using app)
Share profile (mainly just text and a profile image)
I am trying to browse an SSH Service from UI Test Target using NWBrowser
let descriptor = NWBrowser.Descriptor.bonjour(type: "_superapp._tcp", domain: "local.")
let browser = NWBrowser(for: descriptor, using: .tcp)
browser.stateUpdateHandler = { newState in
print("browser.stateUpdateHandler \(newState)")
}
I get failed(-65555: NoAuth) error
nw_browser_fail_on_dns_error_locked [B1] DNSServiceBrowse failed: NoAuth(-65555)
I have added _superapp._tcp in Bonjour Services for UI Test Target Info.plist.
I have also added Local Network Permission in UI Test Target Info.plist.
Everything works fine, when I call this Bonjour service from App Target.
Doesn't work when I call this from UI Test Target.
Good day. From IOS 17 have a problem with connecting to local ip devices. When i try to scan local network:
zeroconf.scan('http', 'tcp', 'local.');
i get en error:
Error: { NSNetServicesErrorCode = "-72007"; NSNetServicesErrorDomain = 10; }
I use the react-native-zeroconf libruarry, config the infoPlist with:
"NSBonjourServices": ["_http._tcp"],
"NSLocalNetworkUsageDescription":
"Allow Turkov application to configure LLC devices"
"NSAppTransportSecurity": {
"NSAllowsArbitraryLoads": true,
"NSExceptionDomains": {
"localhost": {
"NSExceptionAllowsInsecureHTTPLoads": true
}
}
And also i get approve sertificate to use multicast from apple team, and apply it in project
"entitlements": {
"com.apple.developer.networking.multicast": true
},
Below IOS17 (i tested at 16.6) - all work fine.. Can some one help with that problem?
We are currently developing an application that runs in the background and continuously scans for other nearby devices via peer-to-peer networking. Generally, the high-level goals are:
Scan for nearby devices while the app is in the background state. We only need to discover devices that are also running our app.
Read a small token of data from each peer device found (no need for full-duplex connection)
Submit this token to our server via a background network request
On Android we have demonstrated this functionality using both Bluetooth LE and WifiDirect service discovery, and background operation is easily achieved with Android services. We are currently trying to expand our application to support cross-platform compatibility between IOS and Android, including IOS<-->IOS and IOS<-->Android discovery (in the background). Is there a way to achieve this desired functionality on IOS?
Hello,
I am not exactly sure this is the right place to ask this since it involves Microsoft's Visual Studio, but because the problem I am having involves iOS I figured I would anyway.
Info:
I am trying to develop a cross-platform application using .NET Maui in Visual Studio. I am on a Windows machine pairing to a mac with Xcode installed, so I can build for iOS. My local device is an iPhone 13 running on iOS Version 17.5.1. The simulators I am using in Visual Studio are all iOS Version 17+. I am using the .NET NuGet package Zeroconf which should work for both iOS and Android (Repo:https://github.com/novotnyllc/Zeroconf). I also believe I have given the correct permissions for iOS in my Info.plist.
Problem:
The problem I am coming across is that when I build and run my application in one of the installed iOS Simulators and I go to scan for local devices it is able to come back with 80-100 devices that we want to find, but when I build and run it on my local device it comes back with nothing found. I had searched for similar problems that other people were having and found that iOS 17+ has some potential problems when it comes to searching for devices. Is this true? If someone can help me solve this issue between the simulator and local device I would greatly appreciate it.
If there is any other information that I can give to help with solving this problem please let me know.
Thanks!
Discovery Code:
TimeSpan scanTime = TimeSpan.FromMilliseconds(2000);
int retries = 4;
int retryDelayMilliseconds = 2000;
Action<IZeroconfHost> callback = null;
CancellationToken cancellationToken = default;
System.Net.NetworkInformation.NetworkInterface[] arrayofnics = NetworkInterface.GetAllNetworkInterfaces();
int index = 0;
for (int i = 0; i < arrayofnics.Length; i++)
{
// en0 is for iOS 0 is for android.
if (arrayofnics[i].Description.Equals("en0") || arrayofnics[i].Description.Equals("0"))
{
index = i;
break;
}
}
System.Net.NetworkInformation.NetworkInterface wifi = arrayofnics[index];
System.Net.NetworkInformation.NetworkInterface[] netInterfacesToSendRequestOn = { wifi };
IReadOnlyList<IZeroconfHost> results = null;
IReadOnlyList<string> domains;
var browseDomains = await ZeroconfResolver.BrowseDomainsAsync();
domains = browseDomains.Select(g => g.Key).ToList();
results = await ZeroconfResolver.ResolveAsync("_http._tcp.local.", scanTime, retries, retryDelayMilliseconds, callback, cancellationToken, netInterfacesToSendRequestOn);
Info.plist:
<key>NSLocalNetworkUsageDescription</key>
<string>This app requires local network access to discover devices.</string>
<key>NSBonjourServices</key>
<array>
<string>_ipspeaker._tcp.local</string>
<string>_ipspeaker._tcp.local.</string>
<string>_ipspeaker._tcp.</string>
<string>_http._tcp.local.</string>
<string>_http._tcp.</string>
</array>
Hi, I'm using Multipeer Connectivity in my application and when I run it on my physical device, I receive following warning:
NSNetServiceBrowser did not search with error dict [{
NSNetServicesErrorCode = "-72008";
NSNetServicesErrorDomain = 10;
}].
I've found out that this is associated with not having proper permissions in info.plist according to https://developer.apple.com/forums/thread/653316
I've set description for Privacy - Local Network Usage Description, however, I'm not able to find any key for setting my Bonjour Services.
Also, I do not see any popup on my device displaying request to approve local network usage. Could you please provide me an information how can I register my privileges properly?
Using NWBrowser and NWListener I'm trying to send a small package of data from the listener/server to the device.
However the device never receives the actual bytes. It either:
gets stuck the preparing state
the connection gets reset
the data is null and is marked as isComplete = true
The only way I can get the device to receive the data is by calling cancel on the NWConnection on the server/NWListener end.
Here is some sample code I am working with:
https://github.com/leogdion/JustBonjour/tree/nwlistener
Is this expected behavior that cancel is required?
I have an application that uses Bonjour to communicate with other instances of the app on other devices.
If I start an NWBrowser and the user has "Local Network" turned off for my app, the stateUpdateHandler for the browser gets .waiting with an error containing the string "PolicyDenied." This lets me show an alert to the user explaining what's happening, with a link to the app's Settings screen.
But if I use NWListener (the counterpart of NWBrowser) and have "Local Network" turned off, there's no indication of any problem. After I start the listener, stateUpdateHandler is called with .ready as the state - even though it's not really ready to listen at all.
The FAQ for Local Network Privacy suggests that any Bonjour operation will raise kDNSServiceErr_PolicyDenied if Local Network is off. However, in my application, that only seems to be true for browsing, not listening.
Is there a way to detect a missing Local Network entitlement for NWListener? I know there are solutions involving sending a message to localhost, etc, but ideally there would be something simpler.
I am using NWBrowser to detect SignalK servers on a network using the following Swift code:
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_http._tcp", domain: nil), using: NWParameters())
browser.browseResultsChangedHandler = { results, changes in
print("Found \(results.count) results and \(changes.count) changes")
}
When this is run on a network with 5 devices then the output is often
Found 5 results and 5 changes
But, sometime it is:
Found 2 results and 2 changes
Found 5 results and 3 changes
indicating that the browseResultsChangedHandler is being called more than once.
So my question is how do I determine when the browsing process has finished (obviously without the knowledge that there are 5 devices)?
The depreciated NetServiceBrowser had a delegate method (netServiceBrowser(_:didFind:moreComing:) but I can't see an equivalent for NWBrowser.
The only method I can think of is to apply a short time out.
Hello 👋
I need to implement a logic for searching for devices with our own service type using Bonjour. Using the NWBrowser, I can receive a list of all devices and connect to them. I need to utilize a WebSocket connection. By the property endpoint of NWBrowser.Result objects I can create NWConnection. Below is my implementation which works fine on iOS 17:
let params = NWParameters.tcp
let webSocketOptions = NWProtocolWebSocket.Options()
params.defaultProtocolStack.applicationProtocols.insert(webSocketOptions, at: 0)
// The `endpoint` is from `browseResultsChangedHandler` of NWBrowser
let connection = NWConnection(to: endpoint, using: params)
However, it doesn't work on iOS 15 and 16 because of the crash:
2024-06-01 16:07:18.136068+0300 MyApp[591:16845549] [] nw_endpoint_get_url called with null endpoint
2024-06-01 16:07:18.136932+0300 MyApp[591:16845549] [] nw_endpoint_get_url called with null endpoint, dumping backtrace:
[arm64] libnetcore-3100.102.1
0 Network 0x000000018530e174 __nw_create_backtrace_string + 188
1 Network 0x000000018538ba20 nw_endpoint_get_url + 852
2 Network 0x0000000185310020 nw_ws_create_client_request + 84
3 Network 0x0000000184f4b3cc __nw_ws_create_state_block_invoke + 416
4 Network 0x000000018504bc68 nw_protocol_options_access_handle + 92
5 Network 0x0000000184f41e98 nw_ws_create_state + 204
6 Network 0x0000000184f41aec __nw_protocol_copy_ws_definition_block_invoke_2 + 176
7 Network 0x0000000184f69188 nw_framer_protocol_connected + 348
8 Network 0x00000001854a6638 _ZL29nw_socket_handle_socket_eventP9nw_socket + 1560
9 libdispatch.dylib 0x0000000126b89d50 _dispatch_client_callout + 16
10 libdispatch.dylib 0x0000000126b8d208 _dispatch_continuation_pop + 756
11 libdispatch.dylib 0x0000000126ba48d4 _dispatch_source_invoke + 1676
12 libdispatch.dylib 0x0000000126b94398 _dispatch_workloop_invoke + 2428
13 libdispatch.dylib 0x0000000126ba0b74 _dispatch_workloop_worker_thread + 1716
14 libsystem_pthread.dylib 0x000000012371f814 _pthread_wqthread + 284
15 libsystem_pthread.dylib 0x000000012371e5d4 start_wqthread + 8
Also, there is the stack trace of bt-command in the debug console:
* thread #20, queue = 'com.apple.network.connections', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
* frame #0: 0x0000000123078c24 libsystem_platform.dylib`_platform_strlen + 4
frame #1: 0x00000001803c538c CoreFoundation`CFStringCreateWithCString + 40
frame #2: 0x0000000185310030 Network`nw_ws_create_client_request + 100
frame #3: 0x0000000184f4b3cc Network`__nw_ws_create_state_block_invoke + 416
frame #4: 0x000000018504bc68 Network`nw_protocol_options_access_handle + 92
frame #5: 0x0000000184f41e98 Network`nw_ws_create_state + 204
frame #6: 0x0000000184f41aec Network`__nw_protocol_copy_ws_definition_block_invoke_2 + 176
frame #7: 0x0000000184f69188 Network`nw_framer_protocol_connected + 348
frame #8: 0x00000001854a6638 Network`nw_socket_handle_socket_event(nw_socket*) + 1560
frame #9: 0x0000000126b89d50 libdispatch.dylib`_dispatch_client_callout + 16
frame #10: 0x0000000126b8d208 libdispatch.dylib`_dispatch_continuation_pop + 756
frame #11: 0x0000000126ba48d4 libdispatch.dylib`_dispatch_source_invoke + 1676
frame #12: 0x0000000126b94398 libdispatch.dylib`_dispatch_workloop_invoke + 2428
frame #13: 0x0000000126ba0b74 libdispatch.dylib`_dispatch_workloop_worker_thread + 1716
frame #14: 0x000000012371f814 libsystem_pthread.dylib`_pthread_wqthread + 284
I have found out a couple things:
There are no crashes if I initialize the NWConnection object with using, for instance, the NWEndpoint.url(_:). initializer:
let urlHost = URL(string: "ws://10.20.30.40:5060")!
let endpoint = NWEndpoint.url(urlHost)
let params = NWParameters.tcp
let webSocketOptions = NWProtocolWebSocket.Options()
params.defaultProtocolStack.applicationProtocols.insert(webSocketOptions, at: 0)
let connection = NWConnection(to: endpoint, using: params)
self.connection = connection
But, in this case, I must extract IP-addresses 🙇♂️ Meanwhile, there is a topic such as Don’t Try to Get the Device’s IP Address..
I have tried to find anything that could help me move forward in this problem and run into some odd behaviour. There is a property skipHandshake of NWProtocolWebSocket.Options object. If I set the property value to true, there are no crashes as well as no connection to a device.
I have an app that utilizes the Network Extension ( Packet Tunnel Provider ), but also uses MDNS to find local devices for data transfer via Network Extensions.
However, once connected over Peer to Peer using AWDL0 or NWConnections, it works as expected until a user shuts the screen down. It looks like there's a difference in behavior when the device is plugged in vs when it's on just battery alone.
So we can be happily sending data over p2p ( awdl0 ) then a screen shuts off and it kills the connection.
Is this expected behavior and if so is there documentation?
Also, Network Extensions do not appear to be able to discover over P2P, they can only connect to endpoints directly. Is this expected behavior?
My thoughts;
If a user allows both the Network Extension Permission and Local Network Permissions that the Network Extension should be able to discover peers via p2p. The connections ( if not asleep ) should stay active while in use.
Hi,
I observed some unexpected behavior and hope that someone can enlighten me as to what this is about:
mDNSResponder prepends IP / network based default search domains that are checked before any other search domain. E.g. 0.1.168.192.in-addr.arpa. would be used for an interface with an address in the the 192.168.1.0/24 subnet. This is done for any configured non-link-local IP address.
I tried to find any mention of an approach like this in RFCs but couldn't spot anything.
Please note that this is indeed a search domain and different from reverse-DNS lookups.
Example output of tcpdump for ping devtest:
10:02:13.850802 IP (tos 0x0, ttl 64, id 43461, offset 0, flags [none], proto UDP (17), length 92)
192.168.1.2.52319 > 192.168.1.1.53: 54890+ [1au] A? devtest.0.1.168.192.in-addr.arpa. (64)
I was able to identify the code that adds those default IP subnet based search domains but failed to spot any indication as to what this is about: https://github.com/apple-oss-distributions/mDNSResponder/blob/d5029b5/mDNSMacOSX/mDNSMacOSX.c#L4171-L4211
Does anyone here have an ideas as to what this might be about?
Like the post at https://forums.developer.apple.com/forums/thread/118035, I'm hitting an issue where I'm receiving:
boringssl_session_set_peer_verification_state_from_session(448) [C1.1.1.1:2][0x12b667210] Unable to extract cached certificates from the SSL_SESSION object
In my app logs. I tried to pin the SSL version to TLS 1.2 per Quinn's advice in that post, and then started digging further enabling CFNETWORK_DIAGNOSTICS=3 to see what was exposed on the Console.log (since it didn't show up in the Xcode console)
The related log lines:
0 debug boringssl 15:43:04.978874-0700 MeetingNotes boringssl_context_log_message(2206) [C5:2][0x11080a760] Reading SSL3_RT_HANDSHAKE 16 bytes
0 debug boringssl 15:43:04.979007-0700 MeetingNotes boringssl_context_log_message(2206) [C5:2][0x11080a760] Writing SSL3_RT_CHANGE_CIPHER_SPEC 1 bytes
0 debug boringssl 15:43:04.979141-0700 MeetingNotes boringssl_context_log_message(2206) [C5:2][0x11080a760] Writing SSL3_RT_HANDSHAKE 16 bytes
0 debug boringssl 15:43:04.979260-0700 MeetingNotes nw_protocol_boringssl_write_bytes(87) [C5:2][0x11080a760] write request: 51
0 debug boringssl 15:43:04.979387-0700 MeetingNotes nw_protocol_boringssl_write_bytes(158) [C5:2][0x11080a760] total bytes written: 51
921460 debug boringssl 15:43:09.937961-0700 MeetingNotes boringssl_context_log_message(2206) [C5:2][0x11080a760] Writing SSL3_RT_ALERT 2 bytes
0 error boringssl 15:43:04.979630-0700 MeetingNotes boringssl_session_set_peer_verification_state_from_session(448) [C5:2][0x11080a760] Unable to extract cached certificates from the SSL_SESSION object
Have a number of references to SSL3_RT in the messages, and I was curious if that indicated that I was using TLS1.3, which apparently doesn't support private shared keys.
The constraints that I used riffs on the sample code from the tic-tac-toe example project:
private static func tlsOptions(passcode: String) -> NWProtocolTLS.Options {
let tlsOptions = NWProtocolTLS.Options()
let authenticationKey = SymmetricKey(data: passcode.data(using: .utf8)!)
let authenticationCode = HMAC<SHA256>.authenticationCode(
for: "MeetingNotes".data(using: .utf8)!,
using: authenticationKey
)
let authenticationDispatchData = authenticationCode.withUnsafeBytes {
DispatchData(bytes: $0)
}
// Private Shared Key (https://datatracker.ietf.org/doc/html/rfc4279) is *not* supported in
// TLS 1.3 [https://tools.ietf.org/html/rfc8446], so this pins the TLS options to use version 1.2:
// @constant tls_protocol_version_TLSv12 TLS 1.2 [https://tools.ietf.org/html/rfc5246]
sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
sec_protocol_options_add_pre_shared_key(
tlsOptions.securityProtocolOptions,
authenticationDispatchData as __DispatchData,
stringToDispatchData("MeetingNotes")! as __DispatchData
)
/* RFC 5487 - PSK with SHA-256/384 and AES GCM */
// Forcing non-standard cipher suite value to UInt16 because for
// whatever reason, it can get returned as UInt32 - such as in
// GitHub actions CI.
let ciphersuiteValue = UInt16(TLS_PSK_WITH_AES_128_GCM_SHA256)
sec_protocol_options_append_tls_ciphersuite(
tlsOptions.securityProtocolOptions,
tls_ciphersuite_t(rawValue: ciphersuiteValue)!
)
return tlsOptions
}
Is there something I'm missing in setting up the proper constraints to request TLS version 1.2 with a private shared key to be used? And beyond that, any suggestions for debugging or narrowing down what might be failing?
I am trying to download XCode apps from my Mac to my Apple Vision Pro for testing. I have tried following the instructions by going into Settings->General->Remote Devices on my Apple Vision Pro, but there my Mac does not show up as a possible connection.
Per this page, I have made sure that both devices are connected to the same WiFi network, updated my Mac to Sonoma, and updated my AVP to the latest OS, and everything else that it asks for.
I am able to mirror my display from my Mac but downloading apps from XCode does not work.
I have also looked to enable Developer Mode by going to Settings -> Privacy & Security -> Enable Developer Mode, but there is no option for enabling developer mode here.
Per this forum, my best guess is that it is a Bonjour Protocol compatibility issue since both devices are on university wifi (WPA2), but I also tried connecting both over a hotspot which also did not work.