cloudflare / quiche

🥧 Savoury implementation of the QUIC transport protocol and HTTP/3
https://docs.quic.tech/quiche/
BSD 2-Clause "Simplified" License
9.4k stars 709 forks source link

Client stall if application close during handshake #1433

Open BiagioFesta opened 1 year ago

BiagioFesta commented 1 year ago

Similar to https://github.com/cloudflare/quiche/issues/1118

If the client performs an app-close before sending the ACK on server-HANDSHAKE, the client seems to stall.

Note the connection.close() is invoked (from the client point of view) after handshake is completed (that is, is_established() == true).

Step to reproduce

1. Modify example-client Set app-close as soon as `is_established`, but before flush egress (`send`) ``` --- a/quiche/examples/client.rs +++ b/quiche/examples/client.rs @@ -198,6 +198,10 @@ fn main() { // Send an HTTP request as soon as the connection is established. if conn.is_established() && !req_sent { + if conn.local_error().is_none() { + conn.close(true, 0, b"bye").unwrap(); + } + info!("sending HTTP request for {}", url.path()); let req = format!("GET {}\r\n", url.path()); ```
2. Analyze traffic ``` 8.374214809 Client -> Server QUIC 1242 Initial, DCID=877468521020d228e9f79e5cb03bd298, SCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, PKN: 0, CRYPTO 8.374273705 Server-> Client QUIC 131 Retry, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128 8.374509771 Client -> Server QUIC 1242 Initial, DCID=c23440bd880a991669e36e9e7da9f09588f6e128, SCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, PKN: 1, CRYPTO 8.376257575 Server-> Client QUIC 1242 Handshake, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128 8.376264956 Server-> Client QUIC 353 Handshake, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128 /// Client here should reply with ACKs + CRYPTO finish + Connection Close 9.376485690 Server-> Client QUIC 1242 Initial, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128, PKN: 1, ACK, CRYPTO 10.375731458 Server-> Client QUIC 1242 Handshake, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128 10.375781019 Server-> Client QUIC 186 Handshake, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128 13.373703361 Server-> Client QUIC 1242 Initial, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128, PKN: 2, ACK, CRYPTO 13.373801916 Server-> Client QUIC 1242 Initial, DCID=1920bdcce00b65ca1c1da20f38a516cf4dd44d12, SCID=c23440bd880a991669e36e9e7da9f09588f6e128, PKN: 3, ACK, PING ```

App-Close cannot be sent on HANDSHAKE epoch, but because progress is inhibited by local_error in send_single.

ljluestc commented 1 month ago
use quiche::Connection;
use std::net::ToSocketAddrs;
use std::time::Duration;

fn main() {
    // Initialization code...

    let mut conn: Connection = // ...initialize your connection

    loop {
        // Poll and handle events...

        if conn.is_established() && !req_sent {
            // Ensure that the connection is properly closed before application exit
            if conn.local_error().is_none() {
                // Send close signal if the connection is established
                if let Err(e) = conn.close(true, 0, b"bye") {
                    eprintln!("Failed to close connection: {}", e);
                }
            }

            // Send HTTP request...
            info!("sending HTTP request for {}", url.path());
            let req = format!("GET {}\r\n", url.path());
            // Send the request...
        }

        // Other code to handle traffic and timeouts...

        // Periodically check and clean up the connection
        if conn.is_closed() {
            break;
        }
    }
}