You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
10 KiB
323 lines
10 KiB
// 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<String> = buf.iter().map(|b| format!("{:02x}", b)).collect();
|
|
|
|
vec.join("")
|
|
}
|