Create network connections to send and receive data using the QUIC protocol.

Quic Documentation

Posts under QUIC tag

14 Posts
Sort by:
Post not yet marked as solved
1 Replies
247 Views
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?
Posted
by MinchoPM.
Last updated
.
Post not yet marked as solved
1 Replies
572 Views
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.
Posted
by palfonso.
Last updated
.
Post not yet marked as solved
0 Replies
224 Views
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
Posted
by JaWo.
Last updated
.
Post not yet marked as solved
3 Replies
750 Views
I would like to use NWProtocolQUIC in Swift's Network.framework to prepare multiple QUIC Streams and send different data to the server for each. class QuicConnection { var acceptConnection: NWConnection? var openConnection: NWConnection? var acceptConnectionState: NWConnection.State? var openConnectionState: NWConnection.State? var receiveFromAcceptConnection: String? static func makeNWParameters() -> NWParameters { let options = NWProtocolQUIC.Options(alpn: ["echo"]) options.direction = .bidirectional let securityProtocolOptions: sec_protocol_options_t = options.securityProtocolOptions sec_protocol_options_set_verify_block(securityProtocolOptions, { (_: sec_protocol_metadata_t, _: sec_trust_t, complete: @escaping sec_protocol_verify_complete_t) in complete(true) }, DispatchQueue.main) return NWParameters(quic: options) } let group: NWConnectionGroup init() { print("init") let parameters = Self.makeNWParameters() let descriptor = NWMultiplexGroup(to: .hostPort(host: "192.168.0.20", port: 4242)) group = NWConnectionGroup(with: descriptor, using: parameters) //subscribe() group.stateUpdateHandler = { (state: NWConnectionGroup.State) in print("state: \(state)") switch state { case .ready: print("quic connected!") default: break } } group.newConnectionHandler = { [weak self] (connection: NWConnection) in print("new connection: \(connection)") self?.acceptConnection = connection self?.acceptConnection?.stateUpdateHandler = { [weak self] (state: NWConnection.State) in self?.acceptConnectionState = state } self?.subscribeAcceptConnection() self?.acceptConnection?.start(queue: DispatchQueue.main) } group.start(queue: DispatchQueue.main) } func createStream() { //guard let group else { return } let options = NWProtocolQUIC.Options() options.direction = .bidirectional let securityProtocolOptions: sec_protocol_options_t = options.securityProtocolOptions sec_protocol_options_set_verify_block(securityProtocolOptions, { (_: sec_protocol_metadata_t, _: sec_trust_t, complete: @escaping sec_protocol_verify_complete_t) in complete(true) // Insecure !!! }, DispatchQueue.main) openConnection = NWConnection(from: group) openConnectionState = openConnection?.state openConnection?.stateUpdateHandler = { [weak self] (state: NWConnection.State) in self?.openConnectionState = state print("state: \(state)") switch state { case .ready: print("stream connected!") DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self?.send(message: "marker1") } default: break } } openConnection?.start(queue: DispatchQueue.main) } func send(message: String) { print("send start") let completion: NWConnection.SendCompletion = .contentProcessed { (error: Error?) in if let error = error { print("send error: \(error)") } else { print("send successful") } } openConnection?.send(content: message.data(using: .utf8)!, contentContext: .defaultMessage, isComplete: true, completion: completion) print("message: \(message)") } } When the app starts, it calls the init function of this QuicConnection class to create an instance and build the QUIC tunnel." quic connected" log appears, and when a specific action is performed in the app, the createStream function is called to put up a stream." stream connected" log is displayed, but when I then try to send data using the send function, the "send successful" message is displayed, but there is no output on the server side indicating that the message was received from the client. However, when I try to send data using the send function after that, I get a "send successful" message. I don't think there is a problem on the server side, because I can communicate well when only NWConnection is used without NQConnectionGroup. The server is using quic-go. I would like to borrow knowledge from those who have handled QUIC streams in Swift. Below are Apple's announcement and official documents that I referred to. I wrote the code referring to these, but even though I can connect the QUIC tunnels, I can not send data by setting up individual streams. https://developer.apple.com/videos/play/wwdc2021/10094/ https://developer.apple.com/documentation/network/nwprotocolquic
Posted
by takt.
Last updated
.
Post marked as solved
8 Replies
587 Views
Hey all here is an example you can try out: https://github.com/paxsonsa/quic-swift-demo I am prototype a QUIC base application system with a client and server. My server is a simple test to experiment with QUIC and Network Framework but I am see some odd behaviour. Selecting Stream Direction for new streams In the example below, we are creating a new multiplexed QUIC connection and establish a new stream once the group connection is ready. In some cases, I want to be able to use a different stream kind (uni/bi). By specifying the options, I get an error in Xcode console like so: running.... group state: waiting(POSIXErrorCode(rawValue: 50): Network is down) group state: ready Connected using QUIC! nw_endpoint_flow_setup_cloned_protocols [C3 127.0.0.1:4567 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: lo0)] could not find protocol to join in existing protocol stack nw_endpoint_flow_failed_with_error [C3 127.0.0.1:4567 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: lo0)] failed to clone from flow, moving directly to failed state Main Connection State: failed(POSIXErrorCode(rawValue: 50): Network is down) quic_recovery_pto PTO fired after validation Here is my swift code: // // main.swift // QuicTool // // Created by Andrew Paxson on 2024-01-14. // import Foundation import Network /// Helper function to create a message frame. func createMessage(version: UInt8, messageType: UInt8, message: String) -> Data { let messageData = message.data(using: .utf8) ?? Data() let length = UInt32(messageData.count) var data = Data() data.append(version) data.append(messageType) // Convert length to 4 bytes and append (big-endian format) let bigEndianLength = length.bigEndian data.append(contentsOf: withUnsafeBytes(of: bigEndianLength) { Array($0) }) // Append 2 bytes of padding for 8-byte alignment data.append(Data(repeating: 0, count: 2)) // Add Message Data. data.append(messageData) return data } // Queue for QUIC things. let queue = DispatchQueue(label: "quic", qos: .userInteractive) // Create Inital Options for the tunnel. // This is using an insecure connection as this operation is meant to be local network. let endpoint = NWEndpoint.hostPort(host: "127.0.0.1", port: .init(integerLiteral: 4567)) let options = NWProtocolQUIC.Options(alpn: ["demo"]) // Set the initial stream to bidirectional. options.direction = .bidirectional sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in sec_protocol_verify_complete(true) }, queue) let parameters = NWParameters(quic: options) // 1) Create a new multiplexed connection let descriptor = NWMultiplexGroup(to: endpoint) let group = NWConnectionGroup(with: descriptor, using: parameters) var mainConn: NWConnection? = nil // Here we are establishing a state handler for when the connection to the // the server is neogiated and "ready". Once its ready we want to establish a // stream using the group with the options set. // // This is the main location of the issue we are seeing where the stream is // established and the data is sent but never updated. group.stateUpdateHandler = { newState in print("group state: \(newState)") switch newState { // Once the tunnel is established, create a new stream with bidirectional parameters. case .ready: print("Connected using QUIC!") // 2) In normal application I may want to open different kinds of streams in providing // new options. Is there a better way to select the stream kind for subsequent streams? let options = NWProtocolQUIC.Options(alpn: ["demo"]) options.direction = .bidirectional // When providing unique options the stream will fail. Removeing the using argument works. mainConn = group.extract()! // force unwrap mainConn?.stateUpdateHandler = { state in print("Main Connection State: \(state)") switch state { case .ready: // Once the connection is ready, lets send some sweet data sauce. // // By establishing this new stream and sending data, on the server this causes the inital // stream with no handle to be open. let version: UInt8 = 1 let messageType: UInt8 = 1 let message = "hello, I am from the multiplex group ready." let messageData = createMessage(version: version, messageType: messageType, message: message) mainConn?.send(content: messageData, isComplete: true, completion: .contentProcessed({ sendError in if let error = sendError { print("There was an error sending data: \(error)") } else { print("Data was sent successfully from Main Connection.") } })) default: break } } // Don't forget to start the connection. mainConn?.start(queue: queue) default: break } } // Receive new incoming streams initiated by the remote endpoint // this is not used for this example. group.newConnectionHandler = { conn in print("New Connection: \(conn)") // Set state update handler on incoming stream conn.stateUpdateHandler = { newState in print("newState: \(newState) for \(conn)") switch newState { case .ready: print("got a new stream!") default: break } } // Start the incoming stream conn.start(queue: queue) } // Start the group with callback queue group.start(queue: queue) print("running....") // We iterate trying to send data on the new stream we created after the // connection is established. while true { switch mainConn?.state { case .ready: // Once the connection is ready, lets send some sweet data sauce. let version: UInt8 = 1 let messageType: UInt8 = 1 let message = "hello, im from the main loop" let messageData = createMessage(version: version, messageType: messageType, message: message) print("Local Stream Send: \(messageData)") mainConn?.send(content: messageData, completion: .contentProcessed({ sendError in if let error = sendError { print("There was an error sending data: \(error)") } })) sleep(1) default: continue } }
Posted
by apaxson.
Last updated
.
Post not yet marked as solved
0 Replies
333 Views
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?
Posted
by nhdev.
Last updated
.
Post not yet marked as solved
1 Replies
427 Views
Hello, I noticed that Facetime attempts to use the QUIC protocol during a Facetime session with the following ports 3478 through 3497 (UDP). Can Facetime use the QUIC protocol for the following ports 3478 through 3497 (UDP) because on the internet I have not found anywhere the possibility that QUIC can be used other than ports 80 and 443?
Posted Last updated
.
Post marked as Apple Recommended
2.6k Views
A large number of crashes occurred only on iOS 17.0 online from July 27, which is consistent with the release time of the 17.0 beta4 version. I suspect something is wrong with 17.0 beta4. Does anyone have the same problem and is there a solution? Thanks a lot Crashed: com.apple.CFNetwork.Connection 0 libquic.dylib 0x21120 quic_recovery_declare_packets_lost + 928 1 libquic.dylib 0x20788 quic_recovery_find_lost_packet_inner + 1272 2 libquic.dylib 0x1e564 quic_recovery_find_lost_packets + 352 3 libquic.dylib 0x11b74 quic_recovery_received_ack + 1180 4 libquic.dylib 0x52634 quic_frame_process_ACK + 368 5 libquic.dylib 0xb3bc8 quic_conn_process_frame + 964 6 libquic.dylib 0xb0370 quic_conn_process_inbound + 1840 7 Network 0x323c40 nw_protocol_data_access_buffer + 840 8 libquic.dylib 0xb6b48 __quic_conn_handle_inbound_block_invoke + 168 9 libquic.dylib 0xb690c quic_conn_handle_inbound + 124 10 Network 0x3102ac __nw_protocol_implementation_get_input_internal_block_invoke + 136 11 Network 0x30f8d4 nw_protocol_implementation_read + 408 12 Network 0x30f128 nw_protocol_implementation_input_available + 96 13 Network 0x1f5c4 nw_channel_update_input_source(nw_channel*, nw_protocol*, bool) + 7024 14 Network 0x2d880 nw_channel_get_input_frames(nw_protocol*, nw_protocol*, unsigned int, unsigned int, unsigned int, nw_frame_array_s*) + 124 15 Network 0x911ac0 nw_protocol_ipv6_get_input_frames(nw_protocol*, nw_protocol*, unsigned int, unsigned int, unsigned int, nw_frame_array_s*) + 268 16 Network 0x1da8a4 nw_protocol_udp_get_input_frames(nw_protocol*, nw_protocol*, unsigned int, unsigned int, unsigned int, nw_frame_array_s*) + 208 17 Network 0x30f868 nw_protocol_implementation_read + 300 18 Network 0x3153e4 nw_protocol_implementation_get_input_frames + 148 19 Network 0x3572e0 nw_flow_service_reads(NWConcrete_nw_endpoint_handler*, NWConcrete_nw_endpoint_flow*, nw_flow_protocol*, bool) + 992 20 Network 0x363ef0 __nw_endpoint_handler_add_read_request_block_invoke + 452 21 Network 0x7ba20 nw_hash_table_apply + 2696 22 Network 0x34d84 nw_endpoint_handler_add_read_request + 1688 23 Network 0x34848 nw_endpoint_handler_add_read_request + 348 24 Network 0x936c nw_connection_add_read_request_on_queue + 232 25 Network 0x9204 nw_connection_add_read_request + 344 26 Network 0x8cc4 nw_connection_receive_internal + 140 27 CFNetwork 0xa7620 CFURLDownloadStart + 66020 28 CFNetwork 0xe194c _CFStreamErrorFromCFError + 24656 29 CFNetwork 0x101918 _CFStreamErrorFromCFError + 155676 30 CFNetwork 0xfe2d8 _CFStreamErrorFromCFError + 141788 31 CFNetwork 0x1008dc _CFStreamErrorFromCFError + 151520 32 CFNetwork 0x1344d4 _CFStreamErrorFromCFError + 363480 33 CFNetwork 0xa73c8 CFURLDownloadStart + 65420 34 CFNetwork 0xb68c0 CFURLDownloadStart + 128132 35 CFNetwork 0x135c5c _CFStreamErrorFromCFError + 369504 36 CFNetwork 0x34f18 CFHTTPMessageCopySerializedMessage + 47008 37 CFNetwork 0x1e8180 CFHTTPCookieStorageUnscheduleFromRunLoop + 225244 38 CFNetwork 0x13585c _CFStreamErrorFromCFError + 368480 39 CFNetwork 0xb4d9c CFURLDownloadStart + 121184 40 libdispatch.dylib 0x13250 _dispatch_block_async_invoke2 + 148 41 libdispatch.dylib 0x4300 _dispatch_client_callout + 20 42 libdispatch.dylib 0xb894 _dispatch_lane_serial_drain + 748 43 libdispatch.dylib 0xc3f8 _dispatch_lane_invoke + 432 44 libdispatch.dylib 0xd6a8 _dispatch_workloop_invoke + 1756 45 libdispatch.dylib 0x17004 _dispatch_root_queue_drain_deferred_wlh + 288 46 libdispatch.dylib 0x16878 _dispatch_workloop_worker_thread + 404 47 libsystem_pthread.dylib 0x1964 _pthread_wqthread + 288 48 libsystem_pthread.dylib 0x1a04 start_wqthread + 8
Posted Last updated
.
Post marked as solved
11 Replies
2.1k Views
When debugging a tvOS application that calls Data(contentsOf: URL) I get an error: nw_protocol_get_quic_image_block_invoke dlopen libquic failed: dlopen(/usr/lib/libquic.dylib, 0x0005): tried: [...a number of folders it tried to find the file in...]. I found similar problems here (https://developer.apple.com/forums/thread/693245), but it is not equal, nor does it have a solution. Any suggestions on how to fix this? I also tried to copy a libquic.tbd file present in other simulators to one of the paths it looks for (a suggestion somewhere on StackOverflow), but it does not help unfortunately.
Posted
by Kobes.
Last updated
.
Post not yet marked as solved
3 Replies
827 Views
Hi, I have run the system h3 on iphone OS 16.6 by learning this:TN3102: HTTP/3 in your app | Apple Developer Documentation; However, I have found that the request iOS sending not support zero rtt. When using chrome to request(h3), it‘s doing zero rtt successfully. So, may I ask if there any plan to support zero-rtt in http/3 ? PS.The UA:CFNetwork/1410.0.3 Darwin/22.6.0
Posted
by tankui.
Last updated
.
Post not yet marked as solved
4 Replies
810 Views
I'm trying to upload large files in a background URLSession to a server that accepts HTTP/3 requests. Per its design, the server uses a self-signed SSL certificate. In my testing, I can create a regular URLSession (foreground) with the following: var request = URLRequest(url: dst, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 6.0) request.assumesHTTP3Capable = true request.httpMethod = "POST" self.session.dataTask(with: request) { <snip> } And handles the SSL certificate challenge by implementing the following for the delegate: func urlSession( _ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) This approach works and I can communicate with the server. But when I try to apply the same for a background session for an upload task, the task seems to be stuck, i.e. never started by the system. More specifically, the background session upload task works with iOS simulator. But on a real device connected to macOS, the task never started, and none of the delegate methods are called. The http/3 server didn't receive any request either. The background session was created: let appBundleName = Bundle.main.bundleURL.lastPathComponent.lowercased().replacingOccurrences(of: " ", with: ".") let sessionIdentifier: String = "com.networking.\(appBundleName)" let config = URLSessionConfiguration.background(withIdentifier: sessionIdentifier) self.session = URLSession(configuration: config, delegate: self, delegateQueue: nil) later, an upload task was created : var request = URLRequest(url: dst, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 6.0) request.assumesHTTP3Capable = true request.httpMethod = "POST" let backgroundTask = self.session.uploadTask(with: request, fromFile: src) backgroundTask.resume() My question(s): why is the background session upload task stuck? Is there a way to find out where it is stuck? Does a background URLSession support app's delegate handling SSL certificate challenge? The test env: Xcode 14.2 macOS 12.6.7 iOS 15.7.7 Thanks!
Posted Last updated
.
Post not yet marked as solved
2 Replies
781 Views
My team are now testing http/3 support in our app and backend. It looks strange to me how the behaviour is different between iOS 15.7.3 and 16.x (now 16.6). It is a default URLSession config and just a regular URLRequest, with no assumesHTTP3Capable flag set. I observe the protocol in use with both collecting URLSessionTaskMetrics and the Network instrument. On 15.7.3 the 1st request is done using h2, the network subsystem caches the Alt-Svc header (per host I suppose). Several seconds pass and then when I issue the same 2nd request a new connection is established and the protocol is upgraded to h3. This is all fine and as described in the documentation or wwdc videos. However, when I take the same steps with a device running e.g. iOS 16.5.1 I see that the protocol is never upgraded to h3. Neither a longer timeout nor an app relaunch make any difference. Interestingly, on my Ventura desktop the same url is also handled differently: h3 by Chrome but always h2 by Safari. In the mobile Safari on iOS 16, I'm also always shown HTTP/2 on e.g. the cloudflare status page for QUIC. What can be the reason for such behaviour on iOS 16 and Apple platforms in general? PS. I've tried running the app with CFNETWORK_DIAGNOSTICS but found no useful log messages to clarify problems with QUIC. Is there still a way to better diagnose such a problem?
Posted
by appaul.
Last updated
.
Post not yet marked as solved
4 Replies
862 Views
In an iOS network extension setup as a Packet Tunnel. I am accepting packets in using NEPacketTunnelFlow and open a NWConnection up to the destination like so let parameters = .quic(alpn: []) let connection = NWConnection(to: .hostPort(host: host, port: port!), using: parameters) but my state handler is never receiving the .ready state. My NWConnection code works fine for TCP and DNS using let parameters = .tcp // OR let parameters = .udp On my NWConnection state handler I am receiving .waiting with an error of dns. I am testing quic by navigating onto the Facebook app and with wireshark intercepting the traffic I can see the DNS queries resolving I have NEDNSSettings applied to my NEPacketTunnelNetworkSettings Is my issue to do with not passing in the alpn value or is there an extra permission/setting I am missing? I have not found a value which changes the outcome at all. I have tried following [https://developer.apple.com/videos/play/wwdc2021/10094/) but no success.
Posted
by rmus18.
Last updated
.