Establish a peer to peer QUIC connection

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)
//...
Answered by DTS Engineer in 827620022

I just replied to a very similar question on another thread. Have a read of that and then post back if you have further questions.

Oh, one more thing: For the initial bringup, use a DNS name endpoint rather than a Bonjour endpoint. I have less experience with the relationship between QUIC and Bonjour endpoints. It should work, but I’ve not tested in personally and I’ve seen vague reports of some weird oddities. That’s not a showstopper, but it is an additional complexity that you don’t want to deal with while you’re trying to sort out the basics.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I just replied to a very similar question on another thread. Have a read of that and then post back if you have further questions.

Oh, one more thing: For the initial bringup, use a DNS name endpoint rather than a Bonjour endpoint. I have less experience with the relationship between QUIC and Bonjour endpoints. It should work, but I’ve not tested in personally and I’ve seen vague reports of some weird oddities. That’s not a showstopper, but it is an additional complexity that you don’t want to deal with while you’re trying to sort out the basics.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn, thanks for replying. I have used the sec_protocol_options_set_verify_block method form the post you pointed out. When using localhost (on my iPad) the closure is called and the certificates get compared correctly. Thanks for this input.

Unfortunately I am still having another problem which might have something to do with your "one more thing" but sadly I don't quite get what you mean or I may have not described my situation precise enough.

I am trying to use AWDL between my iPhone and my iPad. Since Bonjour can only advertise TCP and UDP services, I chose to hardcode a port number into my application where the QUIC Service is listening. When connecting to my iPad I try to rely on the DNS cache the Bonjour service figured out and call NWConnection(host: "iPad.local.", port: Config.quicPort, using: transportProtocol.parameters) so I am not using Bonjour directly with QUIC in my understanding and it doesn't throw a DNS error so I assumed it works. Unfortunately it is still stuck in preparing state and the sec_protocol_options_set_verify_block closure is not even called on the iPhone.

So what do you mean with DNS name endpoint? Can I approach this differently? How can I connect to the iPad service without being able to advertise it?

I assume sending the IP address and port number of the iPads QUIC listener (without the hard coded port number, let the OS allocate a port) via the TCP connection to let the iPhone know the address of the service would work, but I would like to save this additional effort if it works in another way.

Hi Quinn, so I got the application to successfully establish the QUIC connection via AWDL from iPhone to iPad, thank you for your time and input and for pointing me into the right direction.

I used the POSIX getifaddrs call to get the IP addresses of the iPad and printed them. Then I put the IPv6 address of the awdl0 interface into the client. It's needless to say that this solution is technically not beautiful. Any ideas on how to improve this?

Establish a peer to peer QUIC connection
 
 
Q