// Copyright (C) 2018-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; use std::collections::HashMap; use ring::rand::*; const MAX_DATAGRAM_SIZE: usize = 1350; struct PartialResponse { body: Vec, written: usize, } struct Client { conn: quiche::Connection, partial_responses: HashMap, } type ClientMap = HashMap, Client>; 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() != 0 { println!("Usage: {}", cmd); println!("\nSee tools/apps/ for more complete implementations."); return; } // Setup the event loop. let mut poll = mio::Poll::new().unwrap(); let mut events = mio::Events::with_capacity(1024); // Create the UDP listening socket, and register it with the event loop. let mut socket = mio::net::UdpSocket::bind("127.0.0.1:4433".parse().unwrap()).unwrap(); poll.registry() .register(&mut socket, mio::Token(0), mio::Interest::READABLE) .unwrap(); // Create the configuration for the QUIC connections. let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); config .load_cert_chain_from_pem_file("examples/cert.crt") .unwrap(); config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config .set_application_protos( b"\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", ) .unwrap(); config.set_max_idle_timeout(5000); config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE); config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE); 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); config.enable_early_data(); let rng = SystemRandom::new(); let conn_id_seed = ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap(); let mut clients = ClientMap::new(); loop { // Find the shorter timeout from all the active connections. // // TODO: use event loop that properly supports timers let timeout = clients.values().filter_map(|c| c.conn.timeout()).min(); poll.poll(&mut events, 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"); clients.values_mut().for_each(|c| c.conn.on_timeout()); break 'read; } let (len, from) = match socket.recv_from(&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); let pkt_buf = &mut buf[..len]; // Parse the QUIC packet's header. let hdr = match quiche::Header::from_slice( pkt_buf, quiche::MAX_CONN_ID_LEN, ) { Ok(v) => v, Err(e) => { error!("Parsing packet header failed: {:?}", e); continue 'read; }, }; trace!("got packet {:?}", hdr); let conn_id = ring::hmac::sign(&conn_id_seed, &hdr.dcid); let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN]; let conn_id = conn_id.to_vec().into(); // Lookup a connection based on the packet's connection ID. If there // is no connection matching, create a new one. let client = if !clients.contains_key(&hdr.dcid) && !clients.contains_key(&conn_id) { if hdr.ty != quiche::Type::Initial { error!("Packet is not Initial"); continue 'read; } if !quiche::version_is_supported(hdr.version) { warn!("Doing version negotiation"); let len = quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out) .unwrap(); let out = &out[..len]; if let Err(e) = socket.send_to(out, from) { if e.kind() == std::io::ErrorKind::WouldBlock { debug!("send() would block"); break; } panic!("send() failed: {:?}", e); } continue 'read; } let mut scid = [0; quiche::MAX_CONN_ID_LEN]; scid.copy_from_slice(&conn_id); let scid = quiche::ConnectionId::from_ref(&scid); // Token is always present in Initial packets. let token = hdr.token.as_ref().unwrap(); // Do stateless retry if the client didn't send a token. if token.is_empty() { warn!("Doing stateless retry"); let new_token = mint_token(&hdr, &from); let len = quiche::retry( &hdr.scid, &hdr.dcid, &scid, &new_token, hdr.version, &mut out, ) .unwrap(); let out = &out[..len]; if let Err(e) = socket.send_to(out, from) { if e.kind() == std::io::ErrorKind::WouldBlock { debug!("send() would block"); break; } panic!("send() failed: {:?}", e); } continue 'read; } let odcid = validate_token(&from, token); // The token was not valid, meaning the retry failed, so // drop the packet. if odcid.is_none() { error!("Invalid address validation token"); continue 'read; } if scid.len() != hdr.dcid.len() { error!("Invalid destination connection ID"); continue 'read; } // Reuse the source connection ID we sent in the Retry packet, // instead of changing it again. let scid = hdr.dcid.clone(); debug!("New connection: dcid={:?} scid={:?}", hdr.dcid, scid); let conn = quiche::accept(&scid, odcid.as_ref(), from, &mut config) .unwrap(); let client = Client { conn, partial_responses: HashMap::new(), }; clients.insert(scid.clone(), client); clients.get_mut(&scid).unwrap() } else { match clients.get_mut(&hdr.dcid) { Some(v) => v, None => clients.get_mut(&conn_id).unwrap(), } }; let recv_info = quiche::RecvInfo { from }; // Process potentially coalesced packets. let read = match client.conn.recv(pkt_buf, recv_info) { Ok(v) => v, Err(e) => { error!("{} recv failed: {:?}", client.conn.trace_id(), e); continue 'read; }, }; debug!("{} processed {} bytes", client.conn.trace_id(), read); if client.conn.is_in_early_data() || client.conn.is_established() { // Handle writable streams. for stream_id in client.conn.writable() { handle_writable(client, stream_id); } // Process all readable streams. for s in client.conn.readable() { while let Ok((read, fin)) = client.conn.stream_recv(s, &mut buf) { debug!( "{} received {} bytes", client.conn.trace_id(), read ); let stream_buf = &buf[..read]; debug!( "{} stream {} has {} bytes (fin? {})", client.conn.trace_id(), s, stream_buf.len(), fin ); handle_stream(client, s, stream_buf, "examples/root"); } } } } // Generate outgoing QUIC packets for all active connections and send // them on the UDP socket, until quiche reports that there are no more // packets to be sent. for client in clients.values_mut() { loop { let (write, send_info) = match client.conn.send(&mut out) { Ok(v) => v, Err(quiche::Error::Done) => { debug!("{} done writing", client.conn.trace_id()); break; }, Err(e) => { error!("{} send failed: {:?}", client.conn.trace_id(), e); client.conn.close(false, 0x1, b"fail").ok(); break; }, }; if let Err(e) = socket.send_to(&out[..write], send_info.to) { if e.kind() == std::io::ErrorKind::WouldBlock { debug!("send() would block"); break; } panic!("send() failed: {:?}", e); } debug!("{} written {} bytes", client.conn.trace_id(), write); } } // Garbage collect closed connections. clients.retain(|_, ref mut c| { debug!("Collecting garbage"); if c.conn.is_closed() { info!( "{} connection collected {:?}", c.conn.trace_id(), c.conn.stats() ); } !c.conn.is_closed() }); } } /// Generate a stateless retry token. /// /// The token includes the static string `"quiche"` followed by the IP address /// of the client and by the original destination connection ID generated by the /// client. /// /// Note that this function is only an example and doesn't do any cryptographic /// authenticate of the token. *It should not be used in production system*. fn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec { let mut token = Vec::new(); token.extend_from_slice(b"quiche"); let addr = match src.ip() { std::net::IpAddr::V4(a) => a.octets().to_vec(), std::net::IpAddr::V6(a) => a.octets().to_vec(), }; token.extend_from_slice(&addr); token.extend_from_slice(&hdr.dcid); token } /// Validates a stateless retry token. /// /// This checks that the ticket includes the `"quiche"` static string, and that /// the client IP address matches the address stored in the ticket. /// /// Note that this function is only an example and doesn't do any cryptographic /// authenticate of the token. *It should not be used in production system*. fn validate_token<'a>( src: &net::SocketAddr, token: &'a [u8], ) -> Option> { if token.len() < 6 { return None; } if &token[..6] != b"quiche" { return None; } let token = &token[6..]; let addr = match src.ip() { std::net::IpAddr::V4(a) => a.octets().to_vec(), std::net::IpAddr::V6(a) => a.octets().to_vec(), }; if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() { return None; } Some(quiche::ConnectionId::from_ref(&token[addr.len()..])) } /// Handles incoming HTTP/0.9 requests. fn handle_stream(client: &mut Client, stream_id: u64, buf: &[u8], root: &str) { let conn = &mut client.conn; if buf.len() > 4 && &buf[..4] == b"GET " { let uri = &buf[4..buf.len()]; let uri = String::from_utf8(uri.to_vec()).unwrap(); let uri = String::from(uri.lines().next().unwrap()); let uri = std::path::Path::new(&uri); let mut path = std::path::PathBuf::from(root); for c in uri.components() { if let std::path::Component::Normal(v) = c { path.push(v) } } info!( "{} got GET request for {:?} on stream {}", conn.trace_id(), path, stream_id ); let body = std::fs::read(path.as_path()) .unwrap_or_else(|_| b"Not Found!\r\n".to_vec()); info!( "{} sending response of size {} on stream {}", conn.trace_id(), body.len(), stream_id ); let written = match conn.stream_send(stream_id, &body, true) { Ok(v) => v, Err(quiche::Error::Done) => 0, Err(e) => { error!("{} stream send failed {:?}", conn.trace_id(), e); return; }, }; if written < body.len() { let response = PartialResponse { body, written }; client.partial_responses.insert(stream_id, response); } } } /// Handles newly writable streams. fn handle_writable(client: &mut Client, stream_id: u64) { let conn = &mut client.conn; debug!("{} stream {} is writable", conn.trace_id(), stream_id); if !client.partial_responses.contains_key(&stream_id) { return; } let resp = client.partial_responses.get_mut(&stream_id).unwrap(); let body = &resp.body[resp.written..]; let written = match conn.stream_send(stream_id, body, true) { Ok(v) => v, Err(quiche::Error::Done) => 0, Err(e) => { client.partial_responses.remove(&stream_id); error!("{} stream send failed {:?}", conn.trace_id(), e); return; }, }; resp.written += written; if resp.written == resp.body.len() { client.partial_responses.remove(&stream_id); } }