I'm using Network framework for communication between devices. The first time I instantiate an NWBrowser, it will prompt the user with a popup that says:
Allow <app name> to find devices on local networks?
The problem is, once I upgraded from Xcode 15.4 to Xcode 16.4, the popup doesn't appear; it says in the debug window:
nw_browser_fail_on_dns_error_locked [B1] nw_browser_dns_service_browse_callback failed: PolicyDenied(18,446,744,073,709,486,046)
I do have the info.plist keys Privacy-Local Network Usage Description (NSLocalNetworkUsageDescription) and Bonjour Services (NSBonjourServices) so it's not that.
Also, It still works on a real device.
I think something changed with Xcode 16 that tightened the security on a simulator, or maybe disabled Network framework entirely. It's not the firewall on my computer because that is turned off. I'm using an M1 MacBook Pro.
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
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
While trying to use Bonjour, i am encountering an issue. I was following the setup of Bonjour as described here: (https://developer.apple.com/forums/thread/735862)
the response is this :
nw_browser_fail_on_dns_error_locked [B2] nw_browser_dns_service_browse_callback failed: PolicyDenied(-65570)
browser did change state, new: waiting(-65570: PolicyDenied)
i tried modifying the info.plist to include
NSLocalNetworkUsageDescription and
NSBonjourServices but still getting the same
a workout or solution is much appreciated !
We have an iPad application that utilizes Multipeer Connectivity to enable local communication between devices running a copy of our app. Until recently, we were able to test this functionality in the Xcode simulator without any issues. We could easily set up multiple simulators and have them all communicate with each other. However, recently, either due to an upgrade to Xcode or MacOS, this functionality ceased working in the simulator. Surprisingly, it still functions perfectly on physical devices.
If we reboot the development computer and launch the simulator immediately after the reboot (without building and sending from Xcode, but running the existing code on the device), the issue resolves. However, the moment we generate a new build and send it to the simulator from Xcode, the multipeer functionality stops working again in the simulator. The simulators won’t reconnect until a reboot of the physical Mac hardware hosting the simulator.
We’ve tried the usual troubleshooting steps, such as downgrading Xcode, deleting simulators and recreating them, cleaning the build folder, and deleting derived data, but unfortunately, none of these solutions have worked. The next step is to attempt to use a previous version of MacOS (15.3) and see if that helps, but I’d prefer to avoid this if possible.
Does anyone have any obvious suggestions or troubleshooting steps that might help us identify the cause of this issue?
Dear Girls, Guys and Engineers.
I'm currently building a Home Network Scanner App for People which want to know which Bonjour Devices are in her/his Home Network environment. From an older Question I got the answer, that I need an Entitlement to do this.
I started to work on the App and requested the Multicast Entitlement from Apple. They gave me the Entitlement for my App and now I'm trying to discover all devices in my Home Network but I got stuck and need Help.
I only test direct on device, like the recommendation. I also verified that my app is build with the multicast entitlement there where no problems. My problem is now, that is still not possible to discover all Bonjour services in my Home Network with the Help of the NWBrowser.
Can you please help me to make it work ?
I tried to scan for the generic service type:
let browser = NWBrowser(for: .bonjour(type: "_services._dns-sd._udp.", domain: nil), using: .init())
but this is still not working even tough I have the entitlement and the app was verified that the entitlement is correctly enabled
if I scan for this service type, I got the following error:
[browser] nw_browser_fail_on_dns_error_locked [B1] Invalid meta query type specified. nw_browser_start_dns_browser_locked failed: BadParam(-65540)
So what's the correct way now to find all devices in the home network ?
Thank you and best regards
Vinz
I am learning how to use DNS-SD from swift and have created a basic CLI app, however I am not getting callback results.
I can get results from cli. Something I am doing wrong here?
dns-sd -G v6 adet.local
10:06:08.423 Add 40000002 22 adet.local. FE80:0000...
dns-sd -B _adt._udp.
11:19:10.696 Add 2 22 local. _adt._udp. adet
import Foundation
import dnssd
var reference: DNSServiceRef?
func dnsServiceGetAddrInfoReply(ref: DNSServiceRef?, flags: DNSServiceFlags, interfaceIndex: UInt32, errorCode: DNSServiceErrorType, hostname: UnsafePointer<CChar>?, address: UnsafePointer<sockaddr>?, ttl: UInt32, context: UnsafeMutableRawPointer?) {
print("GetAddr'd")
print(hostname.debugDescription.utf8CString)
print(address.debugDescription.utf8CString)
}
var error = DNSServiceGetAddrInfo(&reference, 0, 0, DNSServiceProtocol(kDNSServiceProtocol_IPv6), "adet.local", dnsServiceGetAddrInfoReply, nil)
print("GetAddr: \(error)")
func dnsServiceBrowseReply(ref: DNSServiceRef?, flags: DNSServiceFlags, interfaceIndex: UInt32, errorCode: DNSServiceErrorType, serviceName: UnsafePointer<CChar>?, regType: UnsafePointer<CChar>?, replyDomain: UnsafePointer<CChar>?, context: UnsafeMutableRawPointer?) {
print("Browsed")
print(serviceName.debugDescription.utf8CString)
print(replyDomain.debugDescription.utf8CString)
}
error = DNSServiceBrowse(&reference, 0, 0, "_adt._udp", nil, dnsServiceBrowseReply, nil)
print("Browse: \(error)")
Foundation.RunLoop.main.run()
Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocalNetworkUsageDescription</key>
<string>By the Hammer of Grabthor</string>
<key>NSBonjourServices</key>
<array>
<string>_adt._udp.</string>
<string>_http._tcp.</string>
<string>_http._tcp</string>
<string>_adt._udp</string>
</array>
</dict>
</plist>
We're seeing an issue with bonjour services since macOS 15.4 onwards, specifically when running xcuitests on simulators that communicate with an app via bonjour services, the NWListener fails with -65555: NoAuth
Interestingly it only fails on subsequent iterations of the test, first iteration always succeeds.
The same code works fine on macOS 15.3.1 and earlier, but not 15.4 or 15.5.
Is this related to, or the same issue as here? https://developer.apple.com/forums/thread/780655
Also raised in feedback assistant: FB17804120
Hello,
I'm running into an issue while developing an iOS app that requires local network access. I’m using the latest MacBook Air M4 with macOS sequoia 15.5 and Xcode 16.1. In the iOS Simulator, my app fails to discover devices connected to the same local network.
I’ve already added the necessary key to the Info.plist:
NSLocalNetworkUsageDescription
This app needs access to local network devices.
When I run the app on a real device and M2 Chip Macbook's simulators, it works fine for local network permission as expected. However, in the M4 Chip Macbook's Simulator:
The app can’t find any devices on the local network
Bonjour/mDNS seems not to be working as well
I’ve tried the following without success:
Restarting Simulator and Mac
Resetting network settings in Simulator
Confirming app permissions under System Settings > Privacy & Security
Has anyone else encountered this issue with the new Xcode/macOS combo? Is local network access just broken in the Simulator for now, or is there a workaround?
Thanks in advance!
Hi folks, I'm building an iOS companion app to a local hosted server app (hosted on 0.0.0.0). The MacOS app locally connects to this server hosted, and I took the approach of advertising the server using a Daemon and BonjourwithTXT(for port) and then net service to resolve a local name. Unfortunately if there's not enough time given after the iPhone/iPad is plugged in (usb or ethernet), the app will cycle through attempts and disconnects many times before connecting and I'm trying to find a way to only connect when a viable en interface is available.
I've run into a weird thing in which the en interface only becomes seen on the NWMonitor after multiple connection attempts have been made and failed. If I screen for en before connecting it simply never appears. Is there any way to handle this such that my app can intelligently wait for an en connection before trying to connect? Attaching my code although I have tried a few other setups but none has been perfect.
func startMonitoringAndBrowse() {
DebugLogger.shared.append("Starting Bonjour + Ethernet monitoring")
if !browserStarted {
let params = NWParameters.tcp
params.includePeerToPeer = false
params.requiredInterfaceType = .wiredEthernet
browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_mytcpapp._tcp", domain: nil), using: params)
browser?.stateUpdateHandler = { state in
if case .ready = state {
DebugLogger.shared.append("Bonjour browser ready.")
}
}
browser?.browseResultsChangedHandler = { results, _ in
self.handleBrowseResults(results)
}
browser?.start(queue: .main)
browserStarted = true
}
// Start monitoring for wired ethernet
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { path in
let hasEthernet = path.availableInterfaces.contains { $0.type == .wiredEthernet }
let ethernetInUse = path.usesInterfaceType(.wiredEthernet)
DebugLogger.shared.append("""
NWPathMonitor:
- Status: \(path.status)
- Interfaces: \(path.availableInterfaces.map { "\($0.name)[\($0.type)]" }.joined(separator: ", "))
- Wired Ethernet: \(hasEthernet), In Use: \(ethernetInUse)
""")
self.tryToConnectIfReady()
self.stopMonitoring()
}
monitor?.start(queue: monitorQueue)
}
// MARK: - Internal Logic
private func handleBrowseResults(_ results: Set<NWBrowser.Result>) {
guard !self.isResolving, !self.hasResolvedService else { return }
for result in results {
guard case let .bonjour(txtRecord) = result.metadata,
let portString = txtRecord["actual_port"],
let actualPort = Int(portString),
case let .service(name, type, domain, _) = result.endpoint else {
continue
}
DebugLogger.shared.append("Bonjour result — port: \(actualPort)")
self.resolvedPort = actualPort
self.isResolving = true
self.resolveWithNetService(name: name, type: type, domain: domain)
break
}
}
private func resolveWithNetService(name: String, type: String, domain: String) {
let netService = NetService(domain: domain, type: type, name: name)
netService.delegate = self
netService.includesPeerToPeer = false
netService.resolve(withTimeout: 5.0)
resolvingNetService = netService
DebugLogger.shared.append("Resolving NetService: \(name).\(type)\(domain)")
}
private func tryToConnectIfReady() {
guard hasResolvedService,
let host = resolvedHost, let port = resolvedPort else { return }
DebugLogger.shared.append("Attempting to connect: \(host):\(port)")
discoveredIP = host
discoveredPort = port
connectionPublisher.send(.connecting(ip: host, port: port))
stopBrowsing()
socketManager.connectToServer(ip: host, port: port)
hasResolvedService = false
}
}
// MARK: - NetServiceDelegate
extension BonjourManager: NetServiceDelegate {
func netServiceDidResolveAddress(_ sender: NetService) {
guard let hostname = sender.hostName else {
DebugLogger.shared.append("Resolved service with no hostname")
return
}
DebugLogger.shared.append("Resolved NetService hostname: \(hostname)")
resolvedHost = hostname
isResolving = false
hasResolvedService = true
tryToConnectIfReady()
}
func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) {
DebugLogger.shared.append("NetService failed to resolve: \(errorDict)")
}
}
On an iOS 18 device, after installing the application and initially denying local network permission when prompted, manually enabling this permission in the system settings does not resolve the issue. After uninstalling and reinstalling the app, although local network access is granted, the app cannot discover smart hardware devices over the local area network (LAN) or proceed with configuration. The smart hardware sends configuration data packets over the LAN, but the app fails to receive these packets. This issue persists even after another uninstall and reinstall of the app. However, rebooting the device restores normal functionality.
Steps to Reproduce:
Install the application on an iOS 18 device.
Upon first launch, deny the request for local network permissions.
Manually enable local network permissions via "Settings" > [App Name].
Uninstall and then reinstall the application.
Attempt to discover and configure smart hardware devices using the app. Notice that the app fails to receive configuration data packets sent by the smart hardware over the LAN.
Expected Result:
The application should be able to normally receive configuration data packets from smart hardware devices over the LAN and successfully complete the configuration process after obtaining local network permissions.
Actual Result:
Even after being granted local network permissions, the application cannot discover devices or receive configuration data packets over the LAN unless the iPhone device is rebooted. (reinstall app and obtaining local network permissions is not work too.)
My laptop (M1 Pro, macOS 15.3.2) is connected to a dual stack network via Wi-Fi. The home.arpa. domain is supplied as a search domain via both DHCPv4 (options 15 and 119) and DHCPv6 (option 24). "Details…" for the network connection in System Settings show this domain under the DNS tab.
The laptop uses a Forwarding DNS Resolver of my router, which in turn forwards requests for home.arpa. (including subdomains) to a local DNS server (CoreDNS) which is authoritative for this zone.
The DNS server is configured via the following zone file:
$ORIGIN home.arpa.
$TTL 3600
@ IN SOA @ nobody.invalid. (1 3600 1200 604800 3600)
@ NS @
@ AAAA ….1
gateway A ….1
gateway AAAA …::1
b._dns-sd._udp PTR @
lb._dns-sd._udp PTR @
db._dns-sd._udp PTR @
_services._dns-sd._udp PTR _smb._tcp
_smb._tcp PTR Media._smb._tcp
Media._smb._tcp SRV 0 0 445 gateway
Media._smb._tcp TXT ("path=/media" "u=guest")
Output of dig(1) looks like:
$ dig @….1 -t PTR lb._dns-sd._udp.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43291
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;lb._dns-sd._udp.home.arpa. IN PTR
;; ANSWER SECTION:
lb._dns-sd._udp.home.arpa. 1993 IN PTR home.arpa.
;; AUTHORITY SECTION:
home.arpa. 2771 IN NS home.arpa.
$ dig @….1 -t PTR _services._dns-sd._udp.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9057
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;_services._dns-sd._udp.home.arpa. IN PTR
;; ANSWER SECTION:
_services._dns-sd._udp.home.arpa. 3600 IN PTR _smb._tcp.home.arpa.
;; AUTHORITY SECTION:
home.arpa. 3600 IN NS home.arpa.
$ dig @….1 -t PTR _smb._tcp.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44220
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;_smb._tcp.home.arpa. IN PTR
;; ANSWER SECTION:
_smb._tcp.home.arpa. 3599 IN PTR Media._smb._tcp.home.arpa.
;; AUTHORITY SECTION:
home.arpa. 3599 IN NS home.arpa.
$ dig @….1 -t SRV Media._smb._tcp.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45878
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;Media._smb._tcp.home.arpa. IN SRV
;; ANSWER SECTION:
media._smb._tcp.home.arpa. 3600 IN SRV 0 0 445 gateway.home.arpa.
;; AUTHORITY SECTION:
home.arpa. 3600 IN NS home.arpa.
$ dig @….1 -t A gateway.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2782
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;gateway.home.arpa. IN A
;; ANSWER SECTION:
gateway.home.arpa. 86400 IN A 192.168.99.1
;; AUTHORITY SECTION:
home.arpa. 3578 IN NS home.arpa.
$ dig @….1 -t AAAA gateway.home.arpa.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17297
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;gateway.home.arpa. IN AAAA
;; ANSWER SECTION:
gateway.home.arpa. 3600 IN AAAA fd6f:9784:5753::1
;; AUTHORITY SECTION:
home.arpa. 3600 IN NS home.arpa.
Output of dns-sd(1):
/usr/bin/dns-sd -test
…
Testing for error returns when various strings are > 63 bytes: PASSED
Running basic API input range tests with various pointer parameters set to NULL:
Basic API input range tests: PASSED
$ dns-sd -m -F
Looking for recommended browsing domains:
DATE: ---Fri 11 Apr 2025---
8:50:17.846 ...STARTING...
Timestamp Recommended Browsing domain
8:50:17.847 Added (More) local
8:50:17.847 Added arpa
- > home
$ dns-sd -B _smb._tcp home.arpa.
Browsing for _smb._tcp.home.arpa.
DATE: ---Fri 11 Apr 2025---
8:59:10.044 ...STARTING...
$ dns-sd -L Media _smb._tcp home.arpa.
Lookup Media._smb._tcp.home.arpa.
DATE: ---Fri 11 Apr 2025---
9:15:53.328 ...STARTING...
$ dns-sd -Q _smb._tcp.home.arpa. PTR IN
DATE: ---Fri 11 Apr 2025---
9:16:52.208 ...STARTING...
Timestamp A/R Flags IF Name Type Class Rdata
9:16:52.210 Add 40000002 0 _smb._tcp.home.arpa. PTR IN 0.0.0.0 No Such Record
9:16:52.222 Add 2 0 _smb._tcp.home.arpa. PTR IN 0.0.0.0 No Such Record
Similarly, when I open Finder->Network I see home.arpa but it's empty. Of interest is that on the DNS server side I see the following requests being made:
2025-04-11 09:03:15 container,info,debug [INFO] […]:56541 - 21555 "SOA IN _afpovertcp._tcp.home.arpa. udp 44 false 512" NXDOMAIN qr,aa,rd 112 0.000755089s
2025-04-11 09:03:15 container,info,debug [INFO] […]:56077 - 58266 "SOA IN _smb._tcp.home.arpa. udp 37 false 512" NOERROR qr,aa,rd 105 0.001012632s
2025-04-11 09:03:15 container,info,debug [INFO] […]:45274 - 45976 "SOA IN _rfb._tcp.home.arpa. udp 37 false 512" NXDOMAIN qr,aa,rd 105 0.000762339s
2025-04-11 09:03:15 container,info,debug [INFO] […]:54387 - 32090 "SOA IN _adisk._tcp.home.arpa. udp 39 false 512" NXDOMAIN qr,aa,rd 107 0.001058132s
2025-04-11 09:03:15 container,info,debug [INFO] […]:35855 - 51155 "SOA IN _tcp.home.arpa. udp 32 false 512" NOERROR qr,aa,rd 100 0.000664963s
I suppose that an attempt to locate services is made but it's unsuccessful and I'm not sure why.
What further debugging can I attempt?
I'm using NWBrowser to search for a server that I hosted. The browser does find my service but when it tries to connect to it, it gets stuck in the preparing phase in NWConnection.stateUpdateHandler. When I hardcode the local IP address of my computer (where the server is hosted) into NWConnection it works perfectly fine and is able to connect.
When it gets stuck in the preparing phase, it gives me the warnings and error messages in the image below. You can also see that the service name is correct and it is found.
I have tried _http._tcp and _ssh._tcp types and neither work.
This is what my code looks like:
func findServerAndConnect(port: UInt16) {
print("Searching for server...")
let browser = NWBrowser(for: .bonjour(type: "_ssh._tcp", domain: "local."), using: .tcp)
browser.browseResultsChangedHandler = { results, _ in
print("Found results: \(results)")
for result in results {
if case let NWEndpoint.service(name, type_, domain, interface) = result.endpoint {
if name == "PocketPadServer" {
print("Found service: \(name) of type \(type_) in domain \(domain) on interface \(interface)")
// Construct the full service name, including type and domain
let fullServiceName = "\(name).\(type_).\(domain)"
print("Full service name: \(fullServiceName), \(result.endpoint)")
self.connect(to: result.endpoint, port: port)
browser.cancel()
break
}
}
}
}
browser.start(queue: .main)
}
func connect(to endpoint: NWEndpoint, port: UInt16) {
print("Connecting to \(endpoint) on port \(port)...")
// endpoint = NWEndpoint(
let tcpParams = NWProtocolTCP.Options()
tcpParams.enableFastOpen = true
tcpParams.keepaliveIdle = 2
let params = NWParameters(tls: nil, tcp: tcpParams)
params.includePeerToPeer = true
// connection = NWConnection(host: NWEndpoint.Host("xx.xxx.xxx.xxx"), port: NWEndpoint.Port(3000), using: params)
connection = NWConnection(to: endpoint, using: params)
connection?.pathUpdateHandler = { path in
print("Connection path update: \(path)")
if path.status == .satisfied {
print("Connection path is satisfied")
} else {
print("Connection path is not satisfied: \(path.status)")
}
}
connection?.stateUpdateHandler = { newState in
DispatchQueue.main.async {
switch newState {
case .ready:
print("Connected to server")
self.pairing = true
self.receiveMessage()
case .failed(let error):
print("Connection failed: \(error)")
self.isConnected = false
case .waiting(let error):
print("Waiting for connection... \(error)")
self.isConnected = false
case .cancelled:
print("Connection cancelled")
self.isConnected = false
case .preparing:
print("Preparing connection...")
self.isConnected = false
default:
print("Connection state changed: \(newState)")
break
}
}
}
connection?.start(queue: .main)
}
I'm establishing a connection with NWListener and NWConnection which is working great. However, if the listener disappears, a lot of logs are appearing:
Is there a way to hide these logs?
I'm aware of OS_ACTIVITY_MODE=disabled, but that will also hide a lot of other logs.
I also know you can hide these using Xcode's filtering. I'm looking for a programmatically way to hide these completely. I'm not interested in seeing these at all, or, at least, I want to be in control.
Thanks!
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?
We are currently working on a zero-configuration networking compliant device thru avahi-daemon.
Our Device want to have multiple Instance name for different services.
Example
InstanceA._ipps._tcp.local.
InstanceA._ipp._tcp.local.
InstanceB._ipps._tcp.local.
InstanceB._ipp._tcp.local.
Will BCT confuse this as multiple device connected in the network and cause it to fail? Does Bonjour only allows only a Single Instance name with multiple services?
For important background information, read Extra-ordinary Networking before reading this.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
On Host Names
I commonly see questions like How do I get the device’s host name? This question doesn’t make sense without more context. Apple systems have a variety of things that you might consider to be the host name:
The user-assigned device name — This is a user-visible value, for example, Guy Smiley. People set this in Settings > General > About > Name.
The local host name — This is a DNS name used by Bonjour, for example, guy-smiley.local. By default this is algorithmically derived from the user-assigned device name. On macOS, people can override this in Settings > General > Sharing > Local hostname.
The reverse DNS name associated with the various IP addresses assigned to the device’s various network interfaces
That last one is pretty much useless. You can’t get a single host name because there isn’t a single IP address. For more on that, see Don’t Try to Get the Device’s IP Address.
The other two have well-defined answers, although those answers vary by platform. I’ll talk more about that below.
Before getting to that, however, let’s look at the big picture.
Big Picture
The use cases for the user-assigned device name are pretty clear. I rarely see folks confused about that.
Another use case for this stuff is that you’ve started a server and you want to tell the user how to connect to it. I discuss this in detail in Showing Connection Information in an iOS Server.
However, most folks who run into problems like this do so because they’re suffering from one of the following misconceptions:
The device has a DNS name.
Its DNS name is unique.
Its DNS name doesn’t change.
Its DNS name is in some way useful for networking.
Some of these may be true in some specific circumstances, but none of them are true in all circumstances.
These issues are not unique to Apple platforms — if you look at the Posix spec for gethostname, it says nothing about DNS! — but folks tend to notice these problems more on Apple platforms because Apple devices are often deployed to highly dynamic network environments.
So, before you start using the APIs discussed in this post, think carefully about your assumptions.
And if you actually do want to work with DNS, there are two cases to consider:
If you’re looking for the local host name, use the APIs discussed above.
In other cases, it’s likely that the APIs in this post will not be helpful and you’d be better off focusing on DNS APIs [1].
[1] The API I recommend for this is DNS-SD. See the DNS section in TN3151 Choosing the right networking API.
macOS
To get the user-assigned device name, call the SCDynamicStoreCopyComputerName(_:_:) function. For example:
let userAssignedDeviceName = SCDynamicStoreCopyComputerName(nil, nil) as String?
To get the local host name, call the SCDynamicStoreCopyLocalHostName(_:) function. For example:
let localHostName = SCDynamicStoreCopyLocalHostName(nil) as String?
IMPORTANT This returns just the name label. To form a local host name, append .local..
Both routines return an optional result; code defensively!
If you’re displaying these values to the user, use the System Configuration framework dynamic store notification mechanism to keep your UI up to date.
iOS and Friends
On iOS, iPadOS, tvOS, and visionOS, get the user-assigned device name from the name property on UIDevice.
IMPORTANT Access to this is now restricted. For more on that, see the documentation for the com.apple.developer.device-information.user-assigned-device-name entitlement.
There is no direct mechanism to get the local host name.
Other APIs
There are a wide variety of other APIs that purport to return the host name. These include:
gethostname
The name property on NSHost [1]
The hostName property on NSProcessInfo (ProcessInfo in Swift)
These are problematic for a number of reasons:
They have a complex implementation that makes it hard to predict what value you’ll get back.
They might end up trying to infer the host name from the network environment.
The existing behaviour is hard to change due to compatibility concerns.
Some of them are marked as to-be-deprecated.
IMPORTANT The second issue is particularly problematic, because it involves synchronous DNS requests [2]. That’s slow in general. Worse yet, if the network environment is restricted in some way, these calls can be very slow, taking about 30 seconds to time out.
Given these problems, it’s generally best to avoid calling these routines at all.
[1] It also has a names property, which is a little closer to reality but still not particularly useful.
[2] Actually, that’s not true for gethostname. Rather, that call just returns whatever was last set by sethostname. This is always fast. The System Configuration framework infrastructure calls sethostname to update the host name as the system state changes.
It doesn’t seem like there’s any high level, first-party documentation on how to use what is the recommended API for executing networking logic that you otherwise wouldn’t use URLSession for; which is a lot of things.
There’s a sample app, and docs on how to
choose the right network API in general, but apparently no high level API docs for Network.framework itself. Am I missing something? How do people learn to use this? Know which classes to use? Know the various ways it can be configured?
I'm using Network Framework to transfer files between 2 devices. The "secondary" device sends file requests to the "primary" device, and the primary sends the files back.
When the primary gets the request, it responds like this:
do {
let data = try Data(contentsOf: filePath)
let priSecDataFilePacket = PriSecDataFilePacket(fileName: filename, dataBlob: data)
let jsonData = try JSONEncoder().encode(priSecDataFilePacket)
let message = NWProtocolFramer.Message(priSecMessageType: PriSecMessageType.priToSecDataFile)
let context = NWConnection.ContentContext(identifier: "TransferUtility", metadata: [message])
connection.send(content: encodedJsonToSend, contentContext: context, isComplete: true, completion: .idempotent)
} catch {
print("\(error)")
}
It works great, even for hundreds of file requests. The problem arises if some files being requested are extremely large, like 600MB. You can see the memory speedometer on the primary quickly ramp up to the yellow zone, at which point iOS kills the app for high memory use, and you see the Jetsam log.
I changed the code to skip JSON encoding the binary file as a test, and that helped a bit, but it still goes too high; the real offender is the step where it loads the 600MB file into the data var:
let data = try Data(contentsOf: filePath)
If I remark out everything else and just leave that one line, I can still see the memory use spike.
As a fix, I'm rewriting this so the secondary requests the file in 5MB chunks by telling the primary a byte range such as "0-5242880" or "5242881-10485760", and then reassembling the chunks on the secondary once they all come in. So far this seems promising, but it's a fair amount of work.
My question: Does Network Framework have a built-in way to stream those bytes straight from disk as it sends them? So that I could send all the data in one single request without having to load the bytes into memory?
I'm working on a game that uses NWBrowser and NWListener to create a connection between an iOS and tvOS app.
I've got the initial networking up and running and it works perfectly when running in the simulator(s). However, when I run on-device(s), I've found that browseResultsChangedHandler gets called multiple times for what is ostensibly the same service.
My browser handler (which runs on iOS) looks like this:
browser.browseResultsChangedHandler = { [weak self] results, changes in
if let result = browser.browseResults.first {
self?.onPeerConnected?(PeerConnection(endpoint: result.endpoint))
}
}
The first time it gets called, the interface in the NWBrowser.Result is en0, but the 2nd time it gets called, it is en0 AND awdl0.
Because my current handling is so naive, this re-invocation ends up with two connections being made to the remote server (the Apple TV).
Now, I know that this handler, by its very name, is designed to be called multiple times as things change, so I'm curious as to what strategies I might employ here.
Is there any value in tearing down any previous connections and re-connecting using the latest one? Should I just kill the browser as soon as I handle the first one? Just ignore subsequent ones?
I'm sure that, to a degree, the answer is probably "it depends"... but I'm curious to see if there might be at least some high-level strategies like "whatever you do, don't do xxxx" or "most apps do yyyy" :-)
Thanks.
Hello,
We are currently working on a zero-configuration networking compliant device thru avahi-daemon (for mDNS/DNS-SD handling) and avahi-autoipd (for link-local address configuration).
Our test environment setup is:
Device Under Test (DUT): Debian 9 Linux
avahi-daemon: v0.6.32
avahi-autoipd: v0.6.32
Test Bed: Macmini with macOS Sequoia 15.0
Bonjour Conformance Test v1.5.4
Router: NEC Aterm WR8370N
Devices are connected via LAN
SRV PROBING/ANNOUNCEMENTS BASIC test failure was encountered in BCT during Multicast-DNS test suite execution.
Please see the logs below:
ERROR 2025-01-15 19:36:35.792930+0900: Cache flush bit is set in the SRV probes
NOTICE 2025-01-15 19:36:35.792946+0900: DEVICE-sERvICE-32\._uSCaNs\._tcp\.lOcaL\.._uscAnS._tCP.loCAL., SEND_CONFLICT_WIN -> SEND_CONFLICT_WIN
FAILED (SRV PROBING/ANNOUNCEMENTS BASIC)
START (SRV PROBING/ANNOUNCEMENTS)
DEBUG_2 2025-01-15 19:36:35.792979+0900: received packet (1137 bytes)
DEBUG_2 2025-01-15 19:36:35.792999+0900: srv_cf_probe
WARNING 2025-01-15 19:36:35.793022+0900: SRV Probing/Announcements Failed: See runtime output for PROBING and WINNING SIMULTANEOUS PROBE for details.
FAILED (SRV PROBING/ANNOUNCEMENTS)
We would like to know what causes the above test to fail, is it related to avahi or a an inccorect mDNS service handling wherein the cache flush bit was incorrectly set?
Thank you.
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