// Copyright (C) 2019, Cloudflare, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #[macro_use] extern crate log; use std::net::ToSocketAddrs; use ring::rand::*; const MAX_DATAGRAM_SIZE: usize = 1350; fn main() { let mut buf = [0; 65535]; let mut out = [0; MAX_DATAGRAM_SIZE]; let mut args = std::env::args(); let cmd = &args.next().unwrap(); if args.len() != 1 { println!("Usage: {} URL", cmd); println!("\nSee tools/apps/ for more complete implementations."); return; } let url = url::Url::parse(&args.next().unwrap()).unwrap(); // Setup the event loop. let poll = mio::Poll::new().unwrap(); let mut events = mio::Events::with_capacity(1024); // Resolve server address. let peer_addr = url.to_socket_addrs().unwrap().next().unwrap(); // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the // server address. This is needed on macOS and BSD variants that don't // support binding to IN6ADDR_ANY for both v4 and v6. let bind_addr = match peer_addr { std::net::SocketAddr::V4(_) => "0.0.0.0:0", std::net::SocketAddr::V6(_) => "[::]:0", }; // Create the UDP socket backing the QUIC connection, and register it with // the event loop. let socket = std::net::UdpSocket::bind(bind_addr).unwrap(); socket.connect(peer_addr).unwrap(); let socket = mio::net::UdpSocket::from_socket(socket).unwrap(); poll.register( &socket, mio::Token(0), mio::Ready::readable(), mio::PollOpt::edge(), ) .unwrap(); // Create the configuration for the QUIC connection. let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); // *CAUTION*: this should not be set to `false` in production!!! config.verify_peer(false); config .set_application_protos(quiche::h3::APPLICATION_PROTOCOL) .unwrap(); config.set_max_idle_timeout(5000); config.set_max_udp_payload_size(MAX_DATAGRAM_SIZE as u64); config.set_initial_max_data(10_000_000); config.set_initial_max_stream_data_bidi_local(1_000_000); config.set_initial_max_stream_data_bidi_remote(1_000_000); config.set_initial_max_stream_data_uni(1_000_000); config.set_initial_max_streams_bidi(100); config.set_initial_max_streams_uni(100); config.set_disable_active_migration(true); let mut http3_conn = None; // Generate a random source connection ID for the connection. let mut scid = [0; quiche::MAX_CONN_ID_LEN]; SystemRandom::new().fill(&mut scid[..]).unwrap(); // Create a QUIC connection and initiate handshake. let mut conn = quiche::connect(url.domain(), &scid, &mut config).unwrap(); info!( "connecting to {:} from {:} with scid {}", peer_addr, socket.local_addr().unwrap(), hex_dump(&scid) ); let write = conn.send(&mut out).expect("initial send failed"); while let Err(e) = socket.send(&out[..write]) { if e.kind() == std::io::ErrorKind::WouldBlock { debug!("send() would block"); continue; } panic!("send() failed: {:?}", e); } debug!("written {}", write); let h3_config = quiche::h3::Config::new().unwrap(); // Prepare request. let mut path = String::from(url.path()); if let Some(query) = url.query() { path.push('?'); path.push_str(query); } let req = vec![ quiche::h3::Header::new(":method", "GET"), quiche::h3::Header::new(":scheme", url.scheme()), quiche::h3::Header::new(":authority", url.host_str().unwrap()), quiche::h3::Header::new(":path", &path), quiche::h3::Header::new("user-agent", "quiche"), ]; let req_start = std::time::Instant::now(); let mut req_sent = false; loop { poll.poll(&mut events, conn.timeout()).unwrap(); // Read incoming UDP packets from the socket and feed them to quiche, // until there are no more packets to read. 'read: loop { // If the event loop reported no events, it means that the timeout // has expired, so handle it without attempting to read packets. We // will then proceed with the send loop. if events.is_empty() { debug!("timed out"); conn.on_timeout(); break 'read; } let len = match socket.recv(&mut buf) { Ok(v) => v, Err(e) => { // There are no more UDP packets to read, so end the read // loop. if e.kind() == std::io::ErrorKind::WouldBlock { debug!("recv() would block"); break 'read; } panic!("recv() failed: {:?}", e); }, }; debug!("got {} bytes", len); // Process potentially coalesced packets. let read = match conn.recv(&mut buf[..len]) { Ok(v) => v, Err(e) => { error!("recv failed: {:?}", e); continue 'read; }, }; debug!("processed {} bytes", read); } debug!("done reading"); if conn.is_closed() { info!("connection closed, {:?}", conn.stats()); break; } // Create a new HTTP/3 connection once the QUIC connection is established. if conn.is_established() && http3_conn.is_none() { http3_conn = Some( quiche::h3::Connection::with_transport(&mut conn, &h3_config) .unwrap(), ); } // Send HTTP requests once the QUIC connection is established, and until // all requests have been sent. if let Some(h3_conn) = &mut http3_conn { if !req_sent { info!("sending HTTP request {:?}", req); h3_conn.send_request(&mut conn, &req, true).unwrap(); req_sent = true; } } if let Some(http3_conn) = &mut http3_conn { // Process HTTP/3 events. loop { match http3_conn.poll(&mut conn) { Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => { info!( "got response headers {:?} on stream id {}", list, stream_id ); }, Ok((stream_id, quiche::h3::Event::Data)) => { if let Ok(read) = http3_conn.recv_body(&mut conn, stream_id, &mut buf) { debug!( "got {} bytes of response data on stream {}", read, stream_id ); print!("{}", unsafe { std::str::from_utf8_unchecked(&buf[..read]) }); } }, Ok((_stream_id, quiche::h3::Event::Finished)) => { info!( "response received in {:?}, closing...", req_start.elapsed() ); conn.close(true, 0x00, b"kthxbye").unwrap(); }, Ok((_flow_id, quiche::h3::Event::Datagram)) => (), Ok((goaway_id, quiche::h3::Event::GoAway)) => { info!("GOAWAY id={}", goaway_id); }, Err(quiche::h3::Error::Done) => { break; }, Err(e) => { error!("HTTP/3 processing failed: {:?}", e); break; }, } } } // Generate outgoing QUIC packets and send them on the UDP socket, until // quiche reports that there are no more packets to be sent. loop { let write = match conn.send(&mut out) { Ok(v) => v, Err(quiche::Error::Done) => { debug!("done writing"); break; }, Err(e) => { error!("send failed: {:?}", e); conn.close(false, 0x1, b"fail").ok(); break; }, }; if let Err(e) = socket.send(&out[..write]) { if e.kind() == std::io::ErrorKind::WouldBlock { debug!("send() would block"); break; } panic!("send() failed: {:?}", e); } debug!("written {}", write); } if conn.is_closed() { info!("connection closed, {:?}", conn.stats()); break; } } } fn hex_dump(buf: &[u8]) -> String { let vec: Vec = buf.iter().map(|b| format!("{:02x}", b)).collect(); vec.join("") }