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?
QUIC
RSS for tagCreate network connections to send and receive data using the QUIC protocol.
Posts under QUIC tag
25 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
When creating multiple QUIC streams (NWConnections) sharing one QUIC tunnel (using NWMultiplexGroup), is it possible to assign different priorities to the streams? And if yes, how?
The only prioritization option I found so far is NWConnection.ContentContext.relativePriority, but I assume that is for prioritizing messages within a single NWConnection?
Hello, I have a question about sending data in QUIC DATAGRAM.
Regarding sending data in QUIC DATAGRAM, when I create a NWConnectionGroup and then use the send method of that group to send data in sequence, quite often I get an Optional(POSIXErrorCode(rawValue: 89): Operation canceled) error.
A little Thread.sleep between sends improves the situation somewhat, but the above error still occurs.
Also, since I want to send the frame data of the video in this communication process, adding a wait will drastically reduce performance and make the speed impractical.
However, if send is executed continuously without adding weights, the above error will occur 80% of the time or more.
Is it necessary to send while monitoring some status when sending?
In communication using QUIC Stream, when connecting to the server with NWConnection and sending with its send method, the above error does not occur even if send is executed without wait, and data can be transferred with good performance.
I am trying to use DATAGRAM communication to further increase throughput, but I am not having much success, and there is not much information on DATAGRAM communication.
Thank you in advance for your help.
I am currently developing an iOS app with a new feature that utilizes Quick Start for data migration between devices. We are testing this in a test environment using an app distributed via TestFlight.
However, we are encountering an issue where the app installed on the pre-migration device (distributed via TestFlight) does not transfer to the post-migration device. Could this issue be related to the fact that the app was distributed via TestFlight? Is there any restriction where only apps released via the App Store can be migrated using Quick Start?
We would appreciate it if you could provide some insights into the cause of this issue and any alternative testing methods.
Hi,
We are working with a small QUIC POC, in which the macbook pro is the server and the vision pro the client (we use it to test QUIC's functionality). We have below logic to send small buffers (128k) using only one stream because we want the data to arrive in order and reliably as QUIC guarantees:
private func createDummyData() {
dummyData.append(Data(bytes: &frameNumber, count: MemoryLayout<UInt64>.size))
frameNumber += 1
}
private func sendDataToClient() {
createDummyData()
let start = Date()
Thread.sleep(forTimeInterval: 0.015)
outgoingConnection?.sendBuffer(dummyData) { [weak self] in
let interval = Date().timeIntervalSince(start)
print("--> frame #: \(String(describing: self?.frameNumber)), send took: \(interval) seconds")
self?.dummyData.removeLast(8)
self?.sendDataToClient()
}
}
As you can see we are waiting for the completion handler to call the next send operation. We needed to add a delay (0.015) because even when the data is arriving in order, we are not receiving a considerable amount of buffer on the client side.
If we remove the delay, this is the way we are receiving our data. By the way, we are including a frame number (1,2,3,4....) on each buffer so we know which one arrived at the client :
Connected to QUIC bi-di tunnel id: 0...
Timestamp: 00:42:40.413, Buffer received...
Frame number: 0, received...
Timestamp: 00:42:40.414, Buffer received...
Frame number: 1, received...
Timestamp: 00:42:40.416, Buffer received...
Frame number: 29, received...
Timestamp: 00:42:40.416, Buffer received...
Frame number: 30, received...
Timestamp: 00:42:40.418, Buffer received...
Frame number: 43, received...
Timestamp: 00:42:40.418, Buffer received...
Frame number: 52, received...
Timestamp: 00:42:40.422, Buffer received...
Frame number: 65, received...
Timestamp: 00:42:40.424, Buffer received...
Frame number: 80, received...
Timestamp: 00:42:40.426, Buffer received...
Frame number: 90, received...
As you can see, we have received frames number 0 and 1 but after that we received # 29 and then jumps from 30 to 43 and 52 and 65. Again, if we introduce the delay this is not the case, is not fixing it but at least there are not that many losses.
We thought QUIC had an internal sending queue in which every frame is waiting to be sent and it will be delivered reliably.
Kindly let us know what are we missing.
Hi,
This is basically a fundamental question on the QUIC's implementation via the Network framework.
We are using the NWMultiplexGroup object to deal with multiples streams over the wire, but we would like to understand if this object is using http3 under the hood, because our understanding is the actual connection multiplexing is happening under that protocol.
If this is not the case, can you please elaborate a little bit more on this.
Btw, in this implementation we are not using URLSession at all, is just pure QUIC via Network framework.
Thanks in advance.
Hi,
We have this situation in which we are sending buffers from a server to the Vision Pro in a local network and for some reason when we take the headset off of the user's head, the QUIC stream we are using are getting closed/terminated/disconnected.
What our options are in order to remove this behavior, probably resume or make sure the AVP is ready again to receive the buffers from the server in a graceful manner?
Hi, let us explain the situation we have:
We have a macOS server app which happens to be/act as a QUIC server (this setup is for a live demo).
Once the server receives a streaming request from the client, server starts to send a bunch of QUIC streams to the client.
The server needs to run on a macbook pro for the live demo and everything works fine, now when we click on a different app (the server app looses focus) the server app goes to background state and the network activity just stops going from 90MB/s to almost zero, but when we click on the server app again, the network activity goes back to 90MB/s and it continues normally.
We understand this is the OS taking some decisions by managing resources efficiently.
Question:
Kindly let us know which options do we have to keep the server app QUIC networking tasks continuously running, even if it is not on the foreground (basically for it to behave like an actual server/service)?
Thanks in advance
We would like to understand/double check if it is possible to use QUIC in Swift via Network framework as the client along with some other QUIC solution on the server (ex. s2n-quic, quiche, msquic, etc..) which won't be a macOS server.
If that interoperability is indeed possible, the NWConnectionGroup won't be an approach we could use IMO, since probably we will need to develop that from scratch on both sides.
Thanks in advance.
Hi,
We are using HTTP3 only and hence using assumesHTTP3Capable for every request. It worked so far but now encountered one iPhone that never honor this flag and always tries to create a connection using TCP:
[tcp] tcp_input [C1:3] flags=[R.] seq=0, ack=2023568485, win=0 state=SYN_SENT rcv_nxt=0, snd_una=2023568484
The request is created like this:
let url = URL(string: urlString)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
request.assumesHTTP3Capable = true
return try await urlSession.data(for: request)
iOS: 16
XCode: 15.3
In what cases iOS CFNetwork would not honor "assumesHTTP3Capable" ? (or how can I find out why?)
Hi,
When running my iOS app in Xcode, I got the following message in the console multiple times:
[connection] nw_read_request_report [C1] Receive failed with error "Operation timed out"
It seems not critical as my app still works, but how can I find out more details of the connection that printed this message? For example, the network request the caused this, or the URL?
Xcode: 15.3
iOS 17
SwiftUI app
We have an implementation in which we use QUIC via a connection group, server are client are on Swift using the Network framework.
Our use case is, the server should send data buffers to the client as fast and as much as possible, now the pace to call the send method from the server should be carefully done, because if we send too much data of course the client is not gonna be able to receive it.
The question would be, is there a way to query the congestion window so we know on the server side, how much data we should be able to send at some point? Asking because we are not getting all the data we are sending from the server on our client side...
We are using these settings:
let options = NWProtocolQUIC.Options(alpn: ["h3"])
options.direction = .bidirectional
//
options.idleTimeout = 86_400_000
options.maxUDPPayloadSize = Int.max
options.initialMaxData = Int.max
options.initialMaxStreamDataBidirectionalLocal = Int.max
options.initialMaxStreamDataBidirectionalRemote = Int.max
options.initialMaxStreamDataUnidirectional = Int.max
options.initialMaxStreamsBidirectional = 400
options.initialMaxStreamsUnidirectional = 400
Questions:
1.- Can we get a little more detail in above options, specifically on their impact to the actual connection?
2.- IsinitialMaxData the actual congestion window value
3.- Are we missing something or making incorrect assumptions?
Thanks in advance.
Hello. Wanted to ask about the right way, or the intended way to leverage NWConnectionGroup for a QUIC based streaming solution.
The use case is, we are making a request from the client in order to play a movie, and we want to send as much video frames as possible (and as fast as possible) from the streaming server, which also uses the Network framework.
Our understanding is, NWConnectionGroup will open a QUIC tunnel between both parties so we can multiplex different streams to the client and we are already doing that.
We see a throughput of approx. 20-35MB/s (client device is an iPad and server is an M2 macbook pro running a server app) and we would like to understand if we can improve these results way more.
For example:
1.- Is it a good practice to create a second tunnel (NWConnectionGroup), or is not needed here?. We tried that, but the second one is also coming with id 0 on the metadata object, just as the first group we instantiated, not sure why this is the case.
2.- We are using a pool of several NWConnection (initialized with the group object) already instantiated, that way we send a video buffer in chunks as a stream on each connection. We use one connection for a buffer and when we need to send another buffer we use a different NWConnection pulled from the pool.
We maybe just want a confirmation/validation of what we are doing, or to see if we are missing something on our implementation...
Thanks in advance.
Hi,
We recently released a version of our app where we use 'NWParameters.PrivacyContext'.
On iOS 17.4 and iOS 17.4.1 the app crashes with a following crash:
Distributor ID: com.apple.AppStore
Hardware Model: iPhone12,1
Process: <app>
Path: <path to app>
Identifier: <bundle id>
Version: <version>
AppStoreTools: 15E204
AppVariant: 1:iPhone12,1:15
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: <our bundle id> [4899]
Date/Time: 2024-04-29 01:50:13.4113 +0300
Launch Time: 2024-04-29 01:13:47.6252 +0300
OS Version: iPhone OS 17.4.1 (21E236)
Release Type: User
Baseband Version: 5.00.00
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000054
Exception Codes: 0x0000000000000001, 0x0000000000000054
VM Region Info: 0x54 is not in any region. Bytes before following region: 4334124972
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 102558000-1063e4000 [ 62.5M] r-x/r-x SM=COW
<path to app>
Termination Reason: SIGNAL 11 Segmentation fault: 11
Terminating Process: exc handler [24308]
Triggered by Thread: 18
Kernel Triage:
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter
Thread 18 name:
Thread 18 Crashed:
0 libdispatch.dylib 0x00000001a573b7d8 dispatch_async + 192 (queue.c:940)
1 Network 0x000000019ddbdb38 nw_queue_context_async + 76 (queue.m:87)
2 Network 0x000000019e512748 invocation function for block in nw_socket_init_socket_event_source(nw_socket*, unsigned int) + 1488 (protocol_socket.cpp:4351)
3 libdispatch.dylib 0x00000001a5736dd4 _dispatch_client_callout + 20 (object.m:576)
4 libdispatch.dylib 0x00000001a573a2d8 _dispatch_continuation_pop + 600 (queue.c:321)
5 libdispatch.dylib 0x00000001a574e1c8 _dispatch_source_latch_and_call + 420 (source.c:596)
6 libdispatch.dylib 0x00000001a574cd8c _dispatch_source_invoke + 832 (source.c:961)
7 libdispatch.dylib 0x00000001a5740284 _dispatch_workloop_invoke + 1756 (queue.c:4570)
8 libdispatch.dylib 0x00000001a5749cb4 _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:6998)
9 libdispatch.dylib 0x00000001a5749528 _dispatch_workloop_worker_thread + 404 (queue.c:6592)
10 libsystem_pthread.dylib 0x00000001f981cf20 _pthread_wqthread + 288 (pthread.c:2665)
11 libsystem_pthread.dylib 0x00000001f981cfc0 start_wqthread + 8 (:-1)
Thread 18 crashed with ARM Thread State (64-bit):
x0: 0x0000000301a922e0 x1: 0x000000032471a720 x2: 0x0000000000000000 x3: 0x00000003015e2300
x4: 0x0000000000000003 x5: 0x00000000000022e0 x6: 0x0000000172462ef0 x7: 0x000000000000008b
x8: 0x00000000000008ff x9: 0x0000000000000000 x10: 0x0000000000010000 x11: 0x0000000000000020
x12: 0x00000003016f3854 x13: 0x00000000001ff800 x14: 0x00000000000007fb x15: 0x0000000089800118
x16: 0x00000001a573511c x17: 0x000000019e514740 x18: 0x0000000000000000 x19: 0x0000000000000000
x20: 0x0000000308eae8c0 x21: 0x000000032471a730 x22: 0x000000032471b0e0 x23: 0x000000000000e023
x24: 0x0000000172463085 x25: 0x000000002b1d034c x26: 0x0000000000000800 x27: 0x0000000000000000
x28: 0x0000000000000000 fp: 0x000000032471a6b0 lr: 0xad5ba301a573b750
sp: 0x000000032471a690 pc: 0x00000001a573b7d8 cpsr: 0x80000000
esr: 0x92000006 (Data Abort) byte read Translation fault
What could be the reason for it?
Hello! I'm playing around with QUIC and Swift and using the Network framework. So far, the process has been really straightforward, but I noticed that I can't seem to get a handle on the stream with identifier 0.
If I use NWConnection directly, I only have access to the first stream, which has the stream ID 0. This not what I want since I wanna use multiple streams. Following the documentation, I started using NWMultiplexGroup and starting a NWConnectionGroup with it.
Everything works fine and I can get all streams that my backend service opens using NWMultiplexGroup's newConnectionHandler property. However, whenever backend sends a message on stream_id 0, none of my connections receive it. Looking around with
connection.metadata(definition: NWProtocolQUIC.definition) as? NWProtocolQUIC.Metadata
for each connection, I see that all streams are accounted for except stream 0. Then, using the NWConnectionGroup variant of the above
connectionGroup.metadata(definition: NWProtocolQUIC.definition) as? NWProtocolQUIC.Metadata
I see that the connection group itself has Stream ID 0. However, calling setReceiveHandler does nothing (it's never called, even when backend is sending messages) and when I attempt to send a message using NWConnectionGroup's -send method, a new stream is opened (instead of it being sent on stream ID 0).
How can one get a handle on NWConnection for stream ID 0?
Hi, wondering if IOS supports WebTransport (HTTP/3) yet?
If so, where can I find information on implementing it in my app?
I am currently developing two iOS applications that require peer-to-peer connectivity. To facilitate this, I've implemented NWListener and NWConnection in both apps for network communication. To determine each device's public IP address and port—necessary for establishing a connection over the internet through my mobile operator's Carrier-Grade NAT (CGNAT)—I'm using a STUN server.
Despite successfully retrieving the external IP addresses and ports for both devices, I am unable to establish a peer-to-peer connection between them. My current setup involves initiating a connection using the public addresses and ports discovered through the STUN server response. However, all attempts to connect the devices directly have been unsuccessful.
I am seeking guidance on whether there are additional considerations or specific configurations needed when using NWListener, NWConnection, and a STUN server to establish a direct connection between devices in a CGNAT environment. Is there a particular step or network configuration I might be missing to successfully connect both iOS devices to each other using their external network details?
Hi, we are currently implementing below method for a quick POC in iOS (Xcode 15.3/macOS Sonoma 14.0):
func startQUICConnection() async {
// Set the initial stream to bidirectional.
options.direction = .bidirectional
self.mainConn?.stateUpdateHandler = { [weak self] state in
print("Main Connection State: \(state)")
switch state {
case .ready:
print("Ready...")
default:
break
}
}
// Don't forget to start the connection.
self.mainConn?.start(queue: self.queue)
}
This is what we have in the initializer of the class:
parameters = NWParameters(quic: options)
mainConn = NWConnection(to: endpoint, using: parameters)
These are the class's properties:
let endpoint = NWEndpoint.hostPort(host: "0.0.0.0", port: .init(integerLiteral: 6667))
let options = NWProtocolQUIC.Options(alpn: ["echo"])
let queue = DispatchQueue(label: "quic", qos: .userInteractive)
var mainConn: NWConnection? = nil
let parameters: NWParameters!
As per the logs, we never get to the .ready state for the NWConnection.
Logs:
nw_path_evaluator_create_flow_inner failed NECP_CLIENT_ACTION_ADD_FLOW (null) evaluator parameters: quic, attach protocol listener, attribution: developer, context: Default Network Context (private), proc: 022B7C28-0271-3628-8E5E-26B590B50E5B
nw_path_evaluator_create_flow_inner NECP_CLIENT_ACTION_ADD_FLOW 8FEBF750-979D-437F-B4A8-FB71F4C5A882 [22: Invalid argument]
nw_endpoint_flow_setup_channel [C2 0.0.0.0:6667 in_progress channel-flow (satisfied (Path is satisfied), interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] failed to request add nexus flow
Main Connection State: preparing
Main Connection State: waiting(POSIXErrorCode(rawValue: 22): Invalid argument)
We're running a local server using proxygen on port 6667. It connects with the proxygen client though...
Have tried several thing but results are the same.
I working on a QUIC Client/Server and would like to inspect all underlying protocols via NWConnection.ContextContent in the receive method.
receiveMessage(completion: {(receivedContent, context, isComplete, receivedError)
.receive(minimumIncompleteLength: 1, maximumLength: 65535) { (receivedContent, context, isComplete, receivedError)
As far as I understand is that the parameter .protocolMetadata in ContextContent should provide a list of all involved protocols. I expect an array of 3 NWProtocolMetadata like [NWProtocolIP.Metadata, NWProtocolUDP.Metadata, NWProtocolQUIC.Metadata] but I only get [NWProtocolQUIC.Metadata].
I already managed to get [NWProtocolIP.Metadata, NWProtocolUDP.Metadata] for a UDP connection but I can't get it to work for QUIC.
Is it possible to get NWProtocolIP.Metadata, NWProtocolUDP.Metadata for a QUIC connection within the receive function?
Regards
Jan
I'm building a network client with Swift (using QUIC). I set everything up properly (I know this because I can successfully connect, send and receive streams). But I'm trying to catch connection errors.
For example if I try to connect to a totally bogus IP address, I would like to display Connecting, then ConnectionFailed
I do the following:
create my NWMultiplexGroup descriptor
set my appropriate NWParameters
create my NWConnectionGroup
set up my handlers (setReceiveHandler, newConnectionHandler) and my state update handler
i call connection.start
When I pass a valid address to a server that is listening for the connection, all is good - in my stateUpdateHandler I get the .ready state, but I don't get any intermediate states, and if I pass it a bogus IP address, I get absolutely no callbacks to my handler
(I would have expected to get .waiting and/or .failed)
I couldn't find any quic options that I'm not doing, and the apple documentation is not helpful
Any suggestions as to what I might be missing?