Skip to content

tokio-quiche doesn't propagate handshake errors #2274

@ronhombre

Description

@ronhombre

When running InitialQuicConnection<UdpSocket, M>.start(driver) the handshake and resume is completed within. However, in my testing where mTLS fails due to incorrect host names (doesn't match Common Name in TLS Cert), the .start(driver) silently fails and what happens is my app assumes the quic handshake succeeded but it actually failed so it initializes the app logic and runs the tasks in them since there is no way to know if the handshake failed or not (to my knowledge).

To fix the issue, I implemented this in my app

async fn start_connection<M>
(
    initial_quic_connection: InitialQuicConnection<UdpSocket, M>,
    driver: MyDriver
) -> io::Result<QuicConnection>
where
    M: Metrics
{
    let (q_conn, handshake) = initial_quic_connection.handshake(driver).await?;

    metrics::tokio_task::spawn_with_killswitch(
        "quic_handshake_worker",
        DefaultMetrics, //can be cloned if used within the crate
        async move {
            InitialQuicConnection::<UdpSocket, M>::resume(handshake)
        }
    );

    Ok(q_conn)
}

which is taken from

 pub fn start<A: ApplicationOverQuic>(self, app: A) -> QuicConnection {
    let task_metrics = self.params.metrics.clone();
    let (conn, handshake_fut) = Self::handshake_fut(self, app);

    let fut = async move {
        match handshake_fut.await {
            Ok(running) => Self::resume(running),
            Err(e) => {
                log::error!("QUIC handshake failed in IQC::start"; "error" => e)
            },
        }
    };

    crate::metrics::tokio_task::spawn_with_killswitch(
        "quic_handshake_worker",
        task_metrics,
        fut,
    );

    conn
}

Why don't we create a new start function to support returning the result?

pub async fn start_with_result<A: ApplicationOverQuic>(self, app: A) -> io::Result<QuicConnection> {
    let task_metrics = self.params.metrics.clone();
    let result = self.handshake(app).await;

    match result {
        Ok((q_conn, handshake)) => {
            crate::metrics::tokio_task::spawn_with_killswitch(
                "quic_handshake_worker",
                task_metrics,
                async move {
                    Self::resume(handshake)
                }
            );

            Ok(q_conn)
        },
        Err(err) => {
            log::error!("QUIC handshake failed in IQC::start_with_result"; "error" => &err);
            Err(err) // Pass it upward
        }
    }
}

This would propagate errors upward which can be handled by the user instead being silently logged while also maintaining the same logging behavior as start().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions