Bonjour, also known as zero-configuration networking, enables automatic discovery of devices and services on a local network using industry standard.

Posts under Bonjour tag

179 Posts

Post

Replies

Boosts

Views

Activity

Bonjour Connectivity Optimization
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)") } }
10
0
381
May ’25
After Denying Local Network Permission on iOS 18, Re-granting Doesn't Allow the App to Discover Devices Over LAN for Configuration
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.)
3
0
446
May ’25
macOS does not see an _smb._tcp service defined via Wide-Area DNS-SD
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?
7
0
799
Apr ’25
Connecting to a service found by Bonjour isn't working.
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) }
4
0
222
Apr ’25
Hide OS logs coming out of the Network framework (category: com.apple.network)
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!
4
0
314
Apr ’25
in-addr.arpa default search domains
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 &gt; 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?
1
0
854
Apr ’25
Bonjour Conformance Test - Multiple Instance in Single Device
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?
1
0
147
Apr ’25
On Host Names
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.
0
0
360
Mar ’25
How to use Network.framework
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?
4
0
334
Mar ’25
Jetsam memory crash during Network framework usage
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?
5
0
716
Mar ’25
browseResultsChangedHandler called multiple times
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.
3
0
530
Feb ’25
Bonjour Conformance Test - SRV PROBING/ANNOUNCEMENTS
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.
2
0
686
Jan ’25
First update to NWBrowser is always ready, irrespective of Local Networking privacy status
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
11
1
915
Jan ’25
Continued connection failure after server connection failure while local network permission pop-up is displayed
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.
1
0
652
Dec ’24
C++ MacOS include Bonjour
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
1
0
521
Dec ’24
Using Network Framework + Bonjour + QUIC + TLS
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 :)
19
0
2.5k
Dec ’24
NWListener, P2P and awdl interfaces
I'm attempting to create a service that: Listens on iOS device A using NWListener Broadcasts the NWService ( using NWListener(service:using:)) ) on Bonjour Allows a separate device, iOS device B, to receive information about that service via an NWBrowser Connect to that service using the information contained in NWBrowser.Result 's NWEndpoint I've been able to successfully do this using a SwiftNIO service, in the following environments: iOS device A and iOS device B are physical iOS devices on the same WiFi network. This works. iOS device A and iOS device B are iOS simulators on the same machine. This works. iOS device A is a physical device, and iOS device B is a simulator. iOS device A is not connected to a WiFi network, iOS device B is connected to a WiFi network. This works. However, when iOS device A and iOS device B are physical devices that are not connected to a WiFi network, I encounter the following behavior: The Bonjour service is correctly advertised, and iOS device A and iOS device B are able to observe the advertisement of the service. In both cases, iOS device A and iOS device B, while able to resolve an NWEndpoint for the Bonjour service, are not able to connect to each other, and the connection attempt hangs. My setup for the listener side of things looks roughly like: let opts: NWParameters = .tcp opts.includePeerToPeer = true opts.allowLocalEndpointReuse = true let service = NWListener.Service(name: "aux", type: BONJOUR_SERVICE_TYPE, domain: "") try bootstrap.withNWListener(NWListener(service: service, using: opts)).wait() // bootstrap is an artifact of using SwiftNIO Similarly, my setup on the discovery side of things looks like: let params: NWParameters = .tcp params.includePeerToPeer = true let browser = NWBrowser(for: .bonjour(type: BONJOUR_SERVICE_TYPE, domain: BONJOUR_SERVICE_DOMAIN), using: params) browser.browseResultsChangedHandler =  { (searchResults, changed) in // save the result to pass on its NWEndpoint later } and finally, where I have an NWEndpoint, I use SwiftNIO's NIOTSConnectionBootstrap.connect(endpoint:) to initialize a connection to my TCP service ( a web socket server ). The fact that I am able to get P2P networking (presumably over an awdl interface?) between the simulator and the iOS device suggests to me that I haven't done anything obviously wrong in my setup. Similarly, the fact that it works over the same WiFi network and that, in P2P, I am able to at least observe the Bonjour advertisement, strikes me that I'm somewhere in the right neighborhood of getting this to work. I've also ensured that my Info.plist for the app has a NSLocalNetworkUsageDescription and NSBonjourServices for the Bonjour service type I'm browsing for. I've even attempted to exercise the "Local Network Permission" dialog by using a hacky attempt that sends data to a local IP in order to trigger a permissions dialog, though the hack does not appear to actually force the dialog to appear. Is there some trick or other piece of knowledge regarding allowing the use of P2P w/ Network.framework and TCP connections to services?
7
0
2.3k
Nov ’24
Network framework crashes from nw_browser_cancel call
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
2
0
703
Nov ’24
Combining Bonjour and QUIC multiplex group using Network.framework
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?
12
0
1.1k
Nov ’24
Triggering the Local Network Privacy Alert
IMPORTANT The approach used by this code no longer works. See TN3179 Understanding local network privacy for a replacement. Currently there is no way to explicitly trigger the local network privacy alert (r. 69157424). However, you can bring it up implicitly by sending dummy traffic to a local network address. The code below shows one way to do this. It finds all IPv4 and IPv6 addresses associated with broadcast-capable network interfaces and sends a UDP datagram to each one. This should trigger the local network privacy alert, assuming the alert hasn’t already been displayed for your app. Oh, and if Objective-C is more your style, use this code instead. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@apple.com" import Foundation /// Does a best effort attempt to trigger the local network privacy alert. /// /// It works by sending a UDP datagram to the discard service (port 9) of every /// IP address associated with a broadcast-capable interface. This should /// trigger the local network privacy alert, assuming the alert hasn’t already /// been displayed for this app. /// /// This code takes a ‘best effort’. It handles errors by ignoring them. As /// such, there’s guarantee that it’ll actually trigger the alert. /// /// - note: iOS devices don’t actually run the discard service. I’m using it /// here because I need a port to send the UDP datagram to and port 9 is /// always going to be safe (either the discard service is running, in which /// case it will discard the datagram, or it’s not, in which case the TCP/IP /// stack will discard it). /// /// There should be a proper API for this (r. 69157424). /// /// For more background on this, see [Triggering the Local Network Privacy Alert](https://developer.apple.com/forums/thread/663768). func triggerLocalNetworkPrivacyAlert() { let sock4 = socket(AF_INET, SOCK_DGRAM, 0) guard sock4 >= 0 else { return } defer { close(sock4) } let sock6 = socket(AF_INET6, SOCK_DGRAM, 0) guard sock6 >= 0 else { return } defer { close(sock6) } let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces() var message = [UInt8]("!".utf8) for address in addresses { address.withUnsafeBytes { buf in let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self) let saLen = socklen_t(buf.count) let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6 _ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen) } } } /// Returns the addresses of the discard service (port 9) on every /// broadcast-capable interface. /// /// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`. private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] { var addrList: UnsafeMutablePointer<ifaddrs>? = nil let err = getifaddrs(&addrList) guard err == 0, let start = addrList else { return [] } defer { freeifaddrs(start) } return sequence(first: start, next: { $0.pointee.ifa_next }) .compactMap { i -> Data? in guard (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0, let sa = i.pointee.ifa_addr else { return nil } var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len))) switch CInt(sa.pointee.sa_family) { case AF_INET: result.withUnsafeMutableBytes { buf in let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self) sin.pointee.sin_port = UInt16(9).bigEndian } case AF_INET6: result.withUnsafeMutableBytes { buf in let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self) sin6.pointee.sin6_port = UInt16(9).bigEndian } default: return nil } return result } }
0
0
9.5k
Nov ’24
Bonjour Connectivity Optimization
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&lt;NWBrowser.Result&gt;) { 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)") } }
Replies
10
Boosts
0
Views
381
Activity
May ’25
After Denying Local Network Permission on iOS 18, Re-granting Doesn't Allow the App to Discover Devices Over LAN for Configuration
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" &gt; [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.)
Replies
3
Boosts
0
Views
446
Activity
May ’25
macOS does not see an _smb._tcp service defined via Wide-Area DNS-SD
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?
Replies
7
Boosts
0
Views
799
Activity
Apr ’25
Connecting to a service found by Bonjour isn't working.
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) }
Replies
4
Boosts
0
Views
222
Activity
Apr ’25
Hide OS logs coming out of the Network framework (category: com.apple.network)
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!
Replies
4
Boosts
0
Views
314
Activity
Apr ’25
in-addr.arpa default search domains
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 &gt; 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?
Replies
1
Boosts
0
Views
854
Activity
Apr ’25
Bonjour Conformance Test - Multiple Instance in Single Device
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?
Replies
1
Boosts
0
Views
147
Activity
Apr ’25
On Host Names
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.
Replies
0
Boosts
0
Views
360
Activity
Mar ’25
How to use Network.framework
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?
Replies
4
Boosts
0
Views
334
Activity
Mar ’25
Jetsam memory crash during Network framework usage
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?
Replies
5
Boosts
0
Views
716
Activity
Mar ’25
browseResultsChangedHandler called multiple times
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.
Replies
3
Boosts
0
Views
530
Activity
Feb ’25
Bonjour Conformance Test - SRV PROBING/ANNOUNCEMENTS
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.
Replies
2
Boosts
0
Views
686
Activity
Jan ’25
First update to NWBrowser is always ready, irrespective of Local Networking privacy status
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
Replies
11
Boosts
1
Views
915
Activity
Jan ’25
Continued connection failure after server connection failure while local network permission pop-up is displayed
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.
Replies
1
Boosts
0
Views
652
Activity
Dec ’24
C++ MacOS include Bonjour
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
Replies
1
Boosts
0
Views
521
Activity
Dec ’24
Using Network Framework + Bonjour + QUIC + TLS
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 :)
Replies
19
Boosts
0
Views
2.5k
Activity
Dec ’24
NWListener, P2P and awdl interfaces
I'm attempting to create a service that: Listens on iOS device A using NWListener Broadcasts the NWService ( using NWListener(service:using:)) ) on Bonjour Allows a separate device, iOS device B, to receive information about that service via an NWBrowser Connect to that service using the information contained in NWBrowser.Result 's NWEndpoint I've been able to successfully do this using a SwiftNIO service, in the following environments: iOS device A and iOS device B are physical iOS devices on the same WiFi network. This works. iOS device A and iOS device B are iOS simulators on the same machine. This works. iOS device A is a physical device, and iOS device B is a simulator. iOS device A is not connected to a WiFi network, iOS device B is connected to a WiFi network. This works. However, when iOS device A and iOS device B are physical devices that are not connected to a WiFi network, I encounter the following behavior: The Bonjour service is correctly advertised, and iOS device A and iOS device B are able to observe the advertisement of the service. In both cases, iOS device A and iOS device B, while able to resolve an NWEndpoint for the Bonjour service, are not able to connect to each other, and the connection attempt hangs. My setup for the listener side of things looks roughly like: let opts: NWParameters = .tcp opts.includePeerToPeer = true opts.allowLocalEndpointReuse = true let service = NWListener.Service(name: "aux", type: BONJOUR_SERVICE_TYPE, domain: "") try bootstrap.withNWListener(NWListener(service: service, using: opts)).wait() // bootstrap is an artifact of using SwiftNIO Similarly, my setup on the discovery side of things looks like: let params: NWParameters = .tcp params.includePeerToPeer = true let browser = NWBrowser(for: .bonjour(type: BONJOUR_SERVICE_TYPE, domain: BONJOUR_SERVICE_DOMAIN), using: params) browser.browseResultsChangedHandler =  { (searchResults, changed) in // save the result to pass on its NWEndpoint later } and finally, where I have an NWEndpoint, I use SwiftNIO's NIOTSConnectionBootstrap.connect(endpoint:) to initialize a connection to my TCP service ( a web socket server ). The fact that I am able to get P2P networking (presumably over an awdl interface?) between the simulator and the iOS device suggests to me that I haven't done anything obviously wrong in my setup. Similarly, the fact that it works over the same WiFi network and that, in P2P, I am able to at least observe the Bonjour advertisement, strikes me that I'm somewhere in the right neighborhood of getting this to work. I've also ensured that my Info.plist for the app has a NSLocalNetworkUsageDescription and NSBonjourServices for the Bonjour service type I'm browsing for. I've even attempted to exercise the "Local Network Permission" dialog by using a hacky attempt that sends data to a local IP in order to trigger a permissions dialog, though the hack does not appear to actually force the dialog to appear. Is there some trick or other piece of knowledge regarding allowing the use of P2P w/ Network.framework and TCP connections to services?
Replies
7
Boosts
0
Views
2.3k
Activity
Nov ’24
Network framework crashes from nw_browser_cancel call
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
Replies
2
Boosts
0
Views
703
Activity
Nov ’24
Combining Bonjour and QUIC multiplex group using Network.framework
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?
Replies
12
Boosts
0
Views
1.1k
Activity
Nov ’24
Triggering the Local Network Privacy Alert
IMPORTANT The approach used by this code no longer works. See TN3179 Understanding local network privacy for a replacement. Currently there is no way to explicitly trigger the local network privacy alert (r. 69157424). However, you can bring it up implicitly by sending dummy traffic to a local network address. The code below shows one way to do this. It finds all IPv4 and IPv6 addresses associated with broadcast-capable network interfaces and sends a UDP datagram to each one. This should trigger the local network privacy alert, assuming the alert hasn’t already been displayed for your app. Oh, and if Objective-C is more your style, use this code instead. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@apple.com" import Foundation /// Does a best effort attempt to trigger the local network privacy alert. /// /// It works by sending a UDP datagram to the discard service (port 9) of every /// IP address associated with a broadcast-capable interface. This should /// trigger the local network privacy alert, assuming the alert hasn’t already /// been displayed for this app. /// /// This code takes a ‘best effort’. It handles errors by ignoring them. As /// such, there’s guarantee that it’ll actually trigger the alert. /// /// - note: iOS devices don’t actually run the discard service. I’m using it /// here because I need a port to send the UDP datagram to and port 9 is /// always going to be safe (either the discard service is running, in which /// case it will discard the datagram, or it’s not, in which case the TCP/IP /// stack will discard it). /// /// There should be a proper API for this (r. 69157424). /// /// For more background on this, see [Triggering the Local Network Privacy Alert](https://developer.apple.com/forums/thread/663768). func triggerLocalNetworkPrivacyAlert() { let sock4 = socket(AF_INET, SOCK_DGRAM, 0) guard sock4 >= 0 else { return } defer { close(sock4) } let sock6 = socket(AF_INET6, SOCK_DGRAM, 0) guard sock6 >= 0 else { return } defer { close(sock6) } let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces() var message = [UInt8]("!".utf8) for address in addresses { address.withUnsafeBytes { buf in let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self) let saLen = socklen_t(buf.count) let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6 _ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen) } } } /// Returns the addresses of the discard service (port 9) on every /// broadcast-capable interface. /// /// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`. private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] { var addrList: UnsafeMutablePointer<ifaddrs>? = nil let err = getifaddrs(&addrList) guard err == 0, let start = addrList else { return [] } defer { freeifaddrs(start) } return sequence(first: start, next: { $0.pointee.ifa_next }) .compactMap { i -> Data? in guard (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0, let sa = i.pointee.ifa_addr else { return nil } var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len))) switch CInt(sa.pointee.sa_family) { case AF_INET: result.withUnsafeMutableBytes { buf in let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self) sin.pointee.sin_port = UInt16(9).bigEndian } case AF_INET6: result.withUnsafeMutableBytes { buf in let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self) sin6.pointee.sin6_port = UInt16(9).bigEndian } default: return nil } return result } }
Replies
0
Boosts
0
Views
9.5k
Activity
Nov ’24