I'm using NERelayManager to set Relay configuration which all works perfectly fine.
I then do a curl with the included domain and while I see QUIC connection succeeds with relay server and H3 request goes to the server, the connection gets abruptly closed by the client with "Software caused connection abort".
Console has this information:
default 09:43:04.459517-0700 curl nw_flow_connected [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] Transport protocol connected (quic)
default 09:43:04.459901-0700 curl [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:finish_transport @0.131s
default 09:43:04.460745-0700 curl nw_flow_connected [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] Joined protocol connected (http3)
default 09:43:04.461049-0700 curl [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:finish_transport @0.133s
default 09:43:04.465115-0700 curl [C2 E47A3A0C-7275-4F6B-AEDF-59077ABAE34B 192.168.4.197:4433 quic, multipath service: 1, tls, definite, attribution: developer] cancel
default 09:43:04.465238-0700 curl [C2 E47A3A0C-7275-4F6B-AEDF-59077ABAE34B 192.168.4.197:4433 quic, multipath service: 1, tls, definite, attribution: developer] cancelled
[C2 FCB1CFD1-4BF9-4E37-810E-81265D141087 192.168.4.139:53898<->192.168.4.197:4433]
Connected Path: satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi
Duration: 0.121s, QUIC @0.000s took 0.000s, TLS 1.3 took 0.111s
bytes in/out: 2880/4322, packets in/out: 4/8, rtt: 0.074s, retransmitted bytes: 0, out-of-order bytes: 0
ecn packets sent/acked/marked/lost: 3/1/0/0
default 09:43:04.465975-0700 curl nw_flow_disconnected [C2 192.168.4.197:4433 cancelled multipath-socket-flow ((null))] Output protocol disconnected
default 09:43:04.469189-0700 curl nw_endpoint_proxy_receive_report [C1.1 IPv4#124bdc4d:80 in_progress proxy (satisfied (Path is satisfied), interface: en0[802.11], ipv4, ipv6, dns, proxy, uses wifi)] Privacy proxy failed with error 53 ([C1.1.1] masque Proxy: http://192.168.4.197:4433)
default 09:43:04.469289-0700 curl [C1.1.1 192.168.4.197:4433 failed socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:failed_connect @0.141s, error Software caused connection abort
Relay server otherwise works fine with our QUIC MASQUE clients but not with built-in macOS MASQUE client. Anything I'm missing?
How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here
QUIC
RSS for tagCreate network connections to send and receive data using the QUIC protocol.
Posts under QUIC tag
23 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I am seeking assistance with how to properly handle / save / reuse NWConnections when it comes to the NWBrowser vs NWListener.
Let me give some context surrounding why I am trying to do what I am.
I am building an iOS app that has peer to peer functionality. The design is for a user (for our example the user is Bob) to have N number of devices that have my app installed on it. All these devices are near each other or on the same wifi network. As such I want all the devices to be able to discover each other and automatically connect to each other. For example if Bob had three devices (A, B, C) then A discovers B and C and has a connection to each, B discovers B and C and has a connection to each and finally C discovers A and B and has a connection to each.
In the app there is a concept of a leader and a follower. A leader device issues commands to the follower devices. A follower device just waits for commands. For our example device A is the leader and devices B and C are followers. Any follower device can opt to become a leader. So if Bob taps the “become leader” button on device B - device B sends out a message to all the devices it’s connected to telling them it is becoming the new leader. Device B doesn’t need to do anything but device A needs to set itself as a follower. This detail is to show my need to have everyone connected to everyone.
Please note that I am using .includePeerToPeer = true in my NWParameters. I am using http/3 and QUIC. I am using P12 identity for TLS1.3. I am successfully able to verify certs in sec_protocal_options_set_verify_block. I am able to establish connections - both from the NWBrowser and from NWListener. My issue is that it’s flaky. I found that I have to put a 3 second delay prior to establishing a connection to a peer found by the NWBrowser. I also opted to not save the incoming connection from NWListener. I only save the connection I created from the peer I found in NWBrowser. For this example there is Device X and Device Y. Device X discovers device Y and connects to it and saves the connection. Device Y discovers device X and connects to it and saves the connection. When things work they work great - I am able to send messages back and forth. Device X uses the saved connection to send a message to device Y and device Y uses the saved connection to send a message to device X.
Now here come the questions.
Do I save the connection I create from the peer I discovered from the NWBrowser?
Do I save the connection I get from my NWListener via newConnectionHandler?
And when I save a connection (be it from NWBrowser or NWListener) am I able to reuse it to send data over (ie “i am the new leader command”)?
When my NWBrowser discovers a peer, should I be able to build a connection and connect to it immediately?
I know if I save the connection I create from the peer I discover I am able to send messages with it. I know if I save the connection from NWListener - I am NOT able to send messages with it — but should I be able to?
I have a deterministic algorithm for who makes a connection to who. Each device has an ID - it is a UUID I generate when the app loads - I store it in UserDefaults and the next time I try and fetch it so I’m not generating new UUIDs all the time. I set this deviceID as the name of the NWListener.Service I create. As a result the peer a NWBrowser discovers has the deviceID set as its name. Due to this the NWBrowser is able to determine if it should try and connect to the peer or if it should not because the discovered peer is going to try and connect to it.
So the algorithm above would be great if I could save and use the connection from NWListener to send messages over.
I am trying to make http3 client with Network.framework on Apple platforms.
Codes that implement NWConnectionGroup.start with NWListener don't always work with warning below.
I assume NWConnectionGroup.newConnectionHandler or NWListener.newConnectionHandler will be called to start connection from the server if it works.
nw_protocol_instance_add_new_flow [C1.1.1:2] No listener registered, cannot accept new flow
quic_stream_add_new_flow [C1.1.1:2] [-fde1594b83caa9b7] failed to create new stream for received stream id 3
so I tried:
create the NWListener -> not work
check whether NWConnectionGroup has a member to register or not NWListener -> not work (it doesn't have).
use NWConnection instead of NWConnectionGroup -> not work
Is my understanding correct?
How should I do to set or associate listener with NWConnection/Group for newConnectionHandler is called and to delete wanings?
What is the best practice in the case?
Sample codes are below.
Thanks in advance.
// http3 needs unidirectional stream by the server and client.
// listener
private let _listener: NWListener
let option: NWProtocolQUIC.Options = .init(alpn:["h3"])
let param: NWParameters = .init(quic: option)
_listener = try! .init(using: param)
_listener.stateUpdateHandler = { state in
print("listener state: \(state)")
}
_listener.newConnectionHandler = { newConnection in
print("new connection added")
}
_listener.serviceRegistrationUpdateHandler = { registrationState in
print("connection registrationstate")
}
// create connection
private let _group: NWConnectionGroup
let options: NWProtocolQUIC.Options = .init(alpn: ["h3"])
options.direction = .unidirectional
options.isDatagram = false
options.maxDatagramFrameSize = 65535
sec_protocol_options_set_verify_block(options.securityProtocolOptions, {(_: sec_protocol_metadata_t, _: sec_trust_t, completion: @escaping sec_protocol_verify_complete_t) in
print("cert completion.")
completion(true)
}, .global())
let params: NWParameters = .init(quic: options)
let group: NWMultiplexGroup = .init(
to: .hostPort(host: NWEndpoint.Host("google.com"),
port: NWEndpoint.Port(String(443))!))
_group = .init(with: group, using: params)
_group.setReceiveHandler {message,content,isComplete in
print("receive: \(message)")
}
_group.newConnectionHandler = {newConnection in
print("newConnectionHandler: \(newConnection.state)")
}
_group.stateUpdateHandler = { state in
print("state: \(state)")
}
_group.start(queue: .global())
_listener.start(queue: .global())
if let conn = _group.extract() {
let data: Data = .init()
let _ = _group.reinsert(connection: conn)
conn.send(content: data, completion: .idempotent)
}
HI,
I am currently prototyping an app that compares transport protocol performances using a peer to peer connection. I have already setup TCP and UDP connections and am sending data between the clients, it works like I want it to.
Next I was trying to setup a connection using QUIC, but the NWConnection.State stays in the preparing state and I couldn't find a way to get more information from the framework or the instances about why it was not fully connecting. After searching the internet and stumbling across the forum I noticed that the missing encryption might be the issue, so I created a local root certificate*. Then I used the SecPKCS12Import function to read/extract the SecIdentity instance of the p12 file (cert + private key) stored in my bundle** and set it as a local identity with the sec_protocol_options_set_local_identity function***.
//function that creates/returns different NWParameteres
//...
let quicOptions = NWProtocolQUIC.Options()
quicOptions.alpn = ["test"]
if let identityPath = Bundle.main.path(forResource: "QUICConnect", ofType: "p12"),
let identityData = try? Data(contentsOf: URL(fileURLWithPath: identityPath)) {
if let identity = loadIdentityFromPKCS12(p12Path: identityPath, password: "insecure") { //****
sec_protocol_options_set_local_identity(quicOptions.securityProtocolOptions, sec_identity_create(identity)!)
}
}
let parameters = NWParameters(quic: quicOptions)
parameters.includePeerToPeer = true
return parameter
The documentation comments had me thinking that setting a local identity could be enough, since it consists of the private key for the "server" and the cert for the "client".
Set the local identity to be used for this protocol instance.
Unfortunately at this stage the QUIC Connection is still stuck in preparing state and since I don't know how to extract more information from the networking connection instances/framework, I am stuck.
I have seen the following other functions in Quinns answer and am confident that I could somehow figure it out with some more time put into it, but not really understanding why or how I could do it better in the future. So I am also wondering how I could have found info about this more efficiently and tackled this more strategically without needing to browse through so many forums.
sec_protocol_options_set_verify_block
sec_protocol_options_set_challenge_block
I would really appreciate any help, many thanks.
BR Matthias!
TLDR:
I want to establish a peer to peer QUIC Connection but the state is stuck in preparing. Secondary question is how I could approach a similar topic more efficiently next time, instead of browsing many forums.
* I had to create it with the openssl CLI since the keychain app created a cert, that when using the openssl CLI to get the info would throw an error unless used with the -legacy flag. The root cert, created form the keychain app also wasn't able to be imported by the SecPKCS12Import function. No clue why but it worked with a cert created from the openssl CLI. There's a chance that I messed up something else here, but these were my experiences. Info: Since QUIC is limited to TLS v1.3 I can't use PSK, afaik. Therefore the TicTacToe doesn't help me anymore.
** I know this is highly insecure, I am just using it for prototyping.
*** Forum users Info: One needs to use the sec_identity_create function to convert the SecIdentity instance to the expected parameter type.
****
func loadIdentityFromPKCS12(p12Path: String, password: String) -> SecIdentity? {
guard let p12Data = try? Data(contentsOf: URL(fileURLWithPath: p12Path)) else {
print("didnt find p12 file at path")
return nil
}
let options: NSDictionary = [kSecImportExportPassphrase as String: password, kSecImportToMemoryOnly as String: kCFBooleanTrue!]
var items: CFArray?
let status = SecPKCS12Import(p12Data as CFData, options, &items)
if status == 0, let dict = (items as? [[String: Any]])?.first {
if let identity = dict[kSecImportItemIdentity as String] {
return identity as! SecIdentity
} else {
return nil
}
} else {
return nil
}
}
PS: For TCP and UDP I am using bonjour to discover the peer and connect to the advertised ports. AFAIK I can't just use _testproto._quic to advertise a QUIC service like with tcp and udp. Therefore I am using the local domain name (it's just for prototyping and always the same device) and a hard coded port number to create the peer connection. When using a wrong name the DNS threw an error telling it could not find a peer, so the lookup itself is working I guess. The lookup should come from the cache since I already looked up when connecting to the same peer via Bonjour.
//Server
//....
listener = try NWListener(
using: transportProtocol.parameters,
on: Config.quicPort
)
//...
listener.newConnectionHandler = { [weak self] connection in
self?.connection?.cancel()
self?.connection = nil
self?.connection = C(connection) //here C is a generic that conforms to a custom connection interface, nothing to worry about :)
self?.connectionStatus.value = "Connection established"
}
listener.stateUpdateHandler = { [weak self] state in
self?.connectionStatus.value = "\(state)"
}
listener.start(queue: .global())
//Client
//...
nwConnection = NWConnection(host: "iPad.local.", port: Config.quicPort, using: transportProtocol.parameters)
//...
I'm working on two Swift applications which are using QUIC in Network.framework for communication, one serve as the listener (server) and the other serve as the client so that they can exchange data, both the server and the client app are running under the same LAN, the problem I met is that when client try to connect to the server, the connection will fail due to boring SSL, couple questions:
Since both the server app and client app are running under the same LAN, do they need TLS certificate?
If it does, will self-signed certificate P12 work? I might distribute the app in App Store or in signed/notarized dmg or pkg to our users.
If I need a public certificate and self signed wouldn't work, since they are just pair of apps w/o fixed dns domain etc, Is there any public certificate only for standalone application, not for the fixed web domain?
I want to configure one aspect of my networking configuration (the QUIC keepalive interval). This only seems to be configurable via Network.framework’s nw_quic_set_keepalive_interval. Is there any way to apply this to a URLSession? Or do I need to implement the whole connection management myself using Network.framework?
We are implementing a Transparent Proxy for HTTPS (via TCP and QUIC).
The following rules are set in startProxy:
settings.includedNetworkRules = [
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "0.0.0.0", port: "443"), prefix: 0, protocol: .TCP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "::", port: "443"), prefix: 0, protocol: .TCP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "0.0.0.0", port: "443"), prefix: 0, protocol: .UDP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "::", port: "443"), prefix: 0, protocol: .UDP)
]
Handling TCP connections seems to work fine. But opening UDP flows from Chrome (or Brave) always fails with
Error Domain=NEAppProxyFlowErrorDomain Code=2 "The peer closed the flow"
(Doing the same for Firefox works!)
BTW: We first create a remote UDP connection (using the Network framework) and when it is in the ready state, we use connection?.currentPath?.localEndpoint as the localEndpoint parameter in the open method of the flow.
Is it a known issue that QUIC connections from Chrome cannot be handled by a Transparent Proxy Provider?
Hello,
I have a very basic quic client implementation. When you run this code with some basic quic server, you will see that we can't get a handle to stream identifier 0, but behavior is actually different when we use URLSession/URLRequest, and I can see that some information can be sent over the wire for stream identifier 0 with that implementation.
You can find both code below I'm using to test this.
I'd like to get more info about how I can use stream identifier 0 with NWMultiplexGroup, if I can't use it with NWMultiplexGroup, I need a workaround to use stream with id 0 and use multiple streams over the same connection.
import Foundation
import Network
let dispatchQueue = DispatchQueue(label: "quicConnectionQueue")
let incomingStreamQueue = DispatchQueue(label: "quicIncStreamsQueue")
let outgoingStreamQueue = DispatchQueue(label: "quicOutStreamsQueue")
let quicOptions = NWProtocolQUIC.Options()
quicOptions.alpn = ["test"]
sec_protocol_options_set_verify_block(quicOptions.securityProtocolOptions, { (sec_prot_metadata, sec_trust, complete_callback) in
complete_callback(true)
}, dispatchQueue)
let parameters = NWParameters(quic: quicOptions);
let multiplexGroup = NWMultiplexGroup(to: NWEndpoint.hostPort(host: "127.0.0.1", port: 5000))
let connectionGroup = NWConnectionGroup(with: multiplexGroup, using: parameters)
connectionGroup.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("Connected using QUIC!")
let _ = createNewStream(connGroup: connectionGroup, content: "First Stream")
let _ = createNewStream(connGroup: connectionGroup, content: "Second Stream")
break
default:
print("Default hit: newState: \(newState)")
}
}
connectionGroup.newConnectionHandler = { newConnection in
// Set state update handler on incoming stream
newConnection.stateUpdateHandler = { newState in
// Handle stream states
}
// Start the incoming stream
newConnection.start(queue: incomingStreamQueue)
}
connectionGroup.start(queue: dispatchQueue)
sleep(50)
func createNewStream(connGroup: NWConnectionGroup, content: String) -> NWConnection? {
let stream = NWConnection(from: connectionGroup)
stream?.stateUpdateHandler = { streamState in
switch streamState {
case .ready:
stream?.send(content: content.data(using: .ascii), completion: .contentProcessed({ error in
print("Send completed! Error: \(String(describing: error))")
}))
print("Sent data!")
printStreamId(stream: stream)
break
default:
print("Default hit: streamState: \(streamState)")
}
}
stream?.start(queue: outgoingStreamQueue)
return stream
}
func printStreamId(stream: NWConnection?)
{
let streamMetadata = stream?.metadata(definition: NWProtocolQUIC.definition) as? NWProtocolQUIC.Metadata
print("stream Identifier: \(String(describing: streamMetadata?.streamIdentifier))")
}
URLSession/URLRequest code:
import Foundation
var networkManager = NetworkManager()
networkManager.testHTTP3Request()
sleep(5)
class NetworkManager: NSObject, URLSessionDataDelegate {
private var session: URLSession!
private var operationQueue = OperationQueue()
func testHTTP3Request() {
if self.session == nil {
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalCacheData
self.session = URLSession(configuration: config, delegate: self, delegateQueue: operationQueue)
}
let urlStr = "https://localhost:5000"
let url = URL(string: urlStr)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
request.assumesHTTP3Capable = true
self.session.dataTask(with: request) { (data, response, error) in
if let error = error as NSError? {
print("task transport error \(error.domain) / \(error.code)")
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
print("task response is invalid")
return
}
guard 200 ..< 300 ~= response.statusCode else {
print("task response status code is invalid; received \(response.statusCode), but expected 2xx")
return
}
print("task finished with status \(response.statusCode), bytes \(data.count)")
}.resume()
}
}
extension NetworkManager {
func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
let protocols = metrics.transactionMetrics.map { $0.networkProtocolName ?? "-" }
print("protocols: \(protocols)")
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.serverTrust == nil {
completionHandler(.useCredential, nil)
} else {
let trust: SecTrust = challenge.protectionSpace.serverTrust!
let credential = URLCredential(trust: trust)
completionHandler(.useCredential, credential)
}
}
}
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 :)
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?
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