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
40 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I'm trying to detect the state of Local Network privacy on macOS Sequoia via NWBrowser, as recommended in https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy
Regardless of the state of Local Network privacy - undetermined, allowed or denied, NWBrowser receives an update indicating that its in the ready state.
Scanning does not seem to trigger the Local Network privacy alert for me - I have to use the other recommended method to trigger the prompt. Enabling or disabling Local Network privacy does not seem to send any updates for NWBrowser.
https://developer.apple.com/forums/thread/666431 seems related, and implies that they did receive further updates to NWBrowser.
Filed as FB16077972
We are trying to connect to Webdav.
The file server is in the same network.
So when we try to connect, the local network permission pop-up is displayed.
If the input information is incorrect in the first login attempt when this permission pop-up is displayed,
After that, even after fixing the normal connection, we cannot connect or log in with the message "NSURLErrorDomain Code=-1009", "Internet connection is offline."
This symptom seems to persist even after rebooting or deleting and deleting the app in the actual distributed app.
If you re-debug while debugging Xcode, you can connect normally.
(If you do not re-debug, it fails even if you enter the connection information normally.)
And it affects local connection, so you cannot connect to any local network server such as SMB or FTP.
Also, you cannot browse the server list within the local network. (SMB)
Is there a way to initialize the local network status within the app to improve this phenomenon?
I tried turning Airplane mode ON/OFF, turning Wi-Fi ON/OFF, and turning local network permissions ON/OFF, but it did not work.
Also, this phenomenon seems to be a Sandbox for each app.
When connecting to the same local server from an app installed on the same iPhone/iPad device, the above phenomenon does not occur if the first connection is successful.
** Summary **
If you fail to connect to a server on your local network,
then you will continue to fail to connect to the local server.
This happens even when local network permissions are allowed.
The error message is NSURLErrorDomain Code=-1009
The current device is an iPhone device running iOS 18.1.1.
With little knowledge on C++, but help from ChatGPT, I am trying to write a plugin for OBS.
I would like to include a bonjour service in the plugin. I assume that the framework is already present on every Mac, but I don't know where it resides, and how to #include it.
Anyone can help me here?
Thanks in advance
https://developer.apple.com/forums/thread/735862?login=true
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.