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.
271 lines
8.6 KiB
271 lines
8.6 KiB
// 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::ToSocketAddrs;
|
|
|
|
use ring::rand::*;
|
|
|
|
const MAX_DATAGRAM_SIZE: usize = 1350;
|
|
|
|
const HTTP_REQ_STREAM_ID: u64 = 4;
|
|
|
|
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(b"\x05hq-29\x05hq-28\x05hq-27\x08http/0.9")
|
|
.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_streams_bidi(100);
|
|
config.set_initial_max_streams_uni(100);
|
|
config.set_disable_active_migration(true);
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
// Send an HTTP request as soon as the connection is established.
|
|
if conn.is_established() && !req_sent {
|
|
info!("sending HTTP request for {}", url.path());
|
|
|
|
let req = format!("GET {}\r\n", url.path());
|
|
conn.stream_send(HTTP_REQ_STREAM_ID, req.as_bytes(), true)
|
|
.unwrap();
|
|
|
|
req_sent = true;
|
|
}
|
|
|
|
// Process all readable streams.
|
|
for s in conn.readable() {
|
|
while let Ok((read, fin)) = conn.stream_recv(s, &mut buf) {
|
|
debug!("received {} bytes", read);
|
|
|
|
let stream_buf = &buf[..read];
|
|
|
|
debug!(
|
|
"stream {} has {} bytes (fin? {})",
|
|
s,
|
|
stream_buf.len(),
|
|
fin
|
|
);
|
|
|
|
print!("{}", unsafe {
|
|
std::str::from_utf8_unchecked(&stream_buf)
|
|
});
|
|
|
|
// The server reported that it has no more data to send, which
|
|
// we got the full response. Close the connection.
|
|
if s == HTTP_REQ_STREAM_ID && fin {
|
|
info!(
|
|
"response received in {:?}, closing...",
|
|
req_start.elapsed()
|
|
);
|
|
|
|
conn.close(true, 0x00, b"kthxbye").unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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("")
|
|
}
|