From 749288318434fd9908a22e85909d697e924b02f5 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 17 Nov 2019 13:32:48 -0500 Subject: [PATCH 001/202] Add libp2p-transport-quic crate --- Cargo.toml | 3 ++- transports/quic/Cargo.toml | 9 +++++++++ transports/quic/src/lib.rs | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 transports/quic/Cargo.toml create mode 100644 transports/quic/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 596325e6cae..1b55386f256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ members = [ "transports/tcp", "transports/uds", "transports/websocket", - "transports/wasm-ext" + "transports/wasm-ext", + "transports/quic", ] diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml new file mode 100644 index 00000000000..713b81781f4 --- /dev/null +++ b/transports/quic/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "libp2p-transport-quic" +version = "0.13.2" +authors = ["Parity Technologies "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs new file mode 100644 index 00000000000..31e1bb209f9 --- /dev/null +++ b/transports/quic/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} From 1f98ed92e55113ac4eb8a44a001da541f7b7f627 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 19 Nov 2019 13:10:59 -0500 Subject: [PATCH 002/202] Add copyright notice and beginnings of a transport --- transports/quic/Cargo.toml | 21 ++++++++++++++ transports/quic/src/lib.rs | 56 ++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 713b81781f4..86cc7de3ae0 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,3 +1,22 @@ +# Copyright 2017-2018 Parity Technologies (UK) Ltd. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. [package] name = "libp2p-transport-quic" version = "0.13.2" @@ -7,3 +26,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +quinn-proto = "0.4.0" +rustls = { version = "0.16.0", features = ["dangerous_configuration"] } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 31e1bb209f9..eda91a05731 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -1,7 +1,51 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! QUIC transport for *libp2p* +use quinn_proto::{Connection, Transmit}; +use std::{net::{UdpSocket, addr::SocketAddr}, io::ErrorKind, time::Instant}; +use futures::prelude::*; + +struct QuicTransport { + connection: Connection, + socket: UdpSocket, + transmit: Option, + offset: usize, +} + +impl QuicTransport { + fn poll_transmit(&mut self) -> Result<(), std::io::Error> { + self.transmit = match self.transmit.or_else(|| self.connection.poll_transmit(Instant::now())) { + Some(s) => s, + None => return Ok(()), + }; + while self.transmit.contents.len() > self.offest { + match self.socket.send_to(contents[self.offset..], destination) { + Ok(len) => self.offset += len, + Err(e) => match e.kind() { + ErrorKind::Interrupted => continue, + ErrorKind::WouldBlock => unimplemented!("figure out what to wake!"), + _ => return Err(e), + }, + } + } + Ok(()) + } } From 52b861451b30d312e522fa627240ef5669388eee Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 20 Nov 2019 19:52:46 -0500 Subject: [PATCH 003/202] Bogus implementation that at least compiles --- transports/quic/Cargo.toml | 11 +- transports/quic/src/lib.rs | 513 +++++++++++++++++++++++++++++++++++-- 2 files changed, 495 insertions(+), 29 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 86cc7de3ae0..73c32b4b18c 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -18,7 +18,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. [package] -name = "libp2p-transport-quic" +name = "libp2p-quic" version = "0.13.2" authors = ["Parity Technologies "] edition = "2018" @@ -28,3 +28,12 @@ edition = "2018" [dependencies] quinn-proto = "0.4.0" rustls = { version = "0.16.0", features = ["dangerous_configuration"] } +futures = { version = "0.3.1", features = ["compat"] } +libp2p-core = { path = "../../core", version = "0.13.1" } +log = "0.4.8" +tokio-io = "0.1.12" +quinn = "0.4.0" +tokio = "0.1.22" +get_if_addrs = "0.5.3" +ipnet = "2.1.0" +err-derive = "0.2.1" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index eda91a05731..3d6afda8ea9 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -18,34 +18,491 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! QUIC transport for *libp2p* -use quinn_proto::{Connection, Transmit}; -use std::{net::{UdpSocket, addr::SocketAddr}, io::ErrorKind, time::Instant}; -use futures::prelude::*; +//! Implementation of the libp2p `Transport` trait for QUIC/UDP/IP. +//! +//! Uses [the *tokio* library](https://tokio.rs). +//! +//! # Usage +//! +//! Example: +//! +//! ``` +//! extern crate libp2p_tcp; +//! use libp2p_tcp::QuicConfig; +//! +//! # fn main() { +//! let tcp = QuicConfig::new(); +//! # } +//! ``` +//! +//! The `QuicConfig` structs implements the `Transport` trait of the `swarm` library. See the +//! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. -struct QuicTransport { - connection: Connection, - socket: UdpSocket, - transmit: Option, - offset: usize, -} - -impl QuicTransport { - fn poll_transmit(&mut self) -> Result<(), std::io::Error> { - self.transmit = match self.transmit.or_else(|| self.connection.poll_transmit(Instant::now())) { - Some(s) => s, - None => return Ok(()), - }; - while self.transmit.contents.len() > self.offest { - match self.socket.send_to(contents[self.offset..], destination) { - Ok(len) => self.offset += len, - Err(e) => match e.kind() { - ErrorKind::Interrupted => continue, - ErrorKind::WouldBlock => unimplemented!("figure out what to wake!"), - _ => return Err(e), - }, - } +use futures::{ + compat::{Compat, Compat01As03}, + future::{self, Either}, + prelude::*, + stream::{self, Chain, Once, Stream}, +}; +use get_if_addrs::{get_if_addrs, IfAddr}; +use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerEvent, TransportError}, + Transport, +}; +use log::{debug, trace}; +pub use quinn::{EndpointBuilder, EndpointError, ServerConfig}; +use std::{ + collections::VecDeque, + io::{self, Read, Write}, + iter::{self, FromIterator}, + net::{IpAddr, SocketAddr}, + time::{Duration, Instant}, + vec::IntoIter, +}; +use tokio_io::{AsyncRead, AsyncWrite}; + +/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. +/// +/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams +/// obtained by libp2p through the tokio reactor. +#[derive(Debug, Clone)] +pub struct QuicConfig { + /// The underlying QUIC transport config. Quinn provides functions for creating a suitable + /// one. + pub endpoint_builder: EndpointBuilder, + /// The server configuration. Quinn provides functions for making one. + pub server_configuration: ServerConfig, +} + +/// An error in the QUIC transport +#[derive(Debug, err_derive::Error)] +pub enum QuicError { + /// An I/O error + #[error(display = "I/O error: {}", _0)] + IoError(#[source] std::io::Error), + #[error(display = "QUIC Protocol Error: {}", _0)] + ProtocolError(#[source] quinn::ConnectionError), +} + + +impl QuicConfig { + /// Creates a new configuration object for TCP/IP. + pub fn new() -> Self { + Self::default() + } +} + +impl Default for QuicConfig { + fn default() -> Self { + Self::new() + } +} + +pub struct QuicIncoming { + incoming: Compat01As03, + addr: Multiaddr, +} +type CompatConnecting = Compat< + future::MapErr< + Compat01As03, + fn(quinn::ConnectionError) -> QuicError, + >, +>; +impl Stream for QuicIncoming { + type Item = Result, QuicError>; + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + ctx: &mut std::task::Context, + ) -> std::task::Poll> { + use std::{pin::Pin, task::Poll}; + use futures::compat::Future01CompatExt; + fn dummy_error(s: quinn::ConnectionError) -> QuicError { + QuicError::ProtocolError(s) } - Ok(()) - } + match Pin::new(&mut self.incoming).poll_next(ctx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(Ok(upgrade))) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade: upgrade.compat().map_err(QuicError::ProtocolError as _).compat(), + local_addr: self.addr.clone(), + remote_addr: self.addr.clone(), + }))), + Poll::Ready(Some(Err(()))) => { + panic!("this never happens according to a source code comment in quinn; qed") + } + Poll::Ready(None) => Poll::Ready(None), + } + } +} + +impl Transport for QuicConfig { + type Output = quinn::NewConnection; + type Error = QuicError; + type Listener = Compat; + type ListenerUpgrade = CompatConnecting; + + type Dial = Self::ListenerUpgrade; + + fn listen_on(self, addr: Multiaddr) -> Result> { + use futures::compat::{Stream01CompatExt, Future01CompatExt}; + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { + sa + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + + let (driver, _endpoint, incoming) = + self.endpoint_builder + .bind(&socket_addr) + .map_err(|err| match err { + EndpointError::Config(_) => unreachable!("this only happens if the configuration is invalid; we always pass a valid configuration; qed"), + EndpointError::Socket(e) => TransportError::Other(QuicError::IoError(e)), + EndpointError::Tls(_) | EndpointError::WebPki(_) => unimplemented!(), + })?; + tokio::spawn(driver.compat().map_err(drop).compat()); + Ok(QuicIncoming { + incoming: incoming.compat(), + addr, + } + .compat()) + } + + fn dial(self, addr: Multiaddr) -> Result> { + let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + debug!("Instantly refusing dialing {}, as it is invalid", addr); + return Err(TransportError::Other(QuicError::IoError( + io::ErrorKind::ConnectionRefused.into(), + ))); + } + socket_addr + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + + unimplemented!("Dialing {}", addr); + } +} + +// This type of logic should probably be moved into the multiaddr package +fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { + let mut iter = addr.iter(); + let proto1 = iter.next().ok_or(())?; + let proto2 = iter.next().ok_or(())?; + let proto3 = iter.next().ok_or(())?; + + if iter.next().is_some() { + return Err(()); + } + + match (proto1, proto2, proto3) { + (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + _ => Err(()), + } +} + +// Create a [`Multiaddr`] from the given IP address and port number. +fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { + let proto = match ip { + IpAddr::V4(ip) => Protocol::Ip4(ip), + IpAddr::V6(ip) => Protocol::Ip6(ip), + }; + let it = iter::once(proto).chain(iter::once(Protocol::Udp(port))); + Multiaddr::from_iter(it) +} + +// Collect all local host addresses and use the provided port number as listen port. +fn host_addresses(port: u16) -> io::Result> { + let mut addrs = Vec::new(); + for iface in get_if_addrs()? { + let ip = iface.ip(); + let ma = ip_to_multiaddr(ip, port); + let ipn = match iface.addr { + IfAddr::V4(ip4) => { + let prefix_len = (!u32::from_be_bytes(ip4.netmask.octets())).leading_zeros(); + let ipnet = Ipv4Net::new(ip4.ip, prefix_len as u8) + .expect("prefix_len is the number of bits in a u32, so can not exceed 32"); + IpNet::V4(ipnet) + } + IfAddr::V6(ip6) => { + let prefix_len = (!u128::from_be_bytes(ip6.netmask.octets())).leading_zeros(); + let ipnet = Ipv6Net::new(ip6.ip, prefix_len as u8) + .expect("prefix_len is the number of bits in a u128, so can not exceed 128"); + IpNet::V6(ipnet) + } + }; + addrs.push((ip, ipn, ma)) + } + Ok(addrs) +} + +/// Listen address information. +#[derive(Debug)] +enum Addresses { + /// A specific address is used to listen. + One(Multiaddr), + /// A set of addresses is used to listen. + Many(Vec<(IpAddr, IpNet, Multiaddr)>), } + +#[cfg(test)] +mod tests { + use super::{multiaddr_to_socketaddr, Listener, TcpConfig}; + use futures::{ + future::{self, Loop}, + prelude::*, + stream, + }; + use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::ListenerEvent, + Transport, + }; + use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, + }; + use tokio::runtime::current_thread::{self, Runtime}; + use tokio_io; + + #[test] + fn pause_on_error() { + // We create a stream of values and errors and continue polling even after errors + // have been encountered. We count the number of items (including errors) and assert + // that no item has been missed. + let rs = stream::iter_result(vec![Ok(1), Err(1), Ok(1), Err(1)]); + let ls = Listener::new(rs, Duration::from_secs(1)); + let sum = future::loop_fn((0, ls), |(acc, ls)| { + ls.into_future().then(move |item| match item { + Ok((None, _)) => Ok::<_, std::convert::Infallible>(Loop::Break(acc)), + Ok((Some(n), rest)) => Ok(Loop::Continue((acc + n, rest))), + Err((n, rest)) => Ok(Loop::Continue((acc + n, rest))), + }) + }); + assert_eq!(4, current_thread::block_on_all(sum).unwrap()) + } + + #[test] + fn wildcard_expansion() { + let mut listener = TcpConfig::new() + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .expect("listener"); + + // Get the first address. + let addr = listener + .by_ref() + .wait() + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + // Process all initial `NewAddress` events and make sure they + // do not contain wildcard address or port. + let server = listener + .take_while(|event| match event { + ListenerEvent::NewAddress(a) => { + let mut iter = a.iter(); + match iter.next().expect("ip address") { + Protocol::Ip4(ip) => assert!(!ip.is_unspecified()), + Protocol::Ip6(ip) => assert!(!ip.is_unspecified()), + other => panic!("Unexpected protocol: {}", other), + } + if let Protocol::Tcp(port) = iter.next().expect("port") { + assert_ne!(0, port) + } else { + panic!("No TCP port in address: {}", a) + } + Ok(true) + } + _ => Ok(false), + }) + .for_each(|_| Ok(())); + + let client = TcpConfig::new().dial(addr).expect("dialer"); + tokio::run( + server + .join(client) + .map(|_| ()) + .map_err(|e| panic!("error: {}", e)), + ) + } + + #[test] + fn multiaddr_to_tcp_conversion() { + use std::net::Ipv6Addr; + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) + .is_err() + ); + + assert_eq!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/12345".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/tcp/8080" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr(&"/ip6/::1/tcp/12345".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/tcp/8080" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + )) + ); + } + + #[test] + fn communicating_between_dialer_and_listener() { + use std::io::Write; + + std::thread::spawn(move || { + let addr = "/ip4/127.0.0.1/tcp/12345".parse::().unwrap(); + let tcp = TcpConfig::new(); + let mut rt = Runtime::new().unwrap(); + let handle = rt.handle(); + let listener = tcp + .listen_on(addr) + .unwrap() + .filter_map(ListenerEvent::into_upgrade) + .for_each(|(sock, _)| { + sock.and_then(|sock| { + // Define what to do with the socket that just connected to us + // Which in this case is read 3 bytes + let handle_conn = tokio_io::io::read_exact(sock, [0; 3]) + .map(|(_, buf)| assert_eq!(buf, [1, 2, 3])) + .map_err(|err| panic!("IO error {:?}", err)); + + // Spawn the future as a concurrent task + handle.spawn(handle_conn).unwrap(); + + Ok(()) + }) + }); + + rt.block_on(listener).unwrap(); + rt.run().unwrap(); + }); + std::thread::sleep(std::time::Duration::from_millis(100)); + let addr = "/ip4/127.0.0.1/tcp/12345".parse::().unwrap(); + let tcp = TcpConfig::new(); + // Obtain a future socket through dialing + let socket = tcp.dial(addr.clone()).unwrap(); + // Define what to do with the socket once it's obtained + let action = socket.then(|sock| -> Result<(), ()> { + sock.unwrap().write(&[0x1, 0x2, 0x3]).unwrap(); + Ok(()) + }); + // Execute the future in our event loop + let mut rt = Runtime::new().unwrap(); + let _ = rt.block_on(action).unwrap(); + } + + #[test] + fn replace_port_0_in_returned_multiaddr_ipv4() { + let tcp = TcpConfig::new(); + + let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); + assert!(addr.to_string().contains("tcp/0")); + + let new_addr = tcp + .listen_on(addr) + .unwrap() + .wait() + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); + } + + #[test] + fn replace_port_0_in_returned_multiaddr_ipv6() { + let tcp = TcpConfig::new(); + + let addr: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); + assert!(addr.to_string().contains("tcp/0")); + + let new_addr = tcp + .listen_on(addr) + .unwrap() + .wait() + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); + } + + #[test] + fn larger_addr_denied() { + let tcp = TcpConfig::new(); + + let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" + .parse::() + .unwrap(); + assert!(tcp.listen_on(addr).is_err()); + } +} +struct QuicTransport { + endpoint: quinn::Endpoint, +} +/* + +#[cfg(any())] +impl Transport for &mut QuicTransport { + + fn poll_transmit(&mut self) -> Result<(), std::io::Error> { + self.transmit = match self.transmit.or_else(|| self.connection.poll_transmit(Instant::now())) { + Some(s) => s, + None => return Ok(()), + }; + while self.transmit.contents.len() > self.offest { + match self.socket.send_to(contents[self.offset..], destination) { + Ok(len) => self.offset += len, + Err(e) => match e.kind() { + ErrorKind::Interrupted => continue, + ErrorKind::WouldBlock => unimplemented!("figure out what to wake!"), + _ => return Err(e), + }, + } + } + Ok(()) + } +}*/ From b544f67025068657964b724daeaa1205ffab344e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 21 Nov 2019 12:27:12 -0500 Subject: [PATCH 004/202] Remove an `unimplemented!` --- transports/quic/src/lib.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 3d6afda8ea9..dd75c21d0b6 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -80,8 +80,8 @@ pub struct QuicConfig { #[derive(Debug, err_derive::Error)] pub enum QuicError { /// An I/O error - #[error(display = "I/O error: {}", _0)] - IoError(#[source] std::io::Error), + #[error(display = "Endpoint error: {}", _0)] + EndpointError(#[source] quinn::EndpointError), #[error(display = "QUIC Protocol Error: {}", _0)] ProtocolError(#[source] quinn::ConnectionError), } @@ -155,11 +155,7 @@ impl Transport for QuicConfig { let (driver, _endpoint, incoming) = self.endpoint_builder .bind(&socket_addr) - .map_err(|err| match err { - EndpointError::Config(_) => unreachable!("this only happens if the configuration is invalid; we always pass a valid configuration; qed"), - EndpointError::Socket(e) => TransportError::Other(QuicError::IoError(e)), - EndpointError::Tls(_) | EndpointError::WebPki(_) => unimplemented!(), - })?; + .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; tokio::spawn(driver.compat().map_err(drop).compat()); Ok(QuicIncoming { incoming: incoming.compat(), @@ -172,9 +168,9 @@ impl Transport for QuicConfig { let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::Other(QuicError::IoError( + return Err(TransportError::Other(QuicError::EndpointError(EndpointError::Socket( io::ErrorKind::ConnectionRefused.into(), - ))); + )))); } socket_addr } else { From 432c1b67ac12a886cb448b51d529f39bedc8195c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 21 Nov 2019 14:56:54 -0500 Subject: [PATCH 005/202] Report peer addresses This requires a branch of `quinn` that has not yet been merged. --- transports/quic/Cargo.toml | 4 +- transports/quic/src/lib.rs | 76 ++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 73c32b4b18c..35be152c6aa 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -28,12 +28,12 @@ edition = "2018" [dependencies] quinn-proto = "0.4.0" rustls = { version = "0.16.0", features = ["dangerous_configuration"] } -futures = { version = "0.3.1", features = ["compat"] } libp2p-core = { path = "../../core", version = "0.13.1" } log = "0.4.8" tokio-io = "0.1.12" -quinn = "0.4.0" +quinn = { git = "https://github.com/paritytech/quinn", rev = "demi-connecting-peer-address" } tokio = "0.1.22" get_if_addrs = "0.5.3" ipnet = "2.1.0" err-derive = "0.2.1" +futures = { version = "0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index dd75c21d0b6..99b433761c7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -39,7 +39,7 @@ //! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. use futures::{ - compat::{Compat, Compat01As03}, + compat::Compat, future::{self, Either}, prelude::*, stream::{self, Chain, Once, Stream}, @@ -79,14 +79,13 @@ pub struct QuicConfig { /// An error in the QUIC transport #[derive(Debug, err_derive::Error)] pub enum QuicError { - /// An I/O error - #[error(display = "Endpoint error: {}", _0)] - EndpointError(#[source] quinn::EndpointError), - #[error(display = "QUIC Protocol Error: {}", _0)] - ProtocolError(#[source] quinn::ConnectionError), + /// An I/O error + #[error(display = "Endpoint error: {}", _0)] + EndpointError(#[source] quinn::EndpointError), + #[error(display = "QUIC Protocol Error: {}", _0)] + ProtocolError(#[source] quinn::ConnectionError), } - impl QuicConfig { /// Creates a new configuration object for TCP/IP. pub fn new() -> Self { @@ -101,36 +100,34 @@ impl Default for QuicConfig { } pub struct QuicIncoming { - incoming: Compat01As03, + incoming: quinn::Incoming, addr: Multiaddr, } -type CompatConnecting = Compat< - future::MapErr< - Compat01As03, - fn(quinn::ConnectionError) -> QuicError, - >, ->; + +type CompatConnecting = + Compat QuicError>>; + impl Stream for QuicIncoming { type Item = Result, QuicError>; fn poll_next( mut self: std::pin::Pin<&mut Self>, ctx: &mut std::task::Context, ) -> std::task::Poll> { - use std::{pin::Pin, task::Poll}; use futures::compat::Future01CompatExt; - fn dummy_error(s: quinn::ConnectionError) -> QuicError { - QuicError::ProtocolError(s) - } + use std::{pin::Pin, task::Poll}; + fn dummy_error(s: quinn::ConnectionError) -> QuicError { + QuicError::ProtocolError(s) + } match Pin::new(&mut self.incoming).poll_next(ctx) { Poll::Pending => Poll::Pending, - Poll::Ready(Some(Ok(upgrade))) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade: upgrade.compat().map_err(QuicError::ProtocolError as _).compat(), - local_addr: self.addr.clone(), - remote_addr: self.addr.clone(), - }))), - Poll::Ready(Some(Err(()))) => { - panic!("this never happens according to a source code comment in quinn; qed") - } + Poll::Ready(Some(upgrade)) => { + let peer = upgrade.remote_address(); + Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + remote_addr: ip_to_multiaddr(peer.ip(), peer.port()), + upgrade: upgrade.map_err(QuicError::ProtocolError as _).compat(), + local_addr: self.addr.clone(), + }))) + } Poll::Ready(None) => Poll::Ready(None), } } @@ -145,32 +142,28 @@ impl Transport for QuicConfig { type Dial = Self::ListenerUpgrade; fn listen_on(self, addr: Multiaddr) -> Result> { - use futures::compat::{Stream01CompatExt, Future01CompatExt}; + use futures::compat::{Future01CompatExt, Stream01CompatExt}; let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { sa } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let (driver, _endpoint, incoming) = - self.endpoint_builder - .bind(&socket_addr) - .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; - tokio::spawn(driver.compat().map_err(drop).compat()); - Ok(QuicIncoming { - incoming: incoming.compat(), - addr, - } - .compat()) + let (driver, _endpoint, incoming) = self + .endpoint_builder + .bind(&socket_addr) + .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; + tokio::spawn(driver.map_err(drop).compat()); + Ok(QuicIncoming { incoming, addr }.compat()) } fn dial(self, addr: Multiaddr) -> Result> { let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::Other(QuicError::EndpointError(EndpointError::Socket( - io::ErrorKind::ConnectionRefused.into(), - )))); + return Err(TransportError::Other(QuicError::EndpointError( + EndpointError::Socket(io::ErrorKind::ConnectionRefused.into()), + ))); } socket_addr } else { @@ -209,7 +202,8 @@ fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { IpAddr::V4(ip) => Protocol::Ip4(ip), IpAddr::V6(ip) => Protocol::Ip6(ip), }; - let it = iter::once(proto).chain(iter::once(Protocol::Udp(port))); + let end = [Protocol::Udp(port), Protocol::Quic]; + let it = iter::once(proto).chain(end.into_iter().cloned()); Multiaddr::from_iter(it) } From 606ede2b34da5de7b7b2771603c8c680d3383fdd Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 21 Nov 2019 15:23:47 -0500 Subject: [PATCH 006/202] Remove code duplication between QUIC and TCP transports --- misc/multiaddr/Cargo.toml | 2 ++ misc/multiaddr/src/lib.rs | 38 ++++++++++++++++++++++++ transports/quic/Cargo.toml | 1 - transports/quic/src/lib.rs | 60 +++++++------------------------------- transports/tcp/src/lib.rs | 50 +++++-------------------------- 5 files changed, 57 insertions(+), 94 deletions(-) diff --git a/misc/multiaddr/Cargo.toml b/misc/multiaddr/Cargo.toml index c7b6b1bc671..d9c4e19d1f7 100644 --- a/misc/multiaddr/Cargo.toml +++ b/misc/multiaddr/Cargo.toml @@ -19,6 +19,8 @@ percent-encoding = "2.1.0" serde = "1.0.70" unsigned-varint = "0.2" url = { version = "2.1.0", default-features = false } +get_if_addrs = "0.5.3" +ipnet = "2.1.0" [dev-dependencies] bincode = "1" diff --git a/misc/multiaddr/src/lib.rs b/misc/multiaddr/src/lib.rs index 5d3f0ae67ce..5fa13cc5452 100644 --- a/misc/multiaddr/src/lib.rs +++ b/misc/multiaddr/src/lib.rs @@ -8,6 +8,8 @@ mod from_url; mod util; use bytes::{Bytes, BytesMut}; +use get_if_addrs::{get_if_addrs, IfAddr}; +use ipnet::{IpNet, Ipv4Net, Ipv6Net}; use serde::{ Deserialize, Deserializer, @@ -18,6 +20,7 @@ use serde::{ use std::{ convert::TryFrom, fmt, + io, iter::FromIterator, net::{IpAddr, Ipv4Addr, Ipv6Addr}, result::Result as StdResult, @@ -304,6 +307,41 @@ impl TryFrom for Multiaddr { } } +/// Create a [`Multiaddr`] from the given IP address and suffix. +pub fn ip_to_multiaddr(ip: IpAddr, suffix: &[Protocol]) -> Multiaddr { + let proto = match ip { + IpAddr::V4(ip) => Protocol::Ip4(ip), + IpAddr::V6(ip) => Protocol::Ip6(ip), + }; + let it = std::iter::once(proto).chain(suffix.into_iter().cloned()); + Multiaddr::from_iter(it) +} + +/// Collect all local host addresses and use the provided port number as listen port. +pub fn host_addresses(suffix: &[Protocol]) -> io::Result> { + let mut addrs = Vec::new(); + for iface in get_if_addrs()? { + let ip = iface.ip(); + let ma = ip_to_multiaddr(ip, suffix); + let ipn = match iface.addr { + IfAddr::V4(ip4) => { + let prefix_len = (!u32::from_be_bytes(ip4.netmask.octets())).leading_zeros(); + let ipnet = Ipv4Net::new(ip4.ip, prefix_len as u8) + .expect("prefix_len is the number of bits in a u32, so can not exceed 32"); + IpNet::V4(ipnet) + } + IfAddr::V6(ip6) => { + let prefix_len = (!u128::from_be_bytes(ip6.netmask.octets())).leading_zeros(); + let ipnet = Ipv6Net::new(ip6.ip, prefix_len as u8) + .expect("prefix_len is the number of bits in a u128, so can not exceed 128"); + IpNet::V6(ipnet) + } + }; + addrs.push((ip, ipn, ma)) + } + Ok(addrs) +} + impl TryFrom for Multiaddr { type Error = Error; diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 35be152c6aa..a7a3653e4f6 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -33,7 +33,6 @@ log = "0.4.8" tokio-io = "0.1.12" quinn = { git = "https://github.com/paritytech/quinn", rev = "demi-connecting-peer-address" } tokio = "0.1.22" -get_if_addrs = "0.5.3" ipnet = "2.1.0" err-derive = "0.2.1" futures = { version = "0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 99b433761c7..5f1318bec40 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -44,14 +44,13 @@ use futures::{ prelude::*, stream::{self, Chain, Once, Stream}, }; -use get_if_addrs::{get_if_addrs, IfAddr}; -use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use ipnet::IpNet; use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, + multiaddr::{Multiaddr, Protocol, host_addresses, ip_to_multiaddr}, transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, trace}; +use log::debug; pub use quinn::{EndpointBuilder, EndpointError, ServerConfig}; use std::{ collections::VecDeque, @@ -115,19 +114,16 @@ impl Stream for QuicIncoming { ) -> std::task::Poll> { use futures::compat::Future01CompatExt; use std::{pin::Pin, task::Poll}; - fn dummy_error(s: quinn::ConnectionError) -> QuicError { - QuicError::ProtocolError(s) - } match Pin::new(&mut self.incoming).poll_next(ctx) { Poll::Pending => Poll::Pending, Poll::Ready(Some(upgrade)) => { - let peer = upgrade.remote_address(); - Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - remote_addr: ip_to_multiaddr(peer.ip(), peer.port()), - upgrade: upgrade.map_err(QuicError::ProtocolError as _).compat(), - local_addr: self.addr.clone(), - }))) - } + let peer = upgrade.remote_address(); + Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + remote_addr: ip_to_multiaddr(peer.ip(), &[Protocol::Udp(peer.port()), Protocol::Quic]), + upgrade: upgrade.map_err(QuicError::ProtocolError as _).compat(), + local_addr: self.addr.clone(), + }))) + } Poll::Ready(None) => Poll::Ready(None), } } @@ -196,42 +192,6 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { } } -// Create a [`Multiaddr`] from the given IP address and port number. -fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { - let proto = match ip { - IpAddr::V4(ip) => Protocol::Ip4(ip), - IpAddr::V6(ip) => Protocol::Ip6(ip), - }; - let end = [Protocol::Udp(port), Protocol::Quic]; - let it = iter::once(proto).chain(end.into_iter().cloned()); - Multiaddr::from_iter(it) -} - -// Collect all local host addresses and use the provided port number as listen port. -fn host_addresses(port: u16) -> io::Result> { - let mut addrs = Vec::new(); - for iface in get_if_addrs()? { - let ip = iface.ip(); - let ma = ip_to_multiaddr(ip, port); - let ipn = match iface.addr { - IfAddr::V4(ip4) => { - let prefix_len = (!u32::from_be_bytes(ip4.netmask.octets())).leading_zeros(); - let ipnet = Ipv4Net::new(ip4.ip, prefix_len as u8) - .expect("prefix_len is the number of bits in a u32, so can not exceed 32"); - IpNet::V4(ipnet) - } - IfAddr::V6(ip6) => { - let prefix_len = (!u128::from_be_bytes(ip6.netmask.octets())).leading_zeros(); - let ipnet = Ipv6Net::new(ip6.ip, prefix_len as u8) - .expect("prefix_len is the number of bits in a u128, so can not exceed 128"); - IpNet::V6(ipnet) - } - }; - addrs.push((ip, ipn, ma)) - } - Ok(addrs) -} - /// Listen address information. #[derive(Debug)] enum Addresses { diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index 6de747ea4da..c1879cc483d 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -39,11 +39,10 @@ use async_std::net::TcpStream; use futures::{future::{self, Ready}, prelude::*}; use futures_timer::Delay; -use get_if_addrs::{IfAddr, get_if_addrs}; -use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use ipnet::IpNet; use libp2p_core::{ Transport, - multiaddr::{Protocol, Multiaddr}, + multiaddr::{Protocol, Multiaddr, host_addresses, ip_to_multiaddr}, transport::{ListenerEvent, TransportError} }; use log::{debug, trace}; @@ -121,11 +120,11 @@ impl Transport for TcpConfig { // as reported by `get_if_addrs`. let addrs = if socket_addr.ip().is_unspecified() { - let addrs = host_addresses(port)?; + let addrs = host_addresses(&[Protocol::Tcp(port)])?; debug!("Listening on {:?}", addrs.iter().map(|(_, _, ma)| ma).collect::>()); Addresses::Many(addrs) } else { - let ma = ip_to_multiaddr(local_addr.ip(), port); + let ma = ip_to_multiaddr(local_addr.ip(), &[Protocol::Tcp(port)]); debug!("Listening on {:?}", ma); Addresses::One(ma) }; @@ -205,41 +204,6 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { } } -// Create a [`Multiaddr`] from the given IP address and port number. -fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { - let proto = match ip { - IpAddr::V4(ip) => Protocol::Ip4(ip), - IpAddr::V6(ip) => Protocol::Ip6(ip) - }; - let it = iter::once(proto).chain(iter::once(Protocol::Tcp(port))); - Multiaddr::from_iter(it) -} - -// Collect all local host addresses and use the provided port number as listen port. -fn host_addresses(port: u16) -> io::Result> { - let mut addrs = Vec::new(); - for iface in get_if_addrs()? { - let ip = iface.ip(); - let ma = ip_to_multiaddr(ip, port); - let ipn = match iface.addr { - IfAddr::V4(ip4) => { - let prefix_len = (!u32::from_be_bytes(ip4.netmask.octets())).leading_zeros(); - let ipnet = Ipv4Net::new(ip4.ip, prefix_len as u8) - .expect("prefix_len is the number of bits in a u32, so can not exceed 32"); - IpNet::V4(ipnet) - } - IfAddr::V6(ip6) => { - let prefix_len = (!u128::from_be_bytes(ip6.netmask.octets())).leading_zeros(); - let ipnet = Ipv6Net::new(ip6.ip, prefix_len as u8) - .expect("prefix_len is the number of bits in a u128, so can not exceed 128"); - IpNet::V6(ipnet) - } - }; - addrs.push((ip, ipn, ma)) - } - Ok(addrs) -} - /// Applies the socket configuration parameters to a socket. fn apply_config(config: &TcpConfig, socket: &TcpStream) -> Result<(), io::Error> { if let Some(ttl) = config.ttl { @@ -306,7 +270,7 @@ fn check_for_interface_changes( // and expired addresses. // // TODO: We do not detect expired addresses unless there is a new address. - let old_listen_addrs = std::mem::replace(listen_addrs, host_addresses(listen_port)?); + let old_listen_addrs = std::mem::replace(listen_addrs, host_addresses(&[Protocol::Tcp(listen_port)])?); // Check for addresses no longer in use. for (ip, _, ma) in old_listen_addrs.iter() { @@ -374,7 +338,7 @@ impl TcpListenStream { return (Err(err), self); } } - ip_to_multiaddr(sock_addr.ip(), sock_addr.port()) + ip_to_multiaddr(sock_addr.ip(), &[Protocol::Tcp(sock_addr.port())]) } Err(err) => { debug!("Failed to get local address of incoming socket: {:?}", err); @@ -382,7 +346,7 @@ impl TcpListenStream { } }; - let remote_addr = ip_to_multiaddr(sock_addr.ip(), sock_addr.port()); + let remote_addr = ip_to_multiaddr(sock_addr.ip(), &[Protocol::Tcp(sock_addr.port())]); match apply_config(&self.config, &sock) { Ok(()) => { From 10f16ba92085c95fb0e6bace46a524088c7854aa Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 21 Nov 2019 15:50:20 -0500 Subject: [PATCH 007/202] Switch back to quinn master It now incudes the changes needed by libp2p. --- transports/quic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index a7a3653e4f6..f8da4df91d4 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -31,7 +31,7 @@ rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.13.1" } log = "0.4.8" tokio-io = "0.1.12" -quinn = { git = "https://github.com/paritytech/quinn", rev = "demi-connecting-peer-address" } +quinn = { git = "https://github.com/djc/quinn", rev = "master" } tokio = "0.1.22" ipnet = "2.1.0" err-derive = "0.2.1" From bf184c5ab8ec065e8512d42fd4213cd0cec43a77 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 24 Nov 2019 14:30:10 -0500 Subject: [PATCH 008/202] Implement `dial` --- core/src/transport/mod.rs | 6 ++++++ transports/quic/src/lib.rs | 35 +++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/core/src/transport/mod.rs b/core/src/transport/mod.rs index b3127cf2bed..bdaee9f7780 100644 --- a/core/src/transport/mod.rs +++ b/core/src/transport/mod.rs @@ -332,6 +332,12 @@ pub enum TransportError { Other(TErr), } +impl From for TransportError where TErr: std::error::Error { + fn from(source: TErr) -> Self { + Self::Other(source) + } +} + impl TransportError { /// Applies a function to the the error in [`TransportError::Other`]. pub fn map(self, map: impl FnOnce(TErr) -> TNewErr) -> TransportError { diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 5f1318bec40..9199242b65c 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -37,6 +37,10 @@ //! //! The `QuicConfig` structs implements the `Transport` trait of the `swarm` library. See the //! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. +//! +//! Note that QUIC provides transport, security, and multiplexing in a single protocol. Therefore, +//! QUIC connections do not need to be upgraded. You will get a compile-time error if you try. +//! Instead, you must pass all needed configuration into the constructor. use futures::{ compat::Compat, @@ -46,12 +50,12 @@ use futures::{ }; use ipnet::IpNet; use libp2p_core::{ - multiaddr::{Multiaddr, Protocol, host_addresses, ip_to_multiaddr}, + multiaddr::{host_addresses, ip_to_multiaddr, Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, Transport, }; use log::debug; -pub use quinn::{EndpointBuilder, EndpointError, ServerConfig}; +pub use quinn::{Endpoint, EndpointBuilder, EndpointError, ServerConfig}; use std::{ collections::VecDeque, io::{self, Read, Write}, @@ -71,6 +75,8 @@ pub struct QuicConfig { /// The underlying QUIC transport config. Quinn provides functions for creating a suitable /// one. pub endpoint_builder: EndpointBuilder, + /// The underlying QUIC transport endpoint. + endpoint: Option, /// The server configuration. Quinn provides functions for making one. pub server_configuration: ServerConfig, } @@ -82,7 +88,9 @@ pub enum QuicError { #[error(display = "Endpoint error: {}", _0)] EndpointError(#[source] quinn::EndpointError), #[error(display = "QUIC Protocol Error: {}", _0)] - ProtocolError(#[source] quinn::ConnectionError), + ConnectionError(#[source] quinn::ConnectionError), + #[error(display = "QUIC outbound connection error: {}", _0)] + ConnectError(#[source] quinn::ConnectError), } impl QuicConfig { @@ -119,8 +127,11 @@ impl Stream for QuicIncoming { Poll::Ready(Some(upgrade)) => { let peer = upgrade.remote_address(); Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - remote_addr: ip_to_multiaddr(peer.ip(), &[Protocol::Udp(peer.port()), Protocol::Quic]), - upgrade: upgrade.map_err(QuicError::ProtocolError as _).compat(), + remote_addr: ip_to_multiaddr( + peer.ip(), + &[Protocol::Udp(peer.port()), Protocol::Quic], + ), + upgrade: upgrade.map_err(QuicError::ConnectionError as _).compat(), local_addr: self.addr.clone(), }))) } @@ -134,8 +145,7 @@ impl Transport for QuicConfig { type Error = QuicError; type Listener = Compat; type ListenerUpgrade = CompatConnecting; - - type Dial = Self::ListenerUpgrade; + type Dial = CompatConnecting; fn listen_on(self, addr: Multiaddr) -> Result> { use futures::compat::{Future01CompatExt, Stream01CompatExt}; @@ -166,7 +176,16 @@ impl Transport for QuicConfig { return Err(TransportError::MultiaddrNotSupported(addr)); }; - unimplemented!("Dialing {}", addr); + let (driver, endpoint, _incoming) = + self.endpoint_builder + .bind(&([0u8; 16], 0u16).into()) + .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; + + Ok(endpoint + .connect(&socket_addr, &socket_addr.to_string()) + .map_err(QuicError::ConnectError)? + .map_err(QuicError::ConnectionError as _) + .compat()) } } From 2157f520c9b1de71d4b256c4f89d1f1ddaa0d4f5 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 24 Nov 2019 16:22:33 -0500 Subject: [PATCH 009/202] Add `use` lines for std future related types Also update to stable futures. --- transports/quic/Cargo.toml | 1 + transports/quic/src/lib.rs | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index f8da4df91d4..55be43035c6 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -36,3 +36,4 @@ tokio = "0.1.22" ipnet = "2.1.0" err-derive = "0.2.1" futures = { version = "0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } +futures-core = "0.3.0-alpha.19" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9199242b65c..3b1bff0d1ed 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -43,7 +43,6 @@ //! Instead, you must pass all needed configuration into the constructor. use futures::{ - compat::Compat, future::{self, Either}, prelude::*, stream::{self, Chain, Once, Stream}, @@ -61,6 +60,8 @@ use std::{ io::{self, Read, Write}, iter::{self, FromIterator}, net::{IpAddr, SocketAddr}, + pin::Pin, + task::{Context, Poll}, time::{Duration, Instant}, vec::IntoIter, }; @@ -111,17 +112,11 @@ pub struct QuicIncoming { addr: Multiaddr, } -type CompatConnecting = - Compat QuicError>>; +type CompatConnecting = future::MapErr QuicError>; -impl Stream for QuicIncoming { +impl futures_core::stream::Stream for QuicIncoming { type Item = Result, QuicError>; - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - ctx: &mut std::task::Context, - ) -> std::task::Poll> { - use futures::compat::Future01CompatExt; - use std::{pin::Pin, task::Poll}; + fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { match Pin::new(&mut self.incoming).poll_next(ctx) { Poll::Pending => Poll::Pending, Poll::Ready(Some(upgrade)) => { @@ -131,7 +126,7 @@ impl Stream for QuicIncoming { peer.ip(), &[Protocol::Udp(peer.port()), Protocol::Quic], ), - upgrade: upgrade.map_err(QuicError::ConnectionError as _).compat(), + upgrade: upgrade.map_err(QuicError::ConnectionError as _), local_addr: self.addr.clone(), }))) } @@ -143,12 +138,12 @@ impl Stream for QuicIncoming { impl Transport for QuicConfig { type Output = quinn::NewConnection; type Error = QuicError; - type Listener = Compat; + type Listener = QuicIncoming; type ListenerUpgrade = CompatConnecting; type Dial = CompatConnecting; fn listen_on(self, addr: Multiaddr) -> Result> { - use futures::compat::{Future01CompatExt, Stream01CompatExt}; + // use futures::compat::{Future01CompatExt, Stream01CompatExt}; let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { sa } else { @@ -160,7 +155,7 @@ impl Transport for QuicConfig { .bind(&socket_addr) .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; tokio::spawn(driver.map_err(drop).compat()); - Ok(QuicIncoming { incoming, addr }.compat()) + Ok(QuicIncoming { incoming, addr }) } fn dial(self, addr: Multiaddr) -> Result> { @@ -184,8 +179,7 @@ impl Transport for QuicConfig { Ok(endpoint .connect(&socket_addr, &socket_addr.to_string()) .map_err(QuicError::ConnectError)? - .map_err(QuicError::ConnectionError as _) - .compat()) + .map_err(QuicError::ConnectionError as _)) } } From b4fd310afb1f3e35a27e38d7378958a2e2b4f6f3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 29 Nov 2019 21:11:57 -0500 Subject: [PATCH 010/202] Initial StreamMuxer impl There are a LOT of `unimplemented!()` calls here! --- transports/quic/Cargo.toml | 2 +- transports/quic/src/lib.rs | 96 ++++++++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 55be43035c6..0a7b962c646 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -36,4 +36,4 @@ tokio = "0.1.22" ipnet = "2.1.0" err-derive = "0.2.1" futures = { version = "0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } -futures-core = "0.3.0-alpha.19" +futures-core = { version = "0.3.0-alpha.19" } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 3b1bff0d1ed..8ffd186bae6 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -51,7 +51,7 @@ use ipnet::IpNet; use libp2p_core::{ multiaddr::{host_addresses, ip_to_multiaddr, Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, - Transport, + StreamMuxer, Transport, }; use log::debug; pub use quinn::{Endpoint, EndpointBuilder, EndpointError, ServerConfig}; @@ -61,6 +61,7 @@ use std::{ iter::{self, FromIterator}, net::{IpAddr, SocketAddr}, pin::Pin, + sync::Mutex, task::{Context, Poll}, time::{Duration, Instant}, vec::IntoIter, @@ -108,7 +109,9 @@ impl Default for QuicConfig { } pub struct QuicIncoming { + /// This field uses structural pinning… incoming: quinn::Incoming, + /// but this field does not. addr: Multiaddr, } @@ -135,6 +138,73 @@ impl futures_core::stream::Stream for QuicIncoming { } } +struct QuicMuxer { + bi_streams: quinn::IncomingBiStreams, + connection: quinn::Connection, + driver: quinn::ConnectionDriver, +} + +pub struct SyncQuicMuxer(Mutex); + +pub struct QuicSubstream { + send: quinn::SendStream, + recv: quinn::RecvStream, +} + +// FIXME: if quinn ever starts using `!Unpin` futures, this will require `unsafe` code. +// Will probably fall back to spawning the connection driver in this case 🙁 +impl StreamMuxer for SyncQuicMuxer { + type OutboundSubstream = quinn::OpenBi; + type Substream = (quinn::SendStream, quinn::RecvStream); + type Error = quinn::ConnectionError; + fn poll_inbound(&self, cx: &mut Context) -> Poll> { + let mut this = self.0.lock().unwrap(); + match Pin::new(&mut this.driver).poll(cx) { + Poll::Ready(Ok(())) => unreachable!( + "ConnectionDriver will not resolve as long as `self.bi_streams` is alive" + ), + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => (), + } + match Pin::new(&mut this.bi_streams).poll_next(cx) { + Poll::Ready(Some(stream)) => Poll::Ready(stream), + Poll::Ready(None) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), + Poll::Pending => Poll::Pending, + } + } + fn open_outbound(&self) -> Self::OutboundSubstream { + self.0.lock().unwrap().connection.open_bi() + } + fn destroy_substream(&self, _substream: Self::Substream) { + } + fn destroy_outbound(&self, _substream: Self::OutboundSubstream) { + } + fn is_remote_acknowledged(&self) -> bool { + true + } + fn write_substream(&self, cx: &mut Context, substream: &mut Self::Substream, buf: &[u8]) -> Poll> { + Pin::new(substream.0).poll_write(buf) + } + fn poll_outbound(&self, cx: &mut Context, _substream: &mut Self::OutboundSubstream) -> Poll> { + unimplemented!() + } + fn read_substream(&self, cx: &mut Context, _substream: &mut Self::Substream, _buf: &mut [u8]) -> Poll> { + unimplemented!() + } + fn shutdown_substream(&self, cx: &mut Context, _substream: &mut Self::Substream) -> Poll> { + unimplemented!() + } + fn flush_substream(&self, cx: &mut Context, _substream: &mut Self::Substream) -> Poll> { + unimplemented!() + } + fn flush_all(&self, cx: &mut Context) -> Poll> { + unimplemented!() + } + fn close(&self, cx: &mut Context) -> Poll> { + unimplemented!() + } +} + impl Transport for QuicConfig { type Output = quinn::NewConnection; type Error = QuicError; @@ -143,7 +213,6 @@ impl Transport for QuicConfig { type Dial = CompatConnecting; fn listen_on(self, addr: Multiaddr) -> Result> { - // use futures::compat::{Future01CompatExt, Stream01CompatExt}; let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { sa } else { @@ -446,26 +515,3 @@ mod tests { struct QuicTransport { endpoint: quinn::Endpoint, } -/* - -#[cfg(any())] -impl Transport for &mut QuicTransport { - - fn poll_transmit(&mut self) -> Result<(), std::io::Error> { - self.transmit = match self.transmit.or_else(|| self.connection.poll_transmit(Instant::now())) { - Some(s) => s, - None => return Ok(()), - }; - while self.transmit.contents.len() > self.offest { - match self.socket.send_to(contents[self.offset..], destination) { - Ok(len) => self.offset += len, - Err(e) => match e.kind() { - ErrorKind::Interrupted => continue, - ErrorKind::WouldBlock => unimplemented!("figure out what to wake!"), - _ => return Err(e), - }, - } - } - Ok(()) - } -}*/ From 752c84421af67b12631cf101dcfdcdc9da6c0cec Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 30 Nov 2019 20:15:46 -0500 Subject: [PATCH 011/202] =?UTF-8?q?Last=20version=20using=20=E2=80=98quinn?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All future versions will use ‘quinn-proto’ directly. --- transports/quic/Cargo.toml | 9 +- transports/quic/src/lib.rs | 300 +++---------------------------------- 2 files changed, 28 insertions(+), 281 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 0a7b962c646..467c92c1630 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -30,10 +30,9 @@ quinn-proto = "0.4.0" rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.13.1" } log = "0.4.8" -tokio-io = "0.1.12" -quinn = { git = "https://github.com/djc/quinn", rev = "master" } -tokio = "0.1.22" +quinn = { git = "https://github.com/daxpedda/quinn.git", rev = "750081f2fcc431e9d7597c387b3d3b62a2a2cbb7" } +tokio = { version = "0.2.2", features = ["rt-threaded"] } ipnet = "2.1.0" err-derive = "0.2.1" -futures = { version = "0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } -futures-core = { version = "0.3.0-alpha.19" } +futures = { version = "0.3.1", package = "futures", features = ["compat"] } +futures-core = "0.3.1" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 8ffd186bae6..9ff403e6526 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -43,30 +43,24 @@ //! Instead, you must pass all needed configuration into the constructor. use futures::{ - future::{self, Either}, + future, prelude::*, - stream::{self, Chain, Once, Stream}, }; use ipnet::IpNet; use libp2p_core::{ - multiaddr::{host_addresses, ip_to_multiaddr, Multiaddr, Protocol}, + multiaddr::{ip_to_multiaddr, Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; use log::debug; pub use quinn::{Endpoint, EndpointBuilder, EndpointError, ServerConfig}; use std::{ - collections::VecDeque, - io::{self, Read, Write}, - iter::{self, FromIterator}, + io, net::{IpAddr, SocketAddr}, pin::Pin, sync::Mutex, task::{Context, Poll}, - time::{Duration, Instant}, - vec::IntoIter, }; -use tokio_io::{AsyncRead, AsyncWrite}; /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. /// @@ -146,29 +140,24 @@ struct QuicMuxer { pub struct SyncQuicMuxer(Mutex); -pub struct QuicSubstream { - send: quinn::SendStream, - recv: quinn::RecvStream, -} - // FIXME: if quinn ever starts using `!Unpin` futures, this will require `unsafe` code. // Will probably fall back to spawning the connection driver in this case 🙁 impl StreamMuxer for SyncQuicMuxer { type OutboundSubstream = quinn::OpenBi; - type Substream = (quinn::SendStream, quinn::RecvStream); - type Error = quinn::ConnectionError; + type Substream = Mutex<(quinn::SendStream, quinn::RecvStream)>; + type Error = std::io::Error; fn poll_inbound(&self, cx: &mut Context) -> Poll> { let mut this = self.0.lock().unwrap(); match Pin::new(&mut this.driver).poll(cx) { Poll::Ready(Ok(())) => unreachable!( "ConnectionDriver will not resolve as long as `self.bi_streams` is alive" ), - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), Poll::Pending => (), } match Pin::new(&mut this.bi_streams).poll_next(cx) { - Poll::Ready(Some(stream)) => Poll::Ready(stream), - Poll::Ready(None) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), + Poll::Ready(Some(stream)) => Poll::Ready(stream.map(Mutex::new).map_err(From::from)), + Poll::Ready(None) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed.into())), Poll::Pending => Poll::Pending, } } @@ -183,25 +172,26 @@ impl StreamMuxer for SyncQuicMuxer { true } fn write_substream(&self, cx: &mut Context, substream: &mut Self::Substream, buf: &[u8]) -> Poll> { - Pin::new(substream.0).poll_write(buf) + Pin::new(&mut substream.lock().unwrap().0).poll_write(cx, buf) } - fn poll_outbound(&self, cx: &mut Context, _substream: &mut Self::OutboundSubstream) -> Poll> { - unimplemented!() + fn poll_outbound(&self, cx: &mut Context, substream: &mut Self::OutboundSubstream) -> Poll> { + Pin::new(substream).poll(cx).map_err(From::from).map_ok(Mutex::new) } - fn read_substream(&self, cx: &mut Context, _substream: &mut Self::Substream, _buf: &mut [u8]) -> Poll> { - unimplemented!() + fn read_substream(&self, cx: &mut Context, substream: &mut Self::Substream, buf: &mut [u8]) -> Poll> { + Pin::new(&mut substream.lock().unwrap().1).poll_read(cx, buf) } - fn shutdown_substream(&self, cx: &mut Context, _substream: &mut Self::Substream) -> Poll> { - unimplemented!() + fn shutdown_substream(&self, cx: &mut Context, substream: &mut Self::Substream) -> Poll> { + Pin::new(&mut substream.lock().unwrap().0).poll_close(cx) } - fn flush_substream(&self, cx: &mut Context, _substream: &mut Self::Substream) -> Poll> { - unimplemented!() + fn flush_substream(&self, cx: &mut Context, substream: &mut Self::Substream) -> Poll> { + // The actual implementation does nothing. This at least does something. + self.write_substream(cx, substream, b"").map_ok(drop) } - fn flush_all(&self, cx: &mut Context) -> Poll> { - unimplemented!() + fn flush_all(&self, _cx: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } - fn close(&self, cx: &mut Context) -> Poll> { - unimplemented!() + fn close(&self, _cx: &mut Context) -> Poll> { + Poll::Ready(Ok(self.0.lock().unwrap().connection.close(0u32.into(), b""))) } } @@ -223,7 +213,7 @@ impl Transport for QuicConfig { .endpoint_builder .bind(&socket_addr) .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; - tokio::spawn(driver.map_err(drop).compat()); + tokio::task::spawn(driver.map_err(drop)); Ok(QuicIncoming { incoming, addr }) } @@ -240,7 +230,7 @@ impl Transport for QuicConfig { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let (driver, endpoint, _incoming) = + let (_driver, endpoint, _incoming) = self.endpoint_builder .bind(&([0u8; 16], 0u16).into()) .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; @@ -273,245 +263,3 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { _ => Err(()), } } - -/// Listen address information. -#[derive(Debug)] -enum Addresses { - /// A specific address is used to listen. - One(Multiaddr), - /// A set of addresses is used to listen. - Many(Vec<(IpAddr, IpNet, Multiaddr)>), -} - -#[cfg(test)] -mod tests { - use super::{multiaddr_to_socketaddr, Listener, TcpConfig}; - use futures::{ - future::{self, Loop}, - prelude::*, - stream, - }; - use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - transport::ListenerEvent, - Transport, - }; - use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr}, - time::Duration, - }; - use tokio::runtime::current_thread::{self, Runtime}; - use tokio_io; - - #[test] - fn pause_on_error() { - // We create a stream of values and errors and continue polling even after errors - // have been encountered. We count the number of items (including errors) and assert - // that no item has been missed. - let rs = stream::iter_result(vec![Ok(1), Err(1), Ok(1), Err(1)]); - let ls = Listener::new(rs, Duration::from_secs(1)); - let sum = future::loop_fn((0, ls), |(acc, ls)| { - ls.into_future().then(move |item| match item { - Ok((None, _)) => Ok::<_, std::convert::Infallible>(Loop::Break(acc)), - Ok((Some(n), rest)) => Ok(Loop::Continue((acc + n, rest))), - Err((n, rest)) => Ok(Loop::Continue((acc + n, rest))), - }) - }); - assert_eq!(4, current_thread::block_on_all(sum).unwrap()) - } - - #[test] - fn wildcard_expansion() { - let mut listener = TcpConfig::new() - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .expect("listener"); - - // Get the first address. - let addr = listener - .by_ref() - .wait() - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); - - // Process all initial `NewAddress` events and make sure they - // do not contain wildcard address or port. - let server = listener - .take_while(|event| match event { - ListenerEvent::NewAddress(a) => { - let mut iter = a.iter(); - match iter.next().expect("ip address") { - Protocol::Ip4(ip) => assert!(!ip.is_unspecified()), - Protocol::Ip6(ip) => assert!(!ip.is_unspecified()), - other => panic!("Unexpected protocol: {}", other), - } - if let Protocol::Tcp(port) = iter.next().expect("port") { - assert_ne!(0, port) - } else { - panic!("No TCP port in address: {}", a) - } - Ok(true) - } - _ => Ok(false), - }) - .for_each(|_| Ok(())); - - let client = TcpConfig::new().dial(addr).expect("dialer"); - tokio::run( - server - .join(client) - .map(|_| ()) - .map_err(|e| panic!("error: {}", e)), - ) - } - - #[test] - fn multiaddr_to_tcp_conversion() { - use std::net::Ipv6Addr; - - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) - .is_err() - ); - - assert_eq!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/12345".parse::().unwrap()), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/tcp/8080" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), - 8080, - )) - ); - assert_eq!( - multiaddr_to_socketaddr(&"/ip6/::1/tcp/12345".parse::().unwrap()), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/tcp/8080" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, - )), - 8080, - )) - ); - } - - #[test] - fn communicating_between_dialer_and_listener() { - use std::io::Write; - - std::thread::spawn(move || { - let addr = "/ip4/127.0.0.1/tcp/12345".parse::().unwrap(); - let tcp = TcpConfig::new(); - let mut rt = Runtime::new().unwrap(); - let handle = rt.handle(); - let listener = tcp - .listen_on(addr) - .unwrap() - .filter_map(ListenerEvent::into_upgrade) - .for_each(|(sock, _)| { - sock.and_then(|sock| { - // Define what to do with the socket that just connected to us - // Which in this case is read 3 bytes - let handle_conn = tokio_io::io::read_exact(sock, [0; 3]) - .map(|(_, buf)| assert_eq!(buf, [1, 2, 3])) - .map_err(|err| panic!("IO error {:?}", err)); - - // Spawn the future as a concurrent task - handle.spawn(handle_conn).unwrap(); - - Ok(()) - }) - }); - - rt.block_on(listener).unwrap(); - rt.run().unwrap(); - }); - std::thread::sleep(std::time::Duration::from_millis(100)); - let addr = "/ip4/127.0.0.1/tcp/12345".parse::().unwrap(); - let tcp = TcpConfig::new(); - // Obtain a future socket through dialing - let socket = tcp.dial(addr.clone()).unwrap(); - // Define what to do with the socket once it's obtained - let action = socket.then(|sock| -> Result<(), ()> { - sock.unwrap().write(&[0x1, 0x2, 0x3]).unwrap(); - Ok(()) - }); - // Execute the future in our event loop - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(action).unwrap(); - } - - #[test] - fn replace_port_0_in_returned_multiaddr_ipv4() { - let tcp = TcpConfig::new(); - - let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); - assert!(addr.to_string().contains("tcp/0")); - - let new_addr = tcp - .listen_on(addr) - .unwrap() - .wait() - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); - - assert!(!new_addr.to_string().contains("tcp/0")); - } - - #[test] - fn replace_port_0_in_returned_multiaddr_ipv6() { - let tcp = TcpConfig::new(); - - let addr: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); - assert!(addr.to_string().contains("tcp/0")); - - let new_addr = tcp - .listen_on(addr) - .unwrap() - .wait() - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); - - assert!(!new_addr.to_string().contains("tcp/0")); - } - - #[test] - fn larger_addr_denied() { - let tcp = TcpConfig::new(); - - let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" - .parse::() - .unwrap(); - assert!(tcp.listen_on(addr).is_err()); - } -} -struct QuicTransport { - endpoint: quinn::Endpoint, -} From 202ccdafee1c9cb4e5a97312d85a41a56b889bca Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 3 Dec 2019 19:16:48 -0500 Subject: [PATCH 012/202] Compiling (but not working) quinn-proto based libp2p-quic There are plenty of `unimplemented!()` calls, and TLS certificate processing has not even been started, but this should still be a good start. --- Cargo.toml | 1 + transports/quic/Cargo.toml | 4 +- transports/quic/src/lib.rs | 445 +++++++++++++++++++++++++++---------- 3 files changed, 326 insertions(+), 124 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1b55386f256..a578fbe3fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ libp2p-mdns = { version = "0.13.1", path = "misc/mdns" } libp2p-noise = { version = "0.11.0", path = "protocols/noise" } libp2p-tcp = { version = "0.13.0", path = "transports/tcp" } libp2p-websocket = { version = "0.13.0", path = "transports/websocket", optional = true } +libp2p-quic = { version = "0.13.0", path = "transports/quic" } [dev-dependencies] async-std = "1.0" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 467c92c1630..54aad3886ea 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -26,13 +26,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -quinn-proto = "0.4.0" rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.13.1" } log = "0.4.8" -quinn = { git = "https://github.com/daxpedda/quinn.git", rev = "750081f2fcc431e9d7597c387b3d3b62a2a2cbb7" } tokio = { version = "0.2.2", features = ["rt-threaded"] } ipnet = "2.1.0" err-derive = "0.2.1" futures = { version = "0.3.1", package = "futures", features = ["compat"] } futures-core = "0.3.1" +quinn-proto = { git = "https://github.com/djc/quinn" } +async-std = "1.2.0" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9ff403e6526..03853d48a76 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -42,10 +42,8 @@ //! QUIC connections do not need to be upgraded. You will get a compile-time error if you try. //! Instead, you must pass all needed configuration into the constructor. -use futures::{ - future, - prelude::*, -}; +use async_std::net::UdpSocket; +use futures::{channel::mpsc, future, prelude::*}; use ipnet::IpNet; use libp2p_core::{ multiaddr::{ip_to_multiaddr, Multiaddr, Protocol}, @@ -53,13 +51,15 @@ use libp2p_core::{ StreamMuxer, Transport, }; use log::debug; -pub use quinn::{Endpoint, EndpointBuilder, EndpointError, ServerConfig}; +use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ + collections::HashMap, io, net::{IpAddr, SocketAddr}, pin::Pin, - sync::Mutex, + sync::{Arc, Mutex, MutexGuard}, task::{Context, Poll}, + time::Instant, }; /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. @@ -68,25 +68,10 @@ use std::{ /// obtained by libp2p through the tokio reactor. #[derive(Debug, Clone)] pub struct QuicConfig { - /// The underlying QUIC transport config. Quinn provides functions for creating a suitable - /// one. - pub endpoint_builder: EndpointBuilder, - /// The underlying QUIC transport endpoint. - endpoint: Option, + /// The client configuration. Quinn provides functions for making one. + pub client_configuration: quinn_proto::ClientConfig, /// The server configuration. Quinn provides functions for making one. - pub server_configuration: ServerConfig, -} - -/// An error in the QUIC transport -#[derive(Debug, err_derive::Error)] -pub enum QuicError { - /// An I/O error - #[error(display = "Endpoint error: {}", _0)] - EndpointError(#[source] quinn::EndpointError), - #[error(display = "QUIC Protocol Error: {}", _0)] - ConnectionError(#[source] quinn::ConnectionError), - #[error(display = "QUIC outbound connection error: {}", _0)] - ConnectError(#[source] quinn::ConnectError), + pub server_configuration: quinn_proto::ServerConfig, } impl QuicConfig { @@ -102,105 +87,323 @@ impl Default for QuicConfig { } } -pub struct QuicIncoming { - /// This field uses structural pinning… - incoming: quinn::Incoming, - /// but this field does not. - addr: Multiaddr, +#[derive(Debug)] +pub struct Muxer { + endpoint: Arc, + connection: Connection, + handle: ConnectionHandle, + channel: mpsc::UnboundedReceiver, + /// Tasks blocked on writing + writers: HashMap>, + /// Tasks blocked on reading + readers: HashMap>, + /// Tasks waiting for new connections + acceptors: Vec, + /// Tasks waiting to make a connection + connectors: Vec, +} + +#[derive(Debug)] +struct Endpoint { + inner: Mutex, + socket: UdpSocket, + hashtable: Mutex>>, + channel: mpsc::Sender, } -type CompatConnecting = future::MapErr QuicError>; - -impl futures_core::stream::Stream for QuicIncoming { - type Item = Result, QuicError>; - fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { - match Pin::new(&mut self.incoming).poll_next(ctx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(upgrade)) => { - let peer = upgrade.remote_address(); - Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - remote_addr: ip_to_multiaddr( - peer.ip(), - &[Protocol::Udp(peer.port()), Protocol::Quic], - ), - upgrade: upgrade.map_err(QuicError::ConnectionError as _), - local_addr: self.addr.clone(), - }))) +impl Muxer { + /// Send all outgoing packets + async fn transmit(&mut self) -> Result<(), async_std::io::Error> { + while let Some(quinn_proto::Transmit { + destination, + ecn, + contents, + }) = self.connection.poll_transmit(Instant::now()) + { + drop(ecn); + self.endpoint + .socket + .send_to(&*contents, destination) + .await?; + } + Ok(()) + } + + /// Process all endpoint-facing events for this connection. This is synchronous and will not + /// fail. + fn send_to_endpoint(&mut self) { + let mut endpoint = self.endpoint.inner.lock().unwrap(); + while let Some(endpoint_event) = self.connection.poll_endpoint_events() { + if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { + self.connection.handle_event(connection_event) } - Poll::Ready(None) => Poll::Ready(None), } } + + /// Process application events + fn process_app_events(&mut self) { + use quinn_proto::Event; + self.send_to_endpoint(); + while let Some(event) = self.connection.poll() { + match event { + Event::Connected => { + panic!("is not emitted more than once; we already received it; qed") + } + Event::DatagramSendUnblocked => { + panic!("we never try to send datagrams, so this will never happen; qed") + } + Event::StreamOpened { dir: Dir::Uni } => { + let id = self + .connection + .accept(Dir::Uni) + .expect("we just received an incoming connection; qed"); + self.connection + .stop_sending(id, Default::default()) + .expect("we just accepted this stream, so we know it; qed") + } + Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, + Event::StreamReadable { stream } => drop( + self.readers + .get_mut(&stream) + .and_then(|s| s.pop()) + .map(|w| w.wake()), + ), + Event::StreamWritable { stream } => drop( + self.writers + .get_mut(&stream) + .and_then(|s| s.pop()) + .map(|w| w.wake()), + ), + Event::StreamAvailable { dir: Dir::Bi } => { + drop(self.acceptors.pop().map(|w| w.wake())) + } + Event::ConnectionLost { .. } => break, // nothing more + Event::StreamFinished { .. } => unimplemented!(), + Event::StreamOpened { dir: Dir::Bi } => { + drop(self.connectors.pop().map(|w| w.wake())) + } + } + } + } + + async fn process(&mut self) { + while let Some(s) = self.channel.next().await { + self.connection.handle_event(s) + } + } +} + +#[derive(Debug)] +pub enum FutureMuxer { + Connecting(Option, Muxer), + Connected, } -struct QuicMuxer { - bi_streams: quinn::IncomingBiStreams, - connection: quinn::Connection, - driver: quinn::ConnectionDriver, +impl Future for FutureMuxer { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let inner: &mut _ = Pin::into_inner(self); + match inner { + FutureMuxer::Connected => panic!("polled after yielding Ready"), + FutureMuxer::Connecting(ref mut waker, _) => unimplemented!(), + } + } + // self.0. + // let FutureMuxer(ref mut muxer) = self.into_inner(); + // let muxer = muxer.as_mut().expect("polled after yielding Ready"); + // match muxer.connection.poll() { + // Some(Event::Connected) => true, + // Some(_) => panic!("`Event::Connected` is the first event emitted; we have not seen it yet, so we will not see any other events; qed"), + // None => + // + // + // if self.has_connected { + // Poll::Ready(self.clone()); + // match self.as_mut().connection_waker { + // None => Poll::Ready( } -pub struct SyncQuicMuxer(Mutex); +#[derive(Debug)] +pub struct QuicMuxer(Mutex); + +impl QuicMuxer { + fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { + self.0 + .lock() + .expect("we already panicked, so are in an inconsistent state; qed") + } +} -// FIXME: if quinn ever starts using `!Unpin` futures, this will require `unsafe` code. -// Will probably fall back to spawning the connection driver in this case 🙁 -impl StreamMuxer for SyncQuicMuxer { - type OutboundSubstream = quinn::OpenBi; - type Substream = Mutex<(quinn::SendStream, quinn::RecvStream)>; +impl StreamMuxer for QuicMuxer { + type OutboundSubstream = (); + type Substream = quinn_proto::StreamId; type Error = std::io::Error; + fn open_outbound(&self) {} + fn destroy_outbound(&self, (): ()) {} + fn destroy_substream(&self, substream: Self::Substream) {} + fn is_remote_acknowledged(&self) -> bool { + true + } + fn poll_inbound(&self, cx: &mut Context) -> Poll> { - let mut this = self.0.lock().unwrap(); - match Pin::new(&mut this.driver).poll(cx) { - Poll::Ready(Ok(())) => unreachable!( - "ConnectionDriver will not resolve as long as `self.bi_streams` is alive" - ), - Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), - Poll::Pending => (), + let mut inner = self.inner(); + match inner.connection.accept(quinn_proto::Dir::Bi) { + None => { + inner.acceptors.push(cx.waker().clone()); + Poll::Pending + } + Some(stream) => { + inner.send_to_endpoint(); + Poll::Ready(Ok(stream)) + } } - match Pin::new(&mut this.bi_streams).poll_next(cx) { - Poll::Ready(Some(stream)) => Poll::Ready(stream.map(Mutex::new).map_err(From::from)), - Poll::Ready(None) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed.into())), - Poll::Pending => Poll::Pending, - } } - fn open_outbound(&self) -> Self::OutboundSubstream { - self.0.lock().unwrap().connection.open_bi() + + fn write_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + buf: &[u8], + ) -> Poll> { + use quinn_proto::WriteError; + let mut inner = self.inner(); + match inner.connection.write(*substream, buf) { + Ok(bytes) => { + inner.send_to_endpoint(); + Poll::Ready(Ok(bytes)) + } + Err(WriteError::Blocked) => { + inner + .writers + .entry(*substream) + .or_insert(vec![]) + .push(cx.waker().clone()); + Poll::Pending + } + Err(WriteError::UnknownStream) => { + panic!("libp2p never uses a closed stream, so this cannot happen; qed") + } + Err(WriteError::Stopped { error_code: _ }) => { + Poll::Ready(Err(std::io::ErrorKind::ConnectionAborted.into())) + } + } + } + + fn poll_outbound( + &self, + cx: &mut Context, + substream: &mut Self::OutboundSubstream, + ) -> Poll> { + let mut inner = self.inner(); + match inner.connection.open(Dir::Bi) { + None => { + inner.connectors.push(cx.waker().clone()); + Poll::Pending + } + Some(id) => { + inner.send_to_endpoint(); + Poll::Ready(Ok(id)) + } + } + } + + fn read_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + buf: &mut [u8], + ) -> Poll> { + use quinn_proto::ReadError; + let mut inner = self.inner(); + match inner.connection.read(*substream, buf) { + Ok(Some(bytes)) => { + inner.send_to_endpoint(); + Poll::Ready(Ok(bytes)) + } + Ok(None) => unimplemented!(), + Err(ReadError::Blocked) => { + inner + .readers + .entry(*substream) + .or_insert(vec![]) + .push(cx.waker().clone()); + Poll::Pending + } + Err(ReadError::UnknownStream) => { + panic!("libp2p never uses a closed stream, so this cannot happen; qed") + } + Err(ReadError::Reset { error_code: _ }) => { + Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) + } + } + } + + fn shutdown_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + ) -> Poll> { + Poll::Ready( + self.inner() + .connection + .finish(*substream) + .map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => { + panic!("libp2p never uses a closed stream, so this cannot happen; qed") + } + quinn_proto::FinishError::Stopped { .. } => { + io::ErrorKind::ConnectionReset.into() + } + }), + ) } - fn destroy_substream(&self, _substream: Self::Substream) { - } - fn destroy_outbound(&self, _substream: Self::OutboundSubstream) { - } - fn is_remote_acknowledged(&self) -> bool { - true - } - fn write_substream(&self, cx: &mut Context, substream: &mut Self::Substream, buf: &[u8]) -> Poll> { - Pin::new(&mut substream.lock().unwrap().0).poll_write(cx, buf) - } - fn poll_outbound(&self, cx: &mut Context, substream: &mut Self::OutboundSubstream) -> Poll> { - Pin::new(substream).poll(cx).map_err(From::from).map_ok(Mutex::new) - } - fn read_substream(&self, cx: &mut Context, substream: &mut Self::Substream, buf: &mut [u8]) -> Poll> { - Pin::new(&mut substream.lock().unwrap().1).poll_read(cx, buf) - } - fn shutdown_substream(&self, cx: &mut Context, substream: &mut Self::Substream) -> Poll> { - Pin::new(&mut substream.lock().unwrap().0).poll_close(cx) - } - fn flush_substream(&self, cx: &mut Context, substream: &mut Self::Substream) -> Poll> { - // The actual implementation does nothing. This at least does something. - self.write_substream(cx, substream, b"").map_ok(drop) - } + + fn flush_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + ) -> Poll> { + self.write_substream(cx, substream, b"").map_ok(drop) + } + fn flush_all(&self, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - fn close(&self, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(self.0.lock().unwrap().connection.close(0u32.into(), b""))) - } + Poll::Ready(Ok(())) + } + + fn close(&self, cx: &mut Context) -> Poll> { + Poll::Ready(Ok(self.inner().connection.close( + Instant::now(), + Default::default(), + Default::default(), + ))) + } +} + +impl Stream for Endpoint { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let mut inner = self.inner.lock().unwrap(); + // inner.pending_connections -= 1; + inner.accept(); + unimplemented!() + } +} + +#[derive(Debug)] +struct QuicEndpoint(Arc); + +impl QuicEndpoint { + fn inner(&self) -> MutexGuard<'_, quinn_proto::Endpoint> { + self.0.inner.lock().unwrap() + } } impl Transport for QuicConfig { - type Output = quinn::NewConnection; - type Error = QuicError; - type Listener = QuicIncoming; - type ListenerUpgrade = CompatConnecting; - type Dial = CompatConnecting; + type Output = QuicMuxer; + type Error = io::Error; + type Listener = mpsc::Receiver, Self::Error>>; + type ListenerUpgrade = future::Ready>; + type Dial = FutureMuxer; fn listen_on(self, addr: Multiaddr) -> Result> { let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { @@ -209,36 +412,34 @@ impl Transport for QuicConfig { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let (driver, _endpoint, incoming) = self - .endpoint_builder - .bind(&socket_addr) - .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; - tokio::task::spawn(driver.map_err(drop)); - Ok(QuicIncoming { incoming, addr }) + // NOT blocking, as per man:bind(2) + let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); + Endpoint { + socket, + inner: Mutex::new( + quinn_proto::Endpoint::new(Default::default(), None) + .expect("the default config is valid; qed"), + ), + hashtable: Mutex::new(HashMap::new()), + channel: unimplemented!(), + }; + unimplemented!() } fn dial(self, addr: Multiaddr) -> Result> { let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::Other(QuicError::EndpointError( - EndpointError::Socket(io::ErrorKind::ConnectionRefused.into()), - ))); + return Err(TransportError::Other( + io::ErrorKind::ConnectionRefused.into(), + )); } socket_addr } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let (_driver, endpoint, _incoming) = - self.endpoint_builder - .bind(&([0u8; 16], 0u16).into()) - .map_err(|e| TransportError::Other(QuicError::EndpointError(e)))?; - - Ok(endpoint - .connect(&socket_addr, &socket_addr.to_string()) - .map_err(QuicError::ConnectError)? - .map_err(QuicError::ConnectionError as _)) + unimplemented!() } } From 3a0ba6622ac9f7504dbb95a357d0b458efeac069 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 5 Dec 2019 14:21:37 -0500 Subject: [PATCH 013/202] More progress on libp2p-quic The code compiles and is approaching a point at which it could be useful. --- transports/quic/Cargo.toml | 1 + transports/quic/src/lib.rs | 512 ++++++++++++++++++++++++++++--------- 2 files changed, 399 insertions(+), 114 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 54aad3886ea..8637e96a845 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -36,3 +36,4 @@ futures = { version = "0.3.1", package = "futures", features = ["compat"] } futures-core = "0.3.1" quinn-proto = { git = "https://github.com/djc/quinn" } async-std = "1.2.0" +async-macros = "2.0.0" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 03853d48a76..a9dd28610f6 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -27,11 +27,11 @@ //! Example: //! //! ``` -//! extern crate libp2p_tcp; -//! use libp2p_tcp::QuicConfig; +//! extern crate libp2p_quic; +//! use libp2p_quic::QuicConfig; //! //! # fn main() { -//! let tcp = QuicConfig::new(); +//! let quic = QuicConfig::new(); //! # } //! ``` //! @@ -41,12 +41,19 @@ //! Note that QUIC provides transport, security, and multiplexing in a single protocol. Therefore, //! QUIC connections do not need to be upgraded. You will get a compile-time error if you try. //! Instead, you must pass all needed configuration into the constructor. +//! +//! # Design Notes +//! +//! The entry point is the `QuicEndpoint` struct. It represents a single QUIC endpoint. You +//! should generally have one of these per process. +//! +//! `QuicEndpoint` manages a background task that processes all socket I/O. This includes: +use async_macros::ready; use async_std::net::UdpSocket; use futures::{channel::mpsc, future, prelude::*}; -use ipnet::IpNet; use libp2p_core::{ - multiaddr::{ip_to_multiaddr, Multiaddr, Protocol}, + multiaddr::{Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; @@ -55,9 +62,9 @@ use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, io, - net::{IpAddr, SocketAddr}, + net::SocketAddr, pin::Pin, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex, MutexGuard, Weak}, task::{Context, Poll}, time::Instant, }; @@ -74,6 +81,11 @@ pub struct QuicConfig { pub server_configuration: quinn_proto::ServerConfig, } +pub struct QuicSubstream { + stream: StreamId, + transmit: Option, +} + impl QuicConfig { /// Creates a new configuration object for TCP/IP. pub fn new() -> Self { @@ -91,24 +103,44 @@ impl Default for QuicConfig { pub struct Muxer { endpoint: Arc, connection: Connection, + /// Connection handle handle: ConnectionHandle, - channel: mpsc::UnboundedReceiver, /// Tasks blocked on writing - writers: HashMap>, + writers: HashMap, /// Tasks blocked on reading - readers: HashMap>, + readers: HashMap, /// Tasks waiting for new connections acceptors: Vec, /// Tasks waiting to make a connection connectors: Vec, + /// Channel to request I/O + sender: mpsc::Sender, +} + +#[derive(Debug)] +struct EndpointInner { + inner: quinn_proto::Endpoint, + muxers: HashMap>>, } +/// A QUIC endpoint. You generally need only one of these per process. However, performance may +/// be better if you have one per CPU core. #[derive(Debug)] -struct Endpoint { - inner: Mutex, +pub struct Endpoint { + /// The single UDP socket used for I/O socket: UdpSocket, - hashtable: Mutex>>, - channel: mpsc::Sender, + /// A `Mutex` protecting the QUIC state machine. + inner: Mutex, + /// A channel used to receive messages from the `Muxer`s. + receiver: mpsc::Receiver, + /// The sending side of said channel. A clone of this is included in every `Muxer` created by + /// this `Endpoint`. + sender: mpsc::Sender, + /// The channel on which new connections are sent. This is bounded in practice by the accept + /// backlog. + new_connections: mpsc::UnboundedSender<(ConnectionHandle, Connection)>, + /// The channel used to receive new connections. + receive_connections: mpsc::UnboundedReceiver<(ConnectionHandle, Connection)>, } impl Muxer { @@ -131,19 +163,19 @@ impl Muxer { /// Process all endpoint-facing events for this connection. This is synchronous and will not /// fail. - fn send_to_endpoint(&mut self) { - let mut endpoint = self.endpoint.inner.lock().unwrap(); + fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { while let Some(endpoint_event) = self.connection.poll_endpoint_events() { - if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { + if let Some(connection_event) = endpoint.inner.handle_event(self.handle, endpoint_event) + { self.connection.handle_event(connection_event) } } } /// Process application events - fn process_app_events(&mut self) { + fn process_app_events(&mut self, endpoint: &mut EndpointInner) { use quinn_proto::Event; - self.send_to_endpoint(); + self.send_to_endpoint(endpoint); while let Some(event) = self.connection.poll() { match event { Event::Connected => { @@ -162,69 +194,33 @@ impl Muxer { .expect("we just accepted this stream, so we know it; qed") } Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, - Event::StreamReadable { stream } => drop( - self.readers - .get_mut(&stream) - .and_then(|s| s.pop()) - .map(|w| w.wake()), - ), - Event::StreamWritable { stream } => drop( - self.writers - .get_mut(&stream) - .and_then(|s| s.pop()) - .map(|w| w.wake()), - ), + Event::StreamReadable { stream } => { + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.readers.remove_entry(&stream) { + waker.wake() + } + } + Event::StreamWritable { stream } => { + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + } Event::StreamAvailable { dir: Dir::Bi } => { - drop(self.acceptors.pop().map(|w| w.wake())) + let _: Option<()> = self.acceptors.pop().map(|w| w.wake()); } Event::ConnectionLost { .. } => break, // nothing more Event::StreamFinished { .. } => unimplemented!(), Event::StreamOpened { dir: Dir::Bi } => { - drop(self.connectors.pop().map(|w| w.wake())) + let _: Option<()> = self.connectors.pop().map(|w| w.wake()); } } } } - - async fn process(&mut self) { - while let Some(s) = self.channel.next().await { - self.connection.handle_event(s) - } - } } #[derive(Debug)] -pub enum FutureMuxer { - Connecting(Option, Muxer), - Connected, -} - -impl Future for FutureMuxer { - type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let inner: &mut _ = Pin::into_inner(self); - match inner { - FutureMuxer::Connected => panic!("polled after yielding Ready"), - FutureMuxer::Connecting(ref mut waker, _) => unimplemented!(), - } - } - // self.0. - // let FutureMuxer(ref mut muxer) = self.into_inner(); - // let muxer = muxer.as_mut().expect("polled after yielding Ready"); - // match muxer.connection.poll() { - // Some(Event::Connected) => true, - // Some(_) => panic!("`Event::Connected` is the first event emitted; we have not seen it yet, so we will not see any other events; qed"), - // None => - // - // - // if self.has_connected { - // Poll::Ready(self.clone()); - // match self.as_mut().connection_waker { - // None => Poll::Ready( -} - -#[derive(Debug)] -pub struct QuicMuxer(Mutex); +pub struct QuicMuxer(Arc>); impl QuicMuxer { fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { @@ -236,7 +232,7 @@ impl QuicMuxer { impl StreamMuxer for QuicMuxer { type OutboundSubstream = (); - type Substream = quinn_proto::StreamId; + type Substream = StreamId; type Error = std::io::Error; fn open_outbound(&self) {} fn destroy_outbound(&self, (): ()) {} @@ -252,10 +248,7 @@ impl StreamMuxer for QuicMuxer { inner.acceptors.push(cx.waker().clone()); Poll::Pending } - Some(stream) => { - inner.send_to_endpoint(); - Poll::Ready(Ok(stream)) - } + Some(stream) => Poll::Ready(Ok(stream)), } } @@ -266,18 +259,22 @@ impl StreamMuxer for QuicMuxer { buf: &[u8], ) -> Poll> { use quinn_proto::WriteError; + let mut inner = self.inner(); + ready!(inner.sender.poll_ready(cx).map_err(|_| panic!( + "we have a strong reference to the other end, so it won’t be dropped; qed" + ))); match inner.connection.write(*substream, buf) { Ok(bytes) => { - inner.send_to_endpoint(); + if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + // UDP is unreliable, so the occasional dropped packet is fine. + drop(inner.sender.start_send(transmit)) + } Poll::Ready(Ok(bytes)) } + Err(WriteError::Blocked) => { - inner - .writers - .entry(*substream) - .or_insert(vec![]) - .push(cx.waker().clone()); + inner.writers.insert(*substream, cx.waker().clone()); Poll::Pending } Err(WriteError::UnknownStream) => { @@ -300,10 +297,7 @@ impl StreamMuxer for QuicMuxer { inner.connectors.push(cx.waker().clone()); Poll::Pending } - Some(id) => { - inner.send_to_endpoint(); - Poll::Ready(Ok(id)) - } + Some(id) => Poll::Ready(Ok(id)), } } @@ -315,18 +309,20 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); + ready!(inner.sender.poll_ready(cx).map_err(|_| panic!( + "we have a strong reference to the other end, so it won’t be dropped; qed" + ))); match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { - inner.send_to_endpoint(); + if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + // UDP is unreliable, so the occasional dropped packet is fine. + drop(inner.sender.start_send(transmit)) + } Poll::Ready(Ok(bytes)) } - Ok(None) => unimplemented!(), + Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) => { - inner - .readers - .entry(*substream) - .or_insert(vec![]) - .push(cx.waker().clone()); + inner.readers.insert(*substream, cx.waker().clone()); Poll::Pending } Err(ReadError::UnknownStream) => { @@ -384,45 +380,134 @@ impl Stream for Endpoint { fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let mut inner = self.inner.lock().unwrap(); // inner.pending_connections -= 1; - inner.accept(); + inner.inner.accept(); unimplemented!() } } -#[derive(Debug)] -struct QuicEndpoint(Arc); +impl Endpoint { + fn inner(&self) -> MutexGuard<'_, EndpointInner> { + self.inner + .lock() + .expect("we don’t panic here unless something has already gone horribly wrong") + } + + /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. + async fn process_packet(&self) -> Result<(), io::Error> { + loop { + use quinn_proto::DatagramEvent; + let mut buf = [0; 1800]; + let (bytes, peer) = self.socket.recv_from(&mut buf[..]).await?; + // This takes a mutex, so it must be *after* the `await` call. + let mut inner = self.inner(); + let (handle, event) = + match inner + .inner + .handle(Instant::now(), peer, None, buf[..bytes].into()) + { + Some(e) => e, + None => continue, + }; + let connection = match event { + DatagramEvent::ConnectionEvent(connection_event) => { + let connection = match inner.muxers.get(&handle) { + None => panic!("received a ConnectionEvent for an unknown Connection"), + Some(e) => match e.upgrade() { + Some(e) => e, + None => continue, // FIXME should this be a panic? + }, + }; + (*connection).lock().unwrap().process_app_events(&mut inner); + continue; + } + DatagramEvent::NewConnection(connection) => { + drop(inner); + connection + } + }; + self.new_connections.unbounded_send((handle, connection)).expect("this is an unbounded channel, and we have an instance of the peer, so this will never fail; qed") + } + } +} + +#[derive(Debug, Clone)] +struct QuicEndpoint(Arc, Multiaddr); impl QuicEndpoint { - fn inner(&self) -> MutexGuard<'_, quinn_proto::Endpoint> { + fn inner(&self) -> MutexGuard<'_, EndpointInner> { self.0.inner.lock().unwrap() } + + pub fn new(addr: Multiaddr) -> Result::Error>> { + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { + sa + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + // NOT blocking, as per man:bind(2) + let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); + let (sender, receiver) = mpsc::channel(0); + let (new_connections, receive_connections) = mpsc::unbounded(); + Ok(Self( + Arc::new(Endpoint { + socket, + inner: Mutex::new(EndpointInner { + inner: quinn_proto::Endpoint::new(Default::default(), None) + .expect("the default config is valid; qed"), + muxers: HashMap::new(), + }), + sender, + receiver, + new_connections, + receive_connections, + }), + addr, + )) + } + + fn dispatch_new_connection( + &self, + handle: ConnectionHandle, + connection: Connection, + ) -> QuicMuxer { + let endpoint = self.0.clone(); + let muxer = Arc::new(Mutex::new(Muxer { + connection, + handle, + writers: HashMap::new(), + readers: HashMap::new(), + acceptors: vec![], + connectors: vec![], + sender: endpoint.sender.clone(), + endpoint, + })); + self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); + QuicMuxer(muxer) + } +} + +struct QuicConnecting { + endpoint: Arc, +} + +impl Future for QuicConnecting { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + unimplemented!() + } } -impl Transport for QuicConfig { +impl Transport for QuicEndpoint { type Output = QuicMuxer; type Error = io::Error; type Listener = mpsc::Receiver, Self::Error>>; type ListenerUpgrade = future::Ready>; - type Dial = FutureMuxer; + type Dial = QuicConnecting; fn listen_on(self, addr: Multiaddr) -> Result> { - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { - sa - } else { + if addr != self.1 { return Err(TransportError::MultiaddrNotSupported(addr)); - }; - - // NOT blocking, as per man:bind(2) - let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); - Endpoint { - socket, - inner: Mutex::new( - quinn_proto::Endpoint::new(Default::default(), None) - .expect("the default config is valid; qed"), - ), - hashtable: Mutex::new(HashMap::new()), - channel: unimplemented!(), - }; + } unimplemented!() } @@ -464,3 +549,202 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { _ => Err(()), } } + +#[cfg(any())] +#[cfg(test)] +mod tests { + use super::{multiaddr_to_socketaddr, QuicConfig}; + use futures::prelude::*; + use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::ListenerEvent, + Transport, + }; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + #[test] + fn wildcard_expansion() { + let mut listener = QuicConfig::new() + .listen_on("/ip4/0.0.0.0/udp/0/quic".parse().unwrap()) + .expect("listener"); + + // Get the first address. + let addr = futures::executor::block_on_stream(listener.by_ref()) + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + // Process all initial `NewAddress` events and make sure they + // do not contain wildcard address or port. + let server = listener + .take_while(|event| match event.as_ref().unwrap() { + ListenerEvent::NewAddress(a) => { + let mut iter = a.iter(); + match iter.next().expect("ip address") { + Protocol::Ip4(ip) => assert!(!ip.is_unspecified()), + Protocol::Ip6(ip) => assert!(!ip.is_unspecified()), + other => panic!("Unexpected protocol: {}", other), + } + if let Protocol::Udp(port) = iter.next().expect("port") { + assert_ne!(0, port) + } else { + panic!("No UDP port in address: {}", a) + } + futures::future::ready(true) + } + _ => futures::future::ready(false), + }) + .for_each(|_| futures::future::ready(())); + + let client = QuicConfig::new().dial(addr).expect("dialer"); + futures::executor::block_on(futures::future::join(server, client)) + .1 + .unwrap(); + } + + #[test] + fn multiaddr_to_udp_conversion() { + use std::net::Ipv6Addr; + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) + .is_err() + ); + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()) + .is_err() + ); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + )) + ); + } + + #[cfg(any())] + #[test] + fn communicating_between_dialer_and_listener() { + let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); + let mut ready_tx = Some(ready_tx); + + async_std::task::spawn(async move { + let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); + let tcp = QuicConfig::new(); + let mut listener = tcp.listen_on(addr).unwrap(); + + loop { + match listener.next().await.unwrap().unwrap() { + ListenerEvent::NewAddress(listen_addr) => { + ready_tx.take().unwrap().send(listen_addr).unwrap(); + } + ListenerEvent::Upgrade { upgrade, .. } => { + let mut upgrade = upgrade.await.unwrap(); + let mut buf = [0u8; 3]; + upgrade.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [1, 2, 3]); + upgrade.write_all(&[4, 5, 6]).await.unwrap(); + } + _ => unreachable!(), + } + } + }); + + async_std::task::block_on(async move { + let addr = ready_rx.await.unwrap(); + let tcp = QuicConfig::new(); + + // Obtain a future socket through dialing + let mut socket = tcp.dial(addr.clone()).unwrap().await.unwrap(); + socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); + + let mut buf = [0u8; 3]; + socket.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [4, 5, 6]); + }); + } + + #[test] + fn replace_port_0_in_returned_multiaddr_ipv4() { + let tcp = QuicConfig::new(); + + let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); + assert!(addr.to_string().contains("tcp/0")); + + let new_addr = futures::executor::block_on_stream(tcp.listen_on(addr).unwrap()) + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); + } + + #[test] + fn replace_port_0_in_returned_multiaddr_ipv6() { + let tcp = QuicConfig::new(); + + let addr: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); + assert!(addr.to_string().contains("tcp/0")); + + let new_addr = futures::executor::block_on_stream(tcp.listen_on(addr).unwrap()) + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); + } + + #[test] + fn larger_addr_denied() { + let tcp = QuicConfig::new(); + + let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" + .parse::() + .unwrap(); + assert!(tcp.listen_on(addr).is_err()); + } +} From 3c27505995e85a757789f5e2f6deebccbf854b8c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 6 Dec 2019 10:03:47 -0500 Subject: [PATCH 014/202] The doc test passes! --- Cargo.toml | 1 + transports/quic/src/lib.rs | 161 +++++++++++++++++++------------------ 2 files changed, 83 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a578fbe3fe9..be319bdd7b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ libp2p-quic = { version = "0.13.0", path = "transports/quic" } [dev-dependencies] async-std = "1.0" env_logger = "0.7.1" +parity-multiaddr = { package = "parity-multiaddr", version = "0.6.0", path = "misc/multiaddr" } [workspace] members = [ diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index a9dd28610f6..1be9fb26f9f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -27,11 +27,16 @@ //! Example: //! //! ``` -//! extern crate libp2p_quic; -//! use libp2p_quic::QuicConfig; +//! use libp2p_quic::{QuicConfig, QuicEndpoint}; +//! use libp2p_core::Multiaddr; //! //! # fn main() { -//! let quic = QuicConfig::new(); +//! let quic_config = QuicConfig::new(); +//! let quic_endpoint = QuicEndpoint::new( +//! &quic_config, +//! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), +//! ) +//! .expect("I/O error"); //! # } //! ``` //! @@ -73,12 +78,14 @@ use std::{ /// /// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams /// obtained by libp2p through the tokio reactor. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct QuicConfig { /// The client configuration. Quinn provides functions for making one. - pub client_configuration: quinn_proto::ClientConfig, + pub client_config: quinn_proto::ClientConfig, /// The server configuration. Quinn provides functions for making one. - pub server_configuration: quinn_proto::ServerConfig, + pub server_config: Arc, + /// The endpoint configuration + pub endpoint_config: Arc, } pub struct QuicSubstream { @@ -93,12 +100,6 @@ impl QuicConfig { } } -impl Default for QuicConfig { - fn default() -> Self { - Self::new() - } -} - #[derive(Debug)] pub struct Muxer { endpoint: Arc, @@ -138,9 +139,9 @@ pub struct Endpoint { sender: mpsc::Sender, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. - new_connections: mpsc::UnboundedSender<(ConnectionHandle, Connection)>, + new_connections: mpsc::UnboundedSender, /// The channel used to receive new connections. - receive_connections: mpsc::UnboundedReceiver<(ConnectionHandle, Connection)>, + receive_connections: mpsc::UnboundedReceiver, } impl Muxer { @@ -261,9 +262,8 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::WriteError; let mut inner = self.inner(); - ready!(inner.sender.poll_ready(cx).map_err(|_| panic!( - "we have a strong reference to the other end, so it won’t be dropped; qed" - ))); + ready!(inner.sender.poll_ready(cx)) + .expect("we have a strong reference to the other end, so it won’t be dropped; qed"); match inner.connection.write(*substream, buf) { Ok(bytes) => { if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { @@ -309,9 +309,8 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - ready!(inner.sender.poll_ready(cx).map_err(|_| panic!( - "we have a strong reference to the other end, so it won’t be dropped; qed" - ))); + ready!(inner.sender.poll_ready(cx)) + .expect("we have a strong reference to the other end, so it won’t be dropped; qed"); match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { @@ -391,13 +390,55 @@ impl Endpoint { .lock() .expect("we don’t panic here unless something has already gone horribly wrong") } +} + +#[derive(Debug, Clone)] +pub struct QuicEndpoint(Arc, Multiaddr); + +impl QuicEndpoint { + fn inner(&self) -> MutexGuard<'_, EndpointInner> { + self.0.inner.lock().unwrap() + } + + pub fn new( + config: &QuicConfig, + addr: Multiaddr, + ) -> Result::Error>> { + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { + sa + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + // NOT blocking, as per man:bind(2) + let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); + let (sender, receiver) = mpsc::channel(0); + let (new_connections, receive_connections) = mpsc::unbounded(); + Ok(Self( + Arc::new(Endpoint { + socket, + inner: Mutex::new(EndpointInner { + inner: quinn_proto::Endpoint::new( + config.endpoint_config.clone(), + Some(config.server_config.clone()), + ) + .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, + muxers: HashMap::new(), + }), + sender, + receiver, + new_connections, + receive_connections, + }), + addr, + )) + } /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. async fn process_packet(&self) -> Result<(), io::Error> { loop { use quinn_proto::DatagramEvent; let mut buf = [0; 1800]; - let (bytes, peer) = self.socket.recv_from(&mut buf[..]).await?; + let (bytes, peer) = self.0.socket.recv_from(&mut buf[..]).await?; // This takes a mutex, so it must be *after* the `await` call. let mut inner = self.inner(); let (handle, event) = @@ -425,68 +466,30 @@ impl Endpoint { connection } }; - self.new_connections.unbounded_send((handle, connection)).expect("this is an unbounded channel, and we have an instance of the peer, so this will never fail; qed") + let endpoint = &self.0; + let muxer = Arc::new(Mutex::new(Muxer { + connection, + handle, + writers: HashMap::new(), + readers: HashMap::new(), + acceptors: vec![], + connectors: vec![], + sender: endpoint.sender.clone(), + endpoint: endpoint.clone(), + })); + self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); + self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); + endpoint.new_connections + .unbounded_send(QuicMuxer(muxer)) + .expect( + "this is an unbounded channel, and we have an instance of the peer, so \ + this will never fail; qed", + ); } } } -#[derive(Debug, Clone)] -struct QuicEndpoint(Arc, Multiaddr); - -impl QuicEndpoint { - fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.0.inner.lock().unwrap() - } - - pub fn new(addr: Multiaddr) -> Result::Error>> { - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { - sa - } else { - return Err(TransportError::MultiaddrNotSupported(addr)); - }; - // NOT blocking, as per man:bind(2) - let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); - let (sender, receiver) = mpsc::channel(0); - let (new_connections, receive_connections) = mpsc::unbounded(); - Ok(Self( - Arc::new(Endpoint { - socket, - inner: Mutex::new(EndpointInner { - inner: quinn_proto::Endpoint::new(Default::default(), None) - .expect("the default config is valid; qed"), - muxers: HashMap::new(), - }), - sender, - receiver, - new_connections, - receive_connections, - }), - addr, - )) - } - - fn dispatch_new_connection( - &self, - handle: ConnectionHandle, - connection: Connection, - ) -> QuicMuxer { - let endpoint = self.0.clone(); - let muxer = Arc::new(Mutex::new(Muxer { - connection, - handle, - writers: HashMap::new(), - readers: HashMap::new(), - acceptors: vec![], - connectors: vec![], - sender: endpoint.sender.clone(), - endpoint, - })); - self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); - QuicMuxer(muxer) - } -} - -struct QuicConnecting { +pub struct QuicConnecting { endpoint: Arc, } From 49ebd43196f4fbf5d0a00e0f3013af0ef3cf7ddd Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 9 Dec 2019 11:19:36 -0500 Subject: [PATCH 015/202] Preserve order of outgoing connections --- Cargo.toml | 1 - transports/quic/src/certificate.rs | 21 +++++++ transports/quic/src/lib.rs | 94 +++++++++++++++++++++--------- 3 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 transports/quic/src/certificate.rs diff --git a/Cargo.toml b/Cargo.toml index be319bdd7b7..a578fbe3fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ libp2p-quic = { version = "0.13.0", path = "transports/quic" } [dev-dependencies] async-std = "1.0" env_logger = "0.7.1" -parity-multiaddr = { package = "parity-multiaddr", version = "0.6.0", path = "misc/multiaddr" } [workspace] members = [ diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs new file mode 100644 index 00000000000..fe20081dbfb --- /dev/null +++ b/transports/quic/src/certificate.rs @@ -0,0 +1,21 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Certificate handling for libp2p diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1be9fb26f9f..2c871e9881f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -56,7 +56,11 @@ use async_macros::ready; use async_std::net::UdpSocket; -use futures::{channel::mpsc, future, prelude::*}; +use futures::{ + channel::{mpsc, oneshot}, + future, + prelude::*, +}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, @@ -100,6 +104,8 @@ impl QuicConfig { } } +type StreamSenderQueue = std::collections::VecDeque>; + #[derive(Debug)] pub struct Muxer { endpoint: Arc, @@ -110,10 +116,10 @@ pub struct Muxer { writers: HashMap, /// Tasks blocked on reading readers: HashMap, - /// Tasks waiting for new connections - acceptors: Vec, + /// Task waiting for new connections + accept_waker: Option, /// Tasks waiting to make a connection - connectors: Vec, + connectors: StreamSenderQueue, /// Channel to request I/O sender: mpsc::Sender, } @@ -174,7 +180,7 @@ impl Muxer { } /// Process application events - fn process_app_events(&mut self, endpoint: &mut EndpointInner) { + fn process_app_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { use quinn_proto::Event; self.send_to_endpoint(endpoint); while let Some(event) = self.connection.poll() { @@ -208,12 +214,21 @@ impl Muxer { } } Event::StreamAvailable { dir: Dir::Bi } => { - let _: Option<()> = self.acceptors.pop().map(|w| w.wake()); + if let Some(w) = self.connectors.pop_front() { + let stream = self.connection.open(Dir::Bi) + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + match w.send(stream) { + Ok(()) => (), + Err(_) => unimplemented!(), + } + } } Event::ConnectionLost { .. } => break, // nothing more Event::StreamFinished { .. } => unimplemented!(), Event::StreamOpened { dir: Dir::Bi } => { - let _: Option<()> = self.connectors.pop().map(|w| w.wake()); + if let Some(w) = self.accept_waker.take() { + w.wake() + } } } } @@ -231,12 +246,43 @@ impl QuicMuxer { } } +#[derive(Debug)] +enum OutboundInner { + Complete(StreamId), + Pending(oneshot::Receiver), +} + +pub struct Outbound(OutboundInner); + +impl Future for Outbound { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + match self.get_mut().0 { + OutboundInner::Complete(s) => Poll::Ready(Ok(s)), + OutboundInner::Pending(ref mut receiver) => Pin::new(receiver) + .poll(cx) + .map_err(|oneshot::Canceled| std::io::ErrorKind::ConnectionAborted.into()), + } + } +} + impl StreamMuxer for QuicMuxer { - type OutboundSubstream = (); + type OutboundSubstream = Outbound; type Substream = StreamId; type Error = std::io::Error; - fn open_outbound(&self) {} - fn destroy_outbound(&self, (): ()) {} + fn open_outbound(&self) -> Self::OutboundSubstream { + let mut inner = self.inner(); + match inner.connection.open(Dir::Bi) { + // optimization: if we can complete synchronously, do so. + Some(id) => Outbound(OutboundInner::Complete(id)), + None => { + let (sender, receiver) = oneshot::channel(); + inner.connectors.push_front(sender); + Outbound(OutboundInner::Pending(receiver)) + } + } + } + fn destroy_outbound(&self, _: Outbound) {} fn destroy_substream(&self, substream: Self::Substream) {} fn is_remote_acknowledged(&self) -> bool { true @@ -246,7 +292,7 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); match inner.connection.accept(quinn_proto::Dir::Bi) { None => { - inner.acceptors.push(cx.waker().clone()); + inner.accept_waker = Some(cx.waker().clone()); Poll::Pending } Some(stream) => Poll::Ready(Ok(stream)), @@ -291,14 +337,7 @@ impl StreamMuxer for QuicMuxer { cx: &mut Context, substream: &mut Self::OutboundSubstream, ) -> Poll> { - let mut inner = self.inner(); - match inner.connection.open(Dir::Bi) { - None => { - inner.connectors.push(cx.waker().clone()); - Poll::Pending - } - Some(id) => Poll::Ready(Ok(id)), - } + Pin::new(substream).poll(cx) } fn read_substream( @@ -335,7 +374,7 @@ impl StreamMuxer for QuicMuxer { fn shutdown_substream( &self, - cx: &mut Context, + _cx: &mut Context, substream: &mut Self::Substream, ) -> Poll> { Poll::Ready( @@ -376,7 +415,7 @@ impl StreamMuxer for QuicMuxer { impl Stream for Endpoint { type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { let mut inner = self.inner.lock().unwrap(); // inner.pending_connections -= 1; inner.inner.accept(); @@ -458,7 +497,8 @@ impl QuicEndpoint { None => continue, // FIXME should this be a panic? }, }; - (*connection).lock().unwrap().process_app_events(&mut inner); + let mut connection = connection.lock().expect("we assume we have not already panicked; qed"); + connection.process_app_events(&mut inner, connection_event); continue; } DatagramEvent::NewConnection(connection) => { @@ -472,14 +512,14 @@ impl QuicEndpoint { handle, writers: HashMap::new(), readers: HashMap::new(), - acceptors: vec![], - connectors: vec![], + accept_waker: None, + connectors: Default::default(), sender: endpoint.sender.clone(), endpoint: endpoint.clone(), })); self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); - self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); - endpoint.new_connections + endpoint + .new_connections .unbounded_send(QuicMuxer(muxer)) .expect( "this is an unbounded channel, and we have an instance of the peer, so \ @@ -495,7 +535,7 @@ pub struct QuicConnecting { impl Future for QuicConnecting { type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { unimplemented!() } } From 974d1171904e9856e5888eb1e32475289a00f1ba Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 9 Dec 2019 17:20:09 -0500 Subject: [PATCH 016/202] Test suite compiles! --- transports/quic/src/lib.rs | 93 +++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 2c871e9881f..d07f3169a19 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -108,7 +108,11 @@ type StreamSenderQueue = std::collections::VecDeque>; #[derive(Debug)] pub struct Muxer { + /// The pending stream, if any. + pending_stream: Option, + /// The associated endpoint endpoint: Arc, + /// The `quinn_proto::Connection` struct. connection: Connection, /// Connection handle handle: ConnectionHandle, @@ -214,17 +218,31 @@ impl Muxer { } } Event::StreamAvailable { dir: Dir::Bi } => { - if let Some(w) = self.connectors.pop_front() { - let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); - match w.send(stream) { - Ok(()) => (), - Err(_) => unimplemented!(), + if self.connectors.is_empty() { + // no task to wake up + return; + } + debug_assert!( + self.pending_stream.is_none(), + "we cannot have both pending tasks and a pending stream; qed" + ); + let stream = self.connection.open(Dir::Bi) + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + while let Some(oneshot) = self.connectors.pop_front() { + match oneshot.send(stream) { + Ok(()) => return, + Err(_) => (), } } + self.pending_stream = Some(stream) } Event::ConnectionLost { .. } => break, // nothing more - Event::StreamFinished { .. } => unimplemented!(), + Event::StreamFinished { stream, .. } => { + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + } Event::StreamOpened { dir: Dir::Bi } => { if let Some(w) = self.accept_waker.take() { w.wake() @@ -272,14 +290,16 @@ impl StreamMuxer for QuicMuxer { type Error = std::io::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); - match inner.connection.open(Dir::Bi) { + if let Some(id) = inner.pending_stream.take() { + // mandatory ― otherwise we will fail an assertion above + Outbound(OutboundInner::Complete(id)) + } else if let Some(id) = inner.connection.open(Dir::Bi) { // optimization: if we can complete synchronously, do so. - Some(id) => Outbound(OutboundInner::Complete(id)), - None => { - let (sender, receiver) = oneshot::channel(); - inner.connectors.push_front(sender); - Outbound(OutboundInner::Pending(receiver)) - } + Outbound(OutboundInner::Complete(id)) + } else { + let (sender, receiver) = oneshot::channel(); + inner.connectors.push_front(sender); + Outbound(OutboundInner::Pending(receiver)) } } fn destroy_outbound(&self, _: Outbound) {} @@ -497,8 +517,10 @@ impl QuicEndpoint { None => continue, // FIXME should this be a panic? }, }; - let mut connection = connection.lock().expect("we assume we have not already panicked; qed"); - connection.process_app_events(&mut inner, connection_event); + let mut connection = connection + .lock() + .expect("we assume we have not already panicked; qed"); + connection.process_app_events(&mut inner, connection_event); continue; } DatagramEvent::NewConnection(connection) => { @@ -508,6 +530,7 @@ impl QuicEndpoint { }; let endpoint = &self.0; let muxer = Arc::new(Mutex::new(Muxer { + pending_stream: None, connection, handle, writers: HashMap::new(), @@ -593,10 +616,9 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { } } -#[cfg(any())] #[cfg(test)] mod tests { - use super::{multiaddr_to_socketaddr, QuicConfig}; + use super::{multiaddr_to_socketaddr, QuicConfig, QuicEndpoint}; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, @@ -607,8 +629,10 @@ mod tests { #[test] fn wildcard_expansion() { - let mut listener = QuicConfig::new() - .listen_on("/ip4/0.0.0.0/udp/0/quic".parse().unwrap()) + let addr: Multiaddr = "/ip4/0.0.0.0/udp/0/quic".parse().unwrap(); + let mut listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + .expect("endpoint") + .listen_on(addr) .expect("listener"); // Get the first address. @@ -641,7 +665,10 @@ mod tests { }) .for_each(|_| futures::future::ready(())); - let client = QuicConfig::new().dial(addr).expect("dialer"); + let client = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + .expect("endpoint") + .dial(addr) + .expect("dialer"); futures::executor::block_on(futures::future::join(server, client)) .1 .unwrap(); @@ -749,12 +776,14 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { - let tcp = QuicConfig::new(); + let quic = QuicConfig::new(); - let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); - assert!(addr.to_string().contains("tcp/0")); + let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); + assert!(addr.to_string().ends_with("udp/0/quic")); - let new_addr = futures::executor::block_on_stream(tcp.listen_on(addr).unwrap()) + let quic = QuicEndpoint::new(&quic, addr.clone()).expect("no error"); + + let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() .expect("some event") .expect("no error") @@ -766,12 +795,13 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { - let tcp = QuicConfig::new(); + let config = QuicConfig::new(); - let addr: Multiaddr = "/ip6/::1/tcp/0".parse().unwrap(); - assert!(addr.to_string().contains("tcp/0")); + let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); + assert!(addr.to_string().contains("udp/0/quic")); + let quic = QuicEndpoint::new(&config, addr.clone()).expect("no error"); - let new_addr = futures::executor::block_on_stream(tcp.listen_on(addr).unwrap()) + let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() .expect("some event") .expect("no error") @@ -783,11 +813,10 @@ mod tests { #[test] fn larger_addr_denied() { - let tcp = QuicConfig::new(); - + let config = QuicConfig::new(); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); - assert!(tcp.listen_on(addr).is_err()); + assert!(QuicEndpoint::new(&config, addr).is_err()) } } From c8ae24115301521664f7f71ee51671edef6fc468 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 11 Dec 2019 18:53:00 -0500 Subject: [PATCH 017/202] Handle making new connections --- transports/quic/src/lib.rs | 205 ++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 80 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index d07f3169a19..e0608faf332 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -73,7 +73,10 @@ use std::{ io, net::SocketAddr, pin::Pin, - sync::{Arc, Mutex, MutexGuard, Weak}, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, Mutex, MutexGuard, Weak, + }, task::{Context, Poll}, time::Instant, }; @@ -120,7 +123,7 @@ pub struct Muxer { writers: HashMap, /// Tasks blocked on reading readers: HashMap, - /// Task waiting for new connections + /// Task waiting for new connections, or for this connection to complete. accept_waker: Option, /// Tasks waiting to make a connection connectors: StreamSenderQueue, @@ -132,46 +135,31 @@ pub struct Muxer { struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, + driver: Option>>, } -/// A QUIC endpoint. You generally need only one of these per process. However, performance may -/// be better if you have one per CPU core. #[derive(Debug)] -pub struct Endpoint { +struct Endpoint { /// The single UDP socket used for I/O socket: UdpSocket, /// A `Mutex` protecting the QUIC state machine. inner: Mutex, - /// A channel used to receive messages from the `Muxer`s. + /// A channel used to receive UDP packets from the `Muxer`s. receiver: mpsc::Receiver, /// The sending side of said channel. A clone of this is included in every `Muxer` created by /// this `Endpoint`. sender: mpsc::Sender, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. - new_connections: mpsc::UnboundedSender, + new_connections: mpsc::UnboundedSender, io::Error>>, /// The channel used to receive new connections. - receive_connections: mpsc::UnboundedReceiver, + receive_connections: + Mutex, io::Error>>>>, + /// The `Multiaddr` + address: Multiaddr, } impl Muxer { - /// Send all outgoing packets - async fn transmit(&mut self) -> Result<(), async_std::io::Error> { - while let Some(quinn_proto::Transmit { - destination, - ecn, - contents, - }) = self.connection.poll_transmit(Instant::now()) - { - drop(ecn); - self.endpoint - .socket - .send_to(&*contents, destination) - .await?; - } - Ok(()) - } - /// Process all endpoint-facing events for this connection. This is synchronous and will not /// fail. fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { @@ -189,21 +177,10 @@ impl Muxer { self.send_to_endpoint(endpoint); while let Some(event) = self.connection.poll() { match event { - Event::Connected => { - panic!("is not emitted more than once; we already received it; qed") - } Event::DatagramSendUnblocked => { panic!("we never try to send datagrams, so this will never happen; qed") } - Event::StreamOpened { dir: Dir::Uni } => { - let id = self - .connection - .accept(Dir::Uni) - .expect("we just received an incoming connection; qed"); - self.connection - .stop_sending(id, Default::default()) - .expect("we just accepted this stream, so we know it; qed") - } + Event::StreamOpened { dir: Dir::Uni } => { /* do nothing */ } Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, Event::StreamReadable { stream } => { // Wake up the task waiting on us (if any) @@ -243,7 +220,8 @@ impl Muxer { waker.wake() } } - Event::StreamOpened { dir: Dir::Bi } => { + // These are separate events, but are handled the same way. + Event::StreamOpened { dir: Dir::Bi } | Event::Connected => { if let Some(w) = self.accept_waker.take() { w.wake() } @@ -439,6 +417,7 @@ impl Stream for Endpoint { let mut inner = self.inner.lock().unwrap(); // inner.pending_connections -= 1; inner.inner.accept(); + eprintln!("panicking!"); unimplemented!() } } @@ -451,49 +430,74 @@ impl Endpoint { } } +/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. +/// +/// You and You generally need only one of these per process. Endpoints are thread-safe, so you +/// can share them among as many threads as you like. However, performance may be better if you +/// have one per CPU core, as this reduces lock contention. Most applications will not need to +/// worry about this. `QuicEndpoint` tries to use fine-grained locking to reduce the overhead. +/// +/// `QuicEndpoint` wraps the underlying data structure in an `Arc`, so cloning it just bumps the +/// reference count. All state is shared between the clones. For example, you can pass different +/// clones to `listen_on`. Each incoming connection will be received by exactly one of them. +/// +/// The **only** valid `Multiaddr` to pass to `listen_on` or `dial` is the one used to create the +/// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you +/// will get `TransportError::MultiaddrNotSuppported`. #[derive(Debug, Clone)] -pub struct QuicEndpoint(Arc, Multiaddr); +pub struct QuicEndpoint(Arc); impl QuicEndpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { self.0.inner.lock().unwrap() } + /// Retrieves the `Multiaddr` of this `QuicEndpoint`. + pub fn addr(&self) -> &Multiaddr { + &self.0.address + } + + /// Construct a `QuicEndpoint` with the given `QuicConfig` and `Multiaddr`. pub fn new( config: &QuicConfig, - addr: Multiaddr, + address: Multiaddr, ) -> Result::Error>> { - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { sa } else { - return Err(TransportError::MultiaddrNotSupported(addr)); + return Err(TransportError::MultiaddrNotSupported(address)); }; - // NOT blocking, as per man:bind(2) + // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (sender, receiver) = mpsc::channel(0); let (new_connections, receive_connections) = mpsc::unbounded(); - Ok(Self( - Arc::new(Endpoint { - socket, - inner: Mutex::new(EndpointInner { - inner: quinn_proto::Endpoint::new( - config.endpoint_config.clone(), - Some(config.server_config.clone()), - ) - .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, - muxers: HashMap::new(), - }), - sender, - receiver, - new_connections, - receive_connections, + Ok(Self(Arc::new(Endpoint { + socket, + inner: Mutex::new(EndpointInner { + inner: quinn_proto::Endpoint::new( + config.endpoint_config.clone(), + Some(config.server_config.clone()), + ) + .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, + muxers: HashMap::new(), + driver: None, }), - addr, - )) + sender, + receiver, + new_connections, + receive_connections: Mutex::new(Some(receive_connections)), + address, + }))) + } + + async fn send_outgoing_packets(&self) -> Result<(), io::Error> { + loop { + let mut inner = self.inner(); + } } /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. - async fn process_packet(&self) -> Result<(), io::Error> { + async fn process_incoming_packets(self) -> Result<(), io::Error> { loop { use quinn_proto::DatagramEvent; let mut buf = [0; 1800]; @@ -523,10 +527,7 @@ impl QuicEndpoint { connection.process_app_events(&mut inner, connection_event); continue; } - DatagramEvent::NewConnection(connection) => { - drop(inner); - connection - } + DatagramEvent::NewConnection(connection) => connection, }; let endpoint = &self.0; let muxer = Arc::new(Mutex::new(Muxer { @@ -540,10 +541,16 @@ impl QuicEndpoint { sender: endpoint.sender.clone(), endpoint: endpoint.clone(), })); - self.inner().muxers.insert(handle, Arc::downgrade(&muxer)); + inner.muxers.insert(handle, Arc::downgrade(&muxer)); endpoint .new_connections - .unbounded_send(QuicMuxer(muxer)) + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade: QuicUpgrade { + muxer: Some(QuicMuxer(muxer)), + }, + local_addr: endpoint.address.clone(), + remote_addr: endpoint.address.clone(), + })) .expect( "this is an unbounded channel, and we have an instance of the peer, so \ this will never fail; qed", @@ -552,29 +559,51 @@ impl QuicEndpoint { } } -pub struct QuicConnecting { - endpoint: Arc, +#[derive(Debug)] +pub struct QuicUpgrade { + muxer: Option, } -impl Future for QuicConnecting { +impl Future for QuicUpgrade { type Output = Result; - fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { - unimplemented!() + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let muxer = &mut self.get_mut().muxer; + { + let mut connection = muxer.as_mut().expect("polled after yielding Ready").inner(); + if connection.connection.is_handshaking() { + connection.accept_waker = Some(cx.waker().clone()); + return Poll::Pending; + } + } + Poll::Ready(Ok(muxer.take().expect("impossible"))) } } impl Transport for QuicEndpoint { type Output = QuicMuxer; type Error = io::Error; - type Listener = mpsc::Receiver, Self::Error>>; - type ListenerUpgrade = future::Ready>; - type Dial = QuicConnecting; + type Listener = + mpsc::UnboundedReceiver, Self::Error>>; + type ListenerUpgrade = QuicUpgrade; + type Dial = QuicUpgrade; fn listen_on(self, addr: Multiaddr) -> Result> { - if addr != self.1 { + if addr != self.0.address { return Err(TransportError::MultiaddrNotSupported(addr)); } - unimplemented!() + let res = (self.0) + .receive_connections + .lock() + .unwrap() + .take() + .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); + let mut inner = self.inner(); + if inner.driver.is_none() { + inner.driver = Some(async_std::task::spawn( + self.clone().process_incoming_packets(), + )) + } + res } fn dial(self, addr: Multiaddr) -> Result> { @@ -589,7 +618,14 @@ impl Transport for QuicEndpoint { } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; + let mut inner = self.inner(); + if inner.driver.is_none() { + inner.driver = Some(async_std::task::spawn( + self.clone().process_incoming_packets(), + )) + } + eprintln!("panicking"); unimplemented!() } } @@ -732,7 +768,7 @@ mod tests { ); } - #[cfg(any())] + #[cfg(any())] #[test] fn communicating_between_dialer_and_listener() { let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); @@ -740,8 +776,15 @@ mod tests { async_std::task::spawn(async move { let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); - let tcp = QuicConfig::new(); - let mut listener = tcp.listen_on(addr).unwrap(); + let quic_config = QuicConfig::new(); + let quic_endpoint = QuicEndpoint::new( + &quic_config, + "/ip4/127.0.0.1/udp/12345/quic" + .parse() + .expect("bad address?"), + ) + .expect("I/O error"); + let mut listener = quic_endpoint.listen_on(addr).unwrap(); loop { match listener.next().await.unwrap().unwrap() { @@ -774,6 +817,7 @@ mod tests { }); } + #[cfg(any())] #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { let quic = QuicConfig::new(); @@ -793,6 +837,7 @@ mod tests { assert!(!new_addr.to_string().contains("tcp/0")); } + #[cfg(any())] #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { let config = QuicConfig::new(); From e47e5dea38c21472fdd36f3432148325d651a241 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 13 Dec 2019 10:52:00 -0500 Subject: [PATCH 018/202] All tests compile --- transports/quic/src/lib.rs | 90 ++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index e0608faf332..9d003f7da62 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -58,7 +58,6 @@ use async_macros::ready; use async_std::net::UdpSocket; use futures::{ channel::{mpsc, oneshot}, - future, prelude::*, }; use libp2p_core::{ @@ -73,10 +72,7 @@ use std::{ io, net::SocketAddr, pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering::SeqCst}, - Arc, Mutex, MutexGuard, Weak, - }, + sync::{Arc, Mutex, MutexGuard, Weak}, task::{Context, Poll}, time::Instant, }; @@ -231,7 +227,7 @@ impl Muxer { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); impl QuicMuxer { @@ -411,14 +407,56 @@ impl StreamMuxer for QuicMuxer { } } -impl Stream for Endpoint { - type Item = Result; - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - let mut inner = self.inner.lock().unwrap(); - // inner.pending_connections -= 1; - inner.inner.accept(); - eprintln!("panicking!"); - unimplemented!() +#[cfg(test)] +#[derive(Debug, Clone)] +pub struct QuicStream { + id: StreamId, + muxer: QuicMuxer, +} + +#[cfg(test)] +impl AsyncWrite for QuicStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + let inner = self.get_mut(); + inner.muxer.write_substream(cx, &mut inner.id, buf) + } + + fn poll_close(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } +} + +#[cfg(test)] +impl AsyncRead for QuicStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut [u8], + ) -> Poll> { + let inner = self.get_mut(); + inner.muxer.read_substream(cx, &mut inner.id, buf) + } +} + +#[cfg(test)] +impl Stream for QuicMuxer { + type Item = QuicStream; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_inbound(cx).map(|x| match x { + Ok(id) => Some(QuicStream { + id, + muxer: self.get_mut().clone(), + }), + Err(_) => None, + }) } } @@ -461,7 +499,7 @@ impl QuicEndpoint { pub fn new( config: &QuicConfig, address: Multiaddr, - ) -> Result::Error>> { + ) -> Result::Error>> { let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { sa } else { @@ -579,7 +617,7 @@ impl Future for QuicUpgrade { } } -impl Transport for QuicEndpoint { +impl Transport for &QuicEndpoint { type Output = QuicMuxer; type Error = io::Error; type Listener = @@ -768,14 +806,13 @@ mod tests { ); } - #[cfg(any())] #[test] fn communicating_between_dialer_and_listener() { let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); async_std::task::spawn(async move { - let addr = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); + let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); let quic_config = QuicConfig::new(); let quic_endpoint = QuicEndpoint::new( &quic_config, @@ -792,7 +829,7 @@ mod tests { ready_tx.take().unwrap().send(listen_addr).unwrap(); } ListenerEvent::Upgrade { upgrade, .. } => { - let mut upgrade = upgrade.await.unwrap(); + let mut upgrade = upgrade.await.unwrap().next().await.unwrap(); let mut buf = [0u8; 3]; upgrade.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [1, 2, 3]); @@ -805,10 +842,17 @@ mod tests { async_std::task::block_on(async move { let addr = ready_rx.await.unwrap(); - let tcp = QuicConfig::new(); - + let quic_config = QuicConfig::new(); + let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).unwrap(); // Obtain a future socket through dialing - let mut socket = tcp.dial(addr.clone()).unwrap().await.unwrap(); + let mut socket = quic_endpoint + .dial(addr.clone()) + .unwrap() + .await + .unwrap() + .next() + .await + .unwrap(); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); let mut buf = [0u8; 3]; @@ -817,7 +861,6 @@ mod tests { }); } - #[cfg(any())] #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { let quic = QuicConfig::new(); @@ -837,7 +880,6 @@ mod tests { assert!(!new_addr.to_string().contains("tcp/0")); } - #[cfg(any())] #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { let config = QuicConfig::new(); From f19dd46ccde4f648cd74a8e2da32e5dbaac3ff74 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 14 Dec 2019 17:46:32 -0500 Subject: [PATCH 019/202] Remove remaining unimplemented!() --- transports/quic/src/lib.rs | 176 ++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9d003f7da62..816b3c09b77 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -91,11 +91,6 @@ pub struct QuicConfig { pub endpoint_config: Arc, } -pub struct QuicSubstream { - stream: StreamId, - transmit: Option, -} - impl QuicConfig { /// Creates a new configuration object for TCP/IP. pub fn new() -> Self { @@ -170,6 +165,7 @@ impl Muxer { /// Process application events fn process_app_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { use quinn_proto::Event; + self.connection.handle_event(event); self.send_to_endpoint(endpoint); while let Some(event) = self.connection.poll() { match event { @@ -277,7 +273,7 @@ impl StreamMuxer for QuicMuxer { } } fn destroy_outbound(&self, _: Outbound) {} - fn destroy_substream(&self, substream: Self::Substream) {} + fn destroy_substream(&self, _substream: Self::Substream) {} fn is_remote_acknowledged(&self) -> bool { true } @@ -398,7 +394,7 @@ impl StreamMuxer for QuicMuxer { Poll::Ready(Ok(())) } - fn close(&self, cx: &mut Context) -> Poll> { + fn close(&self, _cx: &mut Context) -> Poll> { Poll::Ready(Ok(self.inner().connection.close( Instant::now(), Default::default(), @@ -460,14 +456,6 @@ impl Stream for QuicMuxer { } } -impl Endpoint { - fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.inner - .lock() - .expect("we don’t panic here unless something has already gone horribly wrong") - } -} - /// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// /// You and You generally need only one of these per process. Endpoints are thread-safe, so you @@ -483,7 +471,7 @@ impl Endpoint { /// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you /// will get `TransportError::MultiaddrNotSuppported`. #[derive(Debug, Clone)] -pub struct QuicEndpoint(Arc); +pub struct QuicEndpoint(Arc, QuicConfig); impl QuicEndpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { @@ -509,29 +497,48 @@ impl QuicEndpoint { let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (sender, receiver) = mpsc::channel(0); let (new_connections, receive_connections) = mpsc::unbounded(); - Ok(Self(Arc::new(Endpoint { - socket, - inner: Mutex::new(EndpointInner { - inner: quinn_proto::Endpoint::new( - config.endpoint_config.clone(), - Some(config.server_config.clone()), - ) - .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, - muxers: HashMap::new(), - driver: None, + Ok(Self( + Arc::new(Endpoint { + socket, + inner: Mutex::new(EndpointInner { + inner: quinn_proto::Endpoint::new( + config.endpoint_config.clone(), + Some(config.server_config.clone()), + ) + .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, + muxers: HashMap::new(), + driver: None, + }), + sender, + receiver, + new_connections, + receive_connections: Mutex::new(Some(receive_connections)), + address, }), - sender, - receiver, - new_connections, - receive_connections: Mutex::new(Some(receive_connections)), - address, - }))) + config.clone(), + )) } - async fn send_outgoing_packets(&self) -> Result<(), io::Error> { - loop { - let mut inner = self.inner(); - } + fn create_muxer( + &self, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, + ) -> Arc> { + let endpoint = &self.0; + let muxer = Arc::new(Mutex::new(Muxer { + pending_stream: None, + connection, + handle, + writers: HashMap::new(), + readers: HashMap::new(), + accept_waker: None, + connectors: Default::default(), + sender: endpoint.sender.clone(), + endpoint: endpoint.clone(), + })); + inner.muxers.insert(handle, Arc::downgrade(&muxer)); + muxer } /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. @@ -550,7 +557,7 @@ impl QuicEndpoint { Some(e) => e, None => continue, }; - let connection = match event { + match event { DatagramEvent::ConnectionEvent(connection_event) => { let connection = match inner.muxers.get(&handle) { None => panic!("received a ConnectionEvent for an unknown Connection"), @@ -563,36 +570,25 @@ impl QuicEndpoint { .lock() .expect("we assume we have not already panicked; qed"); connection.process_app_events(&mut inner, connection_event); - continue; } - DatagramEvent::NewConnection(connection) => connection, - }; - let endpoint = &self.0; - let muxer = Arc::new(Mutex::new(Muxer { - pending_stream: None, - connection, - handle, - writers: HashMap::new(), - readers: HashMap::new(), - accept_waker: None, - connectors: Default::default(), - sender: endpoint.sender.clone(), - endpoint: endpoint.clone(), - })); - inner.muxers.insert(handle, Arc::downgrade(&muxer)); - endpoint - .new_connections - .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade: QuicUpgrade { - muxer: Some(QuicMuxer(muxer)), - }, - local_addr: endpoint.address.clone(), - remote_addr: endpoint.address.clone(), - })) - .expect( - "this is an unbounded channel, and we have an instance of the peer, so \ - this will never fail; qed", - ); + DatagramEvent::NewConnection(connection) => { + let muxer = self.create_muxer(connection, handle, &mut *inner); + let endpoint = &self.0; + endpoint + .new_connections + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade: QuicUpgrade { + muxer: Some(QuicMuxer(muxer)), + }, + local_addr: endpoint.address.clone(), + remote_addr: endpoint.address.clone(), + })) + .expect( + "this is an unbounded channel, and we have an instance of the peer, so \ + this will never fail; qed", + ); + } + } } } } @@ -663,8 +659,21 @@ impl Transport for &QuicEndpoint { )) } - eprintln!("panicking"); - unimplemented!() + let s: Result<(_, Connection), _> = inner + .inner + .connect( + self.1.client_config.clone(), + socket_addr, + &socket_addr.ip().to_string(), + ) + .map_err(|e| { + eprintln!("Connection error: {:?}", e); + TransportError::Other(io::ErrorKind::InvalidInput.into()) + }); + let (handle, conn) = s?; + Ok(QuicUpgrade { + muxer: Some(QuicMuxer(self.create_muxer(conn, handle, &mut inner))), + }) } } @@ -703,19 +712,16 @@ mod tests { #[test] fn wildcard_expansion() { - let addr: Multiaddr = "/ip4/0.0.0.0/udp/0/quic".parse().unwrap(); + let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let mut listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); - - // Get the first address. - let addr = futures::executor::block_on_stream(listener.by_ref()) - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); + let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); + let client = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + .expect("endpoint") + .dial(addr) + .expect("dialer"); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. @@ -739,10 +745,6 @@ mod tests { }) .for_each(|_| futures::future::ready(())); - let client = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) - .expect("endpoint") - .dial(addr) - .expect("dialer"); futures::executor::block_on(futures::future::join(server, client)) .1 .unwrap(); @@ -812,15 +814,11 @@ mod tests { let mut ready_tx = Some(ready_tx); async_std::task::spawn(async move { - let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); + let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" + .parse() + .expect("bad address?"); let quic_config = QuicConfig::new(); - let quic_endpoint = QuicEndpoint::new( - &quic_config, - "/ip4/127.0.0.1/udp/12345/quic" - .parse() - .expect("bad address?"), - ) - .expect("I/O error"); + let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); loop { From f26323c1b6989489250bff43e0a70505fb532e58 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Dec 2019 18:09:59 -0500 Subject: [PATCH 020/202] Simple fixes --- transports/quic/src/lib.rs | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 816b3c09b77..9bceb27b5e4 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -69,11 +69,14 @@ use log::debug; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, - io, + fmt, io, net::SocketAddr, pin::Pin, sync::{Arc, Mutex, MutexGuard, Weak}, - task::{Context, Poll}, + task::{ + Context, + Poll::{self, Pending, Ready}, + }, time::Instant, }; @@ -542,7 +545,11 @@ impl QuicEndpoint { } /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. - async fn process_incoming_packets(self) -> Result<(), io::Error> { + async fn process_incoming_packets(self, address: Multiaddr) -> Result<(), io::Error> { + self.0 + .new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address))) + .expect("we have a reference to the peer, so this will not fail; qed"); loop { use quinn_proto::DatagramEvent; let mut buf = [0; 1800]; @@ -634,7 +641,7 @@ impl Transport for &QuicEndpoint { let mut inner = self.inner(); if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn( - self.clone().process_incoming_packets(), + self.clone().process_incoming_packets(addr), )) } res @@ -655,17 +662,14 @@ impl Transport for &QuicEndpoint { let mut inner = self.inner(); if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn( - self.clone().process_incoming_packets(), + self.clone() + .process_incoming_packets(self.0.address.clone()), )) } let s: Result<(_, Connection), _> = inner .inner - .connect( - self.1.client_config.clone(), - socket_addr, - &socket_addr.ip().to_string(), - ) + .connect(self.1.client_config.clone(), socket_addr, "localhost") .map_err(|e| { eprintln!("Connection error: {:?}", e); TransportError::Other(io::ErrorKind::InvalidInput.into()) @@ -711,9 +715,10 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; #[test] + #[ignore] // this needs cmsg support from async-std fn wildcard_expansion() { let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let mut listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + let listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); @@ -841,16 +846,15 @@ mod tests { async_std::task::block_on(async move { let addr = ready_rx.await.unwrap(); let quic_config = QuicConfig::new(); - let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).unwrap(); + let quic_endpoint = QuicEndpoint::new( + &quic_config, + "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), + ) + .unwrap(); // Obtain a future socket through dialing - let mut socket = quic_endpoint - .dial(addr.clone()) - .unwrap() - .await - .unwrap() - .next() - .await - .unwrap(); + let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + eprintln!("Received a Connection: {:?}", connection); + let mut socket = connection.next().await.unwrap(); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); let mut buf = [0u8; 3]; From 5ca62ae71a349bcd5ffe3de7e0aef4e79566d9d0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 18 Dec 2019 15:56:57 -0500 Subject: [PATCH 021/202] Implement sending messages This implements message transmission. The code relies on UDP datagram sending not blocking indefinitely. Since UDP does not retransmit packets, this should be a decent assumption in practice. --- Cargo.toml | 2 +- misc/mdns/Cargo.toml | 2 +- misc/rw-stream-sink/Cargo.toml | 2 +- muxers/mplex/Cargo.toml | 2 +- protocols/deflate/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/ping/Cargo.toml | 2 +- protocols/secio/Cargo.toml | 2 +- transports/quic/Cargo.toml | 2 +- transports/quic/src/lib.rs | 89 +++++++++++++++++++++------------- transports/tcp/Cargo.toml | 2 +- transports/uds/Cargo.toml | 2 +- 12 files changed, 67 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a578fbe3fe9..b4de642e364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ libp2p-websocket = { version = "0.13.0", path = "transports/websocket", optional libp2p-quic = { version = "0.13.0", path = "transports/quic" } [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } env_logger = "0.7.1" [workspace] diff --git a/misc/mdns/Cargo.toml b/misc/mdns/Cargo.toml index 31372f0ab00..28568aad0f9 100644 --- a/misc/mdns/Cargo.toml +++ b/misc/mdns/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } data-encoding = "2.0" dns-parser = "0.8" either = "1.5.3" diff --git a/misc/rw-stream-sink/Cargo.toml b/misc/rw-stream-sink/Cargo.toml index 2d4709cfe27..cae062e9562 100644 --- a/misc/rw-stream-sink/Cargo.toml +++ b/misc/rw-stream-sink/Cargo.toml @@ -14,4 +14,4 @@ bytes = "0.4.12" futures = "0.3.1" [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 6dc5bbaaeb8..5bc754bb4e7 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -20,5 +20,5 @@ parking_lot = "0.9" unsigned-varint = { version = "0.2.3", features = ["futures-codec"] } [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } diff --git a/protocols/deflate/Cargo.toml b/protocols/deflate/Cargo.toml index 7bf924ccac2..47101bd1b66 100644 --- a/protocols/deflate/Cargo.toml +++ b/protocols/deflate/Cargo.toml @@ -15,7 +15,7 @@ libp2p-core = { version = "0.13.0", path = "../../core" } flate2 = "1.0" [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } env_logger = "0.7.1" libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } rand = "0.7" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index d997109da3c..8a322418c9c 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -23,7 +23,7 @@ wasm-timer = "0.2" unsigned-varint = { version = "0.2.3", features = ["futures-codec"] } [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } libp2p-mplex = { version = "0.13.0", path = "../../muxers/mplex" } libp2p-secio = { version = "0.13.0", path = "../../protocols/secio" } libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index fedf4f470d0..ecbab480576 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -21,7 +21,7 @@ wasm-timer = "0.2" void = "1.0" [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } libp2p-secio = { version = "0.13.0", path = "../../protocols/secio" } libp2p-yamux = { version = "0.13.0", path = "../../muxers/yamux" } diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index 0dd7fdf9ddf..e4e1e22c825 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -43,7 +43,7 @@ secp256k1 = [] aes-all = ["aesni"] [dev-dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } criterion = "0.3" libp2p-mplex = { version = "0.13.0", path = "../../muxers/mplex" } libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 8637e96a845..66432435fe0 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -35,5 +35,5 @@ err-derive = "0.2.1" futures = { version = "0.3.1", package = "futures", features = ["compat"] } futures-core = "0.3.1" quinn-proto = { git = "https://github.com/djc/quinn" } -async-std = "1.2.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } async-macros = "2.0.0" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9bceb27b5e4..5dd0c428127 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -54,6 +54,7 @@ //! //! `QuicEndpoint` manages a background task that processes all socket I/O. This includes: +#![deny(unsafe_code)] use async_macros::ready; use async_std::net::UdpSocket; use futures::{ @@ -121,8 +122,8 @@ pub struct Muxer { accept_waker: Option, /// Tasks waiting to make a connection connectors: StreamSenderQueue, - /// Channel to request I/O - sender: mpsc::Sender, + /// Pending transmit + pending: Option, } #[derive(Debug)] @@ -138,11 +139,6 @@ struct Endpoint { socket: UdpSocket, /// A `Mutex` protecting the QUIC state machine. inner: Mutex, - /// A channel used to receive UDP packets from the `Muxer`s. - receiver: mpsc::Receiver, - /// The sending side of said channel. A clone of this is included in every `Muxer` created by - /// this `Endpoint`. - sender: mpsc::Sender, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. new_connections: mpsc::UnboundedSender, io::Error>>, @@ -165,6 +161,24 @@ impl Muxer { } } + fn poll_transmit( + &mut self, + cx: &mut Context<'_>, + transmit: quinn_proto::Transmit, + ) -> Poll> { + match self + .endpoint + .socket + .poll_send_to(cx, &transmit.contents, &transmit.destination) + { + Pending => { + self.pending = Some(transmit); + Pending + } + r @ Ready(_) => r, + } + } + /// Process application events fn process_app_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { use quinn_proto::Event; @@ -199,7 +213,7 @@ impl Muxer { "we cannot have both pending tasks and a pending stream; qed" ); let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); while let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { Ok(()) => return, @@ -301,15 +315,15 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::WriteError; let mut inner = self.inner(); - ready!(inner.sender.poll_ready(cx)) - .expect("we have a strong reference to the other end, so it won’t be dropped; qed"); + if let Some(transmit) = inner.pending.take() { + ready!(inner.poll_transmit(cx, transmit))?; + } match inner.connection.write(*substream, buf) { Ok(bytes) => { if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { - // UDP is unreliable, so the occasional dropped packet is fine. - drop(inner.sender.start_send(transmit)) + ready!(inner.poll_transmit(cx, transmit))?; } - Poll::Ready(Ok(bytes)) + Ready(Ok(bytes)) } Err(WriteError::Blocked) => { @@ -341,13 +355,13 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - ready!(inner.sender.poll_ready(cx)) - .expect("we have a strong reference to the other end, so it won’t be dropped; qed"); + if let Some(transmit) = inner.pending.take() { + ready!(inner.poll_transmit(cx, transmit))?; + } match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { - // UDP is unreliable, so the occasional dropped packet is fine. - drop(inner.sender.start_send(transmit)) + ready!(inner.poll_transmit(cx, transmit))?; } Poll::Ready(Ok(bytes)) } @@ -539,19 +553,27 @@ impl QuicEndpoint { connectors: Default::default(), sender: endpoint.sender.clone(), endpoint: endpoint.clone(), + pending: None, })); inner.muxers.insert(handle, Arc::downgrade(&muxer)); muxer } - /// Process incoming UDP packets until an error occurs on the socket, or we are dropped. - async fn process_incoming_packets(self, address: Multiaddr) -> Result<(), io::Error> { + /// Process UDP packets until either an error occurs on the socket or we are dropped. + async fn process_udp_traffic(self, address: Multiaddr) -> Result<(), io::Error> { self.0 .new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address))) .expect("we have a reference to the peer, so this will not fail; qed"); + let mut outgoing_packet = None; loop { use quinn_proto::DatagramEvent; + if let Some(packet) = outgoing_packet.take() { + self.0 + .socket + .send_to(&packet.contents, packet.destination) + .await?; + } let mut buf = [0; 1800]; let (bytes, peer) = self.0.socket.recv_from(&mut buf[..]).await?; // This takes a mutex, so it must be *after* the `await` call. @@ -577,23 +599,24 @@ impl QuicEndpoint { .lock() .expect("we assume we have not already panicked; qed"); connection.process_app_events(&mut inner, connection_event); + outgoing_packet = connection.poll_transmit() } DatagramEvent::NewConnection(connection) => { let muxer = self.create_muxer(connection, handle, &mut *inner); let endpoint = &self.0; endpoint - .new_connections - .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade: QuicUpgrade { - muxer: Some(QuicMuxer(muxer)), - }, - local_addr: endpoint.address.clone(), - remote_addr: endpoint.address.clone(), - })) - .expect( - "this is an unbounded channel, and we have an instance of the peer, so \ - this will never fail; qed", - ); + .new_connections + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade: QuicUpgrade { + muxer: Some(QuicMuxer(muxer)), + }, + local_addr: endpoint.address.clone(), + remote_addr: endpoint.address.clone(), + })) + .expect( + "this is an unbounded channel, and we have an instance of the peer, so \ + this will never fail; qed", + ); } } } @@ -641,7 +664,7 @@ impl Transport for &QuicEndpoint { let mut inner = self.inner(); if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn( - self.clone().process_incoming_packets(addr), + self.clone().process_udp_packets(addr), )) } res @@ -663,7 +686,7 @@ impl Transport for &QuicEndpoint { if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn( self.clone() - .process_incoming_packets(self.0.address.clone()), + .process_udp_packets(self.0.address.clone()), )) } diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 12244e13f39..cf45e9f840c 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } bytes = "0.4.12" futures = "0.3.1" futures-timer = "2.0" diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index 8bb00b683ea..d8cdef3eb24 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [target.'cfg(all(unix, not(any(target_os = "emscripten", target_os = "unknown"))))'.dependencies] -async-std = "1.0" +async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4.1" futures = "0.3.1" From 40221da9175d92a06ca165d36676b9d705c54dca Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 20 Dec 2019 10:02:27 -0500 Subject: [PATCH 022/202] Pin futures_codec to avoid compilation errors --- muxers/mplex/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/kad/Cargo.toml | 2 +- protocols/plaintext/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 5bc754bb4e7..f5727c86697 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] bytes = "0.4.5" fnv = "1.0" futures = "0.3.1" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4" parking_lot = "0.9" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index 8a322418c9c..d814a9d31c4 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "0.4" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" futures = "0.3.1" libp2p-core = { version = "0.13.0", path = "../../core" } libp2p-swarm = { version = "0.3.0", path = "../../swarm" } diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index e855b64c925..5f2b87c46e9 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -14,7 +14,7 @@ arrayvec = "0.5.1" bytes = "0.4" either = "1.5" fnv = "1.0" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" futures = "0.3.1" log = "0.4" libp2p-core = { version = "0.13.0", path = "../../core" } diff --git a/protocols/plaintext/Cargo.toml b/protocols/plaintext/Cargo.toml index 1632f8e8a4a..57e7e4b6daa 100644 --- a/protocols/plaintext/Cargo.toml +++ b/protocols/plaintext/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "0.4.12" futures = "0.3.1" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4.8" protobuf = "2.8.1" From 5bf85dcf0beba97dd60279178263a1485acdbc8e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 20 Dec 2019 10:16:45 -0500 Subject: [PATCH 023/202] Fix the rest of the compiler errors --- transports/quic/src/lib.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 5dd0c428127..1a3cc74782e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -70,7 +70,7 @@ use log::debug; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, - fmt, io, + io, net::SocketAddr, pin::Pin, sync::{Arc, Mutex, MutexGuard, Weak}, @@ -186,9 +186,6 @@ impl Muxer { self.send_to_endpoint(endpoint); while let Some(event) = self.connection.poll() { match event { - Event::DatagramSendUnblocked => { - panic!("we never try to send datagrams, so this will never happen; qed") - } Event::StreamOpened { dir: Dir::Uni } => { /* do nothing */ } Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, Event::StreamReadable { stream } => { @@ -333,7 +330,7 @@ impl StreamMuxer for QuicMuxer { Err(WriteError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(WriteError::Stopped { error_code: _ }) => { + Err(WriteError::Stopped(_)) => { Poll::Ready(Err(std::io::ErrorKind::ConnectionAborted.into())) } } @@ -373,9 +370,7 @@ impl StreamMuxer for QuicMuxer { Err(ReadError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(ReadError::Reset { error_code: _ }) => { - Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) - } + Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -512,7 +507,6 @@ impl QuicEndpoint { }; // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); - let (sender, receiver) = mpsc::channel(0); let (new_connections, receive_connections) = mpsc::unbounded(); Ok(Self( Arc::new(Endpoint { @@ -526,8 +520,6 @@ impl QuicEndpoint { muxers: HashMap::new(), driver: None, }), - sender, - receiver, new_connections, receive_connections: Mutex::new(Some(receive_connections)), address, @@ -551,7 +543,6 @@ impl QuicEndpoint { readers: HashMap::new(), accept_waker: None, connectors: Default::default(), - sender: endpoint.sender.clone(), endpoint: endpoint.clone(), pending: None, })); @@ -560,12 +551,12 @@ impl QuicEndpoint { } /// Process UDP packets until either an error occurs on the socket or we are dropped. - async fn process_udp_traffic(self, address: Multiaddr) -> Result<(), io::Error> { + async fn process_udp_packets(self, address: Multiaddr) -> Result<(), io::Error> { self.0 .new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address))) .expect("we have a reference to the peer, so this will not fail; qed"); - let mut outgoing_packet = None; + let mut outgoing_packet: Option = None; loop { use quinn_proto::DatagramEvent; if let Some(packet) = outgoing_packet.take() { @@ -599,7 +590,7 @@ impl QuicEndpoint { .lock() .expect("we assume we have not already panicked; qed"); connection.process_app_events(&mut inner, connection_event); - outgoing_packet = connection.poll_transmit() + outgoing_packet = connection.connection.poll_transmit(Instant::now()) } DatagramEvent::NewConnection(connection) => { let muxer = self.create_muxer(connection, handle, &mut *inner); @@ -685,8 +676,7 @@ impl Transport for &QuicEndpoint { let mut inner = self.inner(); if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn( - self.clone() - .process_udp_packets(self.0.address.clone()), + self.clone().process_udp_packets(self.0.address.clone()), )) } From 67bea72f37d7ba97b9c0e58f51796f078349ad74 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 21 Dec 2019 17:50:25 -0500 Subject: [PATCH 024/202] Bump dependencies This bumps a few dependencies to recent but working versions. --- Cargo.toml | 2 +- core/Cargo.toml | 6 +++--- core/src/transport/timeout.rs | 5 ++--- misc/mdns/Cargo.toml | 2 +- misc/multihash/Cargo.toml | 2 +- muxers/mplex/Cargo.toml | 4 ++-- muxers/yamux/Cargo.toml | 2 +- protocols/deflate/Cargo.toml | 2 +- protocols/floodsub/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 4 ++-- protocols/kad/Cargo.toml | 2 +- protocols/noise/Cargo.toml | 6 +++--- protocols/plaintext/Cargo.toml | 6 +++--- protocols/plaintext/src/pb.rs | 1 + protocols/secio/Cargo.toml | 2 +- transports/tcp/Cargo.toml | 2 +- 16 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 596325e6cae..0e254d77f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ libp2p-swarm = { version = "0.3.0", path = "swarm" } libp2p-uds = { version = "0.13.0", path = "transports/uds" } libp2p-wasm-ext = { version = "0.6.0", path = "transports/wasm-ext" } libp2p-yamux = { version = "0.13.0", path = "muxers/yamux" } -parking_lot = "0.9.0" +parking_lot = "0.10.0" smallvec = "1.0" wasm-timer = "0.2.4" diff --git a/core/Cargo.toml b/core/Cargo.toml index a0b4fdc103d..0c8aba77ca6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -17,18 +17,18 @@ ed25519-dalek = "1.0.0-pre.3" failure = "0.1" fnv = "1.0" futures = { version = "0.3.1", features = ["compat", "io-compat", "executor", "thread-pool"] } -futures-timer = "0.3" +futures-timer = "2.0.2" lazy_static = "1.2" libsecp256k1 = { version = "0.3.1", optional = true } log = "0.4" multiaddr = { package = "parity-multiaddr", version = "0.6.0", path = "../misc/multiaddr" } multihash = { package = "parity-multihash", version = "0.2.0", path = "../misc/multihash" } multistream-select = { version = "0.6.0", path = "../misc/multistream-select" } -parking_lot = "0.9.0" +parking_lot = "0.10.0" pin-project = "0.4.6" protobuf = "2.8" quick-error = "1.2" -rand = "0.7" +rand = "0.7.2" rw-stream-sink = { version = "0.1.1", path = "../misc/rw-stream-sink" } sha2 = "0.8.0" smallvec = "1.0" diff --git a/core/src/transport/timeout.rs b/core/src/transport/timeout.rs index 15fcf855ac6..24da0627756 100644 --- a/core/src/transport/timeout.rs +++ b/core/src/transport/timeout.rs @@ -172,10 +172,9 @@ where Poll::Ready(Err(err)) => return Poll::Ready(Err(TransportTimeoutError::Other(err))), } - match TryFuture::try_poll(Pin::new(&mut this.timer), cx) { + match Pin::new(&mut this.timer).poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(Ok(())) => Poll::Ready(Err(TransportTimeoutError::Timeout)), - Poll::Ready(Err(err)) => Poll::Ready(Err(TransportTimeoutError::TimerError(err))), + Poll::Ready(()) => Poll::Ready(Err(TransportTimeoutError::Timeout)), } } } diff --git a/misc/mdns/Cargo.toml b/misc/mdns/Cargo.toml index 31372f0ab00..f39db819033 100644 --- a/misc/mdns/Cargo.toml +++ b/misc/mdns/Cargo.toml @@ -21,7 +21,7 @@ libp2p-swarm = { version = "0.3.0", path = "../../swarm" } log = "0.4" multiaddr = { package = "parity-multiaddr", version = "0.6.0", path = "../multiaddr" } net2 = "0.2" -rand = "0.6" +rand = "0.7.2" smallvec = "1.0" void = "1.0" wasm-timer = "0.2.4" diff --git a/misc/multihash/Cargo.toml b/misc/multihash/Cargo.toml index 82a231fb6d5..d09ea72b958 100644 --- a/misc/multihash/Cargo.toml +++ b/misc/multihash/Cargo.toml @@ -12,7 +12,7 @@ documentation = "https://docs.rs/parity-multihash/" [dependencies] blake2 = { version = "0.8", default-features = false } bytes = "0.4.12" -rand = { version = "0.6", default-features = false, features = ["std"] } +rand = { version = "0.7.2", default-features = false, features = ["std"] } sha-1 = { version = "0.8", default-features = false } sha2 = { version = "0.8", default-features = false } sha3 = { version = "0.8", default-features = false } diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 6dc5bbaaeb8..a266d8c8c53 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -13,10 +13,10 @@ categories = ["network-programming", "asynchronous"] bytes = "0.4.5" fnv = "1.0" futures = "0.3.1" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4" -parking_lot = "0.9" +parking_lot = "0.10.0" unsigned-varint = { version = "0.2.3", features = ["futures-codec"] } [dev-dependencies] diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index 4b437dc9232..371788b0eea 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -13,6 +13,6 @@ categories = ["network-programming", "asynchronous"] futures = "0.3.1" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4.8" -parking_lot = "0.9" +parking_lot = "0.10.0" thiserror = "1.0" yamux = { git = "https://github.com/paritytech/yamux.git", branch = "develop" } diff --git a/protocols/deflate/Cargo.toml b/protocols/deflate/Cargo.toml index 7bf924ccac2..2fc95073d95 100644 --- a/protocols/deflate/Cargo.toml +++ b/protocols/deflate/Cargo.toml @@ -18,5 +18,5 @@ flate2 = "1.0" async-std = "1.0" env_logger = "0.7.1" libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } -rand = "0.7" +rand = "0.7.2" quickcheck = "0.9" diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index f1c46f6bac5..273e4ca8e0b 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -18,5 +18,5 @@ futures = "0.3.1" libp2p-core = { version = "0.13.0", path = "../../core" } libp2p-swarm = { version = "0.3.0", path = "../../swarm" } protobuf = "2.8" -rand = "0.6" +rand = "0.7.2" smallvec = "1.0" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index d997109da3c..2abead807ae 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "0.4" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" futures = "0.3.1" libp2p-core = { version = "0.13.0", path = "../../core" } libp2p-swarm = { version = "0.3.0", path = "../../swarm" } @@ -27,4 +27,4 @@ async-std = "1.0" libp2p-mplex = { version = "0.13.0", path = "../../muxers/mplex" } libp2p-secio = { version = "0.13.0", path = "../../protocols/secio" } libp2p-tcp = { version = "0.13.0", path = "../../transports/tcp" } -rand = "0.6" +rand = "0.7.2" diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index e855b64c925..5f2b87c46e9 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -14,7 +14,7 @@ arrayvec = "0.5.1" bytes = "0.4" either = "1.5" fnv = "1.0" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" futures = "0.3.1" log = "0.4" libp2p-core = { version = "0.13.0", path = "../../core" } diff --git a/protocols/noise/Cargo.toml b/protocols/noise/Cargo.toml index cc23636836e..412574aeea9 100644 --- a/protocols/noise/Cargo.toml +++ b/protocols/noise/Cargo.toml @@ -9,16 +9,16 @@ edition = "2018" [dependencies] bytes = "0.4" -curve25519-dalek = "1" +curve25519-dalek = "2.0.0" futures = "0.3.1" lazy_static = "1.2" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4" protobuf = "2.8" -rand = "^0.7.2" +rand = "0.7.2" ring = { version = "0.16.9", features = ["alloc"], default-features = false } snow = { version = "0.6.1", features = ["ring-resolver"], default-features = false } -x25519-dalek = "0.5" +x25519-dalek = "0.6.0" zeroize = "1" [dev-dependencies] diff --git a/protocols/plaintext/Cargo.toml b/protocols/plaintext/Cargo.toml index 1632f8e8a4a..6750bacb80d 100644 --- a/protocols/plaintext/Cargo.toml +++ b/protocols/plaintext/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "0.4.12" futures = "0.3.1" -futures_codec = "0.3.1" +futures_codec = "=0.3.3" libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4.8" protobuf = "2.8.1" @@ -23,5 +23,5 @@ void = "1.0.2" [dev-dependencies] env_logger = "0.7.1" quickcheck = "0.9.0" -rand = "0.7" -futures-timer = "2.0" +rand = "0.7.2" +futures-timer = "2.0.2" diff --git a/protocols/plaintext/src/pb.rs b/protocols/plaintext/src/pb.rs index 64e83e5da73..7f2046dedd2 100644 --- a/protocols/plaintext/src/pb.rs +++ b/protocols/plaintext/src/pb.rs @@ -18,4 +18,5 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#[allow(bare_trait_objects)] // bug in protobuf compiler pub mod structs; diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index 0dd7fdf9ddf..c86efb479fa 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -20,7 +20,7 @@ libp2p-core = { version = "0.13.0", path = "../../core" } log = "0.4.6" protobuf = "2.8" quicksink = { git = "https://github.com/paritytech/quicksink.git" } -rand = "0.7" +rand = "0.7.2" rw-stream-sink = { version = "0.1.1", path = "../../misc/rw-stream-sink" } sha2 = "0.8.0" static_assertions = "1" diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 12244e13f39..92bf37c70b0 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] async-std = "1.0" bytes = "0.4.12" futures = "0.3.1" -futures-timer = "2.0" +futures-timer = "2.0.2" get_if_addrs = "0.5.3" ipnet = "2.0.0" libp2p-core = { version = "0.13.0", path = "../../core" } From 7d9d2004a05e9c3b63c1e84b8423d63fb26935e9 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 22 Dec 2019 17:50:48 -0500 Subject: [PATCH 025/202] Implement complete QUIC protocol logic This implements the entire QUIC protocol logic (but no crypto). --- Cargo.toml | 1 - transports/quic/Cargo.toml | 2 + transports/quic/src/lib.rs | 536 ++++++++++++++++++++++++++----------- 3 files changed, 376 insertions(+), 163 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d06cd002714..92dbe8806b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,4 +76,3 @@ members = [ "transports/wasm-ext", "transports/quic", ] - diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 66432435fe0..ec0b4c08869 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -37,3 +37,5 @@ futures-core = "0.3.1" quinn-proto = { git = "https://github.com/djc/quinn" } async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } async-macros = "2.0.0" +futures-timer = "2.0.2" +env_logger = "0.7.1" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1a3cc74782e..f238001c488 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -55,6 +55,7 @@ //! `QuicEndpoint` manages a background task that processes all socket I/O. This includes: #![deny(unsafe_code)] +mod connection; use async_macros::ready; use async_std::net::UdpSocket; use futures::{ @@ -66,7 +67,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; -use log::debug; +use log::{debug, error, trace}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, @@ -102,32 +103,8 @@ impl QuicConfig { } } -type StreamSenderQueue = std::collections::VecDeque>; - -#[derive(Debug)] -pub struct Muxer { - /// The pending stream, if any. - pending_stream: Option, - /// The associated endpoint - endpoint: Arc, - /// The `quinn_proto::Connection` struct. - connection: Connection, - /// Connection handle - handle: ConnectionHandle, - /// Tasks blocked on writing - writers: HashMap, - /// Tasks blocked on reading - readers: HashMap, - /// Task waiting for new connections, or for this connection to complete. - accept_waker: Option, - /// Tasks waiting to make a connection - connectors: StreamSenderQueue, - /// Pending transmit - pending: Option, -} - #[derive(Debug)] -struct EndpointInner { +pub struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, driver: Option>>, @@ -148,95 +125,6 @@ struct Endpoint { /// The `Multiaddr` address: Multiaddr, } - -impl Muxer { - /// Process all endpoint-facing events for this connection. This is synchronous and will not - /// fail. - fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { - while let Some(endpoint_event) = self.connection.poll_endpoint_events() { - if let Some(connection_event) = endpoint.inner.handle_event(self.handle, endpoint_event) - { - self.connection.handle_event(connection_event) - } - } - } - - fn poll_transmit( - &mut self, - cx: &mut Context<'_>, - transmit: quinn_proto::Transmit, - ) -> Poll> { - match self - .endpoint - .socket - .poll_send_to(cx, &transmit.contents, &transmit.destination) - { - Pending => { - self.pending = Some(transmit); - Pending - } - r @ Ready(_) => r, - } - } - - /// Process application events - fn process_app_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { - use quinn_proto::Event; - self.connection.handle_event(event); - self.send_to_endpoint(endpoint); - while let Some(event) = self.connection.poll() { - match event { - Event::StreamOpened { dir: Dir::Uni } => { /* do nothing */ } - Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, - Event::StreamReadable { stream } => { - // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.readers.remove_entry(&stream) { - waker.wake() - } - } - Event::StreamWritable { stream } => { - // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.writers.remove_entry(&stream) { - waker.wake() - } - } - Event::StreamAvailable { dir: Dir::Bi } => { - if self.connectors.is_empty() { - // no task to wake up - return; - } - debug_assert!( - self.pending_stream.is_none(), - "we cannot have both pending tasks and a pending stream; qed" - ); - let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); - while let Some(oneshot) = self.connectors.pop_front() { - match oneshot.send(stream) { - Ok(()) => return, - Err(_) => (), - } - } - self.pending_stream = Some(stream) - } - Event::ConnectionLost { .. } => break, // nothing more - Event::StreamFinished { stream, .. } => { - // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.writers.remove_entry(&stream) { - waker.wake() - } - } - // These are separate events, but are handled the same way. - Event::StreamOpened { dir: Dir::Bi } | Event::Connected => { - if let Some(w) = self.accept_waker.take() { - w.wake() - } - } - } - } - } -} - #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); @@ -294,10 +182,16 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context) -> Poll> { let mut inner = self.inner(); + if let Some(ref e) = inner.close_reason { + return Poll::Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } match inner.connection.accept(quinn_proto::Dir::Bi) { None => { inner.accept_waker = Some(cx.waker().clone()); - Poll::Pending + Pending } Some(stream) => Poll::Ready(Ok(stream)), } @@ -312,12 +206,18 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::WriteError; let mut inner = self.inner(); + if let Some(ref e) = inner.close_reason { + return Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } if let Some(transmit) = inner.pending.take() { ready!(inner.poll_transmit(cx, transmit))?; } match inner.connection.write(*substream, buf) { Ok(bytes) => { - if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { ready!(inner.poll_transmit(cx, transmit))?; } Ready(Ok(bytes)) @@ -325,7 +225,7 @@ impl StreamMuxer for QuicMuxer { Err(WriteError::Blocked) => { inner.writers.insert(*substream, cx.waker().clone()); - Poll::Pending + Pending } Err(WriteError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") @@ -352,12 +252,18 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - if let Some(transmit) = inner.pending.take() { + if let Some(ref e) = inner.close_reason { + return Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + while let Some(transmit) = inner.pending.take() { ready!(inner.poll_transmit(cx, transmit))?; } match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { - if let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { ready!(inner.poll_transmit(cx, transmit))?; } Poll::Ready(Ok(bytes)) @@ -365,7 +271,7 @@ impl StreamMuxer for QuicMuxer { Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) => { inner.readers.insert(*substream, cx.waker().clone()); - Poll::Pending + Pending } Err(ReadError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") @@ -487,7 +393,7 @@ pub struct QuicEndpoint(Arc, QuicConfig); impl QuicEndpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.0.inner.lock().unwrap() + self.0.inner.lock().expect("we assume we have not already panicked; qed") } /// Retrieves the `Multiaddr` of this `QuicEndpoint`. @@ -508,6 +414,9 @@ impl QuicEndpoint { // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (new_connections, receive_connections) = mpsc::unbounded(); + new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) + .expect("we have a reference to the peer, so this will not fail; qed"); Ok(Self( Arc::new(Endpoint { socket, @@ -533,42 +442,38 @@ impl QuicEndpoint { connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, - ) -> Arc> { - let endpoint = &self.0; - let muxer = Arc::new(Mutex::new(Muxer { - pending_stream: None, - connection, - handle, - writers: HashMap::new(), - readers: HashMap::new(), - accept_waker: None, - connectors: Default::default(), - endpoint: endpoint.clone(), - pending: None, - })); + ) -> (QuicMuxer, ConnectionDriver) { + let (driver, muxer) = ConnectionDriver::new(Muxer::new(self.0.clone(), connection, handle)); inner.muxers.insert(handle, Arc::downgrade(&muxer)); - muxer + (QuicMuxer(muxer), driver) } /// Process UDP packets until either an error occurs on the socket or we are dropped. - async fn process_udp_packets(self, address: Multiaddr) -> Result<(), io::Error> { - self.0 - .new_connections - .unbounded_send(Ok(ListenerEvent::NewAddress(address))) - .expect("we have a reference to the peer, so this will not fail; qed"); + async fn process_udp_packets(self, _address: Multiaddr) -> Result<(), io::Error> { let mut outgoing_packet: Option = None; loop { use quinn_proto::DatagramEvent; if let Some(packet) = outgoing_packet.take() { + trace!( + "sending packet of length {} to {}", + packet.contents.len(), + packet.destination + ); self.0 .socket .send_to(&packet.contents, packet.destination) .await?; } - let mut buf = [0; 1800]; + let mut buf = [0; 65000]; let (bytes, peer) = self.0.socket.recv_from(&mut buf[..]).await?; + trace!("got a packet of length {} from {}!", bytes, peer); // This takes a mutex, so it must be *after* the `await` call. let mut inner = self.inner(); + if let Some(packet) = inner.inner.poll_transmit() { + trace!("sending packet from endpoint!"); + outgoing_packet = Some(packet); + continue; + } let (handle, event) = match inner .inner @@ -577,29 +482,35 @@ impl QuicEndpoint { Some(e) => e, None => continue, }; + trace!("have an event!"); match event { DatagramEvent::ConnectionEvent(connection_event) => { + trace!("got a connection event: {:?}", connection_event); let connection = match inner.muxers.get(&handle) { None => panic!("received a ConnectionEvent for an unknown Connection"), Some(e) => match e.upgrade() { Some(e) => e, - None => continue, // FIXME should this be a panic? + None => { + error!("lost our connection!"); + continue; + } }, }; let mut connection = connection .lock() .expect("we assume we have not already panicked; qed"); - connection.process_app_events(&mut inner, connection_event); - outgoing_packet = connection.connection.poll_transmit(Instant::now()) + outgoing_packet = + connection.process_connection_events(&mut inner, connection_event); } DatagramEvent::NewConnection(connection) => { - let muxer = self.create_muxer(connection, handle, &mut *inner); + let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); + async_std::task::spawn(driver); let endpoint = &self.0; endpoint .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { upgrade: QuicUpgrade { - muxer: Some(QuicMuxer(muxer)), + muxer: Some(muxer), }, local_addr: endpoint.address.clone(), remote_addr: endpoint.address.clone(), @@ -623,11 +534,24 @@ impl Future for QuicUpgrade { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let muxer = &mut self.get_mut().muxer; + trace!("outbound polling!"); { - let mut connection = muxer.as_mut().expect("polled after yielding Ready").inner(); - if connection.connection.is_handshaking() { - connection.accept_waker = Some(cx.waker().clone()); - return Poll::Pending; + let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); + if let Some(ref e) = inner.close_reason { + return Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + while let Some(transmit) = inner.pending.take() { + ready!(inner.poll_transmit(cx, transmit))?; + } + while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + ready!(inner.poll_transmit(cx, transmit))?; + } + if inner.connection.is_handshaking() { + inner.accept_waker = Some(cx.waker().clone()); + return Pending; } } Poll::Ready(Ok(muxer.take().expect("impossible"))) @@ -649,7 +573,7 @@ impl Transport for &QuicEndpoint { let res = (self.0) .receive_connections .lock() - .unwrap() + .expect("we assume we have not already panicked; qed") .take() .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); let mut inner = self.inner(); @@ -684,13 +608,13 @@ impl Transport for &QuicEndpoint { .inner .connect(self.1.client_config.clone(), socket_addr, "localhost") .map_err(|e| { - eprintln!("Connection error: {:?}", e); + error!("Connection error: {:?}", e); TransportError::Other(io::ErrorKind::InvalidInput.into()) }); let (handle, conn) = s?; - Ok(QuicUpgrade { - muxer: Some(QuicMuxer(self.create_muxer(conn, handle, &mut inner))), - }) + let (muxer, driver) = self.create_muxer(conn, handle, &mut inner); + async_std::task::spawn(driver); + Ok(QuicUpgrade { muxer: Some(muxer) }) } } @@ -716,6 +640,287 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { } } +type StreamSenderQueue = std::collections::VecDeque>; + +#[derive(Debug)] +pub struct Muxer { + /// The pending stream, if any. + pending_stream: Option, + /// The associated endpoint + endpoint: Arc, + /// The `quinn_proto::Connection` struct. + connection: Connection, + /// Connection handle + handle: ConnectionHandle, + /// Tasks blocked on writing + writers: HashMap, + /// Tasks blocked on reading + readers: HashMap, + /// Task waiting for new connections, or for this connection to complete. + accept_waker: Option, + /// Tasks waiting to make a connection + connectors: StreamSenderQueue, + /// Pending transmit + pending: Option, + /// The timers being used by this connection + timers: quinn_proto::TimerTable>, + /// The close reason, if this connection has been lost + close_reason: Option, +} +impl Muxer { + fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { + Muxer { + pending_stream: None, + connection, + handle, + writers: HashMap::new(), + readers: HashMap::new(), + accept_waker: None, + connectors: Default::default(), + endpoint: endpoint.clone(), + pending: None, + timers: quinn_proto::TimerTable::new(|| None), + close_reason: None, + } + } + + /// Process all endpoint-facing events for this connection. This is synchronous and will not + /// fail. + pub fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { + while let Some(endpoint_event) = self.connection.poll_endpoint_events() { + if let Some(connection_event) = endpoint.inner.handle_event(self.handle, endpoint_event) + { + self.connection.handle_event(connection_event) + } + } + } + + pub fn poll_transmit( + &mut self, + cx: &mut Context<'_>, + transmit: quinn_proto::Transmit, + ) -> Poll> { + trace!( + "sending packet of length {} to {}", + transmit.contents.len(), + transmit.destination + ); + match self + .endpoint + .socket + .poll_send_to(cx, &transmit.contents, &transmit.destination) + { + Pending => { + self.pending = Some(transmit); + Pending + } + r @ Ready(_) => r, + } + } + + /// Process application events + pub fn process_connection_events( + &mut self, + endpoint: &mut EndpointInner, + event: ConnectionEvent, + ) -> Option { + self.connection.handle_event(event); + self.send_to_endpoint(endpoint); + self.process_app_events(); + if self.close_reason.is_none() { + self.connection.poll_transmit(Instant::now()) + } else { + None + } + } + + pub fn process_app_events(&mut self) { + use quinn_proto::Event; + while let Some(event) = self.connection.poll() { + match event { + Event::StreamOpened { dir: Dir::Uni } => { + trace!("Unidirectional stream opened!"); + } + Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, + Event::StreamReadable { stream } => { + trace!("Stream {:?} readable", stream); + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.readers.remove_entry(&stream) { + waker.wake() + } + } + Event::StreamWritable { stream } => { + trace!("Stream {:?} writable", stream); + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + } + Event::StreamAvailable { dir: Dir::Bi } => { + trace!("Bidirectional stream available"); + if self.connectors.is_empty() { + // no task to wake up + return; + } + debug_assert!( + self.pending_stream.is_none(), + "we cannot have both pending tasks and a pending stream; qed" + ); + let stream = self.connection.open(Dir::Bi) + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + if let Some(oneshot) = self.connectors.pop_front() { + match oneshot.send(stream) { + Ok(()) => return, + Err(_) => (), + } + } + self.pending_stream = Some(stream) + } + Event::ConnectionLost { reason } => { + debug!("lost connection due to {:?}", reason); + self.close_reason = Some(reason); + if let Some(w) = self.accept_waker.take() { + w.wake() + } + for (_, v) in self.writers.drain() { + v.wake(); + } + for (_, v) in self.readers.drain() { + v.wake(); + } + self.connectors.truncate(0); + debug!("Self is {:?}", self); + } + Event::StreamFinished { stream, .. } => { + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + } + // These are separate events, but are handled the same way. + Event::StreamOpened { dir: Dir::Bi } | Event::Connected => { + debug!("connected or stream opened!"); + if let Some(w) = self.accept_waker.take() { + w.wake() + } + } + } + } + } +} + +#[derive(Debug)] +struct ConnectionDriver { + inner: Arc>, + endpoint: Arc, + outgoing_packet: Option, +} + +impl Unpin for ConnectionDriver {} + +impl ConnectionDriver { + fn new(muxer: Muxer) -> (Self, Arc>) { + let endpoint = muxer.endpoint.clone(); + let inner = Arc::new(Mutex::new(muxer)); + ( + Self { + inner: inner.clone(), + endpoint, + outgoing_packet: None, + }, + inner, + ) + } + fn poll_transmit( + &mut self, + cx: &mut Context<'_>, + transmit: quinn_proto::Transmit, + ) -> Poll> { + trace!( + "sending packet of length {} to {}", + transmit.contents.len(), + transmit.destination + ); + match self + .endpoint + .socket + .poll_send_to(cx, &transmit.contents, &transmit.destination) + { + x @ Pending => { + self.outgoing_packet = Some(transmit); + x + } + x @ Ready(_) => x, + } + } +} + +impl Future for ConnectionDriver { + type Output = Result<(), std::io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let now = Instant::now(); + let this = self.get_mut(); + if let Some(packet) = this.outgoing_packet.take() { + ready!(this.poll_transmit(cx, packet)).expect("error handling not implemented"); + } + trace!("being polled for timers!"); + let mut inner = (*this.inner).lock().expect("poisoned"); + loop { + trace!("loop iteration"); + let mut needs_timer_update = false; + let Muxer { + ref mut timers, + ref mut connection, + .. + } = *inner; + for (timer, timer_ref) in timers + .iter_mut() + .filter_map(|(x, y)| y.as_mut().map(|z| (x, z))) + { + match timer_ref.poll_unpin(cx) { + Pending => continue, + Ready(()) => { + trace!("timer {:?} ready at time {:?}", timer, now); + connection.handle_timeout(now, timer); + } + } + } + while let Some(quinn_proto::TimerUpdate { timer, update }) = connection.poll_timers() { + trace!("got a timer update!"); + use quinn_proto::TimerSetting; + match update { + TimerSetting::Stop => timers[timer] = None, + TimerSetting::Start(instant) => { + trace!("setting a timer for {:?}", instant - now); + let mut new_timer = futures_timer::Delay::new(instant - now); + match new_timer.poll_unpin(cx) { + Ready(()) => needs_timer_update = true, + Pending => {} + } + timers[timer] = Some(new_timer) + } + } + } + if !needs_timer_update { + break; + } + } + inner.process_app_events(); + while let Some(tx) = inner.connection.poll_transmit(now) { + ready!(inner.poll_transmit(cx, tx)).expect("error handling not implemented"); + } + if inner.connection.is_drained() { + debug!( + "Connection drained: close reason {}", + inner.close_reason.as_ref().expect("we never have a closed connection with no reason; qed") + ); + Ready(Ok(())) + } else { + Pending + } + } +} + #[cfg(test)] mod tests { use super::{multiaddr_to_socketaddr, QuicConfig, QuicEndpoint}; @@ -728,8 +933,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; #[test] - #[ignore] // this needs cmsg support from async-std fn wildcard_expansion() { + env_logger::try_init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) .expect("endpoint") @@ -748,8 +953,8 @@ mod tests { ListenerEvent::NewAddress(a) => { let mut iter = a.iter(); match iter.next().expect("ip address") { - Protocol::Ip4(ip) => assert!(!ip.is_unspecified()), - Protocol::Ip6(ip) => assert!(!ip.is_unspecified()), + Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), + Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), other => panic!("Unexpected protocol: {}", other), } if let Protocol::Udp(port) = iter.next().expect("port") { @@ -763,8 +968,8 @@ mod tests { }) .for_each(|_| futures::future::ready(())); - futures::executor::block_on(futures::future::join(server, client)) - .1 + async_std::task::spawn(server); + futures::executor::block_on(client) .unwrap(); } @@ -772,6 +977,7 @@ mod tests { fn multiaddr_to_udp_conversion() { use std::net::Ipv6Addr; + env_logger::try_init(); assert!( multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) .is_err() @@ -828,6 +1034,8 @@ mod tests { #[test] fn communicating_between_dialer_and_listener() { + use super::trace; + env_logger::try_init(); let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); @@ -840,6 +1048,7 @@ mod tests { let mut listener = quic_endpoint.listen_on(addr).unwrap(); loop { + trace!("awaiting connection"); match listener.next().await.unwrap().unwrap() { ListenerEvent::NewAddress(listen_addr) => { ready_tx.take().unwrap().send(listen_addr).unwrap(); @@ -866,7 +1075,7 @@ mod tests { .unwrap(); // Obtain a future socket through dialing let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); - eprintln!("Received a Connection: {:?}", connection); + trace!("Received a Connection: {:?}", connection); let mut socket = connection.next().await.unwrap(); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); @@ -878,6 +1087,7 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { + env_logger::try_init(); let quic = QuicConfig::new(); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); @@ -897,6 +1107,7 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { + env_logger::try_init(); let config = QuicConfig::new(); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); @@ -915,6 +1126,7 @@ mod tests { #[test] fn larger_addr_denied() { + env_logger::try_init(); let config = QuicConfig::new(); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() From 804cf955a63d89d37db621b32e342af3123ecd81 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 15:17:07 -0500 Subject: [PATCH 026/202] Fix warning in testsuite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t actually need the the result of env_logger::try_init(). Just drop it. --- transports/quic/src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index f238001c488..483f54c2f04 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -932,9 +932,13 @@ mod tests { }; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + fn init() { + drop(env_logger::try_init()); + } + #[test] fn wildcard_expansion() { - env_logger::try_init(); + init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) .expect("endpoint") @@ -976,8 +980,7 @@ mod tests { #[test] fn multiaddr_to_udp_conversion() { use std::net::Ipv6Addr; - - env_logger::try_init(); + init(); assert!( multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) .is_err() @@ -1035,7 +1038,7 @@ mod tests { #[test] fn communicating_between_dialer_and_listener() { use super::trace; - env_logger::try_init(); + init(); let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); @@ -1087,7 +1090,7 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { - env_logger::try_init(); + init(); let quic = QuicConfig::new(); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); @@ -1107,7 +1110,7 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { - env_logger::try_init(); + init(); let config = QuicConfig::new(); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); @@ -1126,7 +1129,7 @@ mod tests { #[test] fn larger_addr_denied() { - env_logger::try_init(); + init(); let config = QuicConfig::new(); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() From 514ee6bb7304e9c5d76c53f1e055eff138bb86f6 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 15:21:18 -0500 Subject: [PATCH 027/202] Switch to parking_lot --- transports/quic/Cargo.toml | 1 + transports/quic/src/lib.rs | 18 +++++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index ec0b4c08869..1d38a19abe3 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -39,3 +39,4 @@ async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-re async-macros = "2.0.0" futures-timer = "2.0.2" env_logger = "0.7.1" +parking_lot = "0.10.0" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 483f54c2f04..1fa7e405918 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -67,14 +67,15 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; +use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, io, net::SocketAddr, pin::Pin, - sync::{Arc, Mutex, MutexGuard, Weak}, + sync::{Arc, Weak}, task::{ Context, Poll::{self, Pending, Ready}, @@ -130,9 +131,7 @@ pub struct QuicMuxer(Arc>); impl QuicMuxer { fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { - self.0 - .lock() - .expect("we already panicked, so are in an inconsistent state; qed") + self.0.lock() } } @@ -393,7 +392,7 @@ pub struct QuicEndpoint(Arc, QuicConfig); impl QuicEndpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.0.inner.lock().expect("we assume we have not already panicked; qed") + self.0.inner.lock() } /// Retrieves the `Multiaddr` of this `QuicEndpoint`. @@ -496,9 +495,7 @@ impl QuicEndpoint { } }, }; - let mut connection = connection - .lock() - .expect("we assume we have not already panicked; qed"); + let mut connection = connection.lock(); outgoing_packet = connection.process_connection_events(&mut inner, connection_event); } @@ -573,7 +570,6 @@ impl Transport for &QuicEndpoint { let res = (self.0) .receive_connections .lock() - .expect("we assume we have not already panicked; qed") .take() .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); let mut inner = self.inner(); @@ -864,7 +860,7 @@ impl Future for ConnectionDriver { ready!(this.poll_transmit(cx, packet)).expect("error handling not implemented"); } trace!("being polled for timers!"); - let mut inner = (*this.inner).lock().expect("poisoned"); + let mut inner = (*this.inner).lock(); loop { trace!("loop iteration"); let mut needs_timer_update = false; From 827424dc03601d900c1e6508adfced4900cbc5f8 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 15:25:10 -0500 Subject: [PATCH 028/202] Outbound connections must fail when a connection is lost --- transports/quic/src/lib.rs | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1fa7e405918..fb145803dfd 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -137,8 +137,9 @@ impl QuicMuxer { #[derive(Debug)] enum OutboundInner { - Complete(StreamId), + Complete(Result), Pending(oneshot::Receiver), + Done, } pub struct Outbound(OutboundInner); @@ -147,10 +148,16 @@ impl Future for Outbound { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { match self.get_mut().0 { - OutboundInner::Complete(s) => Poll::Ready(Ok(s)), - OutboundInner::Pending(ref mut receiver) => Pin::new(receiver) - .poll(cx) + ref mut inner @ OutboundInner::Complete(_) => { + match std::mem::replace(inner, OutboundInner::Done) { + OutboundInner::Complete(e) => Ready(e), + _ => unreachable!(), + } + } + OutboundInner::Pending(ref mut receiver) => receiver + .poll_unpin(cx) .map_err(|oneshot::Canceled| std::io::ErrorKind::ConnectionAborted.into()), + OutboundInner::Done => panic!("polled after yielding Ready"), } } } @@ -161,12 +168,17 @@ impl StreamMuxer for QuicMuxer { type Error = std::io::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); - if let Some(id) = inner.pending_stream.take() { + if let Some(ref e) = inner.close_reason { + Outbound(OutboundInner::Complete(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + )))) + } else if let Some(id) = inner.pending_stream.take() { // mandatory ― otherwise we will fail an assertion above - Outbound(OutboundInner::Complete(id)) + Outbound(OutboundInner::Complete(Ok(id))) } else if let Some(id) = inner.connection.open(Dir::Bi) { // optimization: if we can complete synchronously, do so. - Outbound(OutboundInner::Complete(id)) + Outbound(OutboundInner::Complete(Ok(id))) } else { let (sender, receiver) = oneshot::channel(); inner.connectors.push_front(sender); @@ -540,18 +552,33 @@ impl Future for QuicUpgrade { e.clone(), ))); } - while let Some(transmit) = inner.pending.take() { + if let Some(transmit) = inner.pending.take() { + trace!("Sending pending packet"); ready!(inner.poll_transmit(cx, transmit))?; } - while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + let now = Instant::now(); + while let Some(transmit) = inner.connection.poll_transmit(now) { + trace!("Sending packet during connection"); ready!(inner.poll_transmit(cx, transmit))?; } if inner.connection.is_handshaking() { + assert!(inner.close_reason.is_none()); + assert!(!inner.connection.is_drained(), "deadlock"); inner.accept_waker = Some(cx.waker().clone()); return Pending; + } else if inner.connection.is_drained() { + debug!("connection already drained; failing"); + return Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + inner + .close_reason + .as_ref() + .expect("drained connections have a close reason") + .clone(), + ))); } } - Poll::Ready(Ok(muxer.take().expect("impossible"))) + Ready(Ok(muxer.take().expect("impossible"))) } } From ac74e6bcb710defc0e487ba1b47cfb84e059dd6e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 16:15:51 -0500 Subject: [PATCH 029/202] Add missing wakeups The code would deadlock because certain wakeups were missed. --- transports/quic/src/lib.rs | 70 ++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index fb145803dfd..3cb66a799d5 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -228,7 +228,9 @@ impl StreamMuxer for QuicMuxer { } match inner.connection.write(*substream, buf) { Ok(bytes) => { - while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + inner.driver_waker.take().map(|w| w.wake()); + let now = Instant::now(); + while let Some(transmit) = inner.connection.poll_transmit(now) { ready!(inner.poll_transmit(cx, transmit))?; } Ready(Ok(bytes)) @@ -269,15 +271,17 @@ impl StreamMuxer for QuicMuxer { e.clone(), ))); } - while let Some(transmit) = inner.pending.take() { + if let Some(transmit) = inner.pending.take() { ready!(inner.poll_transmit(cx, transmit))?; } match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { - while let Some(transmit) = inner.connection.poll_transmit(Instant::now()) { + let now = Instant::now(); + inner.driver_waker.take().map(|w| w.wake()); + while let Some(transmit) = inner.connection.poll_transmit(now) { ready!(inner.poll_transmit(cx, transmit))?; } - Poll::Ready(Ok(bytes)) + Ready(Ok(bytes)) } Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) => { @@ -508,8 +512,9 @@ impl QuicEndpoint { }, }; let mut connection = connection.lock(); - outgoing_packet = - connection.process_connection_events(&mut inner, connection_event); + connection.process_connection_events(&mut inner, connection_event); + outgoing_packet = connection.connection.poll_transmit(Instant::now()); + connection.driver_waker.take().map(|w| w.wake()); } DatagramEvent::NewConnection(connection) => { let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); @@ -552,15 +557,7 @@ impl Future for QuicUpgrade { e.clone(), ))); } - if let Some(transmit) = inner.pending.take() { - trace!("Sending pending packet"); - ready!(inner.poll_transmit(cx, transmit))?; - } let now = Instant::now(); - while let Some(transmit) = inner.connection.poll_transmit(now) { - trace!("Sending packet during connection"); - ready!(inner.poll_transmit(cx, transmit))?; - } if inner.connection.is_handshaking() { assert!(inner.close_reason.is_none()); assert!(!inner.connection.is_drained(), "deadlock"); @@ -689,7 +686,10 @@ pub struct Muxer { timers: quinn_proto::TimerTable>, /// The close reason, if this connection has been lost close_reason: Option, + /// Waker to wake up the driver + driver_waker: Option, } + impl Muxer { fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { @@ -704,6 +704,7 @@ impl Muxer { pending: None, timers: quinn_proto::TimerTable::new(|| None), close_reason: None, + driver_waker: None, } } @@ -718,7 +719,19 @@ impl Muxer { } } - pub fn poll_transmit( + pub fn transmit(&mut self, cx: &mut Context<'_>, now: Instant) -> Poll> { + debug_assert!( + self.pending.is_none(), + "You called Muxer::transmit with a pending packet" + ); + while let Some(transmit) = self.connection.poll_transmit(now) { + self.driver_waker.take().map(|w| w.wake()); + ready!(self.poll_transmit(cx, transmit))?; + } + Ready(Ok(())) + } + + fn poll_transmit( &mut self, cx: &mut Context<'_>, transmit: quinn_proto::Transmit, @@ -746,15 +759,13 @@ impl Muxer { &mut self, endpoint: &mut EndpointInner, event: ConnectionEvent, - ) -> Option { + ) { + if self.close_reason.is_some() { + return; + } self.connection.handle_event(event); self.send_to_endpoint(endpoint); self.process_app_events(); - if self.close_reason.is_none() { - self.connection.poll_transmit(Instant::now()) - } else { - None - } } pub fn process_app_events(&mut self) { @@ -887,7 +898,8 @@ impl Future for ConnectionDriver { ready!(this.poll_transmit(cx, packet)).expect("error handling not implemented"); } trace!("being polled for timers!"); - let mut inner = (*this.inner).lock(); + let mut inner = this.inner.lock(); + inner.driver_waker = Some(cx.waker().clone()); loop { trace!("loop iteration"); let mut needs_timer_update = false; @@ -929,13 +941,14 @@ impl Future for ConnectionDriver { } } inner.process_app_events(); - while let Some(tx) = inner.connection.poll_transmit(now) { - ready!(inner.poll_transmit(cx, tx)).expect("error handling not implemented"); - } + ready!(inner.transmit(cx, now)); if inner.connection.is_drained() { debug!( "Connection drained: close reason {}", - inner.close_reason.as_ref().expect("we never have a closed connection with no reason; qed") + inner + .close_reason + .as_ref() + .expect("we never have a closed connection with no reason; qed") ); Ready(Ok(())) } else { @@ -995,9 +1008,8 @@ mod tests { }) .for_each(|_| futures::future::ready(())); - async_std::task::spawn(server); - futures::executor::block_on(client) - .unwrap(); + async_std::task::spawn(server); + futures::executor::block_on(client).unwrap(); } #[test] From 5395065754ac81704b2f84282e304f8f5c6b77e2 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 18:01:13 -0500 Subject: [PATCH 030/202] Move all of the driver-waking code into a single function --- transports/quic/src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 3cb66a799d5..51bfac8ad6a 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -67,7 +67,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; -use log::{debug, error, trace, warn}; +use log::{debug, error, trace}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ @@ -228,7 +228,7 @@ impl StreamMuxer for QuicMuxer { } match inner.connection.write(*substream, buf) { Ok(bytes) => { - inner.driver_waker.take().map(|w| w.wake()); + inner.wake_driver(); let now = Instant::now(); while let Some(transmit) = inner.connection.poll_transmit(now) { ready!(inner.poll_transmit(cx, transmit))?; @@ -277,7 +277,7 @@ impl StreamMuxer for QuicMuxer { match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { let now = Instant::now(); - inner.driver_waker.take().map(|w| w.wake()); + inner.wake_driver(); while let Some(transmit) = inner.connection.poll_transmit(now) { ready!(inner.poll_transmit(cx, transmit))?; } @@ -514,7 +514,7 @@ impl QuicEndpoint { let mut connection = connection.lock(); connection.process_connection_events(&mut inner, connection_event); outgoing_packet = connection.connection.poll_transmit(Instant::now()); - connection.driver_waker.take().map(|w| w.wake()); + connection.wake_driver(); } DatagramEvent::NewConnection(connection) => { let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); @@ -557,7 +557,6 @@ impl Future for QuicUpgrade { e.clone(), ))); } - let now = Instant::now(); if inner.connection.is_handshaking() { assert!(inner.close_reason.is_none()); assert!(!inner.connection.is_drained(), "deadlock"); @@ -724,13 +723,21 @@ impl Muxer { self.pending.is_none(), "You called Muxer::transmit with a pending packet" ); + let mut need_wake = false; while let Some(transmit) = self.connection.poll_transmit(now) { - self.driver_waker.take().map(|w| w.wake()); + need_wake = true; ready!(self.poll_transmit(cx, transmit))?; } + if need_wake { + self.wake_driver() + } Ready(Ok(())) } + fn wake_driver(&mut self) { + drop(self.driver_waker.take().map(|w| w.wake())) + } + fn poll_transmit( &mut self, cx: &mut Context<'_>, From 9c2f14d56ba731e521e1412d0dc4e897472ef04d Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 18:15:29 -0500 Subject: [PATCH 031/202] Consolodate the I/O code --- transports/quic/src/lib.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 51bfac8ad6a..df21ea52987 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -227,15 +227,7 @@ impl StreamMuxer for QuicMuxer { ready!(inner.poll_transmit(cx, transmit))?; } match inner.connection.write(*substream, buf) { - Ok(bytes) => { - inner.wake_driver(); - let now = Instant::now(); - while let Some(transmit) = inner.connection.poll_transmit(now) { - ready!(inner.poll_transmit(cx, transmit))?; - } - Ready(Ok(bytes)) - } - + Ok(bytes) => inner.on_application_io(cx, bytes), Err(WriteError::Blocked) => { inner.writers.insert(*substream, cx.waker().clone()); Pending @@ -275,14 +267,7 @@ impl StreamMuxer for QuicMuxer { ready!(inner.poll_transmit(cx, transmit))?; } match inner.connection.read(*substream, buf) { - Ok(Some(bytes)) => { - let now = Instant::now(); - inner.wake_driver(); - while let Some(transmit) = inner.connection.poll_transmit(now) { - ready!(inner.poll_transmit(cx, transmit))?; - } - Ready(Ok(bytes)) - } + Ok(Some(bytes)) => inner.on_application_io(cx, bytes), Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) => { inner.readers.insert(*substream, cx.waker().clone()); @@ -738,6 +723,20 @@ impl Muxer { drop(self.driver_waker.take().map(|w| w.wake())) } + /// Call when I/O is done by the application. `bytes` is the number of bytes of I/O done. + fn on_application_io( + &mut self, + cx: &mut Context<'_>, + bytes: usize, + ) -> Poll> { + self.wake_driver(); + let now = Instant::now(); + while let Some(transmit) = self.connection.poll_transmit(now) { + ready!(self.poll_transmit(cx, transmit))?; + } + Ready(Ok(bytes)) + } + fn poll_transmit( &mut self, cx: &mut Context<'_>, From 59d2065943a52efac77d9e13ce4c6e6c2418bcd6 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 18:31:25 -0500 Subject: [PATCH 032/202] Refactor some common code --- transports/quic/src/lib.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index df21ea52987..40062a90306 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -126,6 +126,7 @@ struct Endpoint { /// The `Multiaddr` address: Multiaddr, } + #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); @@ -215,17 +216,8 @@ impl StreamMuxer for QuicMuxer { buf: &[u8], ) -> Poll> { use quinn_proto::WriteError; - let mut inner = self.inner(); - if let Some(ref e) = inner.close_reason { - return Ready(Err(std::io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); - } - if let Some(transmit) = inner.pending.take() { - ready!(inner.poll_transmit(cx, transmit))?; - } + ready!(inner.pre_application_io(cx))?; match inner.connection.write(*substream, buf) { Ok(bytes) => inner.on_application_io(cx, bytes), Err(WriteError::Blocked) => { @@ -257,15 +249,7 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - if let Some(ref e) = inner.close_reason { - return Ready(Err(std::io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); - } - if let Some(transmit) = inner.pending.take() { - ready!(inner.poll_transmit(cx, transmit))?; - } + ready!(inner.pre_application_io(cx))?; match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => inner.on_application_io(cx, bytes), Ok(None) => Poll::Ready(Ok(0)), @@ -737,6 +721,19 @@ impl Muxer { Ready(Ok(bytes)) } + fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { + if let Some(ref e) = self.close_reason { + return Ready(Err(std::io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + if let Some(transmit) = self.pending.take() { + ready!(self.poll_transmit(cx, transmit))?; + } + Ready(Ok(())) + } + fn poll_transmit( &mut self, cx: &mut Context<'_>, From 98cd81ac7ff9c81400ccff816694c63a79d28097 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Dec 2019 19:22:34 -0500 Subject: [PATCH 033/202] More cleanups --- transports/quic/src/lib.rs | 43 +++++++++++++++----------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 40062a90306..66adaa94e6f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -67,7 +67,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; -use log::{debug, error, trace}; +use log::{debug, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ @@ -469,13 +469,13 @@ impl QuicEndpoint { trace!("have an event!"); match event { DatagramEvent::ConnectionEvent(connection_event) => { - trace!("got a connection event: {:?}", connection_event); + // trace!("got a connection event: {:?}", connection_event); let connection = match inner.muxers.get(&handle) { None => panic!("received a ConnectionEvent for an unknown Connection"), Some(e) => match e.upgrade() { Some(e) => e, None => { - error!("lost our connection!"); + debug!("lost our connection!"); continue; } }, @@ -499,8 +499,8 @@ impl QuicEndpoint { remote_addr: endpoint.address.clone(), })) .expect( - "this is an unbounded channel, and we have an instance of the peer, so \ - this will never fail; qed", + "this is an unbounded channel, and we have an instance of the peer, so \ + this will never fail; qed", ); } } @@ -596,7 +596,7 @@ impl Transport for &QuicEndpoint { .inner .connect(self.1.client_config.clone(), socket_addr, "localhost") .map_err(|e| { - error!("Connection error: {:?}", e); + warn!("Connection error: {:?}", e); TransportError::Other(io::ErrorKind::InvalidInput.into()) }); let (handle, conn) = s?; @@ -687,22 +687,6 @@ impl Muxer { } } - pub fn transmit(&mut self, cx: &mut Context<'_>, now: Instant) -> Poll> { - debug_assert!( - self.pending.is_none(), - "You called Muxer::transmit with a pending packet" - ); - let mut need_wake = false; - while let Some(transmit) = self.connection.poll_transmit(now) { - need_wake = true; - ready!(self.poll_transmit(cx, transmit))?; - } - if need_wake { - self.wake_driver() - } - Ready(Ok(())) - } - fn wake_driver(&mut self) { drop(self.driver_waker.take().map(|w| w.wake())) } @@ -826,7 +810,6 @@ impl Muxer { v.wake(); } self.connectors.truncate(0); - debug!("Self is {:?}", self); } Event::StreamFinished { stream, .. } => { // Wake up the task waiting on us (if any) @@ -903,6 +886,7 @@ impl Future for ConnectionDriver { trace!("being polled for timers!"); let mut inner = this.inner.lock(); inner.driver_waker = Some(cx.waker().clone()); + let mut need_application_processing = false; loop { trace!("loop iteration"); let mut needs_timer_update = false; @@ -919,6 +903,7 @@ impl Future for ConnectionDriver { Pending => continue, Ready(()) => { trace!("timer {:?} ready at time {:?}", timer, now); + need_application_processing = true; connection.handle_timeout(now, timer); } } @@ -929,7 +914,7 @@ impl Future for ConnectionDriver { match update { TimerSetting::Stop => timers[timer] = None, TimerSetting::Start(instant) => { - trace!("setting a timer for {:?}", instant - now); + trace!("setting timer {:?} for {:?}", timer, instant - now); let mut new_timer = futures_timer::Delay::new(instant - now); match new_timer.poll_unpin(cx) { Ready(()) => needs_timer_update = true, @@ -939,12 +924,18 @@ impl Future for ConnectionDriver { } } } + while let Some(tx) = inner.connection.poll_transmit(now) { + needs_timer_update = true; + drop(ready!(inner.poll_transmit(cx, tx))) + } if !needs_timer_update { break; } } - inner.process_app_events(); - ready!(inner.transmit(cx, now)); + if need_application_processing { + inner.process_app_events(); + } + if inner.connection.is_drained() { debug!( "Connection drained: close reason {}", From 7a43bf0fa6eb2285e6bb0d1f286c009ede5597c3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 27 Dec 2019 12:24:59 -0500 Subject: [PATCH 034/202] Formatting cleanups --- transports/quic/src/lib.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 66adaa94e6f..b77fc97e342 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -52,9 +52,11 @@ //! The entry point is the `QuicEndpoint` struct. It represents a single QUIC endpoint. You //! should generally have one of these per process. //! -//! `QuicEndpoint` manages a background task that processes all socket I/O. This includes: +//! `QuicEndpoint` manages a background task that processes all incoming packets. Each +//! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![deny(unsafe_code)] +#![forbid(unsafe_code, dead_code)] +mod certificate; mod connection; use async_macros::ready; use async_std::net::UdpSocket; @@ -195,7 +197,7 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context) -> Poll> { let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(std::io::Error::new( + return Ready(Err(std::io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); @@ -205,7 +207,7 @@ impl StreamMuxer for QuicMuxer { inner.accept_waker = Some(cx.waker().clone()); Pending } - Some(stream) => Poll::Ready(Ok(stream)), + Some(stream) => Ready(Ok(stream)), } } @@ -227,9 +229,7 @@ impl StreamMuxer for QuicMuxer { Err(WriteError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(WriteError::Stopped(_)) => { - Poll::Ready(Err(std::io::ErrorKind::ConnectionAborted.into())) - } + Err(WriteError::Stopped(_)) => Ready(Err(std::io::ErrorKind::ConnectionAborted.into())), } } @@ -252,7 +252,7 @@ impl StreamMuxer for QuicMuxer { ready!(inner.pre_application_io(cx))?; match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => inner.on_application_io(cx, bytes), - Ok(None) => Poll::Ready(Ok(0)), + Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { inner.readers.insert(*substream, cx.waker().clone()); Pending @@ -260,7 +260,7 @@ impl StreamMuxer for QuicMuxer { Err(ReadError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), + Err(ReadError::Reset(_)) => Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -269,7 +269,7 @@ impl StreamMuxer for QuicMuxer { _cx: &mut Context, substream: &mut Self::Substream, ) -> Poll> { - Poll::Ready( + Ready( self.inner() .connection .finish(*substream) @@ -293,11 +293,11 @@ impl StreamMuxer for QuicMuxer { } fn flush_all(&self, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(())) + Ready(Ok(())) } fn close(&self, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(self.inner().connection.close( + Ready(Ok(self.inner().connection.close( Instant::now(), Default::default(), Default::default(), @@ -324,11 +324,11 @@ impl AsyncWrite for QuicStream { } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(())) + Ready(Ok(())) } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(())) + Ready(Ok(())) } } From 7406e185c99cbe1434657e43fecb056ccb0f689b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 27 Dec 2019 14:21:42 -0500 Subject: [PATCH 035/202] Implement certificate generation for libp2p libp2p requrires custom certificate generation. This implements it, but does not hook it up to the rest of the codebase. --- transports/quic/Cargo.toml | 3 ++ transports/quic/src/certificate.rs | 62 ++++++++++++++++++++++++++++++ transports/quic/src/lib.rs | 4 +- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 1d38a19abe3..f191089c172 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -40,3 +40,6 @@ async-macros = "2.0.0" futures-timer = "2.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" +rcgen = "0.7.0" +protobuf = "2.8.1" +yasna = "0.3.1" diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index fe20081dbfb..4ffc55233e9 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -19,3 +19,65 @@ // DEALINGS IN THE SOFTWARE. //! Certificate handling for libp2p +//! +//! This handles generation, signing, and verification. + +//! The libp2p extension OID +pub const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; +pub const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; +static LIBP2P_SIGNATURE_ALGORITHM: &'static rcgen::SignatureAlgorithm = + &rcgen::PKCS_ECDSA_P256_SHA256; +use libp2p_core::identity; + +fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { + let public_key = public_key.into_protobuf_encoding(); + let contents = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer + .next() + .write_bitvec_bytes(&public_key, public_key.len() * 8); + writer + .next() + .write_bitvec_bytes(signature, signature.len() * 8); + }) + }); + let mut libp2p_extension = rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents); + libp2p_extension.set_criticality(true); + libp2p_extension +} + +fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::CustomExtension) { + let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM) + .expect("we pass valid parameters, and assume we have enough memory and randomness; qed"); + let mut signing_buf = [0u8; 21 + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; + let public = temp_keypair.public_key_raw(); + assert_eq!(public.len(), LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, "ECDSA public keys are 65 bytes"); + signing_buf[..21].copy_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + signing_buf[21..].copy_from_slice(public); + let signature = keypair.sign(&signing_buf).expect("signing failed"); + ( + temp_keypair, + encode_signed_key(keypair.public(), &signature), + ) +} + +pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { + let mut params = rcgen::CertificateParams::new(vec![]); + let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); + params.custom_extensions.push(libp2p_extension); + params.alg = &LIBP2P_SIGNATURE_ALGORITHM; + params.key_pair = Some(cert_keypair); + rcgen::Certificate::from_params(params) + .expect("certificate generation with valid params will succeed") +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn can_make_a_certificate() { + make_cert(&identity::Keypair::generate_ed25519()); + make_cert(&identity::Keypair::generate_secp256k1()); + } +} diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index b77fc97e342..0dd7da543c7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -55,9 +55,10 @@ //! `QuicEndpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid(unsafe_code, dead_code)] +#![deny(unsafe_code, dead_code)] mod certificate; mod connection; +mod public_key_proto; use async_macros::ready; use async_std::net::UdpSocket; use futures::{ @@ -84,6 +85,7 @@ use std::{ }, time::Instant, }; +pub use certificate::make_cert; /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. /// From 0914f09a121bfe26eb3a8f05a9b8dd1d656f3722 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 27 Dec 2019 23:33:54 -0500 Subject: [PATCH 036/202] Start working on certificate verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the outermost level of parsing is implemented yet. I will be doing parsing manually, rather than using the x509-parser crate, as I don’t need to extract all the information that crate extracts. --- transports/quic/src/certificate.rs | 66 +++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 4ffc55233e9..11c777b8f9f 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -25,6 +25,7 @@ //! The libp2p extension OID pub const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; pub const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &'static rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; @@ -50,11 +51,16 @@ fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::CustomExtension) { let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM) .expect("we pass valid parameters, and assume we have enough memory and randomness; qed"); - let mut signing_buf = [0u8; 21 + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; + let mut signing_buf = + [0u8; LIBP2P_SIGNING_PREFIX_LENGTH + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; let public = temp_keypair.public_key_raw(); - assert_eq!(public.len(), LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, "ECDSA public keys are 65 bytes"); - signing_buf[..21].copy_from_slice(&LIBP2P_SIGNING_PREFIX[..]); - signing_buf[21..].copy_from_slice(public); + assert_eq!( + public.len(), + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, + "ECDSA public keys are 65 bytes" + ); + signing_buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + signing_buf[LIBP2P_SIGNING_PREFIX_LENGTH..].copy_from_slice(public); let signature = keypair.sign(&signing_buf).expect("signing failed"); ( temp_keypair, @@ -72,12 +78,60 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { .expect("certificate generation with valid params will succeed") } +#[cfg_attr(not(test), allow(dead_code))] +pub struct X509Certificate { + #[cfg_attr(test, allow(dead_code))] + signed_data: Vec, + #[cfg_attr(test, allow(dead_code))] + algorithm: yasna::models::ObjectIdentifier, + #[cfg_attr(test, allow(dead_code))] + signature_value: Vec, +} + +/// Parse an X.509 certificate. Does NOT verify it. +#[cfg_attr(not(test), allow(dead_code))] +pub fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result { + yasna::parse_der(certificate, |reader| { + reader.read_sequence(|reader| { + let signed_data = reader.next().read_der()?; + let algorithm = reader.next().read_sequence(|reader| { + let oid = reader.next().read_oid()?; + reader.read_optional(|_: yasna::BERReader| -> Result<(), _> { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Extra)) + })?; + Ok(oid) + })?; + let (signature_value, bits) = reader.next().read_bitvec_bytes()?; + // be extra careful regarding overflow + if (bits & 7) != 0 || signature_value.len() != (bits >> 3) { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } else { + Ok(X509Certificate { + signed_data, + algorithm, + signature_value, + }) + } + }) + }) +} + #[cfg(test)] mod test { use super::*; #[test] fn can_make_a_certificate() { - make_cert(&identity::Keypair::generate_ed25519()); - make_cert(&identity::Keypair::generate_secp256k1()); + parse_certificate( + &make_cert(&identity::Keypair::generate_ed25519()) + .serialize_der() + .unwrap(), + ) + .unwrap(); + parse_certificate( + &make_cert(&identity::Keypair::generate_secp256k1()) + .serialize_der() + .unwrap(), + ) + .unwrap(); } } From f78c89f20aa67f40490092bb9d25c4fdde224291 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 27 Dec 2019 23:35:52 -0500 Subject: [PATCH 037/202] Add connection.rs This will someday hold connection code, once I do a refactor. --- transports/quic/src/connection.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 transports/quic/src/connection.rs diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs new file mode 100644 index 00000000000..110531d5832 --- /dev/null +++ b/transports/quic/src/connection.rs @@ -0,0 +1,19 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. From 9f5b847ae73f04984abef3cee80bfc6c04eb3316 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 29 Dec 2019 21:11:00 -0500 Subject: [PATCH 038/202] Finish X.509 parsing Extension verification is not yet implemented --- transports/quic/Cargo.toml | 2 +- transports/quic/src/certificate.rs | 201 ++++++++++++++++++++++++----- 2 files changed, 169 insertions(+), 34 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index f191089c172..308b93f0ce9 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -42,4 +42,4 @@ env_logger = "0.7.1" parking_lot = "0.10.0" rcgen = "0.7.0" protobuf = "2.8.1" -yasna = "0.3.1" +yasna = { version = "0.3.1", features = ["num-bigint"] } diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 11c777b8f9f..5e8916fcdb5 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -21,8 +21,11 @@ //! Certificate handling for libp2p //! //! This handles generation, signing, and verification. - -//! The libp2p extension OID +//! +//! This crate uses the `log` crate to emit log output. Events that will occur normally are output +//! at `trace` level, while “expected” error conditions (ones that can result during correct use of the +//! library) are logged at `debug` level. +use log::{debug, trace}; pub const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; pub const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); @@ -75,62 +78,194 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { params.alg = &LIBP2P_SIGNATURE_ALGORITHM; params.key_pair = Some(cert_keypair); rcgen::Certificate::from_params(params) - .expect("certificate generation with valid params will succeed") + .expect("certificate generation with valid params will succeed; qed") +} + +/// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. +fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1Error> { + let (value, bits) = reader.next().read_bitvec_bytes()?; + // be extra careful regarding overflow + if (bits & 7) == 0 && value.len() == (bits >> 3) { + Ok(value) + } else { + debug!("value was of wrong length, sorry!"); + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } +} + +/// Read an X.509 Algorithm Identifier +fn read_algid(reader: &mut yasna::BERReaderSeq) -> Result { + reader.next().read_sequence(|reader| { + // We don’t support signature algorithms with parameters. + Ok(AlgorithmIdentifier { + algorithm: reader.next().read_oid()?, + parameters: reader + .read_optional(|reader| reader.read_der())? + .unwrap_or(vec![]), + }) + }) +} + +#[derive(PartialEq, Eq, Debug)] +struct AlgorithmIdentifier { + #[cfg_attr(test, allow(dead_code))] + algorithm: yasna::models::ObjectIdentifier, + #[cfg_attr(test, allow(dead_code))] + parameters: Vec, } #[cfg_attr(not(test), allow(dead_code))] pub struct X509Certificate { #[cfg_attr(test, allow(dead_code))] - signed_data: Vec, + tbs_certificate: Vec, #[cfg_attr(test, allow(dead_code))] - algorithm: yasna::models::ObjectIdentifier, + algorithm: AlgorithmIdentifier, #[cfg_attr(test, allow(dead_code))] signature_value: Vec, } -/// Parse an X.509 certificate. Does NOT verify it. -#[cfg_attr(not(test), allow(dead_code))] -pub fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result { - yasna::parse_der(certificate, |reader| { - reader.read_sequence(|reader| { - let signed_data = reader.next().read_der()?; - let algorithm = reader.next().read_sequence(|reader| { - let oid = reader.next().read_oid()?; - reader.read_optional(|_: yasna::BERReader| -> Result<(), _> { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Extra)) - })?; - Ok(oid) - })?; - let (signature_value, bits) = reader.next().read_bitvec_bytes()?; - // be extra careful regarding overflow - if (bits & 7) != 0 || signature_value.len() != (bits >> 3) { +fn parse_x509_extensions( + reader: &mut yasna::BERReaderSeq, + public_key: &identity::PublicKey, + certificate_key: &[u8], +) -> Result<(), yasna::ASN1Error> { + reader.next().read_tagged(yasna::Tag::context(3), |reader| { + reader.read_sequence_of(|reader| { + trace!("reading an extension"); + parse_x509_extension(reader, &public_key, &certificate_key) + }) + }) +} + +fn parse_x509_extension( + reader: yasna::BERReader, + public_key: &identity::PublicKey, + certificate_key: &[u8], +) -> Result<(), yasna::ASN1Error> { + reader.read_sequence(|reader| { + let oid = reader.next().read_oid()?; + trace!("read extensions with oid {:?}", oid); + let mut is_critical = false; + // ASN.1 DER forbids explicitly specifying a default value. However, other + // tools allow specifying `false` for `is_critical`, so so do we. + reader.read_optional(|reader| Ok(is_critical = reader.read_bool()?))?; + trace!("certificate critical? {}", is_critical); + let contents = reader.next().read_bytes()?; + if *oid.components() != LIBP2P_OID { + if is_critical { + // unknown critical extension Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) } else { - Ok(X509Certificate { - signed_data, - algorithm, - signature_value, - }) + Ok(()) } - }) + } else { + verify_libp2p_extension(&contents, public_key, certificate_key) + } }) } +fn verify_libp2p_extension( + extension: &[u8], + public_key: &identity::PublicKey, + certificate_key: &[u8], +) -> yasna::ASN1Result<()> { + unimplemented!() +} + +/// Parse an X.509 certificate. Does NOT verify it. +#[cfg_attr(not(test), allow(dead_code))] +pub fn parse_certificate( + certificate: &[u8], + public_key: &identity::PublicKey, +) -> yasna::ASN1Result { + trace!("parsing certificate"); + let raw_certificate = yasna::parse_der(certificate, |reader| { + reader.read_sequence(|mut reader| { + let tbs_certificate = reader.next().read_der()?; + let algorithm = read_algid(&mut reader)?; + let signature_value = read_bitvec(&mut reader)?; + Ok(X509Certificate { + tbs_certificate, + algorithm, + signature_value, + }) + }) + })?; + yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { + trace!("parsing TBScertificate"); + reader.read_sequence(|mut reader| { + trace!("getting X509 version"); + let version = reader.next().read_der()?; + // this is the encoding of 2 with context 0 + if version != [160, 3, 2, 1, 2] { + debug!("got invalid version {:?}", version); + return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; + } + let serial_number = reader.next().read_biguint()?; + trace!("got serial number {:?}", serial_number); + drop(serial_number); + if read_algid(&mut reader)? != raw_certificate.algorithm { + debug!( + "expected algid to be {:?}, but it is not", + raw_certificate.algorithm + ); + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? + } + reader + .next() + .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the issuer + trace!("reading validity"); + reader.next().read_sequence(|reader| { + reader.next().read_der()?; + reader.next().read_der() + })?; // we don’t care about the validity + trace!("reading subject"); + reader + .next() + .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the subject + trace!("reading subjectPublicKeyInfo"); + let key = reader.next().read_sequence(|mut reader| { + trace!("reading subject key algorithm"); + let algid = read_algid(&mut reader)?; + trace!("reading subject key"); + let key = read_bitvec(&mut reader)?; + if algid != raw_certificate.algorithm { + trace!( + "expected algid to be {:?}, but it is {:?}. Key is {:?}. Ignoring subject’s key!", + raw_certificate.algorithm, + algid, + key, + ); + } + Ok(key) + })?; + trace!("reading issuerUniqueId"); + reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the issuerUniqueId + trace!("reading subjectUniqueId"); + reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the subjectUniqueId + trace!("reading extensions"); + parse_x509_extensions(reader, public_key, &key) + }) + })?; + Ok(raw_certificate) +} + #[cfg(test)] mod test { use super::*; #[test] fn can_make_a_certificate() { + drop(env_logger::try_init()); + let keypair = identity::Keypair::generate_ed25519(); parse_certificate( - &make_cert(&identity::Keypair::generate_ed25519()) - .serialize_der() - .unwrap(), + &make_cert(&keypair).serialize_der().unwrap(), + &keypair.public(), ) .unwrap(); + let keypair = identity::Keypair::generate_secp256k1(); parse_certificate( - &make_cert(&identity::Keypair::generate_secp256k1()) - .serialize_der() - .unwrap(), + &make_cert(&keypair).serialize_der().unwrap(), + &keypair.public(), ) .unwrap(); } From 9e2c63a699b7a83a7ec1be7ecd585e25df9f7db0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 30 Dec 2019 19:05:12 -0500 Subject: [PATCH 039/202] Some wrong attempts at certificate verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn’t pass its tests, but that is okay, as I will be swtiching to a completely different approach: using webpki for certificate verification. --- transports/quic/Cargo.toml | 2 +- transports/quic/src/certificate.rs | 275 ++++++++++++++++++++--------- transports/quic/src/lib.rs | 2 +- 3 files changed, 189 insertions(+), 90 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 308b93f0ce9..2492712995d 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -40,6 +40,6 @@ async-macros = "2.0.0" futures-timer = "2.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" -rcgen = "0.7.0" +rcgen = { version = "0.7.0", features = ["x509-parser"] } protobuf = "2.8.1" yasna = { version = "0.3.1", features = ["num-bigint"] } diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 5e8916fcdb5..ac25ff1f380 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -93,15 +93,75 @@ fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1E } } +const SHA256_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; +const SHA384_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 2]; +const SHA512_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 3]; +const MGF1_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; +const RSA_PSS_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; +const SHA256_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 11]; +const SHA384_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 12]; +const SHA512_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 13]; + +/// Read an optional NULL. Returns None for easier chaining. +fn read_null( + reader: &mut yasna::BERReaderSeq, +) -> Result, yasna::ASN1Error> { + reader + .read_optional(|reader| reader.read_null()) + .map(|_| None) +} + +/// Read an X.509 hash algorithm identifier. +fn read_hash_algid( + reader: &mut yasna::BERReaderSeq, +) -> Result<(yasna::models::ObjectIdentifier, u8), yasna::ASN1Error> { + // This is optional, but the default is SHA-1 which is insecure. + // So we treat it as required. + reader.next().read_sequence(|reader| { + let oid = reader.next().read_oid()?; + let length = match &**oid.components() { + SHA256_OID => 32, + SHA384_OID => 48, + SHA512_OID => 64, + _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), + }; + read_null(reader)?; + Ok((oid, length)) + }) +} + /// Read an X.509 Algorithm Identifier fn read_algid(reader: &mut yasna::BERReaderSeq) -> Result { - reader.next().read_sequence(|reader| { - // We don’t support signature algorithms with parameters. + reader.next().read_sequence(|mut reader| { + let algorithm = reader.next().read_oid()?; + let parameters = match &**algorithm.components() { + RSA_PSS_OID => reader.next().read_sequence(|mut reader| { + let (hash_oid, length) = read_hash_algid(&mut reader)?; + reader.next().read_sequence(|mut reader| { + let mgf = reader.next().read_oid()?; + if &**mgf.components() != MGF1_OID || length != read_hash_algid(&mut reader)?.1 + { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } else { + Ok(()) + } + })?; + if reader.next().read_u8()? != length { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } else { + Ok(Some(hash_oid)) + } + }), + SHA256_WITH_RSA_ENCRYPTION_OID + | SHA384_WITH_RSA_ENCRYPTION_OID + | SHA512_WITH_RSA_ENCRYPTION_OID => read_null(&mut reader), + // _ => Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), + _ => unimplemented!("non-RSA keys"), + }?; + Ok(AlgorithmIdentifier { - algorithm: reader.next().read_oid()?, - parameters: reader - .read_optional(|reader| reader.read_der())? - .unwrap_or(vec![]), + algorithm, + parameters, }) }) } @@ -111,10 +171,11 @@ struct AlgorithmIdentifier { #[cfg_attr(test, allow(dead_code))] algorithm: yasna::models::ObjectIdentifier, #[cfg_attr(test, allow(dead_code))] - parameters: Vec, + parameters: Option, } #[cfg_attr(not(test), allow(dead_code))] +#[derive(Debug)] pub struct X509Certificate { #[cfg_attr(test, allow(dead_code))] tbs_certificate: Vec, @@ -126,29 +187,45 @@ pub struct X509Certificate { fn parse_x509_extensions( reader: &mut yasna::BERReaderSeq, - public_key: &identity::PublicKey, certificate_key: &[u8], -) -> Result<(), yasna::ASN1Error> { +) -> Result { reader.next().read_tagged(yasna::Tag::context(3), |reader| { + let mut public_key = None; + let mut oids_seen = std::collections::HashSet::new(); reader.read_sequence_of(|reader| { trace!("reading an extension"); - parse_x509_extension(reader, &public_key, &certificate_key) - }) + Ok(public_key = parse_x509_extension(reader, &certificate_key, &mut oids_seen)?) + })?; + match public_key { + Some(e) => Ok(e), + None => Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Eof)), + } }) } fn parse_x509_extension( reader: yasna::BERReader, - public_key: &identity::PublicKey, certificate_key: &[u8], -) -> Result<(), yasna::ASN1Error> { + oids_seen: &mut std::collections::HashSet, +) -> Result, yasna::ASN1Error> { reader.read_sequence(|reader| { let oid = reader.next().read_oid()?; trace!("read extensions with oid {:?}", oid); + if !oids_seen.insert(oid.clone()) { + debug!( + "found the second extension with oid {:?} in the same certificate", + oid + ); + return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); + } let mut is_critical = false; - // ASN.1 DER forbids explicitly specifying a default value. However, other - // tools allow specifying `false` for `is_critical`, so so do we. - reader.read_optional(|reader| Ok(is_critical = reader.read_bool()?))?; + reader.read_optional(|reader| { + if reader.read_bool()? { + Ok(is_critical = true) + } else { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } + })?; trace!("certificate critical? {}", is_critical); let contents = reader.next().read_bytes()?; if *oid.components() != LIBP2P_OID { @@ -156,28 +233,41 @@ fn parse_x509_extension( // unknown critical extension Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) } else { - Ok(()) + Ok(None) } } else { - verify_libp2p_extension(&contents, public_key, certificate_key) + Ok(Some(verify_libp2p_extension(&contents, certificate_key)?)) } }) } fn verify_libp2p_extension( extension: &[u8], - public_key: &identity::PublicKey, certificate_key: &[u8], -) -> yasna::ASN1Result<()> { - unimplemented!() +) -> yasna::ASN1Result { + yasna::parse_der(extension, |reader| { + reader.read_sequence(|mut reader| { + let public_key = read_bitvec(&mut reader)?; + let signature = read_bitvec(&mut reader)?; + let public_key = identity::PublicKey::from_protobuf_encoding(&public_key) + .map_err(|_| yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; + let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); + v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + v.extend_from_slice(certificate_key); + if public_key.verify(&v, &signature) { + Ok(public_key) + } else { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } + }) + }) } /// Parse an X.509 certificate. Does NOT verify it. #[cfg_attr(not(test), allow(dead_code))] pub fn parse_certificate( certificate: &[u8], - public_key: &identity::PublicKey, -) -> yasna::ASN1Result { +) -> yasna::ASN1Result<(X509Certificate, identity::PublicKey)> { trace!("parsing certificate"); let raw_certificate = yasna::parse_der(certificate, |reader| { reader.read_sequence(|mut reader| { @@ -191,63 +281,57 @@ pub fn parse_certificate( }) }) })?; - yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { - trace!("parsing TBScertificate"); - reader.read_sequence(|mut reader| { - trace!("getting X509 version"); - let version = reader.next().read_der()?; - // this is the encoding of 2 with context 0 - if version != [160, 3, 2, 1, 2] { - debug!("got invalid version {:?}", version); - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; - } - let serial_number = reader.next().read_biguint()?; - trace!("got serial number {:?}", serial_number); - drop(serial_number); - if read_algid(&mut reader)? != raw_certificate.algorithm { - debug!( - "expected algid to be {:?}, but it is not", - raw_certificate.algorithm - ); - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? - } - reader - .next() - .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the issuer - trace!("reading validity"); - reader.next().read_sequence(|reader| { - reader.next().read_der()?; - reader.next().read_der() - })?; // we don’t care about the validity - trace!("reading subject"); - reader - .next() - .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the subject - trace!("reading subjectPublicKeyInfo"); - let key = reader.next().read_sequence(|mut reader| { - trace!("reading subject key algorithm"); - let algid = read_algid(&mut reader)?; - trace!("reading subject key"); - let key = read_bitvec(&mut reader)?; - if algid != raw_certificate.algorithm { - trace!( - "expected algid to be {:?}, but it is {:?}. Key is {:?}. Ignoring subject’s key!", - raw_certificate.algorithm, - algid, - key, + let (_certificate_key, identity_key) = + yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { + trace!("parsing TBScertificate"); + reader.read_sequence(|mut reader| { + trace!("getting X509 version"); + let version = reader.next().read_der()?; + // this is the encoding of 2 with context 0 + if version != [160, 3, 2, 1, 2] { + debug!("got invalid version {:?}", version); + return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; + } + let serial_number = reader.next().read_biguint()?; + trace!("got serial number {:?}", serial_number); + drop(serial_number); + if read_algid(&mut reader)? != raw_certificate.algorithm { + debug!( + "expected algid to be {:?}, but it is not", + raw_certificate.algorithm ); + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? } - Ok(key) - })?; - trace!("reading issuerUniqueId"); - reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the issuerUniqueId - trace!("reading subjectUniqueId"); - reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the subjectUniqueId - trace!("reading extensions"); - parse_x509_extensions(reader, public_key, &key) - }) - })?; - Ok(raw_certificate) + reader + .next() + .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the issuer + trace!("reading validity"); + reader.next().read_sequence(|reader| { + reader.next().read_der()?; + reader.next().read_der() + })?; // we don’t care about the validity + trace!("reading subject"); + reader + .next() + .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the subject + trace!("reading subjectPublicKeyInfo"); + let key = reader.next().read_sequence(|mut reader| { + trace!("reading subject key algorithm"); + let _algid = read_algid(&mut reader)?; + trace!("reading subject key"); + let key = read_bitvec(&mut reader)?; + Ok(key) + })?; + trace!("reading issuerUniqueId"); + reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the issuerUniqueId + trace!("reading subjectUniqueId"); + reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the subjectUniqueId + trace!("reading extensions"); + let identity_key = parse_x509_extensions(reader, &key)?; + Ok((key, identity_key)) + }) + })?; + Ok((raw_certificate, identity_key)) } #[cfg(test)] @@ -257,16 +341,31 @@ mod test { fn can_make_a_certificate() { drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); - parse_certificate( - &make_cert(&keypair).serialize_der().unwrap(), - &keypair.public(), - ) - .unwrap(); + assert_eq!( + parse_certificate(&make_cert(&keypair).serialize_der().unwrap(),) + .unwrap() + .1, + keypair.public() + ); + log::trace!("trying secp256k1!"); let keypair = identity::Keypair::generate_secp256k1(); - parse_certificate( - &make_cert(&keypair).serialize_der().unwrap(), - &keypair.public(), - ) - .unwrap(); + log::trace!("have a key!"); + let public = keypair.public(); + log::trace!("have a public key!"); + assert_eq!(public, public, "key is not equal to itself?"); + // assert_eq!( + // identity::PublicKey::from_protobuf_encoding(&public.clone().into_protobuf_encoding()) + // .unwrap(), + // public, + // "encoding and decoding corrupted key!" + // ); + log::error!("have a valid key!"); + assert_eq!( + parse_certificate(&make_cert(&keypair).serialize_der().unwrap(),) + .unwrap() + .1 + .into_protobuf_encoding(), + keypair.public().into_protobuf_encoding() + ); } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 0dd7da543c7..91db6f97bbd 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -55,7 +55,7 @@ //! `QuicEndpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![deny(unsafe_code, dead_code)] +#![deny(unsafe_code, dead_code, warnings)] mod certificate; mod connection; mod public_key_proto; From b8091e46a7a1d89bc2f325a5fc15643d1406c297 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 30 Dec 2019 20:24:52 -0500 Subject: [PATCH 040/202] Use webpki to verify the certificate self-signature This avoids massive X.509 headaches. --- transports/quic/Cargo.toml | 4 +- transports/quic/src/certificate.rs | 179 ++++++++++++++--------------- transports/quic/src/lib.rs | 6 +- 3 files changed, 92 insertions(+), 97 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 2492712995d..8f1ceb9b349 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -29,7 +29,6 @@ edition = "2018" rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.13.1" } log = "0.4.8" -tokio = { version = "0.2.2", features = ["rt-threaded"] } ipnet = "2.1.0" err-derive = "0.2.1" futures = { version = "0.3.1", package = "futures", features = ["compat"] } @@ -40,6 +39,7 @@ async-macros = "2.0.0" futures-timer = "2.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" -rcgen = { version = "0.7.0", features = ["x509-parser"] } +rcgen = "0.7.0" protobuf = "2.8.1" yasna = { version = "0.3.1", features = ["num-bigint"] } +webpki = "0.21.0" diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index ac25ff1f380..cfe2e9fdd42 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -46,9 +46,7 @@ fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen .write_bitvec_bytes(signature, signature.len() * 8); }) }); - let mut libp2p_extension = rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents); - libp2p_extension.set_criticality(true); - libp2p_extension + rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents) } fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::CustomExtension) { @@ -77,8 +75,11 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { params.custom_extensions.push(libp2p_extension); params.alg = &LIBP2P_SIGNATURE_ALGORITHM; params.key_pair = Some(cert_keypair); - rcgen::Certificate::from_params(params) - .expect("certificate generation with valid params will succeed; qed") + let cert = rcgen::Certificate::from_params(params) + .expect("certificate generation with valid params will succeed; qed"); + webpki::EndEntityCert::from(&cert.serialize_der().expect("serializing will work; qed")) + .expect("we just made the certificate, so it is valid; qed"); + cert } /// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. @@ -93,72 +94,11 @@ fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1E } } -const SHA256_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; -const SHA384_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 2]; -const SHA512_OID: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 3]; -const MGF1_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; -const RSA_PSS_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; -const SHA256_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 11]; -const SHA384_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 12]; -const SHA512_WITH_RSA_ENCRYPTION_OID: &[u64] = &[1, 2, 840, 113549, 1, 1, 13]; - -/// Read an optional NULL. Returns None for easier chaining. -fn read_null( - reader: &mut yasna::BERReaderSeq, -) -> Result, yasna::ASN1Error> { - reader - .read_optional(|reader| reader.read_null()) - .map(|_| None) -} - -/// Read an X.509 hash algorithm identifier. -fn read_hash_algid( - reader: &mut yasna::BERReaderSeq, -) -> Result<(yasna::models::ObjectIdentifier, u8), yasna::ASN1Error> { - // This is optional, but the default is SHA-1 which is insecure. - // So we treat it as required. - reader.next().read_sequence(|reader| { - let oid = reader.next().read_oid()?; - let length = match &**oid.components() { - SHA256_OID => 32, - SHA384_OID => 48, - SHA512_OID => 64, - _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), - }; - read_null(reader)?; - Ok((oid, length)) - }) -} - /// Read an X.509 Algorithm Identifier fn read_algid(reader: &mut yasna::BERReaderSeq) -> Result { - reader.next().read_sequence(|mut reader| { + reader.next().read_sequence(|reader| { let algorithm = reader.next().read_oid()?; - let parameters = match &**algorithm.components() { - RSA_PSS_OID => reader.next().read_sequence(|mut reader| { - let (hash_oid, length) = read_hash_algid(&mut reader)?; - reader.next().read_sequence(|mut reader| { - let mgf = reader.next().read_oid()?; - if &**mgf.components() != MGF1_OID || length != read_hash_algid(&mut reader)?.1 - { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } else { - Ok(()) - } - })?; - if reader.next().read_u8()? != length { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } else { - Ok(Some(hash_oid)) - } - }), - SHA256_WITH_RSA_ENCRYPTION_OID - | SHA384_WITH_RSA_ENCRYPTION_OID - | SHA512_WITH_RSA_ENCRYPTION_OID => read_null(&mut reader), - // _ => Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), - _ => unimplemented!("non-RSA keys"), - }?; - + let parameters = reader.read_optional(|reader| reader.read_der())?; Ok(AlgorithmIdentifier { algorithm, parameters, @@ -171,7 +111,7 @@ struct AlgorithmIdentifier { #[cfg_attr(test, allow(dead_code))] algorithm: yasna::models::ObjectIdentifier, #[cfg_attr(test, allow(dead_code))] - parameters: Option, + parameters: Option>, } #[cfg_attr(not(test), allow(dead_code))] @@ -263,11 +203,59 @@ fn verify_libp2p_extension( }) } -/// Parse an X.509 certificate. Does NOT verify it. +fn compute_signature_algorithm( + key: &[u8], + alg: &AlgorithmIdentifier, +) -> yasna::ASN1Result<&'static webpki::SignatureAlgorithm> { + #![cfg_attr(not(test), allow(dead_code))] + use webpki::*; + Ok(match (key.len(), &**alg.algorithm.components()) { + (32, &[1, 3, 101, 111]) => &ED25519, + (33, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P256_SHA256, + (65, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P256_SHA256, + (33, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P256_SHA384, + (65, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P256_SHA384, + (49, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P384_SHA256, + (97, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P384_SHA256, + (49, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P384_SHA384, + (97, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P384_SHA384, + (_, &[1, 2, 840, 113549, 1, 1, 11]) => &RSA_PKCS1_2048_8192_SHA256, + (_, &[1, 2, 840, 113549, 1, 1, 12]) => &RSA_PKCS1_2048_8192_SHA384, + (_, &[1, 2, 840, 113549, 1, 1, 13]) => &RSA_PKCS1_2048_8192_SHA512, + (_, &[1, 2, 840, 113549, 1, 1, 10]) => match alg.parameters { + None => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), + Some(ref e) => yasna::parse_der(e, |reader| { + reader.read_sequence(|reader| { + let keytype = reader.next().read_sequence(|reader| { + let keytype = match &**reader.next().read_oid()?.components() { + &[2, 16, 840, 1, 101, 3, 4, 2, 1] => { + &RSA_PSS_2048_8192_SHA256_LEGACY_KEY + } + &[2, 16, 840, 1, 101, 3, 4, 2, 2] => { + &RSA_PSS_2048_8192_SHA384_LEGACY_KEY + } + &[2, 16, 840, 1, 101, 3, 4, 2, 3] => { + &RSA_PSS_2048_8192_SHA512_LEGACY_KEY + } + _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), + }; + reader.read_optional(|reader| reader.read_null())?; + Ok(keytype) + })?; + reader.next().read_der()?; // ignore maskGenAlgorithm + reader.next().read_der()?; // ignore saltLength + Ok(keytype) + }) + })?, + }, + _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), + }) +} + #[cfg_attr(not(test), allow(dead_code))] -pub fn parse_certificate( +fn parse_certificate( certificate: &[u8], -) -> yasna::ASN1Result<(X509Certificate, identity::PublicKey)> { +) -> yasna::ASN1Result<(X509Certificate, Vec, identity::PublicKey)> { trace!("parsing certificate"); let raw_certificate = yasna::parse_der(certificate, |reader| { reader.read_sequence(|mut reader| { @@ -281,7 +269,7 @@ pub fn parse_certificate( }) }) })?; - let (_certificate_key, identity_key) = + let (certificate_key, identity_key) = yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { trace!("parsing TBScertificate"); reader.read_sequence(|mut reader| { @@ -302,25 +290,17 @@ pub fn parse_certificate( ); Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? } - reader - .next() - .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the issuer + reader.next().read_der()?; // we don’t care about the issuer trace!("reading validity"); - reader.next().read_sequence(|reader| { - reader.next().read_der()?; - reader.next().read_der() - })?; // we don’t care about the validity + reader.next().read_der()?; // we don’t care about the validity trace!("reading subject"); - reader - .next() - .read_sequence_of(|reader| reader.read_der().map(drop))?; // we don’t care about the subject + reader.next().read_der()?; // we don’t care about the subject trace!("reading subjectPublicKeyInfo"); let key = reader.next().read_sequence(|mut reader| { trace!("reading subject key algorithm"); - let _algid = read_algid(&mut reader)?; + reader.next().read_der()?; trace!("reading subject key"); - let key = read_bitvec(&mut reader)?; - Ok(key) + read_bitvec(&mut reader) })?; trace!("reading issuerUniqueId"); reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the issuerUniqueId @@ -331,7 +311,25 @@ pub fn parse_certificate( Ok((key, identity_key)) }) })?; - Ok((raw_certificate, identity_key)) + Ok((raw_certificate, certificate_key, identity_key)) +} + +pub fn verify_libp2p_certificate(certificate: &[u8]) -> Result { + #![cfg_attr(not(test), allow(dead_code))] + let end_entity_cert = webpki::EndEntityCert::from(certificate) + .map_err(|e| log::debug!("webpki found invalid certificate: {:?}", e))?; + let (raw_certificate, certificate_key, identity_key): (_, Vec, _) = + parse_certificate(certificate).map_err(|e| log::debug!("error in parsing: {:?}", e))?; + let algorithm = compute_signature_algorithm(&certificate_key, &raw_certificate.algorithm) + .map_err(|e| log::debug!("error getting signature algorithm: {:?}", e))?; + end_entity_cert + .verify_signature( + &algorithm, + &raw_certificate.tbs_certificate, + &raw_certificate.signature_value, + ) + .map_err(|e| log::debug!("error from webpki: {:?}", e))?; + Ok(identity_key) } #[cfg(test)] @@ -342,9 +340,7 @@ mod test { drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); assert_eq!( - parse_certificate(&make_cert(&keypair).serialize_der().unwrap(),) - .unwrap() - .1, + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), keypair.public() ); log::trace!("trying secp256k1!"); @@ -361,9 +357,8 @@ mod test { // ); log::error!("have a valid key!"); assert_eq!( - parse_certificate(&make_cert(&keypair).serialize_der().unwrap(),) + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(),) .unwrap() - .1 .into_protobuf_encoding(), keypair.public().into_protobuf_encoding() ); diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 91db6f97bbd..6cec1246742 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -55,12 +55,13 @@ //! `QuicEndpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![deny(unsafe_code, dead_code, warnings)] +#![forbid(unused_must_use, unstable_features, warnings, missing_copy_implementations)] +#![deny(trivial_casts)] mod certificate; mod connection; -mod public_key_proto; use async_macros::ready; use async_std::net::UdpSocket; +pub use certificate::make_cert; use futures::{ channel::{mpsc, oneshot}, prelude::*, @@ -85,7 +86,6 @@ use std::{ }, time::Instant, }; -pub use certificate::make_cert; /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. /// From 2f135c1272b42ed5449c52e298c6522682e127ad Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 30 Dec 2019 20:26:44 -0500 Subject: [PATCH 041/202] Remove spurious debug assertion --- transports/quic/src/certificate.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index cfe2e9fdd42..d31ab4bf759 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -75,11 +75,8 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { params.custom_extensions.push(libp2p_extension); params.alg = &LIBP2P_SIGNATURE_ALGORITHM; params.key_pair = Some(cert_keypair); - let cert = rcgen::Certificate::from_params(params) + rcgen::Certificate::from_params(params) .expect("certificate generation with valid params will succeed; qed"); - webpki::EndEntityCert::from(&cert.serialize_der().expect("serializing will work; qed")) - .expect("we just made the certificate, so it is valid; qed"); - cert } /// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. From fbc7e94490b88ae332571da3d0ed0ae579a53b94 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 30 Dec 2019 20:39:01 -0500 Subject: [PATCH 042/202] Remove commented-out code and excessive logging --- transports/quic/src/certificate.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index d31ab4bf759..561e9493f52 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -346,13 +346,7 @@ mod test { let public = keypair.public(); log::trace!("have a public key!"); assert_eq!(public, public, "key is not equal to itself?"); - // assert_eq!( - // identity::PublicKey::from_protobuf_encoding(&public.clone().into_protobuf_encoding()) - // .unwrap(), - // public, - // "encoding and decoding corrupted key!" - // ); - log::error!("have a valid key!"); + log::debug!("have a valid key!"); assert_eq!( verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(),) .unwrap() From 0bfe0d0e085bb8ee19062b70547dc7e2383d8abc Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 31 Dec 2019 13:42:02 -0500 Subject: [PATCH 043/202] Cleanup certificate verification --- transports/quic/src/certificate.rs | 57 +++++++++++++++++------------- transports/quic/src/lib.rs | 7 +++- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 561e9493f52..7415bd3d244 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -25,7 +25,7 @@ //! This crate uses the `log` crate to emit log output. Events that will occur normally are output //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. -use log::{debug, trace}; +use log::{debug, trace, warn}; pub const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; pub const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); @@ -76,7 +76,7 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { params.alg = &LIBP2P_SIGNATURE_ALGORITHM; params.key_pair = Some(cert_keypair); rcgen::Certificate::from_params(params) - .expect("certificate generation with valid params will succeed; qed"); + .expect("certificate generation with valid params will succeed; qed") } /// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. @@ -86,7 +86,7 @@ fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1E if (bits & 7) == 0 && value.len() == (bits >> 3) { Ok(value) } else { - debug!("value was of wrong length, sorry!"); + warn!("value was of wrong length, sorry!"); Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) } } @@ -149,7 +149,7 @@ fn parse_x509_extension( let oid = reader.next().read_oid()?; trace!("read extensions with oid {:?}", oid); if !oids_seen.insert(oid.clone()) { - debug!( + warn!( "found the second extension with oid {:?} in the same certificate", oid ); @@ -200,25 +200,31 @@ fn verify_libp2p_extension( }) } +/// Compute the signature algorithm corresponding to an algorithm identifier. +/// These OIDs come from various RFCs. +/// +/// Potential future optimization: operate directly on serialized data. +/// +/// Note that this is NOT a validating parser! We rely on webpki to do that for us. We just do +/// the bare minimum. fn compute_signature_algorithm( key: &[u8], alg: &AlgorithmIdentifier, ) -> yasna::ASN1Result<&'static webpki::SignatureAlgorithm> { #![cfg_attr(not(test), allow(dead_code))] - use webpki::*; Ok(match (key.len(), &**alg.algorithm.components()) { - (32, &[1, 3, 101, 111]) => &ED25519, - (33, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P256_SHA256, - (65, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P256_SHA256, - (33, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P256_SHA384, - (65, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P256_SHA384, - (49, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P384_SHA256, - (97, &[1, 2, 840, 10045, 4, 3, 2]) => &ECDSA_P384_SHA256, - (49, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P384_SHA384, - (97, &[1, 2, 840, 10045, 4, 3, 3]) => &ECDSA_P384_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 11]) => &RSA_PKCS1_2048_8192_SHA256, - (_, &[1, 2, 840, 113549, 1, 1, 12]) => &RSA_PKCS1_2048_8192_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 13]) => &RSA_PKCS1_2048_8192_SHA512, + (32, &[1, 3, 101, 111]) => &webpki::ED25519, + (33, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P256_SHA256, + (65, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P256_SHA256, + (33, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P256_SHA384, + (65, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P256_SHA384, + (49, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P384_SHA256, + (97, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P384_SHA256, + (49, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P384_SHA384, + (97, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P384_SHA384, + (_, &[1, 2, 840, 113549, 1, 1, 11]) => &webpki::RSA_PKCS1_2048_8192_SHA256, + (_, &[1, 2, 840, 113549, 1, 1, 12]) => &webpki::RSA_PKCS1_2048_8192_SHA384, + (_, &[1, 2, 840, 113549, 1, 1, 13]) => &webpki::RSA_PKCS1_2048_8192_SHA512, (_, &[1, 2, 840, 113549, 1, 1, 10]) => match alg.parameters { None => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), Some(ref e) => yasna::parse_der(e, |reader| { @@ -226,15 +232,18 @@ fn compute_signature_algorithm( let keytype = reader.next().read_sequence(|reader| { let keytype = match &**reader.next().read_oid()?.components() { &[2, 16, 840, 1, 101, 3, 4, 2, 1] => { - &RSA_PSS_2048_8192_SHA256_LEGACY_KEY + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY } &[2, 16, 840, 1, 101, 3, 4, 2, 2] => { - &RSA_PSS_2048_8192_SHA384_LEGACY_KEY + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY } &[2, 16, 840, 1, 101, 3, 4, 2, 3] => { - &RSA_PSS_2048_8192_SHA512_LEGACY_KEY + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY + } + _ => { + warn!("unsupported signature algorithm, rejecting!"); + return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); } - _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), }; reader.read_optional(|reader| reader.read_null())?; Ok(keytype) @@ -274,12 +283,10 @@ fn parse_certificate( let version = reader.next().read_der()?; // this is the encoding of 2 with context 0 if version != [160, 3, 2, 1, 2] { - debug!("got invalid version {:?}", version); + warn!("got invalid version {:?}", version); return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; } - let serial_number = reader.next().read_biguint()?; - trace!("got serial number {:?}", serial_number); - drop(serial_number); + reader.next().read_biguint()?; // ignore the serial number if read_algid(&mut reader)? != raw_certificate.algorithm { debug!( "expected algid to be {:?}, but it is not", diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 6cec1246742..85d6165fe51 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -55,7 +55,12 @@ //! `QuicEndpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid(unused_must_use, unstable_features, warnings, missing_copy_implementations)] +#![forbid( + unused_must_use, + unstable_features, + warnings, + missing_copy_implementations +)] #![deny(trivial_casts)] mod certificate; mod connection; From 20f63a4e7218a0c953a24d54f9a9c32379bb0fbe Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 31 Dec 2019 14:43:11 -0500 Subject: [PATCH 044/202] Remove connection.rs It was going to be used for the connection struct, but there is not enough code to justify this, at least for now. --- transports/quic/src/connection.rs | 19 ------------------- transports/quic/src/lib.rs | 1 - 2 files changed, 20 deletions(-) delete mode 100644 transports/quic/src/connection.rs diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs deleted file mode 100644 index 110531d5832..00000000000 --- a/transports/quic/src/connection.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2017-2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 85d6165fe51..c0d561510d5 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -63,7 +63,6 @@ )] #![deny(trivial_casts)] mod certificate; -mod connection; use async_macros::ready; use async_std::net::UdpSocket; pub use certificate::make_cert; From b02cf2eb8e03c2323d3263228db2f94f84aaf061 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 31 Dec 2019 19:59:13 -0500 Subject: [PATCH 045/202] Get QUIC connections to work This implements QUIC certificate generation. Verification is not yet implemented. --- transports/quic/Cargo.toml | 1 + transports/quic/src/certificate.rs | 62 +++++++------- transports/quic/src/lib.rs | 125 ++++++++++++++++++++++------- transports/quic/src/verifier.rs | 76 ++++++++++++++++++ 4 files changed, 207 insertions(+), 57 deletions(-) create mode 100644 transports/quic/src/verifier.rs diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 8f1ceb9b349..0e27726796a 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -42,4 +42,5 @@ parking_lot = "0.10.0" rcgen = "0.7.0" protobuf = "2.8.1" yasna = { version = "0.3.1", features = ["num-bigint"] } +ring = "0.16.9" webpki = "0.21.0" diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 7415bd3d244..56b865a754c 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -210,21 +210,22 @@ fn verify_libp2p_extension( fn compute_signature_algorithm( key: &[u8], alg: &AlgorithmIdentifier, -) -> yasna::ASN1Result<&'static webpki::SignatureAlgorithm> { +) -> yasna::ASN1Result<&'static dyn ring::signature::VerificationAlgorithm> { #![cfg_attr(not(test), allow(dead_code))] Ok(match (key.len(), &**alg.algorithm.components()) { - (32, &[1, 3, 101, 111]) => &webpki::ED25519, - (33, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P256_SHA256, - (65, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P256_SHA256, - (33, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P256_SHA384, - (65, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P256_SHA384, - (49, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P384_SHA256, - (97, &[1, 2, 840, 10045, 4, 3, 2]) => &webpki::ECDSA_P384_SHA256, - (49, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P384_SHA384, - (97, &[1, 2, 840, 10045, 4, 3, 3]) => &webpki::ECDSA_P384_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 11]) => &webpki::RSA_PKCS1_2048_8192_SHA256, - (_, &[1, 2, 840, 113549, 1, 1, 12]) => &webpki::RSA_PKCS1_2048_8192_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 13]) => &webpki::RSA_PKCS1_2048_8192_SHA512, + (32, &[1, 3, 101, 111]) => &ring::signature::ED25519, + (33, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P256_SHA256_ASN1, + (65, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P256_SHA256_ASN1, + (33, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P256_SHA384_ASN1, + (65, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P256_SHA384_ASN1, + (49, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P384_SHA256_ASN1, + (97, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P384_SHA256_ASN1, + (49, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P384_SHA384_ASN1, + (97, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P384_SHA384_ASN1, + (_, &[1, 2, 840, 113549, 1, 1, 11]) => &ring::signature::RSA_PKCS1_2048_8192_SHA256, + (_, &[1, 2, 840, 113549, 1, 1, 12]) => &ring::signature::RSA_PKCS1_2048_8192_SHA384, + (_, &[1, 2, 840, 113549, 1, 1, 13]) => &ring::signature::RSA_PKCS1_2048_8192_SHA512, + (_, &[1, 2, 840, 113549, 1, 1, 14]) => &ring::signature::RSA_PKCS1_3072_8192_SHA384, (_, &[1, 2, 840, 113549, 1, 1, 10]) => match alg.parameters { None => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), Some(ref e) => yasna::parse_der(e, |reader| { @@ -232,13 +233,13 @@ fn compute_signature_algorithm( let keytype = reader.next().read_sequence(|reader| { let keytype = match &**reader.next().read_oid()?.components() { &[2, 16, 840, 1, 101, 3, 4, 2, 1] => { - &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY + &ring::signature::RSA_PSS_2048_8192_SHA256 } &[2, 16, 840, 1, 101, 3, 4, 2, 2] => { - &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY + &ring::signature::RSA_PSS_2048_8192_SHA384 } &[2, 16, 840, 1, 101, 3, 4, 2, 3] => { - &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY + &ring::signature::RSA_PSS_2048_8192_SHA512 } _ => { warn!("unsupported signature algorithm, rejecting!"); @@ -318,21 +319,26 @@ fn parse_certificate( Ok((raw_certificate, certificate_key, identity_key)) } -pub fn verify_libp2p_certificate(certificate: &[u8]) -> Result { +/// The name is a misnomer. We don’t bother checking if the certificate is actually well-formed. +/// We just check that its self-signature is valid, and that its public key is suitably signed. +pub fn verify_libp2p_certificate( + certificate: &[u8], +) -> Result { #![cfg_attr(not(test), allow(dead_code))] - let end_entity_cert = webpki::EndEntityCert::from(certificate) - .map_err(|e| log::debug!("webpki found invalid certificate: {:?}", e))?; let (raw_certificate, certificate_key, identity_key): (_, Vec, _) = - parse_certificate(certificate).map_err(|e| log::debug!("error in parsing: {:?}", e))?; + parse_certificate(certificate).map_err(|e| { + log::debug!("error in parsing: {:?}", e); + ring::error::Unspecified + })?; let algorithm = compute_signature_algorithm(&certificate_key, &raw_certificate.algorithm) - .map_err(|e| log::debug!("error getting signature algorithm: {:?}", e))?; - end_entity_cert - .verify_signature( - &algorithm, - &raw_certificate.tbs_certificate, - &raw_certificate.signature_value, - ) - .map_err(|e| log::debug!("error from webpki: {:?}", e))?; + .map_err(|e| { + log::debug!("error getting signature algorithm: {:?}", e); + ring::error::Unspecified + })?; + ring::signature::UnparsedPublicKey::new(algorithm, &certificate_key).verify( + &raw_certificate.tbs_certificate, + &raw_certificate.signature_value, + )?; Ok(identity_key) } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c0d561510d5..b74f975ca2a 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -31,7 +31,7 @@ //! use libp2p_core::Multiaddr; //! //! # fn main() { -//! let quic_config = QuicConfig::new(); +//! let quic_config = QuicConfig::default(); //! let quic_endpoint = QuicEndpoint::new( //! &quic_config, //! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), @@ -63,6 +63,7 @@ )] #![deny(trivial_casts)] mod certificate; +mod verifier; use async_macros::ready; use async_std::net::UdpSocket; pub use certificate::make_cert; @@ -95,7 +96,7 @@ use std::{ /// /// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams /// obtained by libp2p through the tokio reactor. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct QuicConfig { /// The client configuration. Quinn provides functions for making one. pub client_config: quinn_proto::ClientConfig, @@ -105,10 +106,64 @@ pub struct QuicConfig { pub endpoint_config: Arc, } -impl QuicConfig { +fn make_client_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ClientConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni = 0; + transport.datagram_receive_buffer_size = None; + let mut crypto = rustls::ClientConfig::new(); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto.enable_early_data = true; + crypto.set_single_client_cert(vec![certificate], key); + let verifier = verifier::VeryInsecureAllowAllCertificatesWithoutChecking; + crypto + .dangerous() + .set_certificate_verifier(Arc::new(verifier)); + quinn_proto::ClientConfig { + transport: Arc::new(transport), + crypto: Arc::new(crypto), + } +} + +fn make_server_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ServerConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni = 0; + transport.datagram_receive_buffer_size = None; + let mut crypto = rustls::ServerConfig::new(Arc::new( + verifier::VeryInsecureRequireClientCertificateButDoNotCheckIt, + )); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto + .set_single_cert(vec![certificate], key) + .expect("we are given a valid cert; qed"); + let mut config = quinn_proto::ServerConfig::default(); + config.transport = Arc::new(transport); + config.crypto = Arc::new(crypto); + config +} + +impl Default for QuicConfig { /// Creates a new configuration object for TCP/IP. - pub fn new() -> Self { - Self::default() + fn default() -> Self { + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let cert = make_cert(&keypair); + let (cert, key) = ( + rustls::Certificate( + cert.serialize_der() + .expect("serialization of a valid cert will succeed; qed"), + ), + rustls::PrivateKey(cert.serialize_private_key_der()), + ); + Self { + client_config: make_client_config(cert.clone(), key.clone()), + server_config: Arc::new(make_server_config(cert, key)), + endpoint_config: Default::default(), + } } } @@ -330,11 +385,11 @@ impl AsyncWrite for QuicStream { } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Ready(Ok(())) + unimplemented!() } - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Ready(Ok(())) + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_write(cx, b"").map_ok(drop) } } @@ -765,10 +820,12 @@ impl Muxer { use quinn_proto::Event; while let Some(event) = self.connection.poll() { match event { - Event::StreamOpened { dir: Dir::Uni } => { - trace!("Unidirectional stream opened!"); + Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { + panic!("we disabled incoming unidirectional streams and datagrams") + } + Event::StreamAvailable { dir: Dir::Uni } => { + panic!("we don’t use unidirectional streams") } - Event::StreamAvailable { dir: Dir::Uni } | Event::DatagramReceived => continue, Event::StreamReadable { stream } => { trace!("Stream {:?} readable", stream); // Wake up the task waiting on us (if any) @@ -920,6 +977,10 @@ impl Future for ConnectionDriver { match update { TimerSetting::Stop => timers[timer] = None, TimerSetting::Start(instant) => { + if instant < now { + needs_timer_update = true; + continue; + } trace!("setting timer {:?} for {:?}", timer, instant - now); let mut new_timer = futures_timer::Delay::new(instant - now); match new_timer.poll_unpin(cx) { @@ -976,12 +1037,12 @@ mod tests { fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let listener = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + let listener = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); - let client = QuicEndpoint::new(&QuicConfig::new(), addr.clone()) + let client = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) .expect("endpoint") .dial(addr) .expect("dialer"); @@ -1072,7 +1133,7 @@ mod tests { #[test] fn communicating_between_dialer_and_listener() { - use super::trace; + use super::{trace, StreamMuxer}; init(); let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); @@ -1081,7 +1142,7 @@ mod tests { let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" .parse() .expect("bad address?"); - let quic_config = QuicConfig::new(); + let quic_config = QuicConfig::default(); let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); @@ -1092,11 +1153,14 @@ mod tests { ready_tx.take().unwrap().send(listen_addr).unwrap(); } ListenerEvent::Upgrade { upgrade, .. } => { - let mut upgrade = upgrade.await.unwrap().next().await.unwrap(); + let mut muxer = upgrade.await.expect("upgrade failed"); + let mut socket = muxer.next().await.expect("no incoming stream"); + log::warn!("writing data!"); + socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); + let mut buf = [0u8; 3]; - upgrade.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, [1, 2, 3]); - upgrade.write_all(&[4, 5, 6]).await.unwrap(); + socket.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [4, 5, 6]); } _ => unreachable!(), } @@ -1105,28 +1169,31 @@ mod tests { async_std::task::block_on(async move { let addr = ready_rx.await.unwrap(); - let quic_config = QuicConfig::new(); + let quic_config = QuicConfig::default(); let quic_endpoint = QuicEndpoint::new( &quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), ) .unwrap(); // Obtain a future socket through dialing - let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); - let mut socket = connection.next().await.unwrap(); - socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); - + let mut stream = super::QuicStream { + id: connection.open_outbound().await.expect("failed"), + muxer: connection.clone(), + }; + log::warn!("have a new stream!"); let mut buf = [0u8; 3]; - socket.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, [4, 5, 6]); + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [1u8, 2, 3]); + stream.write_all(&[4u8, 5, 6]).await.unwrap(); }); } #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { init(); - let quic = QuicConfig::new(); + let quic = QuicConfig::default(); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); @@ -1146,7 +1213,7 @@ mod tests { #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { init(); - let config = QuicConfig::new(); + let config = QuicConfig::default(); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); @@ -1165,7 +1232,7 @@ mod tests { #[test] fn larger_addr_denied() { init(); - let config = QuicConfig::new(); + let config = QuicConfig::default(); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs new file mode 100644 index 00000000000..a822747c135 --- /dev/null +++ b/transports/quic/src/verifier.rs @@ -0,0 +1,76 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +/// A ServerCertVerifier that considers any certificate to be valid, and does no checking +/// whatsoever. +/// +/// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! +/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any certificate +/// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic +/// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key +/// corresponing to its `PeerId`, unless that endpoint has done something insecure. +pub struct VeryInsecureAllowAllCertificatesWithoutChecking; + +/// A ClientCertVerifier that requires client authentication, but considers any certificate to be +/// valid, and does no checking whatsoever. +/// +/// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! +/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any certificate +/// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic +/// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key +/// corresponing to its `PeerId`, unless that endpoint has done something insecure. +pub struct VeryInsecureRequireClientCertificateButDoNotCheckIt; + +impl rustls::ServerCertVerifier for VeryInsecureAllowAllCertificatesWithoutChecking { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef, + _ocsp_response: &[u8], + ) -> Result { + if presented_certs.len() > 0 { + Ok(rustls::ServerCertVerified::assertion()) + } else { + Err(rustls::TLSError::NoCertificatesPresented) + } + } +} + +impl rustls::ClientCertVerifier for VeryInsecureRequireClientCertificateButDoNotCheckIt { + fn offer_client_auth(&self) -> bool { + true + } + + fn client_auth_root_subjects(&self) -> rustls::internal::msgs::handshake::DistinguishedNames { + Default::default() + } + + fn verify_client_cert( + &self, + presented_certs: &[rustls::Certificate], + ) -> Result { + if presented_certs.len() > 0 { + Ok(rustls::ClientCertVerified::assertion()) + } else { + Err(rustls::TLSError::NoCertificatesPresented) + } + } +} From b8b9907e7e42e983f2fe1e9d6726cd0cf3c5e6c4 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Jan 2020 17:51:59 -0500 Subject: [PATCH 046/202] Fix the tests to avoid hangs --- transports/quic/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index b74f975ca2a..299756ee793 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -1155,12 +1155,12 @@ mod tests { ListenerEvent::Upgrade { upgrade, .. } => { let mut muxer = upgrade.await.expect("upgrade failed"); let mut socket = muxer.next().await.expect("no incoming stream"); - log::warn!("writing data!"); - socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); let mut buf = [0u8; 3]; socket.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [4, 5, 6]); + log::warn!("writing data!"); + socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); } _ => unreachable!(), } @@ -1183,10 +1183,10 @@ mod tests { muxer: connection.clone(), }; log::warn!("have a new stream!"); + stream.write_all(&[4u8, 5, 6]).await.unwrap(); let mut buf = [0u8; 3]; stream.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [1u8, 2, 3]); - stream.write_all(&[4u8, 5, 6]).await.unwrap(); }); } From bb8dd01294eafa36c0d4f570adce614f0317b1d5 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Jan 2020 18:25:04 -0500 Subject: [PATCH 047/202] testing --- transports/quic/src/lib.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 299756ee793..045ecacc534 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -210,17 +210,21 @@ pub struct Outbound(OutboundInner); impl Future for Outbound { type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.get_mut().0 { - ref mut inner @ OutboundInner::Complete(_) => { - match std::mem::replace(inner, OutboundInner::Done) { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = &mut *self; + match this.0 { + OutboundInner::Complete(_) => { + match std::mem::replace(&mut this.0, OutboundInner::Done) { OutboundInner::Complete(e) => Ready(e), _ => unreachable!(), } } - OutboundInner::Pending(ref mut receiver) => receiver - .poll_unpin(cx) - .map_err(|oneshot::Canceled| std::io::ErrorKind::ConnectionAborted.into()), + OutboundInner::Pending(ref mut receiver) => { + let result = ready!(receiver.poll_unpin(cx)) + .map_err(|oneshot::Canceled| std::io::ErrorKind::ConnectionAborted.into()); + this.0 = OutboundInner::Done; + Ready(result) + } OutboundInner::Done => panic!("polled after yielding Ready"), } } @@ -299,7 +303,7 @@ impl StreamMuxer for QuicMuxer { cx: &mut Context, substream: &mut Self::OutboundSubstream, ) -> Poll> { - Pin::new(substream).poll(cx) + substream.poll_unpin(cx) } fn read_substream( @@ -441,11 +445,6 @@ impl QuicEndpoint { self.0.inner.lock() } - /// Retrieves the `Multiaddr` of this `QuicEndpoint`. - pub fn addr(&self) -> &Multiaddr { - &self.0.address - } - /// Construct a `QuicEndpoint` with the given `QuicConfig` and `Multiaddr`. pub fn new( config: &QuicConfig, From ef68cb2875cb02025363bde64b91e50479e6e5a7 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Jan 2020 18:47:52 -0500 Subject: [PATCH 048/202] Actually accept connections This slows the tests down considerably. I need to figure out why. --- transports/quic/src/lib.rs | 380 ++++++++++++++++++++++--------------- 1 file changed, 231 insertions(+), 149 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 045ecacc534..398620d1a1e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -168,10 +168,98 @@ impl Default for QuicConfig { } #[derive(Debug)] -pub struct EndpointInner { +enum EndpointMessage { + ConnectionAccepted, + EndpointEvent { + handle: ConnectionHandle, + event: quinn_proto::EndpointEvent, + }, +} + +#[derive(Debug)] +struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, driver: Option>>, + /// Pending packet + outgoing_packet: Option, + /// Used to receive events from connections + event_receiver: mpsc::Receiver, +} + +impl EndpointInner { + fn drive_receive( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + ) -> Result<(bool, Option<(ConnectionHandle, Connection)>), io::Error> { + use quinn_proto::DatagramEvent; + let mut buf = [0; 1800]; + trace!("endpoint polling for incoming packets!"); + let (bytes, peer) = match socket.poll_recv_from(cx, &mut buf[..]) { + Pending => return Ok((false, None)), + Ready(Err(e)) => return Err(e), + Ready(Ok(data)) => data, + }; + trace!("got a packet of length {} from {}!", bytes, peer); + let (handle, event) = + match self + .inner + .handle(Instant::now(), peer, None, buf[..bytes].into()) + { + Some(e) => e, + None => return Ok((false, None)), + }; + trace!("have an event!"); + match event { + DatagramEvent::ConnectionEvent(connection_event) => { + let connection = match self.muxers.get(&handle) { + None => panic!("received a ConnectionEvent for an unknown Connection"), + Some(e) => match e.upgrade() { + Some(e) => e, + None => { + debug!("lost our connection!"); + return Ok((true, None)); + } + }, + }; + trace!("taking connection mutex!"); + let mut connection = connection.lock(); + trace!("connection mutex aquired!"); + connection.process_connection_events(self, connection_event); + + trace!("connection mutex released!"); + } + DatagramEvent::NewConnection(connection) => { + return Ok((true, Some((handle, connection)))) + } + } + Ok((true, None)) + } + + fn poll_transmit( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + packet: quinn_proto::Transmit, + ) -> Poll> { + match socket.poll_send_to(cx, &packet.contents, &packet.destination) { + Pending => { + self.outgoing_packet = Some(packet); + return Pending; + } + Ready(Ok(size)) => { + debug_assert_eq!(size, packet.contents.len()); + trace!( + "sent packet of length {} to {}", + packet.contents.len(), + packet.destination + ); + Ready(Ok(())) + } + Ready(Err(e)) => return Ready(Err(e)), + } + } } #[derive(Debug)] @@ -186,6 +274,8 @@ struct Endpoint { /// The channel used to receive new connections. receive_connections: Mutex, io::Error>>>>, + /// Connections send their events to this + event_channel: mpsc::Sender, /// The `Multiaddr` address: Multiaddr, } @@ -246,10 +336,12 @@ impl StreamMuxer for QuicMuxer { Outbound(OutboundInner::Complete(Ok(id))) } else if let Some(id) = inner.connection.open(Dir::Bi) { // optimization: if we can complete synchronously, do so. + inner.wake_driver(); Outbound(OutboundInner::Complete(Ok(id))) } else { let (sender, receiver) = oneshot::channel(); inner.connectors.push_front(sender); + inner.wake_driver(); Outbound(OutboundInner::Pending(receiver)) } } @@ -260,6 +352,7 @@ impl StreamMuxer for QuicMuxer { } fn poll_inbound(&self, cx: &mut Context) -> Poll> { + debug!("being polled for inbound connections!"); let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { return Ready(Err(std::io::Error::new( @@ -284,9 +377,9 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::WriteError; let mut inner = self.inner(); - ready!(inner.pre_application_io(cx))?; + inner.wake_driver(); match inner.connection.write(*substream, buf) { - Ok(bytes) => inner.on_application_io(cx, bytes), + Ok(bytes) => Ready(Ok(bytes)), Err(WriteError::Blocked) => { inner.writers.insert(*substream, cx.waker().clone()); Pending @@ -316,10 +409,14 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); ready!(inner.pre_application_io(cx))?; match inner.connection.read(*substream, buf) { - Ok(Some(bytes)) => inner.on_application_io(cx, bytes), + Ok(Some(bytes)) => { + ready!(inner.on_application_io(cx)?); + Ready(Ok(bytes)) + } Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { inner.readers.insert(*substream, cx.waker().clone()); + inner.wake_driver(); Pending } Err(ReadError::UnknownStream) => { @@ -458,6 +555,7 @@ impl QuicEndpoint { // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (new_connections, receive_connections) = mpsc::unbounded(); + let (event_channel, event_receiver) = mpsc::channel(5); new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) .expect("we have a reference to the peer, so this will not fail; qed"); @@ -472,10 +570,13 @@ impl QuicEndpoint { .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, muxers: HashMap::new(), driver: None, + event_receiver, + outgoing_packet: None, }), - new_connections, - receive_connections: Mutex::new(Some(receive_connections)), address, + receive_connections: Mutex::new(Some(receive_connections)), + new_connections, + event_channel, }), config.clone(), )) @@ -491,78 +592,70 @@ impl QuicEndpoint { inner.muxers.insert(handle, Arc::downgrade(&muxer)); (QuicMuxer(muxer), driver) } - - /// Process UDP packets until either an error occurs on the socket or we are dropped. - async fn process_udp_packets(self, _address: Multiaddr) -> Result<(), io::Error> { - let mut outgoing_packet: Option = None; - loop { - use quinn_proto::DatagramEvent; - if let Some(packet) = outgoing_packet.take() { - trace!( - "sending packet of length {} to {}", - packet.contents.len(), - packet.destination - ); - self.0 - .socket - .send_to(&packet.contents, packet.destination) - .await?; - } - let mut buf = [0; 65000]; - let (bytes, peer) = self.0.socket.recv_from(&mut buf[..]).await?; - trace!("got a packet of length {} from {}!", bytes, peer); - // This takes a mutex, so it must be *after* the `await` call. - let mut inner = self.inner(); - if let Some(packet) = inner.inner.poll_transmit() { - trace!("sending packet from endpoint!"); - outgoing_packet = Some(packet); - continue; +} +impl Future for QuicEndpoint { + type Output = Result<(), io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + let mut inner = this.inner(); + 'a: loop { + let mut should_continue = false; + if let Some(packet) = inner.outgoing_packet.take() { + ready!(inner.poll_transmit(&this.0.socket, cx, packet))?; } - let (handle, event) = - match inner - .inner - .handle(Instant::now(), peer, None, buf[..bytes].into()) - { - Some(e) => e, - None => continue, + while let Ready(e) = inner.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { + let (handle, endpoint_event) = match e { + EndpointMessage::ConnectionAccepted => { + debug!("accepting connection!"); + inner.inner.accept(); + continue; + } + EndpointMessage::EndpointEvent { handle, event } => (handle, event), }; - trace!("have an event!"); - match event { - DatagramEvent::ConnectionEvent(connection_event) => { - // trace!("got a connection event: {:?}", connection_event); + debug!("we have an event from connection {:?}", handle); + if let Some(connection_event) = inner.inner.handle_event(handle, endpoint_event) { + debug!("we have an event from our endpoint"); let connection = match inner.muxers.get(&handle) { None => panic!("received a ConnectionEvent for an unknown Connection"), Some(e) => match e.upgrade() { Some(e) => e, None => { - debug!("lost our connection!"); - continue; + warn!("lost our connection!"); + break; } }, }; let mut connection = connection.lock(); connection.process_connection_events(&mut inner, connection_event); - outgoing_packet = connection.connection.poll_transmit(Instant::now()); - connection.wake_driver(); } - DatagramEvent::NewConnection(connection) => { - let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); + } + loop { + let (received_something, new_connection) = + inner.drive_receive(&this.0.socket, cx)?; + should_continue |= received_something; + if let Some((handle, connection)) = new_connection { + let (muxer, driver) = this.create_muxer(connection, handle, &mut *inner); async_std::task::spawn(driver); - let endpoint = &self.0; - endpoint + this.0 .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { upgrade: QuicUpgrade { muxer: Some(muxer), }, - local_addr: endpoint.address.clone(), - remote_addr: endpoint.address.clone(), + local_addr: this.0.address.clone(), + remote_addr: this.0.address.clone(), })) .expect( - "this is an unbounded channel, and we have an instance of the peer, so \ - this will never fail; qed", + "this is an unbounded channel, and we have an instance of the peer, so \ + this will never fail; qed", ); } + if !received_something { + break; + } + } + if !should_continue { + break Pending; } } } @@ -585,8 +678,7 @@ impl Future for QuicUpgrade { io::ErrorKind::ConnectionAborted, e.clone(), ))); - } - if inner.connection.is_handshaking() { + } else if inner.connection.is_handshaking() { assert!(inner.close_reason.is_none()); assert!(!inner.connection.is_drained(), "deadlock"); inner.accept_waker = Some(cx.waker().clone()); @@ -601,6 +693,13 @@ impl Future for QuicUpgrade { .expect("drained connections have a close reason") .clone(), ))); + } else if inner.connection.side().is_server() { + ready!(inner.endpoint_channel.poll_ready(cx)) + .expect("the other side is not closed; qed"); + inner + .endpoint_channel + .start_send(EndpointMessage::ConnectionAccepted) + .expect("we just checked that we can send some data; qed"); } } Ready(Ok(muxer.take().expect("impossible"))) @@ -626,9 +725,7 @@ impl Transport for &QuicEndpoint { .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); let mut inner = self.inner(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn( - self.clone().process_udp_packets(addr), - )) + inner.driver = Some(async_std::task::spawn(self.clone())) } res } @@ -647,9 +744,7 @@ impl Transport for &QuicEndpoint { }; let mut inner = self.inner(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn( - self.clone().process_udp_packets(self.0.address.clone()), - )) + inner.driver = Some(async_std::task::spawn(self.clone())) } let s: Result<(_, Connection), _> = inner @@ -715,10 +810,18 @@ pub struct Muxer { /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver - driver_waker: Option, + waker: Option, + /// Channel for endpoint events + endpoint_channel: mpsc::Sender, } impl Muxer { + fn wake_driver(&mut self) { + if let Some(waker) = self.waker.take() { + waker.wake() + } + } + fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { pending_stream: None, @@ -732,13 +835,14 @@ impl Muxer { pending: None, timers: quinn_proto::TimerTable::new(|| None), close_reason: None, - driver_waker: None, + waker: None, + endpoint_channel: endpoint.event_channel.clone(), } } /// Process all endpoint-facing events for this connection. This is synchronous and will not /// fail. - pub fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { + fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { while let Some(endpoint_event) = self.connection.poll_endpoint_events() { if let Some(connection_event) = endpoint.inner.handle_event(self.handle, endpoint_event) { @@ -747,22 +851,21 @@ impl Muxer { } } - fn wake_driver(&mut self) { - drop(self.driver_waker.take().map(|w| w.wake())) - } - /// Call when I/O is done by the application. `bytes` is the number of bytes of I/O done. - fn on_application_io( - &mut self, - cx: &mut Context<'_>, - bytes: usize, - ) -> Poll> { - self.wake_driver(); + fn on_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { let now = Instant::now(); + let mut needs_polling = false; + while let Some(event) = self.connection.poll_endpoint_events() { + needs_polling = true; + self.endpoint_channel.start_send(EndpointMessage::EndpointEvent { handle: self.handle, event }).expect("we checked in `pre_application_io` that this channel had space; that is always called first, and there is a lock preventing concurrency problems; qed"); + ready!(self.endpoint_channel.poll_ready(cx)) + .expect("we have a reference to the peer; qed"); + } while let Some(transmit) = self.connection.poll_transmit(now) { ready!(self.poll_transmit(cx, transmit))?; + needs_polling = true; } - Ready(Ok(bytes)) + Ready(Ok(needs_polling)) } fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -775,7 +878,14 @@ impl Muxer { if let Some(transmit) = self.pending.take() { ready!(self.poll_transmit(cx, transmit))?; } - Ready(Ok(())) + match self.endpoint_channel.poll_ready(cx) { + Pending => { + debug!("blocking on endpoint channel"); + Pending + } + Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), + Ready(Ok(e)) => Ready(Ok(e)), + } } fn poll_transmit( @@ -797,22 +907,26 @@ impl Muxer { self.pending = Some(transmit); Pending } - r @ Ready(_) => r, + r @ Ready(_) => { + trace!( + "sent packet of length {} to {}", + transmit.contents.len(), + transmit.destination + ); + r + } } } /// Process application events - pub fn process_connection_events( - &mut self, - endpoint: &mut EndpointInner, - event: ConnectionEvent, - ) { + fn process_connection_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { if self.close_reason.is_some() { return; } self.connection.handle_event(event); self.send_to_endpoint(endpoint); self.process_app_events(); + self.wake_driver(); } pub fn process_app_events(&mut self) { @@ -850,7 +964,7 @@ impl Muxer { "we cannot have both pending tasks and a pending stream; qed" ); let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); if let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { Ok(()) => return, @@ -913,28 +1027,6 @@ impl ConnectionDriver { inner, ) } - fn poll_transmit( - &mut self, - cx: &mut Context<'_>, - transmit: quinn_proto::Transmit, - ) -> Poll> { - trace!( - "sending packet of length {} to {}", - transmit.contents.len(), - transmit.destination - ); - match self - .endpoint - .socket - .poll_send_to(cx, &transmit.contents, &transmit.destination) - { - x @ Pending => { - self.outgoing_packet = Some(transmit); - x - } - x @ Ready(_) => x, - } - } } impl Future for ConnectionDriver { @@ -942,65 +1034,55 @@ impl Future for ConnectionDriver { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let now = Instant::now(); let this = self.get_mut(); - if let Some(packet) = this.outgoing_packet.take() { - ready!(this.poll_transmit(cx, packet)).expect("error handling not implemented"); - } - trace!("being polled for timers!"); + debug!("being polled for timers!"); let mut inner = this.inner.lock(); - inner.driver_waker = Some(cx.waker().clone()); - let mut need_application_processing = false; loop { trace!("loop iteration"); - let mut needs_timer_update = false; + ready!(inner.pre_application_io(cx))?; + let mut needs_timer_update = ready!(inner.on_application_io(cx))?; let Muxer { ref mut timers, ref mut connection, .. } = *inner; - for (timer, timer_ref) in timers - .iter_mut() - .filter_map(|(x, y)| y.as_mut().map(|z| (x, z))) - { - match timer_ref.poll_unpin(cx) { - Pending => continue, - Ready(()) => { - trace!("timer {:?} ready at time {:?}", timer, now); - need_application_processing = true; - connection.handle_timeout(now, timer); + for (timer, timer_ref) in timers.iter_mut() { + if let Some(ref mut timer_future) = timer_ref { + match timer_future.poll_unpin(cx) { + Pending => continue, + Ready(()) => { + trace!("timer {:?} ready at time {:?}", timer, now); + needs_timer_update = true; + connection.handle_timeout(now, timer); + *timer_ref = None; + } } } } - while let Some(quinn_proto::TimerUpdate { timer, update }) = connection.poll_timers() { - trace!("got a timer update!"); - use quinn_proto::TimerSetting; - match update { - TimerSetting::Stop => timers[timer] = None, - TimerSetting::Start(instant) => { - if instant < now { - needs_timer_update = true; - continue; - } - trace!("setting timer {:?} for {:?}", timer, instant - now); - let mut new_timer = futures_timer::Delay::new(instant - now); - match new_timer.poll_unpin(cx) { - Ready(()) => needs_timer_update = true, - Pending => {} + if needs_timer_update { + while let Some(quinn_proto::TimerUpdate { timer, update }) = + connection.poll_timers() + { + use quinn_proto::TimerSetting; + match update { + TimerSetting::Stop => timers[timer] = None, + TimerSetting::Start(instant) => { + if instant < now { + panic!(); + } + trace!("setting timer {:?} for {:?}", timer, instant - now); + let mut new_timer = futures_timer::Delay::new(instant - now); + match new_timer.poll_unpin(cx) { + Ready(()) => {} + Pending => timers[timer] = Some(new_timer), + } } - timers[timer] = Some(new_timer) } } - } - while let Some(tx) = inner.connection.poll_transmit(now) { - needs_timer_update = true; - drop(ready!(inner.poll_transmit(cx, tx))) - } - if !needs_timer_update { + inner.process_app_events(); + } else { break; } } - if need_application_processing { - inner.process_app_events(); - } if inner.connection.is_drained() { debug!( From 5213f6ceef1c7d3971936182918710d81a99c67c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Jan 2020 19:16:10 -0500 Subject: [PATCH 049/202] Ensure the driver can actually be woken up --- transports/quic/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 398620d1a1e..09832bdba30 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -416,7 +416,7 @@ impl StreamMuxer for QuicMuxer { Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { inner.readers.insert(*substream, cx.waker().clone()); - inner.wake_driver(); + inner.wake_driver(); Pending } Err(ReadError::UnknownStream) => { @@ -555,7 +555,7 @@ impl QuicEndpoint { // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (new_connections, receive_connections) = mpsc::unbounded(); - let (event_channel, event_receiver) = mpsc::channel(5); + let (event_channel, event_receiver) = mpsc::channel(0); new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) .expect("we have a reference to the peer, so this will not fail; qed"); @@ -1035,8 +1035,9 @@ impl Future for ConnectionDriver { let now = Instant::now(); let this = self.get_mut(); debug!("being polled for timers!"); - let mut inner = this.inner.lock(); loop { + let mut inner = this.inner.lock(); + inner.waker = Some(cx.waker().clone()); trace!("loop iteration"); ready!(inner.pre_application_io(cx))?; let mut needs_timer_update = ready!(inner.on_application_io(cx))?; @@ -1079,11 +1080,15 @@ impl Future for ConnectionDriver { } } inner.process_app_events(); + ready!(inner.on_application_io(cx))?; + inner.wake_driver(); + break; } else { break; } } + let inner = this.inner.lock(); if inner.connection.is_drained() { debug!( "Connection drained: close reason {}", From b7ea623789cf9a810ea4616642a87e8296b73f6a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 6 Jan 2020 15:05:32 -0500 Subject: [PATCH 050/202] Futures need to be polled to completion! Failure to do so caused missed wakeups and hangs. --- transports/quic/src/lib.rs | 183 +++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 09832bdba30..b37e69fb7d3 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -192,49 +192,59 @@ impl EndpointInner { &mut self, socket: &UdpSocket, cx: &mut Context, - ) -> Result<(bool, Option<(ConnectionHandle, Connection)>), io::Error> { + ) -> Poll> { use quinn_proto::DatagramEvent; let mut buf = [0; 1800]; trace!("endpoint polling for incoming packets!"); - let (bytes, peer) = match socket.poll_recv_from(cx, &mut buf[..]) { - Pending => return Ok((false, None)), - Ready(Err(e)) => return Err(e), - Ready(Ok(data)) => data, - }; - trace!("got a packet of length {} from {}!", bytes, peer); - let (handle, event) = - match self - .inner - .handle(Instant::now(), peer, None, buf[..bytes].into()) - { - Some(e) => e, - None => return Ok((false, None)), - }; - trace!("have an event!"); - match event { - DatagramEvent::ConnectionEvent(connection_event) => { - let connection = match self.muxers.get(&handle) { - None => panic!("received a ConnectionEvent for an unknown Connection"), - Some(e) => match e.upgrade() { - Some(e) => e, - None => { - debug!("lost our connection!"); - return Ok((true, None)); - } - }, + assert!(self.outgoing_packet.is_none()); + assert!(self.inner.poll_transmit().is_none()); + loop { + let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; + trace!("got a packet of length {} from {}!", bytes, peer); + let (handle, event) = + match self + .inner + .handle(Instant::now(), peer, None, buf[..bytes].into()) + { + Some(e) => e, + None => { + ready!(self.poll_transmit_pending(socket, cx))?; + continue; + } }; - trace!("taking connection mutex!"); - let mut connection = connection.lock(); - trace!("connection mutex aquired!"); - connection.process_connection_events(self, connection_event); - - trace!("connection mutex released!"); - } - DatagramEvent::NewConnection(connection) => { - return Ok((true, Some((handle, connection)))) + trace!("have an event!"); + match event { + DatagramEvent::ConnectionEvent(connection_event) => { + match self + .muxers + .get(&handle) + .expect("received a ConnectionEvent for an unknown Connection") + .upgrade() + { + Some(connection) => connection + .lock() + .process_connection_events(self, connection_event), + None => debug!("lost our connection!"), + } + ready!(self.poll_transmit_pending(socket, cx))? + } + DatagramEvent::NewConnection(connection) => break Ready(Ok((handle, connection))), } } - Ok((true, None)) + } + + fn poll_transmit_pending( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + ) -> Poll> { + if let Some(tx) = self.outgoing_packet.take() { + ready!(self.poll_transmit(socket, cx, tx))? + } + while let Some(tx) = self.inner.poll_transmit() { + ready!(self.poll_transmit(socket, cx, tx))? + } + Ready(Ok(())) } fn poll_transmit( @@ -592,71 +602,65 @@ impl QuicEndpoint { inner.muxers.insert(handle, Arc::downgrade(&muxer)); (QuicMuxer(muxer), driver) } + + fn accept_muxer( + &self, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, + ) { + let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); + async_std::task::spawn(driver); + self.0 + .new_connections + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade: QuicUpgrade { muxer: Some(muxer) }, + local_addr: self.0.address.clone(), + remote_addr: self.0.address.clone(), + })) + .expect( + "this is an unbounded channel, and we have an instance \ + of the peer, so this will never fail; qed", + ); + } } + impl Future for QuicEndpoint { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.get_mut(); let mut inner = this.inner(); 'a: loop { - let mut should_continue = false; - if let Some(packet) = inner.outgoing_packet.take() { - ready!(inner.poll_transmit(&this.0.socket, cx, packet))?; - } + ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; while let Ready(e) = inner.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { - let (handle, endpoint_event) = match e { + match e { EndpointMessage::ConnectionAccepted => { debug!("accepting connection!"); inner.inner.accept(); - continue; } - EndpointMessage::EndpointEvent { handle, event } => (handle, event), - }; - debug!("we have an event from connection {:?}", handle); - if let Some(connection_event) = inner.inner.handle_event(handle, endpoint_event) { - debug!("we have an event from our endpoint"); - let connection = match inner.muxers.get(&handle) { - None => panic!("received a ConnectionEvent for an unknown Connection"), - Some(e) => match e.upgrade() { - Some(e) => e, - None => { - warn!("lost our connection!"); - break; + EndpointMessage::EndpointEvent { handle, event } => { + debug!("we have an event from connection {:?}", handle); + if let Some(connection_event) = inner.inner.handle_event(handle, event) { + debug!("we have an event from our endpoint"); + match inner + .muxers + .get(&handle) + .expect("received a ConnectionEvent for an unknown Connection") + .upgrade() + { + None => warn!("lost our connection!"), + Some(connection) => connection + .lock() + .process_connection_events(&mut inner, connection_event), } - }, - }; - let mut connection = connection.lock(); - connection.process_connection_events(&mut inner, connection_event); - } - } - loop { - let (received_something, new_connection) = - inner.drive_receive(&this.0.socket, cx)?; - should_continue |= received_something; - if let Some((handle, connection)) = new_connection { - let (muxer, driver) = this.create_muxer(connection, handle, &mut *inner); - async_std::task::spawn(driver); - this.0 - .new_connections - .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade: QuicUpgrade { - muxer: Some(muxer), - }, - local_addr: this.0.address.clone(), - remote_addr: this.0.address.clone(), - })) - .expect( - "this is an unbounded channel, and we have an instance of the peer, so \ - this will never fail; qed", - ); - } - if !received_something { - break; + } + } } + ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; } - if !should_continue { - break Pending; - } + let (handle, connection) = ready!(inner.drive_receive(&this.0.socket, cx))?; + this.accept_muxer(connection, handle, &mut *inner); + ready!(inner.poll_transmit_pending(&this.0.socket, cx))? } } } @@ -927,6 +931,11 @@ impl Muxer { self.send_to_endpoint(endpoint); self.process_app_events(); self.wake_driver(); + assert!(self.connection.poll_endpoint_events().is_none()); + assert!(self.connection.poll().is_none()); + if let Some(tx) = self.connection.poll_transmit(Instant::now()) { + assert!(std::mem::replace(&mut endpoint.outgoing_packet, Some(tx)).is_none()) + } } pub fn process_app_events(&mut self) { From a6be20ef4da96cb98ca9d5677217f615a6f53d61 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 6 Jan 2020 15:37:28 -0500 Subject: [PATCH 051/202] Clean up the driver and I/O code Now the driver tasks take care of all I/O. --- transports/quic/src/lib.rs | 137 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 63 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index b37e69fb7d3..3f5b68977a4 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -389,8 +389,12 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); inner.wake_driver(); match inner.connection.write(*substream, buf) { - Ok(bytes) => Ready(Ok(bytes)), + Ok(bytes) => { + inner.wake_driver(); + Ready(Ok(bytes)) + } Err(WriteError::Blocked) => { + inner.wake_driver(); inner.writers.insert(*substream, cx.waker().clone()); Pending } @@ -420,18 +424,16 @@ impl StreamMuxer for QuicMuxer { ready!(inner.pre_application_io(cx))?; match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { - ready!(inner.on_application_io(cx)?); + inner.wake_driver(); Ready(Ok(bytes)) } Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { - inner.readers.insert(*substream, cx.waker().clone()); inner.wake_driver(); + inner.readers.insert(*substream, cx.waker().clone()); Pending } - Err(ReadError::UnknownStream) => { - panic!("libp2p never uses a closed stream, so this cannot happen; qed") - } + Err(ReadError::UnknownStream) => unreachable!("use of a closed stream by libp2p"), Err(ReadError::Reset(_)) => Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -659,8 +661,8 @@ impl Future for QuicEndpoint { ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; } let (handle, connection) = ready!(inner.drive_receive(&this.0.socket, cx))?; - this.accept_muxer(connection, handle, &mut *inner); - ready!(inner.poll_transmit_pending(&this.0.socket, cx))? + this.accept_muxer(connection, handle, &mut *inner); + ready!(inner.poll_transmit_pending(&this.0.socket, cx))? } } } @@ -826,6 +828,46 @@ impl Muxer { } } + fn drive_timers(&mut self, cx: &mut Context, now: &mut Instant) -> bool { + let mut keep_going = false; + for (timer, timer_ref) in self.timers.iter_mut() { + if let Some(ref mut timer_future) = timer_ref { + match timer_future.poll_unpin(cx) { + Pending => continue, + Ready(()) => { + trace!("timer {:?} ready at time {:?}", timer, now); + keep_going = true; + self.connection.handle_timeout(*now, timer); + *timer_ref = None; + } + } + } + } + while let Some(quinn_proto::TimerUpdate { timer, update }) = self.connection.poll_timers() { + use quinn_proto::TimerSetting; + match update { + TimerSetting::Stop => self.timers[timer] = None, + TimerSetting::Start(instant) => { + assert!(instant >= *now); + trace!("setting timer {:?} for {:?}", timer, instant - *now); + let mut new_timer = futures_timer::Delay::new(instant - *now); + match new_timer.poll_unpin(cx) { + Ready(()) => { + warn!("timer {:?} is already Ready!", timer); + *now = Instant::now(); + self.timers[timer] = None; + self.connection.handle_timeout(*now, timer); + keep_going = true; + continue; + } + Pending => self.timers[timer] = Some(new_timer), + } + } + } + } + keep_going + } + fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { pending_stream: None, @@ -861,7 +903,16 @@ impl Muxer { let mut needs_polling = false; while let Some(event) = self.connection.poll_endpoint_events() { needs_polling = true; - self.endpoint_channel.start_send(EndpointMessage::EndpointEvent { handle: self.handle, event }).expect("we checked in `pre_application_io` that this channel had space; that is always called first, and there is a lock preventing concurrency problems; qed"); + self.endpoint_channel + .start_send(EndpointMessage::EndpointEvent { + handle: self.handle, + event, + }) + .expect( + "we checked in `pre_application_io` that this channel had space; \ + that is always called first, and \ + there is a lock preventing concurrency problems; qed", + ); ready!(self.endpoint_channel.poll_ready(cx)) .expect("we have a reference to the peer; qed"); } @@ -872,7 +923,7 @@ impl Muxer { Ready(Ok(needs_polling)) } - fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { + fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { if let Some(ref e) = self.close_reason { return Ready(Err(std::io::Error::new( io::ErrorKind::ConnectionAborted, @@ -882,14 +933,13 @@ impl Muxer { if let Some(transmit) = self.pending.take() { ready!(self.poll_transmit(cx, transmit))?; } - match self.endpoint_channel.poll_ready(cx) { - Pending => { - debug!("blocking on endpoint channel"); - Pending - } - Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), - Ready(Ok(e)) => Ready(Ok(e)), + ready!(self.endpoint_channel.poll_ready(cx)).expect("we have a reference to the peer; qed"); + let mut keep_going = false; + while let Some(transmit) = self.connection.poll_transmit(Instant::now()) { + keep_going = true; + ready!(self.poll_transmit(cx, transmit))?; } + Ready(Ok(keep_going)) } fn poll_transmit( @@ -1041,58 +1091,19 @@ impl ConnectionDriver { impl Future for ConnectionDriver { type Output = Result<(), std::io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let now = Instant::now(); + let mut now = Instant::now(); let this = self.get_mut(); debug!("being polled for timers!"); loop { let mut inner = this.inner.lock(); + let mut needs_timer_update = false; inner.waker = Some(cx.waker().clone()); trace!("loop iteration"); - ready!(inner.pre_application_io(cx))?; - let mut needs_timer_update = ready!(inner.on_application_io(cx))?; - let Muxer { - ref mut timers, - ref mut connection, - .. - } = *inner; - for (timer, timer_ref) in timers.iter_mut() { - if let Some(ref mut timer_future) = timer_ref { - match timer_future.poll_unpin(cx) { - Pending => continue, - Ready(()) => { - trace!("timer {:?} ready at time {:?}", timer, now); - needs_timer_update = true; - connection.handle_timeout(now, timer); - *timer_ref = None; - } - } - } - } - if needs_timer_update { - while let Some(quinn_proto::TimerUpdate { timer, update }) = - connection.poll_timers() - { - use quinn_proto::TimerSetting; - match update { - TimerSetting::Stop => timers[timer] = None, - TimerSetting::Start(instant) => { - if instant < now { - panic!(); - } - trace!("setting timer {:?} for {:?}", timer, instant - now); - let mut new_timer = futures_timer::Delay::new(instant - now); - match new_timer.poll_unpin(cx) { - Ready(()) => {} - Pending => timers[timer] = Some(new_timer), - } - } - } - } - inner.process_app_events(); - ready!(inner.on_application_io(cx))?; - inner.wake_driver(); - break; - } else { + needs_timer_update |= ready!(inner.pre_application_io(cx))?; + needs_timer_update |= ready!(inner.on_application_io(cx))?; + needs_timer_update |= inner.drive_timers(cx, &mut now); + inner.process_app_events(); + if !needs_timer_update { break; } } From a56a6afd0708a3071ebc6d9c15c57ff002d61064 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 6 Jan 2020 16:46:25 -0500 Subject: [PATCH 052/202] Go much farther before hanging There is still at least one missed wakeup. --- transports/quic/src/lib.rs | 120 +++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 3f5b68977a4..f601ea3accc 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -82,6 +82,7 @@ use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, io, + mem::replace, net::SocketAddr, pin::Pin, sync::{Arc, Weak}, @@ -309,19 +310,17 @@ enum OutboundInner { pub struct Outbound(OutboundInner); impl Future for Outbound { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = &mut *self; match this.0 { - OutboundInner::Complete(_) => { - match std::mem::replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Ready(e), - _ => unreachable!(), - } - } + OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { + OutboundInner::Complete(e) => Ready(e), + _ => unreachable!(), + }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) - .map_err(|oneshot::Canceled| std::io::ErrorKind::ConnectionAborted.into()); + .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); this.0 = OutboundInner::Done; Ready(result) } @@ -333,16 +332,17 @@ impl Future for Outbound { impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; type Substream = StreamId; - type Error = std::io::Error; + type Error = io::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { - Outbound(OutboundInner::Complete(Err(std::io::Error::new( + Outbound(OutboundInner::Complete(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), )))) } else if let Some(id) = inner.pending_stream.take() { // mandatory ― otherwise we will fail an assertion above + inner.wake_driver(); Outbound(OutboundInner::Complete(Ok(id))) } else if let Some(id) = inner.connection.open(Dir::Bi) { // optimization: if we can complete synchronously, do so. @@ -365,14 +365,17 @@ impl StreamMuxer for QuicMuxer { debug!("being polled for inbound connections!"); let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { - return Ready(Err(std::io::Error::new( + return Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); } + inner.wake_driver(); match inner.connection.accept(quinn_proto::Dir::Bi) { None => { - inner.accept_waker = Some(cx.waker().clone()); + if let Some(waker) = replace(&mut inner.accept_waker, Some(cx.waker().clone())) { + waker.wake() + } Pending } Some(stream) => Ready(Ok(stream)), @@ -401,7 +404,7 @@ impl StreamMuxer for QuicMuxer { Err(WriteError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(WriteError::Stopped(_)) => Ready(Err(std::io::ErrorKind::ConnectionAborted.into())), + Err(WriteError::Stopped(_)) => Ready(Err(io::ErrorKind::ConnectionAborted.into())), } } @@ -421,7 +424,6 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - ready!(inner.pre_application_io(cx))?; match inner.connection.read(*substream, buf) { Ok(Some(bytes)) => { inner.wake_driver(); @@ -680,7 +682,7 @@ impl Future for QuicUpgrade { { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); if let Some(ref e) = inner.close_reason { - return Ready(Err(std::io::Error::new( + return Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); @@ -691,7 +693,7 @@ impl Future for QuicUpgrade { return Pending; } else if inner.connection.is_drained() { debug!("connection already drained; failing"); - return Ready(Err(std::io::Error::new( + return Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, inner .close_reason @@ -824,48 +826,66 @@ pub struct Muxer { impl Muxer { fn wake_driver(&mut self) { if let Some(waker) = self.waker.take() { - waker.wake() + waker.wake(); } } fn drive_timers(&mut self, cx: &mut Context, now: &mut Instant) -> bool { let mut keep_going = false; - for (timer, timer_ref) in self.timers.iter_mut() { - if let Some(ref mut timer_future) = timer_ref { - match timer_future.poll_unpin(cx) { - Pending => continue, - Ready(()) => { - trace!("timer {:?} ready at time {:?}", timer, now); - keep_going = true; - self.connection.handle_timeout(*now, timer); - *timer_ref = None; + loop { + let mut continue_loop = false; + for (timer, timer_ref) in self.timers.iter_mut() { + if let Some(ref mut timer_future) = timer_ref { + match timer_future.poll_unpin(cx) { + Pending => continue, + Ready(()) => { + trace!("timer {:?} ready at time {:?}", timer, now); + keep_going = true; + continue_loop = true; + self.connection.handle_timeout(*now, timer); + *timer_ref = None; + } } } } - } - while let Some(quinn_proto::TimerUpdate { timer, update }) = self.connection.poll_timers() { - use quinn_proto::TimerSetting; - match update { - TimerSetting::Stop => self.timers[timer] = None, - TimerSetting::Start(instant) => { - assert!(instant >= *now); - trace!("setting timer {:?} for {:?}", timer, instant - *now); - let mut new_timer = futures_timer::Delay::new(instant - *now); - match new_timer.poll_unpin(cx) { - Ready(()) => { - warn!("timer {:?} is already Ready!", timer); - *now = Instant::now(); + while let Some(quinn_proto::TimerUpdate { timer, update }) = + self.connection.poll_timers() + { + keep_going = true; + continue_loop = true; + use quinn_proto::TimerSetting; + match update { + TimerSetting::Stop => self.timers[timer] = None, + TimerSetting::Start(instant) => { + if instant < *now { self.timers[timer] = None; self.connection.handle_timeout(*now, timer); keep_going = true; + continue_loop = true; continue; } - Pending => self.timers[timer] = Some(new_timer), + + trace!("setting timer {:?} for {:?}", timer, instant - *now); + let mut new_timer = futures_timer::Delay::new(instant - *now); + match new_timer.poll_unpin(cx) { + Ready(()) => { + warn!("timer {:?} is already Ready!", timer); + *now = Instant::now(); + self.timers[timer] = None; + self.connection.handle_timeout(*now, timer); + keep_going = true; + continue_loop = true; + continue; + } + Pending => self.timers[timer] = Some(new_timer), + } } } } + if !continue_loop { + break keep_going + } } - keep_going } fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { @@ -925,7 +945,7 @@ impl Muxer { fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { if let Some(ref e) = self.close_reason { - return Ready(Err(std::io::Error::new( + return Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); @@ -984,7 +1004,7 @@ impl Muxer { assert!(self.connection.poll_endpoint_events().is_none()); assert!(self.connection.poll().is_none()); if let Some(tx) = self.connection.poll_transmit(Instant::now()) { - assert!(std::mem::replace(&mut endpoint.outgoing_packet, Some(tx)).is_none()) + assert!(replace(&mut endpoint.outgoing_packet, Some(tx)).is_none()) } } @@ -1016,7 +1036,7 @@ impl Muxer { trace!("Bidirectional stream available"); if self.connectors.is_empty() { // no task to wake up - return; + continue; } debug_assert!( self.pending_stream.is_none(), @@ -1026,7 +1046,7 @@ impl Muxer { .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); if let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { - Ok(()) => return, + Ok(()) => continue, Err(_) => (), } } @@ -1089,13 +1109,13 @@ impl ConnectionDriver { } impl Future for ConnectionDriver { - type Output = Result<(), std::io::Error>; + type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let mut now = Instant::now(); let this = self.get_mut(); debug!("being polled for timers!"); + let mut inner = this.inner.lock(); loop { - let mut inner = this.inner.lock(); let mut needs_timer_update = false; inner.waker = Some(cx.waker().clone()); trace!("loop iteration"); @@ -1107,8 +1127,6 @@ impl Future for ConnectionDriver { break; } } - - let inner = this.inner.lock(); if inner.connection.is_drained() { debug!( "Connection drained: close reason {}", @@ -1263,9 +1281,10 @@ mod tests { let mut socket = muxer.next().await.expect("no incoming stream"); let mut buf = [0u8; 3]; + log::error!("reading data from accepted stream!"); socket.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [4, 5, 6]); - log::warn!("writing data!"); + log::error!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); } _ => unreachable!(), @@ -1291,6 +1310,7 @@ mod tests { log::warn!("have a new stream!"); stream.write_all(&[4u8, 5, 6]).await.unwrap(); let mut buf = [0u8; 3]; + log::error!("reading data!"); stream.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [1u8, 2, 3]); }); From 44a6cfbd70276646eb4c3502e2f16c459db25a0b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 6 Jan 2020 17:44:37 -0500 Subject: [PATCH 053/202] Make libp2p-quic more robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There may be a rare, intermittent hang under certain conditions, but I am having trouble reproducing it, and in libp2p it would be harmless ― it would result in a timeout and immediate reconnection, which would almost always succeed. Libp2p needs to be able to handle such errors anyway, since they can happen due to network problems, for example. Therefore, I consider the futures portion of this code to be ready for review. --- transports/quic/src/lib.rs | 132 +++++++++++++++---------------------- 1 file changed, 54 insertions(+), 78 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index f601ea3accc..78c43b7a098 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -644,19 +644,16 @@ impl Future for QuicEndpoint { } EndpointMessage::EndpointEvent { handle, event } => { debug!("we have an event from connection {:?}", handle); - if let Some(connection_event) = inner.inner.handle_event(handle, event) { - debug!("we have an event from our endpoint"); - match inner - .muxers - .get(&handle) - .expect("received a ConnectionEvent for an unknown Connection") - .upgrade() - { - None => warn!("lost our connection!"), - Some(connection) => connection + match inner.muxers.get(&handle).and_then(|e| e.upgrade()) { + None => drop(inner.muxers.remove(&handle)), + Some(connection) => match inner.inner.handle_event(handle, event) { + None => { + connection.lock().process_endpoint_communication(&mut inner) + } + Some(event) => connection .lock() - .process_connection_events(&mut inner, connection_event), - } + .process_connection_events(&mut inner, event), + }, } } } @@ -826,66 +823,44 @@ pub struct Muxer { impl Muxer { fn wake_driver(&mut self) { if let Some(waker) = self.waker.take() { + debug!("driver awoken!"); waker.wake(); } } - fn drive_timers(&mut self, cx: &mut Context, now: &mut Instant) -> bool { + fn drive_timers(&mut self, cx: &mut Context) -> bool { let mut keep_going = false; - loop { - let mut continue_loop = false; - for (timer, timer_ref) in self.timers.iter_mut() { - if let Some(ref mut timer_future) = timer_ref { - match timer_future.poll_unpin(cx) { - Pending => continue, - Ready(()) => { - trace!("timer {:?} ready at time {:?}", timer, now); - keep_going = true; - continue_loop = true; - self.connection.handle_timeout(*now, timer); - *timer_ref = None; - } + let now = Instant::now(); + for (timer, timer_ref) in self.timers.iter_mut() { + if let Some(ref mut timer_future) = timer_ref { + match timer_future.poll_unpin(cx) { + Pending => continue, + Ready(()) => { + keep_going = true; + self.connection.handle_timeout(now, timer); + *timer_ref = None; } } } - while let Some(quinn_proto::TimerUpdate { timer, update }) = - self.connection.poll_timers() - { - keep_going = true; - continue_loop = true; - use quinn_proto::TimerSetting; - match update { - TimerSetting::Stop => self.timers[timer] = None, - TimerSetting::Start(instant) => { - if instant < *now { - self.timers[timer] = None; - self.connection.handle_timeout(*now, timer); - keep_going = true; - continue_loop = true; - continue; - } - - trace!("setting timer {:?} for {:?}", timer, instant - *now); - let mut new_timer = futures_timer::Delay::new(instant - *now); - match new_timer.poll_unpin(cx) { - Ready(()) => { - warn!("timer {:?} is already Ready!", timer); - *now = Instant::now(); - self.timers[timer] = None; - self.connection.handle_timeout(*now, timer); - keep_going = true; - continue_loop = true; - continue; - } - Pending => self.timers[timer] = Some(new_timer), - } + } + while let Some(quinn_proto::TimerUpdate { timer, update }) = self.connection.poll_timers() { + keep_going = true; + use quinn_proto::TimerSetting; + match update { + TimerSetting::Stop => self.timers[timer] = None, + TimerSetting::Start(instant) => { + if instant < now { + self.timers[timer] = None; + self.connection.handle_timeout(now, timer); + continue; } + + trace!("setting timer {:?} for {:?}", timer, instant - now); + self.timers[timer] = Some(futures_timer::Delay::new(instant - now)); } } - if !continue_loop { - break keep_going - } } + keep_going } fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { @@ -994,10 +969,14 @@ impl Muxer { /// Process application events fn process_connection_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { + self.connection.handle_event(event); + self.process_endpoint_communication(endpoint) + } + + fn process_endpoint_communication(&mut self, endpoint: &mut EndpointInner) { if self.close_reason.is_some() { return; } - self.connection.handle_event(event); self.send_to_endpoint(endpoint); self.process_app_events(); self.wake_driver(); @@ -1111,34 +1090,31 @@ impl ConnectionDriver { impl Future for ConnectionDriver { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let mut now = Instant::now(); let this = self.get_mut(); debug!("being polled for timers!"); let mut inner = this.inner.lock(); + inner.waker = Some(cx.waker().clone()); loop { + if inner.connection.is_drained() { + debug!( + "Connection drained: close reason {}", + inner + .close_reason + .as_ref() + .expect("we never have a closed connection with no reason; qed") + ); + break Ready(Ok(())); + } let mut needs_timer_update = false; - inner.waker = Some(cx.waker().clone()); - trace!("loop iteration"); + debug!("loop iteration"); needs_timer_update |= ready!(inner.pre_application_io(cx))?; needs_timer_update |= ready!(inner.on_application_io(cx))?; - needs_timer_update |= inner.drive_timers(cx, &mut now); + needs_timer_update |= inner.drive_timers(cx); inner.process_app_events(); if !needs_timer_update { - break; + break Pending; } } - if inner.connection.is_drained() { - debug!( - "Connection drained: close reason {}", - inner - .close_reason - .as_ref() - .expect("we never have a closed connection with no reason; qed") - ); - Ready(Ok(())) - } else { - Pending - } } } From cc80c1a46015194c9fdbc57c2b9e0b987accac9f Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 7 Jan 2020 09:35:52 -0500 Subject: [PATCH 054/202] Cleanups --- transports/quic/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 78c43b7a098..29963feecd3 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -392,12 +392,8 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); inner.wake_driver(); match inner.connection.write(*substream, buf) { - Ok(bytes) => { - inner.wake_driver(); - Ready(Ok(bytes)) - } + Ok(bytes) => Ready(Ok(bytes)), Err(WriteError::Blocked) => { - inner.wake_driver(); inner.writers.insert(*substream, cx.waker().clone()); Pending } @@ -424,14 +420,11 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); + inner.wake_driver(); match inner.connection.read(*substream, buf) { - Ok(Some(bytes)) => { - inner.wake_driver(); - Ready(Ok(bytes)) - } + Ok(Some(bytes)) => Ready(Ok(bytes)), Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { - inner.wake_driver(); inner.readers.insert(*substream, cx.waker().clone()); Pending } From 040d98641095ac6f3575b032c7042b8a6095d7fb Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Jan 2020 23:36:12 -0500 Subject: [PATCH 055/202] Futures portion of libp2p-quic almost complete The futures part of libp2p-quic is almost complete. Only a small amount of crypto code needs to be written. --- transports/quic/src/lib.rs | 627 ++++++++++++++--------------------- transports/quic/src/tests.rs | 328 ++++++++++++++++++ 2 files changed, 572 insertions(+), 383 deletions(-) create mode 100644 transports/quic/src/tests.rs diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 29963feecd3..920be965aaf 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// Copyright 2017-2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -55,14 +55,12 @@ //! `QuicEndpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid( - unused_must_use, - unstable_features, - warnings, - missing_copy_implementations -)] +#![forbid(unused_must_use, unstable_features, warnings)] +#![deny(missing_copy_implementations)] #![deny(trivial_casts)] mod certificate; +#[cfg(test)] +mod tests; mod verifier; use async_macros::ready; use async_std::net::UdpSocket; @@ -92,6 +90,36 @@ use std::{ }, time::Instant, }; +#[derive(Debug)] +pub struct QuicSubstream { + id: StreamId, + status: SubstreamStatus, +} + +#[derive(Debug)] +enum SubstreamStatus { + Live, + Finishing(oneshot::Receiver<()>), + Finished, +} + +impl QuicSubstream { + fn new(id: StreamId) -> Self { + let status = SubstreamStatus::Live; + Self { id, status } + } + + fn id(&self) -> StreamId { + self.id + } + + fn is_live(&self) -> bool { + match self.status { + SubstreamStatus::Live => true, + SubstreamStatus::Finishing(_) | SubstreamStatus::Finished => false, + } + } +} /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. /// @@ -310,16 +338,17 @@ enum OutboundInner { pub struct Outbound(OutboundInner); impl Future for Outbound { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = &mut *self; match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Ready(e), + OutboundInner::Complete(e) => Ready(e.map(QuicSubstream::new)), _ => unreachable!(), }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) + .map(QuicSubstream::new) .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); this.0 = OutboundInner::Done; Ready(result) @@ -331,7 +360,7 @@ impl Future for Outbound { impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; - type Substream = StreamId; + type Substream = QuicSubstream; type Error = io::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); @@ -356,7 +385,21 @@ impl StreamMuxer for QuicMuxer { } } fn destroy_outbound(&self, _: Outbound) {} - fn destroy_substream(&self, _substream: Self::Substream) {} + fn destroy_substream(&self, substream: Self::Substream) { + let mut inner = self.inner(); + if let Some(waker) = inner.writers.remove(&substream.id()) { + waker.wake(); + } + if let Some(waker) = inner.readers.remove(&substream.id()) { + waker.wake(); + } + drop(inner.connection.finish(substream.id())); + drop( + inner + .connection + .stop_sending(substream.id(), Default::default()), + ) + } fn is_remote_acknowledged(&self) -> bool { true } @@ -378,7 +421,7 @@ impl StreamMuxer for QuicMuxer { } Pending } - Some(stream) => Ready(Ok(stream)), + Some(id) => Ready(Ok(QuicSubstream::new(id))), } } @@ -389,12 +432,32 @@ impl StreamMuxer for QuicMuxer { buf: &[u8], ) -> Poll> { use quinn_proto::WriteError; + if !substream.is_live() { + return Ready(Err(io::ErrorKind::ConnectionAborted.into())); + } let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.write(*substream, buf) { + if let Some(ref e) = inner.close_reason { + return Ready(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + + assert!( + !inner.connection.is_drained(), + "attempting to write to a drained connection" + ); + match inner.connection.write(substream.id(), buf) { Ok(bytes) => Ready(Ok(bytes)), Err(WriteError::Blocked) => { - inner.writers.insert(*substream, cx.waker().clone()); + if let Some(ref e) = inner.close_reason { + return Ready(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + inner.writers.insert(substream.id(), cx.waker().clone()); Pending } Err(WriteError::UnknownStream) => { @@ -421,11 +484,15 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::ReadError; let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.read(*substream, buf) { + if inner.close_reason.is_some() { + // FIXME!!! + return Ready(Ok(0)); + } + match inner.connection.read(substream.id(), buf) { Ok(Some(bytes)) => Ready(Ok(bytes)), Ok(None) => Ready(Ok(0)), Err(ReadError::Blocked) => { - inner.readers.insert(*substream, cx.waker().clone()); + inner.readers.insert(substream.id(), cx.waker().clone()); Pending } Err(ReadError::UnknownStream) => unreachable!("use of a closed stream by libp2p"), @@ -435,95 +502,55 @@ impl StreamMuxer for QuicMuxer { fn shutdown_substream( &self, - _cx: &mut Context, + cx: &mut Context, substream: &mut Self::Substream, ) -> Poll> { - Ready( - self.inner() - .connection - .finish(*substream) - .map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => { - panic!("libp2p never uses a closed stream, so this cannot happen; qed") - } - quinn_proto::FinishError::Stopped { .. } => { - io::ErrorKind::ConnectionReset.into() - } - }), - ) + match substream.status { + SubstreamStatus::Finished => return Ready(Ok(())), + SubstreamStatus::Finishing(ref mut channel) => { + self.inner().wake_driver(); + return channel + .poll_unpin(cx) + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())); + } + SubstreamStatus::Live => {} + } + let mut inner = self.inner(); + inner.wake_driver(); + inner + .connection + .finish(substream.id()) + .map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => { + unreachable!("we checked for this above!") + } + quinn_proto::FinishError::Stopped { .. } => { + io::Error::from(io::ErrorKind::ConnectionReset) + } + })?; + let (sender, mut receiver) = oneshot::channel(); + assert!(receiver.poll_unpin(cx).is_pending()); + substream.status = SubstreamStatus::Finishing(receiver); + assert!(inner.finishers.insert(substream.id(), sender).is_none()); + Pending } fn flush_substream( &self, - cx: &mut Context, - substream: &mut Self::Substream, + _cx: &mut Context, + _substream: &mut Self::Substream, ) -> Poll> { - self.write_substream(cx, substream, b"").map_ok(drop) + Ready(Ok(())) } fn flush_all(&self, _cx: &mut Context) -> Poll> { Ready(Ok(())) } - fn close(&self, _cx: &mut Context) -> Poll> { - Ready(Ok(self.inner().connection.close( - Instant::now(), - Default::default(), - Default::default(), - ))) - } -} - -#[cfg(test)] -#[derive(Debug, Clone)] -pub struct QuicStream { - id: StreamId, - muxer: QuicMuxer, -} - -#[cfg(test)] -impl AsyncWrite for QuicStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &[u8], - ) -> Poll> { - let inner = self.get_mut(); - inner.muxer.write_substream(cx, &mut inner.id, buf) - } - - fn poll_close(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - unimplemented!() - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.poll_write(cx, b"").map_ok(drop) - } -} - -#[cfg(test)] -impl AsyncRead for QuicStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &mut [u8], - ) -> Poll> { - let inner = self.get_mut(); - inner.muxer.read_substream(cx, &mut inner.id, buf) - } -} - -#[cfg(test)] -impl Stream for QuicMuxer { - type Item = QuicStream; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.poll_inbound(cx).map(|x| match x { - Ok(id) => Some(QuicStream { - id, - muxer: self.get_mut().clone(), - }), - Err(_) => None, - }) + fn close(&self, cx: &mut Context) -> Poll> { + let mut inner = self.inner(); + inner.shutdown(); + inner.driver().poll_unpin(cx) } } @@ -594,10 +621,11 @@ impl QuicEndpoint { connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, - ) -> (QuicMuxer, ConnectionDriver) { + ) -> QuicMuxer { let (driver, muxer) = ConnectionDriver::new(Muxer::new(self.0.clone(), connection, handle)); inner.muxers.insert(handle, Arc::downgrade(&muxer)); - (QuicMuxer(muxer), driver) + (*muxer).lock().driver = Some(async_std::task::spawn(driver)); + QuicMuxer(muxer) } fn accept_muxer( @@ -606,8 +634,7 @@ impl QuicEndpoint { handle: ConnectionHandle, inner: &mut EndpointInner, ) { - let (muxer, driver) = self.create_muxer(connection, handle, &mut *inner); - async_std::task::spawn(driver); + let muxer = self.create_muxer(connection, handle, &mut *inner); self.0 .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { @@ -753,8 +780,7 @@ impl Transport for &QuicEndpoint { TransportError::Other(io::ErrorKind::InvalidInput.into()) }); let (handle, conn) = s?; - let (muxer, driver) = self.create_muxer(conn, handle, &mut inner); - async_std::task::spawn(driver); + let muxer = self.create_muxer(conn, handle, &mut inner); Ok(QuicUpgrade { muxer: Some(muxer) }) } } @@ -797,20 +823,43 @@ pub struct Muxer { writers: HashMap, /// Tasks blocked on reading readers: HashMap, + /// Tasks blocked on finishing + finishers: HashMap>, /// Task waiting for new connections, or for this connection to complete. accept_waker: Option, /// Tasks waiting to make a connection connectors: StreamSenderQueue, /// Pending transmit pending: Option, - /// The timers being used by this connection - timers: quinn_proto::TimerTable>, + /// The timer being used by this connection + timer: Option, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver waker: Option, /// Channel for endpoint events endpoint_channel: mpsc::Sender, + /// Last timeout + last_timeout: Option, + /// Join handle for the driver + driver: Option>>, +} + +impl Drop for Muxer { + fn drop(&mut self) { + self.shutdown() + } +} + +impl Drop for QuicMuxer { + fn drop(&mut self) { + let mut inner = self.inner(); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + // inner + // .connection + // .close(Instant::now(), Default::default(), Default::default()); + inner.shutdown(); + } } impl Muxer { @@ -821,56 +870,62 @@ impl Muxer { } } - fn drive_timers(&mut self, cx: &mut Context) -> bool { + fn driver(&mut self) -> &mut async_std::task::JoinHandle> { + self.driver + .as_mut() + .expect("we don’t call this until the driver is spawned; qed") + } + + fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { let mut keep_going = false; - let now = Instant::now(); - for (timer, timer_ref) in self.timers.iter_mut() { - if let Some(ref mut timer_future) = timer_ref { - match timer_future.poll_unpin(cx) { - Pending => continue, - Ready(()) => { - keep_going = true; - self.connection.handle_timeout(now, timer); - *timer_ref = None; - } + loop { + match self.connection.poll_timeout() { + None => { + self.timer = None; + self.last_timeout = None + } + Some(t) if t <= now => { + self.connection.handle_timeout(now); + keep_going = true; + continue; + } + t if t == self.last_timeout => {} + t => { + let delay = t.expect("already checked to be Some; qed") - now; + self.timer = Some(futures_timer::Delay::new(delay)) } } + break; } - while let Some(quinn_proto::TimerUpdate { timer, update }) = self.connection.poll_timers() { - keep_going = true; - use quinn_proto::TimerSetting; - match update { - TimerSetting::Stop => self.timers[timer] = None, - TimerSetting::Start(instant) => { - if instant < now { - self.timers[timer] = None; - self.connection.handle_timeout(now, timer); - continue; - } - trace!("setting timer {:?} for {:?}", timer, instant - now); - self.timers[timer] = Some(futures_timer::Delay::new(instant - now)); - } + while let Some(ref mut timer) = self.timer { + if timer.poll_unpin(cx).is_pending() { + break; } + self.connection.handle_timeout(now); + keep_going = true } keep_going } fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { + last_timeout: None, pending_stream: None, connection, handle, writers: HashMap::new(), readers: HashMap::new(), + finishers: HashMap::new(), accept_waker: None, connectors: Default::default(), endpoint: endpoint.clone(), pending: None, - timers: quinn_proto::TimerTable::new(|| None), + timer: None, close_reason: None, waker: None, endpoint_channel: endpoint.event_channel.clone(), + driver: None, } } @@ -912,12 +967,6 @@ impl Muxer { } fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { - if let Some(ref e) = self.close_reason { - return Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); - } if let Some(transmit) = self.pending.take() { ready!(self.poll_transmit(cx, transmit))?; } @@ -960,6 +1009,26 @@ impl Muxer { } } + fn shutdown(&mut self) { + if let Some(w) = self.accept_waker.take() { + w.wake() + } + for (_, v) in self.writers.drain() { + v.wake(); + } + for (_, v) in self.readers.drain() { + v.wake(); + } + for (_, _) in self.finishers.drain() {} + self.connectors.truncate(0); + if !self.connection.is_drained() { + self.connection + .close(Instant::now(), Default::default(), Default::default()); + self.process_app_events(); + } + self.wake_driver(); + } + /// Process application events fn process_connection_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { self.connection.handle_event(event); @@ -1016,10 +1085,10 @@ impl Muxer { ); let stream = self.connection.open(Dir::Bi) .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); - if let Some(oneshot) = self.connectors.pop_front() { + while let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { - Ok(()) => continue, - Err(_) => (), + Ok(()) => break, + Err(_) => {} } } self.pending_stream = Some(stream) @@ -1027,22 +1096,17 @@ impl Muxer { Event::ConnectionLost { reason } => { debug!("lost connection due to {:?}", reason); self.close_reason = Some(reason); - if let Some(w) = self.accept_waker.take() { - w.wake() - } - for (_, v) in self.writers.drain() { - v.wake(); - } - for (_, v) in self.readers.drain() { - v.wake(); - } - self.connectors.truncate(0); + self.shutdown(); } Event::StreamFinished { stream, .. } => { // Wake up the task waiting on us (if any) + debug!("Stream {:?} finished!", stream); if let Some((_, waker)) = self.writers.remove_entry(&stream) { waker.wake() } + if let Some((_, sender)) = self.finishers.remove_entry(&stream) { + drop(sender.send(())) + } } // These are separate events, but are handled the same way. Event::StreamOpened { dir: Dir::Bi } | Event::Connected => { @@ -1084,253 +1148,50 @@ impl Future for ConnectionDriver { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = self.get_mut(); - debug!("being polled for timers!"); + debug!("being polled for timer!"); let mut inner = this.inner.lock(); inner.waker = Some(cx.waker().clone()); + let now = Instant::now(); loop { - if inner.connection.is_drained() { - debug!( - "Connection drained: close reason {}", - inner - .close_reason - .as_ref() - .expect("we never have a closed connection with no reason; qed") - ); - break Ready(Ok(())); - } let mut needs_timer_update = false; - debug!("loop iteration"); + needs_timer_update |= inner.drive_timer(cx, now); needs_timer_update |= ready!(inner.pre_application_io(cx))?; needs_timer_update |= ready!(inner.on_application_io(cx))?; - needs_timer_update |= inner.drive_timers(cx); inner.process_app_events(); - if !needs_timer_update { + if inner.connection.is_drained() { + break Ready( + match inner + .close_reason + .clone() + .expect("we never have a closed connection with no reason; qed") + { + quinn_proto::ConnectionError::LocallyClosed => { + if needs_timer_update { + debug!("continuing until all events are finished"); + continue; + } else { + debug!("exiting driver"); + Ok(()) + } + } + e @ quinn_proto::ConnectionError::TimedOut => { + Err(io::Error::new(io::ErrorKind::TimedOut, e)) + } + e @ quinn_proto::ConnectionError::Reset => { + Err(io::Error::new(io::ErrorKind::ConnectionReset, e)) + } + e @ quinn_proto::ConnectionError::TransportError { .. } => { + Err(io::Error::new(io::ErrorKind::InvalidData, e)) + } + e @ quinn_proto::ConnectionError::ConnectionClosed { .. } => { + Err(io::Error::new(io::ErrorKind::ConnectionAborted, e)) + } + e => Err(io::Error::new(io::ErrorKind::Other, e)), + }, + ); + } else if !needs_timer_update { break Pending; } } } } - -#[cfg(test)] -mod tests { - use super::{multiaddr_to_socketaddr, QuicConfig, QuicEndpoint}; - use futures::prelude::*; - use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - transport::ListenerEvent, - Transport, - }; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - - fn init() { - drop(env_logger::try_init()); - } - - #[test] - fn wildcard_expansion() { - init(); - let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let listener = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) - .expect("endpoint") - .listen_on(addr) - .expect("listener"); - let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); - let client = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) - .expect("endpoint") - .dial(addr) - .expect("dialer"); - - // Process all initial `NewAddress` events and make sure they - // do not contain wildcard address or port. - let server = listener - .take_while(|event| match event.as_ref().unwrap() { - ListenerEvent::NewAddress(a) => { - let mut iter = a.iter(); - match iter.next().expect("ip address") { - Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), - Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), - other => panic!("Unexpected protocol: {}", other), - } - if let Protocol::Udp(port) = iter.next().expect("port") { - assert_ne!(0, port) - } else { - panic!("No UDP port in address: {}", a) - } - futures::future::ready(true) - } - _ => futures::future::ready(false), - }) - .for_each(|_| futures::future::ready(())); - - async_std::task::spawn(server); - futures::executor::block_on(client).unwrap(); - } - - #[test] - fn multiaddr_to_udp_conversion() { - use std::net::Ipv6Addr; - init(); - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) - .is_err() - ); - - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()) - .is_err() - ); - - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), - 8080, - )) - ); - assert_eq!( - multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, - )), - 8080, - )) - ); - } - - #[test] - fn communicating_between_dialer_and_listener() { - use super::{trace, StreamMuxer}; - init(); - let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); - let mut ready_tx = Some(ready_tx); - - async_std::task::spawn(async move { - let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" - .parse() - .expect("bad address?"); - let quic_config = QuicConfig::default(); - let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).expect("I/O error"); - let mut listener = quic_endpoint.listen_on(addr).unwrap(); - - loop { - trace!("awaiting connection"); - match listener.next().await.unwrap().unwrap() { - ListenerEvent::NewAddress(listen_addr) => { - ready_tx.take().unwrap().send(listen_addr).unwrap(); - } - ListenerEvent::Upgrade { upgrade, .. } => { - let mut muxer = upgrade.await.expect("upgrade failed"); - let mut socket = muxer.next().await.expect("no incoming stream"); - - let mut buf = [0u8; 3]; - log::error!("reading data from accepted stream!"); - socket.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, [4, 5, 6]); - log::error!("writing data!"); - socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); - } - _ => unreachable!(), - } - } - }); - - async_std::task::block_on(async move { - let addr = ready_rx.await.unwrap(); - let quic_config = QuicConfig::default(); - let quic_endpoint = QuicEndpoint::new( - &quic_config, - "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), - ) - .unwrap(); - // Obtain a future socket through dialing - let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); - trace!("Received a Connection: {:?}", connection); - let mut stream = super::QuicStream { - id: connection.open_outbound().await.expect("failed"), - muxer: connection.clone(), - }; - log::warn!("have a new stream!"); - stream.write_all(&[4u8, 5, 6]).await.unwrap(); - let mut buf = [0u8; 3]; - log::error!("reading data!"); - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, [1u8, 2, 3]); - }); - } - - #[test] - fn replace_port_0_in_returned_multiaddr_ipv4() { - init(); - let quic = QuicConfig::default(); - - let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); - assert!(addr.to_string().ends_with("udp/0/quic")); - - let quic = QuicEndpoint::new(&quic, addr.clone()).expect("no error"); - - let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); - - assert!(!new_addr.to_string().contains("tcp/0")); - } - - #[test] - fn replace_port_0_in_returned_multiaddr_ipv6() { - init(); - let config = QuicConfig::default(); - - let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); - assert!(addr.to_string().contains("udp/0/quic")); - let quic = QuicEndpoint::new(&config, addr.clone()).expect("no error"); - - let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) - .next() - .expect("some event") - .expect("no error") - .into_new_address() - .expect("listen address"); - - assert!(!new_addr.to_string().contains("tcp/0")); - } - - #[test] - fn larger_addr_denied() { - init(); - let config = QuicConfig::default(); - let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" - .parse::() - .unwrap(); - assert!(QuicEndpoint::new(&config, addr).is_err()) - } -} diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs new file mode 100644 index 00000000000..1c5ea3418fc --- /dev/null +++ b/transports/quic/src/tests.rs @@ -0,0 +1,328 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::*; +use futures::prelude::*; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::ListenerEvent, + Transport, +}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +#[derive(Debug)] +pub struct QuicStream { + id: Option, + muxer: QuicMuxer, +} + +impl AsyncWrite for QuicStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + let inner = self.get_mut(); + inner.muxer.write_substream( + cx, + &mut inner.id.as_mut().expect("written to after being closed"), + buf, + ) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + debug!("trying to close"); + let inner = self.get_mut(); + inner.muxer.shutdown_substream( + cx, + inner.id.as_mut().expect("written to after being closed"), + ) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + Ready(Ok(())) + } +} + +impl AsyncRead for QuicStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut [u8], + ) -> Poll> { + let inner = self.get_mut(); + inner + .muxer + .read_substream(cx, &mut inner.id.as_mut().expect("read after close"), buf) + } +} + +impl Drop for QuicStream { + fn drop(&mut self) { + match self.id.take() { + None => {} + Some(id) => self.muxer.destroy_substream(id), + } + } +} + +impl futures::Stream for QuicMuxer { + type Item = QuicStream; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_inbound(cx).map(|x| match x { + Ok(id) => Some(QuicStream { + id: Some(id), + muxer: self.get_mut().clone(), + }), + Err(_) => None, + }) + } +} + +fn init() { + drop(env_logger::try_init()); +} + +impl Future for QuicMuxer { + type Output = Result<(), io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + self.get_mut().close(cx) + } +} + +#[test] +fn wildcard_expansion() { + init(); + let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); + let listener = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) + .expect("endpoint") + .listen_on(addr) + .expect("listener"); + let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); + let client = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) + .expect("endpoint") + .dial(addr) + .expect("dialer"); + + // Process all initial `NewAddress` events and make sure they + // do not contain wildcard address or port. + let server = listener + .take_while(|event| match event.as_ref().unwrap() { + ListenerEvent::NewAddress(a) => { + let mut iter = a.iter(); + match iter.next().expect("ip address") { + Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), + Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), + other => panic!("Unexpected protocol: {}", other), + } + if let Protocol::Udp(port) = iter.next().expect("port") { + assert_ne!(0, port) + } else { + panic!("No UDP port in address: {}", a) + } + futures::future::ready(true) + } + _ => futures::future::ready(false), + }) + .for_each(|_| futures::future::ready(())); + + async_std::task::spawn(server); + futures::executor::block_on(client).unwrap(); +} + +#[test] +fn multiaddr_to_udp_conversion() { + use std::net::Ipv6Addr; + init(); + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() + ); + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()).is_err() + ); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + )) + ); +} + +#[test] +fn communicating_between_dialer_and_listener() { + use super::{trace, StreamMuxer}; + init(); + let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); + let mut ready_tx = Some(ready_tx); + + let _handle = async_std::task::spawn(async move { + let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" + .parse() + .expect("bad address?"); + let quic_config = QuicConfig::default(); + let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).expect("I/O error"); + let mut listener = quic_endpoint.listen_on(addr).unwrap(); + + loop { + trace!("awaiting connection"); + match listener.next().await.unwrap().unwrap() { + ListenerEvent::NewAddress(listen_addr) => { + ready_tx.take().unwrap().send(listen_addr).unwrap(); + } + ListenerEvent::Upgrade { upgrade, .. } => { + let mut muxer: QuicMuxer = upgrade.await.expect("upgrade failed"); + let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); + + let mut buf = [0u8; 3]; + log::error!("reading data from accepted stream!"); + socket.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [4, 5, 6]); + log::error!("writing data!"); + socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); + log::error!("data written!"); + socket.close().await.unwrap(); + assert_eq!(socket.read(&mut buf).await.unwrap(), 0); + drop(socket); + log::error!("end of stream"); + muxer.await.unwrap(); + break; + } + _ => unreachable!(), + } + } + }); + + async_std::task::block_on(async move { + let addr = ready_rx.await.unwrap(); + let quic_config = QuicConfig::default(); + let quic_endpoint = QuicEndpoint::new( + &quic_config, + "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), + ) + .unwrap(); + // Obtain a future socket through dialing + let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + trace!("Received a Connection: {:?}", connection); + let mut stream = QuicStream { + id: Some(connection.open_outbound().await.expect("failed")), + muxer: connection.clone(), + }; + log::warn!("have a new stream!"); + stream.write_all(&[4u8, 5, 6]).await.unwrap(); + let mut buf = [0u8; 3]; + log::error!("reading data!"); + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, [1u8, 2, 3]); + log::error!("data read!"); + stream.close().await.unwrap(); + assert_eq!(stream.read(&mut buf).await.unwrap(), 0); + drop(stream); + connection.await.unwrap(); + log::error!("awaiting handle!"); + _handle.await; + }); +} + +#[test] +fn replace_port_0_in_returned_multiaddr_ipv4() { + init(); + let quic = QuicConfig::default(); + + let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); + assert!(addr.to_string().ends_with("udp/0/quic")); + + let quic = QuicEndpoint::new(&quic, addr.clone()).expect("no error"); + + let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); +} + +#[test] +fn replace_port_0_in_returned_multiaddr_ipv6() { + init(); + let config = QuicConfig::default(); + + let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); + assert!(addr.to_string().contains("udp/0/quic")); + let quic = QuicEndpoint::new(&config, addr.clone()).expect("no error"); + + let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) + .next() + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert!(!new_addr.to_string().contains("tcp/0")); +} + +#[test] +fn larger_addr_denied() { + init(); + let config = QuicConfig::default(); + let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" + .parse::() + .unwrap(); + assert!(QuicEndpoint::new(&config, addr).is_err()) +} From 4dc5f46d531bcfb61d19f23e69e28cf44d5c699c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 14 Jan 2020 12:01:42 -0500 Subject: [PATCH 056/202] Move the endpoint code into a separate module This makes the code clearer. --- transports/quic/src/endpoint.rs | 454 ++++++++++++++++++++++++++++++++ transports/quic/src/lib.rs | 377 ++------------------------ transports/quic/src/tests.rs | 153 +++++------ 3 files changed, 540 insertions(+), 444 deletions(-) create mode 100644 transports/quic/src/endpoint.rs diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs new file mode 100644 index 00000000000..9bb243416a3 --- /dev/null +++ b/transports/quic/src/endpoint.rs @@ -0,0 +1,454 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +use crate::QuicConfig; +use async_macros::ready; +use async_std::net::{SocketAddr, UdpSocket}; +use futures::{channel::mpsc, prelude::*}; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerEvent, TransportError}, + Transport, +}; +use log::{debug, trace, warn}; +use parking_lot::{Mutex, MutexGuard}; +use quinn_proto::{Connection, ConnectionHandle}; +use std::{ + collections::HashMap, + io, + pin::Pin, + sync::{Arc, Weak}, + task::{ + Context, + Poll::{self, Pending, Ready}, + }, + time::Instant, +}; + +#[derive(Debug)] +pub(super) struct EndpointInner { + inner: quinn_proto::Endpoint, + muxers: HashMap>>, + driver: Option>>, + /// Pending packet + outgoing_packet: Option, + /// Used to receive events from connections + event_receiver: mpsc::Receiver, +} + +impl EndpointInner { + pub(super) fn handle_event( + &mut self, + handle: quinn_proto::ConnectionHandle, + event: quinn_proto::EndpointEvent, + ) -> Option { + if event.is_drained() { + self.muxers.remove_entry(&handle); + } + self.inner.handle_event(handle, event) + } + + pub(super) fn set_pending_packet(&mut self, packet: quinn_proto::Transmit) { + assert!(std::mem::replace(&mut self.outgoing_packet, Some(packet)).is_none()) + } + + fn drive_receive( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + ) -> Poll> { + use quinn_proto::DatagramEvent; + let mut buf = [0; 1800]; + trace!("endpoint polling for incoming packets!"); + assert!(self.outgoing_packet.is_none()); + assert!(self.inner.poll_transmit().is_none()); + loop { + let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; + trace!("got a packet of length {} from {}!", bytes, peer); + let (handle, event) = + match self + .inner + .handle(Instant::now(), peer, None, buf[..bytes].into()) + { + Some(e) => e, + None => { + ready!(self.poll_transmit_pending(socket, cx))?; + continue; + } + }; + trace!("have an event!"); + match event { + DatagramEvent::ConnectionEvent(connection_event) => { + match self + .muxers + .get(&handle) + .expect("received a ConnectionEvent for an unknown Connection") + .upgrade() + { + Some(connection) => connection + .lock() + .process_connection_events(self, connection_event), + None => debug!("lost our connection!"), + } + ready!(self.poll_transmit_pending(socket, cx))? + } + DatagramEvent::NewConnection(connection) => break Ready(Ok((handle, connection))), + } + } + } + + fn poll_transmit_pending( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + ) -> Poll> { + if let Some(tx) = self.outgoing_packet.take() { + ready!(self.poll_transmit(socket, cx, tx))? + } + while let Some(tx) = self.inner.poll_transmit() { + ready!(self.poll_transmit(socket, cx, tx))? + } + Ready(Ok(())) + } + + fn poll_transmit( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + packet: quinn_proto::Transmit, + ) -> Poll> { + match socket.poll_send_to(cx, &packet.contents, &packet.destination) { + Pending => { + self.outgoing_packet = Some(packet); + return Pending; + } + Ready(Ok(size)) => { + debug_assert_eq!(size, packet.contents.len()); + trace!( + "sent packet of length {} to {}", + packet.contents.len(), + packet.destination + ); + Ready(Ok(())) + } + Ready(Err(e)) => return Ready(Err(e)), + } + } +} + +#[derive(Debug)] +struct EndpointData { + /// The single UDP socket used for I/O + socket: UdpSocket, + /// A `Mutex` protecting the QUIC state machine. + inner: Mutex, + /// The channel on which new connections are sent. This is bounded in practice by the accept + /// backlog. + new_connections: mpsc::UnboundedSender, io::Error>>, + /// The channel used to receive new connections. + receive_connections: Mutex< + Option, io::Error>>>, + >, + /// Connections send their events to this + event_channel: mpsc::Sender, + /// The `Multiaddr` + address: Multiaddr, + /// The configuration + config: QuicConfig, +} + +/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. +/// +/// You generally need only one of these per process. Endpoints are `Send` and `Sync`, so you +/// can share them among as many threads as you like. However, performance may be better if you +/// have one per CPU core, as this reduces lock contention. Most applications will not need to +/// worry about this. `QuicEndpoint` tries to use fine-grained locking to reduce the overhead. +/// +/// `QuicEndpoint` wraps the underlying data structure in an `Arc`, so cloning it just bumps the +/// reference count. All state is shared between the clones. For example, you can pass different +/// clones to `listen_on`. Each incoming connection will be received by exactly one of them. +/// +/// The **only** valid `Multiaddr` to pass to `listen_on` or `dial` is the one used to create the +/// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you +/// will get `TransportError::MultiaddrNotSuppported`. +#[derive(Debug, Clone)] +pub struct Endpoint(Arc); + +impl Endpoint { + fn inner(&self) -> MutexGuard<'_, EndpointInner> { + self.0.inner.lock() + } + + pub(super) fn event_channel(&self) -> mpsc::Sender { + self.0.event_channel.clone() + } + + pub(super) fn poll_send_to( + &self, + cx: &mut Context, + transmit: &[u8], + destination: &SocketAddr, + ) -> Poll> { + self.0.socket.poll_send_to(cx, transmit, destination) + } + + /// Construct a `Endpoint` with the given `QuicConfig` and `Multiaddr`. + pub fn new( + config: QuicConfig, + address: Multiaddr, + ) -> Result::Error>> { + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { + sa + } else { + return Err(TransportError::MultiaddrNotSupported(address)); + }; + // NOT blocking, as per man:bind(2), as we pass an IP address. + let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); + let (new_connections, receive_connections) = mpsc::unbounded(); + let (event_channel, event_receiver) = mpsc::channel(0); + new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) + .expect("we have a reference to the peer, so this will not fail; qed"); + Ok(Self(Arc::new(EndpointData { + socket, + inner: Mutex::new(EndpointInner { + inner: quinn_proto::Endpoint::new( + config.endpoint_config.clone(), + Some(config.server_config.clone()), + ), + muxers: HashMap::new(), + driver: None, + event_receiver, + outgoing_packet: None, + }), + address, + receive_connections: Mutex::new(Some(receive_connections)), + new_connections, + event_channel, + config, + }))) + } + + fn create_muxer( + &self, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, + ) -> super::QuicMuxer { + let (driver, muxer) = + super::ConnectionDriver::new(super::Muxer::new(self.clone(), connection, handle)); + inner.muxers.insert(handle, Arc::downgrade(&muxer)); + (*muxer).lock().driver = Some(async_std::task::spawn(driver)); + super::QuicMuxer(muxer) + } + + fn accept_muxer( + &self, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, + ) { + let muxer = self.create_muxer(connection, handle, &mut *inner); + self.0 + .new_connections + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade: super::QuicUpgrade { muxer: Some(muxer) }, + local_addr: self.0.address.clone(), + remote_addr: self.0.address.clone(), + })) + .expect( + "this is an unbounded channel, and we have an instance \ + of the peer, so this will never fail; qed", + ); + } +} + +impl Future for Endpoint { + type Output = Result<(), io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + use super::EndpointMessage; + let this = self.get_mut(); + let mut inner = this.inner(); + 'a: loop { + ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; + while let Ready(e) = inner.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { + match e { + EndpointMessage::ConnectionAccepted => { + debug!("accepting connection!"); + inner.inner.accept(); + } + EndpointMessage::EndpointEvent { handle, event } => { + debug!("we have an event from connection {:?}", handle); + match inner.muxers.get(&handle).and_then(|e| e.upgrade()) { + None => drop(inner.muxers.remove(&handle)), + Some(connection) => match inner.inner.handle_event(handle, event) { + None => { + connection.lock().process_endpoint_communication(&mut inner) + } + Some(event) => connection + .lock() + .process_connection_events(&mut inner, event), + }, + } + } + } + ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; + } + let (handle, connection) = ready!(inner.drive_receive(&this.0.socket, cx))?; + this.accept_muxer(connection, handle, &mut *inner); + ready!(inner.poll_transmit_pending(&this.0.socket, cx))? + } + } +} + +impl Transport for &Endpoint { + type Output = super::QuicMuxer; + type Error = io::Error; + type Listener = + mpsc::UnboundedReceiver, Self::Error>>; + type ListenerUpgrade = super::QuicUpgrade; + type Dial = super::QuicUpgrade; + + fn listen_on(self, addr: Multiaddr) -> Result> { + if addr != self.0.address { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let res = (self.0) + .receive_connections + .lock() + .take() + .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); + let mut inner = self.inner(); + if inner.driver.is_none() { + inner.driver = Some(async_std::task::spawn(self.clone())) + } + res + } + + fn dial(self, addr: Multiaddr) -> Result> { + let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + debug!("Instantly refusing dialing {}, as it is invalid", addr); + return Err(TransportError::Other( + io::ErrorKind::ConnectionRefused.into(), + )); + } + socket_addr + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + let mut inner = self.inner(); + if inner.driver.is_none() { + inner.driver = Some(async_std::task::spawn(self.clone())) + } + + let s: Result<(_, Connection), _> = inner + .inner + .connect( + self.0.config.client_config.clone(), + socket_addr, + "localhost", + ) + .map_err(|e| { + warn!("Connection error: {:?}", e); + TransportError::Other(io::ErrorKind::InvalidInput.into()) + }); + let (handle, conn) = s?; + let muxer = self.create_muxer(conn, handle, &mut inner); + Ok(super::QuicUpgrade { muxer: Some(muxer) }) + } +} + +// This type of logic should probably be moved into the multiaddr package +fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { + let mut iter = addr.iter(); + let proto1 = iter.next().ok_or(())?; + let proto2 = iter.next().ok_or(())?; + let proto3 = iter.next().ok_or(())?; + + if iter.next().is_some() { + return Err(()); + } + + match (proto1, proto2, proto3) { + (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + _ => Err(()), + } +} + +#[test] +fn multiaddr_to_udp_conversion() { + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + crate::tests::init(); + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() + ); + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()).is_err() + ); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + )) + ); +} diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 920be965aaf..c18da223f1d 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -27,13 +27,13 @@ //! Example: //! //! ``` -//! use libp2p_quic::{QuicConfig, QuicEndpoint}; +//! use libp2p_quic::{QuicConfig, Endpoint}; //! use libp2p_core::Multiaddr; //! //! # fn main() { //! let quic_config = QuicConfig::default(); -//! let quic_endpoint = QuicEndpoint::new( -//! &quic_config, +//! let quic_endpoint = Endpoint::new( +//! quic_config, //! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), //! ) //! .expect("I/O error"); @@ -49,41 +49,38 @@ //! //! # Design Notes //! -//! The entry point is the `QuicEndpoint` struct. It represents a single QUIC endpoint. You +//! The entry point is the `Endpoint` struct. It represents a single QUIC endpoint. You //! should generally have one of these per process. //! -//! `QuicEndpoint` manages a background task that processes all incoming packets. Each +//! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. #![forbid(unused_must_use, unstable_features, warnings)] #![deny(missing_copy_implementations)] #![deny(trivial_casts)] mod certificate; +mod endpoint; #[cfg(test)] mod tests; mod verifier; use async_macros::ready; -use async_std::net::UdpSocket; pub use certificate::make_cert; +pub use endpoint::Endpoint; +use endpoint::EndpointInner; use futures::{ channel::{mpsc, oneshot}, prelude::*, }; -use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - transport::{ListenerEvent, TransportError}, - StreamMuxer, Transport, -}; -use log::{debug, trace, warn}; +use libp2p_core::StreamMuxer; +use log::{debug, trace}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, io, mem::replace, - net::SocketAddr, pin::Pin, - sync::{Arc, Weak}, + sync::Arc, task::{ Context, Poll::{self, Pending, Ready}, @@ -140,8 +137,8 @@ fn make_client_config( key: rustls::PrivateKey, ) -> quinn_proto::ClientConfig { let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni = 0; - transport.datagram_receive_buffer_size = None; + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; @@ -161,8 +158,8 @@ fn make_server_config( key: rustls::PrivateKey, ) -> quinn_proto::ServerConfig { let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni = 0; - transport.datagram_receive_buffer_size = None; + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); let mut crypto = rustls::ServerConfig::new(Arc::new( verifier::VeryInsecureRequireClientCertificateButDoNotCheckIt, )); @@ -205,120 +202,6 @@ enum EndpointMessage { }, } -#[derive(Debug)] -struct EndpointInner { - inner: quinn_proto::Endpoint, - muxers: HashMap>>, - driver: Option>>, - /// Pending packet - outgoing_packet: Option, - /// Used to receive events from connections - event_receiver: mpsc::Receiver, -} - -impl EndpointInner { - fn drive_receive( - &mut self, - socket: &UdpSocket, - cx: &mut Context, - ) -> Poll> { - use quinn_proto::DatagramEvent; - let mut buf = [0; 1800]; - trace!("endpoint polling for incoming packets!"); - assert!(self.outgoing_packet.is_none()); - assert!(self.inner.poll_transmit().is_none()); - loop { - let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; - trace!("got a packet of length {} from {}!", bytes, peer); - let (handle, event) = - match self - .inner - .handle(Instant::now(), peer, None, buf[..bytes].into()) - { - Some(e) => e, - None => { - ready!(self.poll_transmit_pending(socket, cx))?; - continue; - } - }; - trace!("have an event!"); - match event { - DatagramEvent::ConnectionEvent(connection_event) => { - match self - .muxers - .get(&handle) - .expect("received a ConnectionEvent for an unknown Connection") - .upgrade() - { - Some(connection) => connection - .lock() - .process_connection_events(self, connection_event), - None => debug!("lost our connection!"), - } - ready!(self.poll_transmit_pending(socket, cx))? - } - DatagramEvent::NewConnection(connection) => break Ready(Ok((handle, connection))), - } - } - } - - fn poll_transmit_pending( - &mut self, - socket: &UdpSocket, - cx: &mut Context, - ) -> Poll> { - if let Some(tx) = self.outgoing_packet.take() { - ready!(self.poll_transmit(socket, cx, tx))? - } - while let Some(tx) = self.inner.poll_transmit() { - ready!(self.poll_transmit(socket, cx, tx))? - } - Ready(Ok(())) - } - - fn poll_transmit( - &mut self, - socket: &UdpSocket, - cx: &mut Context, - packet: quinn_proto::Transmit, - ) -> Poll> { - match socket.poll_send_to(cx, &packet.contents, &packet.destination) { - Pending => { - self.outgoing_packet = Some(packet); - return Pending; - } - Ready(Ok(size)) => { - debug_assert_eq!(size, packet.contents.len()); - trace!( - "sent packet of length {} to {}", - packet.contents.len(), - packet.destination - ); - Ready(Ok(())) - } - Ready(Err(e)) => return Ready(Err(e)), - } - } -} - -#[derive(Debug)] -struct Endpoint { - /// The single UDP socket used for I/O - socket: UdpSocket, - /// A `Mutex` protecting the QUIC state machine. - inner: Mutex, - /// The channel on which new connections are sent. This is bounded in practice by the accept - /// backlog. - new_connections: mpsc::UnboundedSender, io::Error>>, - /// The channel used to receive new connections. - receive_connections: - Mutex, io::Error>>>>, - /// Connections send their events to this - event_channel: mpsc::Sender, - /// The `Multiaddr` - address: Multiaddr, -} - #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); @@ -495,7 +378,7 @@ impl StreamMuxer for QuicMuxer { inner.readers.insert(substream.id(), cx.waker().clone()); Pending } - Err(ReadError::UnknownStream) => unreachable!("use of a closed stream by libp2p"), + Err(ReadError::UnknownStream) => Ready(Ok(0)), Err(ReadError::Reset(_)) => Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -554,138 +437,6 @@ impl StreamMuxer for QuicMuxer { } } -/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. -/// -/// You and You generally need only one of these per process. Endpoints are thread-safe, so you -/// can share them among as many threads as you like. However, performance may be better if you -/// have one per CPU core, as this reduces lock contention. Most applications will not need to -/// worry about this. `QuicEndpoint` tries to use fine-grained locking to reduce the overhead. -/// -/// `QuicEndpoint` wraps the underlying data structure in an `Arc`, so cloning it just bumps the -/// reference count. All state is shared between the clones. For example, you can pass different -/// clones to `listen_on`. Each incoming connection will be received by exactly one of them. -/// -/// The **only** valid `Multiaddr` to pass to `listen_on` or `dial` is the one used to create the -/// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you -/// will get `TransportError::MultiaddrNotSuppported`. -#[derive(Debug, Clone)] -pub struct QuicEndpoint(Arc, QuicConfig); - -impl QuicEndpoint { - fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.0.inner.lock() - } - - /// Construct a `QuicEndpoint` with the given `QuicConfig` and `Multiaddr`. - pub fn new( - config: &QuicConfig, - address: Multiaddr, - ) -> Result::Error>> { - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { - sa - } else { - return Err(TransportError::MultiaddrNotSupported(address)); - }; - // NOT blocking, as per man:bind(2), as we pass an IP address. - let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); - let (new_connections, receive_connections) = mpsc::unbounded(); - let (event_channel, event_receiver) = mpsc::channel(0); - new_connections - .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) - .expect("we have a reference to the peer, so this will not fail; qed"); - Ok(Self( - Arc::new(Endpoint { - socket, - inner: Mutex::new(EndpointInner { - inner: quinn_proto::Endpoint::new( - config.endpoint_config.clone(), - Some(config.server_config.clone()), - ) - .map_err(|_| TransportError::Other(io::ErrorKind::InvalidData.into()))?, - muxers: HashMap::new(), - driver: None, - event_receiver, - outgoing_packet: None, - }), - address, - receive_connections: Mutex::new(Some(receive_connections)), - new_connections, - event_channel, - }), - config.clone(), - )) - } - - fn create_muxer( - &self, - connection: Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, - ) -> QuicMuxer { - let (driver, muxer) = ConnectionDriver::new(Muxer::new(self.0.clone(), connection, handle)); - inner.muxers.insert(handle, Arc::downgrade(&muxer)); - (*muxer).lock().driver = Some(async_std::task::spawn(driver)); - QuicMuxer(muxer) - } - - fn accept_muxer( - &self, - connection: Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, - ) { - let muxer = self.create_muxer(connection, handle, &mut *inner); - self.0 - .new_connections - .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade: QuicUpgrade { muxer: Some(muxer) }, - local_addr: self.0.address.clone(), - remote_addr: self.0.address.clone(), - })) - .expect( - "this is an unbounded channel, and we have an instance \ - of the peer, so this will never fail; qed", - ); - } -} - -impl Future for QuicEndpoint { - type Output = Result<(), io::Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let this = self.get_mut(); - let mut inner = this.inner(); - 'a: loop { - ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; - while let Ready(e) = inner.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { - match e { - EndpointMessage::ConnectionAccepted => { - debug!("accepting connection!"); - inner.inner.accept(); - } - EndpointMessage::EndpointEvent { handle, event } => { - debug!("we have an event from connection {:?}", handle); - match inner.muxers.get(&handle).and_then(|e| e.upgrade()) { - None => drop(inner.muxers.remove(&handle)), - Some(connection) => match inner.inner.handle_event(handle, event) { - None => { - connection.lock().process_endpoint_communication(&mut inner) - } - Some(event) => connection - .lock() - .process_connection_events(&mut inner, event), - }, - } - } - } - ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; - } - let (handle, connection) = ready!(inner.drive_receive(&this.0.socket, cx))?; - this.accept_muxer(connection, handle, &mut *inner); - ready!(inner.poll_transmit_pending(&this.0.socket, cx))? - } - } -} - #[derive(Debug)] pub struct QuicUpgrade { muxer: Option, @@ -731,82 +482,6 @@ impl Future for QuicUpgrade { } } -impl Transport for &QuicEndpoint { - type Output = QuicMuxer; - type Error = io::Error; - type Listener = - mpsc::UnboundedReceiver, Self::Error>>; - type ListenerUpgrade = QuicUpgrade; - type Dial = QuicUpgrade; - - fn listen_on(self, addr: Multiaddr) -> Result> { - if addr != self.0.address { - return Err(TransportError::MultiaddrNotSupported(addr)); - } - let res = (self.0) - .receive_connections - .lock() - .take() - .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); - let mut inner = self.inner(); - if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(self.clone())) - } - res - } - - fn dial(self, addr: Multiaddr) -> Result> { - let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::Other( - io::ErrorKind::ConnectionRefused.into(), - )); - } - socket_addr - } else { - return Err(TransportError::MultiaddrNotSupported(addr)); - }; - let mut inner = self.inner(); - if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(self.clone())) - } - - let s: Result<(_, Connection), _> = inner - .inner - .connect(self.1.client_config.clone(), socket_addr, "localhost") - .map_err(|e| { - warn!("Connection error: {:?}", e); - TransportError::Other(io::ErrorKind::InvalidInput.into()) - }); - let (handle, conn) = s?; - let muxer = self.create_muxer(conn, handle, &mut inner); - Ok(QuicUpgrade { muxer: Some(muxer) }) - } -} - -// This type of logic should probably be moved into the multiaddr package -fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { - let mut iter = addr.iter(); - let proto1 = iter.next().ok_or(())?; - let proto2 = iter.next().ok_or(())?; - let proto3 = iter.next().ok_or(())?; - - if iter.next().is_some() { - return Err(()); - } - - match (proto1, proto2, proto3) { - (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::Quic) => { - Ok(SocketAddr::new(ip.into(), port)) - } - (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::Quic) => { - Ok(SocketAddr::new(ip.into(), port)) - } - _ => Err(()), - } -} - type StreamSenderQueue = std::collections::VecDeque>; #[derive(Debug)] @@ -814,7 +489,7 @@ pub struct Muxer { /// The pending stream, if any. pending_stream: Option, /// The associated endpoint - endpoint: Arc, + endpoint: Endpoint, /// The `quinn_proto::Connection` struct. connection: Connection, /// Connection handle @@ -908,7 +583,7 @@ impl Muxer { keep_going } - fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { + fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { last_timeout: None, pending_stream: None, @@ -919,12 +594,12 @@ impl Muxer { finishers: HashMap::new(), accept_waker: None, connectors: Default::default(), - endpoint: endpoint.clone(), + endpoint_channel: endpoint.event_channel(), + endpoint: endpoint, pending: None, timer: None, close_reason: None, waker: None, - endpoint_channel: endpoint.event_channel.clone(), driver: None, } } @@ -933,8 +608,7 @@ impl Muxer { /// fail. fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { while let Some(endpoint_event) = self.connection.poll_endpoint_events() { - if let Some(connection_event) = endpoint.inner.handle_event(self.handle, endpoint_event) - { + if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { self.connection.handle_event(connection_event) } } @@ -991,7 +665,6 @@ impl Muxer { ); match self .endpoint - .socket .poll_send_to(cx, &transmit.contents, &transmit.destination) { Pending => { @@ -1019,7 +692,9 @@ impl Muxer { for (_, v) in self.readers.drain() { v.wake(); } - for (_, _) in self.finishers.drain() {} + for (_, sender) in self.finishers.drain() { + drop(sender.send(())) + } self.connectors.truncate(0); if !self.connection.is_drained() { self.connection @@ -1045,7 +720,7 @@ impl Muxer { assert!(self.connection.poll_endpoint_events().is_none()); assert!(self.connection.poll().is_none()); if let Some(tx) = self.connection.poll_transmit(Instant::now()) { - assert!(replace(&mut endpoint.outgoing_packet, Some(tx)).is_none()) + endpoint.set_pending_packet(tx) } } @@ -1123,7 +798,7 @@ impl Muxer { #[derive(Debug)] struct ConnectionDriver { inner: Arc>, - endpoint: Arc, + endpoint: Endpoint, outgoing_packet: Option, } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 1c5ea3418fc..ee99951a6f8 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -25,12 +25,12 @@ use libp2p_core::{ transport::ListenerEvent, Transport, }; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; #[derive(Debug)] pub struct QuicStream { id: Option, muxer: QuicMuxer, + shutdown: bool, } impl AsyncWrite for QuicStream { @@ -39,21 +39,21 @@ impl AsyncWrite for QuicStream { cx: &mut Context, buf: &[u8], ) -> Poll> { + assert!(!self.shutdown, "written after close"); let inner = self.get_mut(); - inner.muxer.write_substream( - cx, - &mut inner.id.as_mut().expect("written to after being closed"), - buf, - ) + inner + .muxer + .write_substream(cx, inner.id.as_mut().unwrap(), buf) } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { debug!("trying to close"); + self.shutdown = true; let inner = self.get_mut(); - inner.muxer.shutdown_substream( - cx, - inner.id.as_mut().expect("written to after being closed"), - ) + ready!(inner + .muxer + .shutdown_substream(cx, inner.id.as_mut().unwrap()))?; + Ready(Ok(())) } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { @@ -70,7 +70,8 @@ impl AsyncRead for QuicStream { let inner = self.get_mut(); inner .muxer - .read_substream(cx, &mut inner.id.as_mut().expect("read after close"), buf) + .read_substream(cx, inner.id.as_mut().unwrap(), buf) + .map_err(|e| panic!("unexpected error {:?}", e)) } } @@ -86,17 +87,15 @@ impl Drop for QuicStream { impl futures::Stream for QuicMuxer { type Item = QuicStream; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.poll_inbound(cx).map(|x| match x { - Ok(id) => Some(QuicStream { - id: Some(id), - muxer: self.get_mut().clone(), - }), - Err(_) => None, - }) + Ready(Some(QuicStream { + id: Some(ready!(self.poll_inbound(cx)).expect("bug")), + muxer: self.get_mut().clone(), + shutdown: false, + })) } } -fn init() { +pub(crate) fn init() { drop(env_logger::try_init()); } @@ -111,12 +110,12 @@ impl Future for QuicMuxer { fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let listener = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) + let listener = Endpoint::new(QuicConfig::default(), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); - let client = QuicEndpoint::new(&QuicConfig::default(), addr.clone()) + let client = Endpoint::new(QuicConfig::default(), addr.clone()) .expect("endpoint") .dial(addr) .expect("dialer"); @@ -147,62 +146,6 @@ fn wildcard_expansion() { futures::executor::block_on(client).unwrap(); } -#[test] -fn multiaddr_to_udp_conversion() { - use std::net::Ipv6Addr; - init(); - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() - ); - - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()).is_err() - ); - - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), - 8080, - )) - ); - assert_eq!( - multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, - )), - 8080, - )) - ); -} - #[test] fn communicating_between_dialer_and_listener() { use super::{trace, StreamMuxer}; @@ -210,12 +153,29 @@ fn communicating_between_dialer_and_listener() { let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); + #[cfg(any())] + async fn create_slowdown() { + futures_timer::Delay::new(std::time::Duration::new(1, 0)).await + } + + #[cfg(any())] + struct BlockJoin { + handle: Option>, + } + + #[cfg(any())] + impl Drop for BlockJoin { + fn drop(&mut self) { + drop(async_std::task::block_on(self.handle.take().unwrap())) + } + } + let _handle = async_std::task::spawn(async move { let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" .parse() .expect("bad address?"); let quic_config = QuicConfig::default(); - let quic_endpoint = QuicEndpoint::new(&quic_config, addr.clone()).expect("I/O error"); + let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); loop { @@ -229,16 +189,16 @@ fn communicating_between_dialer_and_listener() { let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); let mut buf = [0u8; 3]; - log::error!("reading data from accepted stream!"); + log::debug!("reading data from accepted stream!"); socket.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [4, 5, 6]); - log::error!("writing data!"); + log::debug!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); - log::error!("data written!"); + log::debug!("data written!"); socket.close().await.unwrap(); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); drop(socket); - log::error!("end of stream"); + log::debug!("end of stream"); muxer.await.unwrap(); break; } @@ -246,12 +206,16 @@ fn communicating_between_dialer_and_listener() { } } }); + #[cfg(any())] + let _join = BlockJoin { + handle: Some(_handle), + }; - async_std::task::block_on(async move { + let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); let quic_config = QuicConfig::default(); - let quic_endpoint = QuicEndpoint::new( - &quic_config, + let quic_endpoint = Endpoint::new( + quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), ) .unwrap(); @@ -261,21 +225,24 @@ fn communicating_between_dialer_and_listener() { let mut stream = QuicStream { id: Some(connection.open_outbound().await.expect("failed")), muxer: connection.clone(), + shutdown: false, }; log::warn!("have a new stream!"); stream.write_all(&[4u8, 5, 6]).await.unwrap(); let mut buf = [0u8; 3]; - log::error!("reading data!"); + log::debug!("reading data!"); stream.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, [1u8, 2, 3]); - log::error!("data read!"); + log::debug!("data read!"); stream.close().await.unwrap(); + log::debug!("checking for EOF!"); assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); connection.await.unwrap(); - log::error!("awaiting handle!"); - _handle.await; + log::debug!("awaiting handle!"); }); + async_std::task::block_on(_handle); + async_std::task::block_on(second_handle); } #[test] @@ -286,7 +253,7 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); - let quic = QuicEndpoint::new(&quic, addr.clone()).expect("no error"); + let quic = Endpoint::new(quic, addr.clone()).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -305,7 +272,7 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); - let quic = QuicEndpoint::new(&config, addr.clone()).expect("no error"); + let quic = Endpoint::new(config, addr.clone()).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -324,5 +291,5 @@ fn larger_addr_denied() { let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); - assert!(QuicEndpoint::new(&config, addr).is_err()) + assert!(Endpoint::new(config, addr).is_err()) } From b6d137b060022a99a82b0df4556a4094045bc6dd Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 15 Jan 2020 11:35:48 -0500 Subject: [PATCH 057/202] Sending outgoing traffic must not block processing incoming traffic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a deadlock (that took weeks to debug) by processing received packets even if there are packets waiting to be transmitted. If packets can’t be transmitted fast enough, they are simply dropped. QUIC will retransmit them, so that is okay. We still log this at `warn` level, since it is an undesirable occurrence. The deadlock is the classic circular dependency that is found in Ethernet networks using priority flow control. UDP is an unreliable protocol, but reliable, in-order delivery with flow control is still allowed. That is, there is no guarantee that the kernel will not block senders for an arbitrary amount of time. This is most likely to be an issue on loopback, but could be an issue anywhere. Also, increase the receive buffer size, to avoid problems on large MTU networks. There are still many problems that need to be fixed before this is production-ready, including (but not limited to): * Properly shutdown outgoint streams when `QuicMuxer::close()` is called. * Do not loop excessively when incoming data arrives too quickly. * Do not allocate a fresh receive buffer for each incoming packet. * Properly cleanup background tasks. * Add more tests. * Report `PeerId`s for incoming connections. --- transports/quic/src/endpoint.rs | 14 +++-- transports/quic/src/lib.rs | 95 +++++++++++++++++++++------------ transports/quic/src/tests.rs | 3 +- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 9bb243416a3..18ea800d25c 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -74,10 +74,8 @@ impl EndpointInner { cx: &mut Context, ) -> Poll> { use quinn_proto::DatagramEvent; - let mut buf = [0; 1800]; + let mut buf = vec![0; 65535]; trace!("endpoint polling for incoming packets!"); - assert!(self.outgoing_packet.is_none()); - assert!(self.inner.poll_transmit().is_none()); loop { let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; trace!("got a packet of length {} from {}!", bytes, peer); @@ -87,10 +85,7 @@ impl EndpointInner { .handle(Instant::now(), peer, None, buf[..bytes].into()) { Some(e) => e, - None => { - ready!(self.poll_transmit_pending(socket, cx))?; - continue; - } + None => continue, }; trace!("have an event!"); match event { @@ -135,8 +130,11 @@ impl EndpointInner { ) -> Poll> { match socket.poll_send_to(cx, &packet.contents, &packet.destination) { Pending => { + if self.outgoing_packet.is_some() { + warn!("Discarding packet!") + } self.outgoing_packet = Some(packet); - return Pending; + return Ready(Ok(())); } Ready(Ok(size)) => { debug_assert_eq!(size, packet.contents.len()); diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c18da223f1d..9800b4cd3fd 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -72,7 +72,7 @@ use futures::{ prelude::*, }; use libp2p_core::StreamMuxer; -use log::{debug, trace}; +use log::{debug, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ @@ -412,7 +412,10 @@ impl StreamMuxer for QuicMuxer { } })?; let (sender, mut receiver) = oneshot::channel(); - assert!(receiver.poll_unpin(cx).is_pending()); + assert!( + receiver.poll_unpin(cx).is_pending(), + "we haven’t written to the peer yet" + ); substream.status = SubstreamStatus::Finishing(receiver); assert!(inner.finishers.insert(substream.id(), sender).is_none()); Pending @@ -442,43 +445,46 @@ pub struct QuicUpgrade { muxer: Option, } +#[cfg(test)] +impl Drop for QuicUpgrade { + fn drop(&mut self) { + debug!("dropping upgrade!"); + assert!( + self.muxer.is_none(), + "dropped before being polled to completion" + ); + } +} + impl Future for QuicUpgrade { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); - { + let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); if let Some(ref e) = inner.close_reason { - return Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); + Err(io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())) } else if inner.connection.is_handshaking() { - assert!(inner.close_reason.is_none()); assert!(!inner.connection.is_drained(), "deadlock"); - inner.accept_waker = Some(cx.waker().clone()); + inner.handshake_waker = Some(cx.waker().clone()); return Pending; - } else if inner.connection.is_drained() { - debug!("connection already drained; failing"); - return Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - inner - .close_reason - .as_ref() - .expect("drained connections have a close reason") - .clone(), - ))); } else if inner.connection.side().is_server() { ready!(inner.endpoint_channel.poll_ready(cx)) - .expect("the other side is not closed; qed"); - inner + .expect("we have a reference to the peer; qed"); + Ok(inner .endpoint_channel .start_send(EndpointMessage::ConnectionAccepted) - .expect("we just checked that we can send some data; qed"); + .expect( + "we only send one datum per clone of the channel, so we have capacity \ + to send this; qed", + )) + } else { + Ok(()) } - } - Ready(Ok(muxer.take().expect("impossible"))) + }; + let muxer = muxer.take().expect("polled after yielding Ready"); + Ready(res.map(|()| muxer)) } } @@ -500,7 +506,9 @@ pub struct Muxer { readers: HashMap, /// Tasks blocked on finishing finishers: HashMap>, - /// Task waiting for new connections, or for this connection to complete. + /// Task waiting for new connections + handshake_waker: Option, + /// Task waiting for new connections accept_waker: Option, /// Tasks waiting to make a connection connectors: StreamSenderQueue, @@ -528,12 +536,13 @@ impl Drop for Muxer { impl Drop for QuicMuxer { fn drop(&mut self) { - let mut inner = self.inner(); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - // inner - // .connection - // .close(Instant::now(), Default::default(), Default::default()); - inner.shutdown(); + let inner = self.inner(); + debug!("dropping muxer with side {:?}", inner.connection.side()); + #[cfg(test)] + assert!( + !inner.connection.is_handshaking(), + "dropped a connection that was still handshaking" + ); } } @@ -593,6 +602,7 @@ impl Muxer { readers: HashMap::new(), finishers: HashMap::new(), accept_waker: None, + handshake_waker: None, connectors: Default::default(), endpoint_channel: endpoint.event_channel(), endpoint: endpoint, @@ -668,6 +678,13 @@ impl Muxer { .poll_send_to(cx, &transmit.contents, &transmit.destination) { Pending => { + if let Some(packet) = self.pending.take() { + warn!( + "dropping packet of length {} to {} on the floor!", + packet.contents.len(), + packet.destination + ); + } self.pending = Some(transmit); Pending } @@ -686,6 +703,9 @@ impl Muxer { if let Some(w) = self.accept_waker.take() { w.wake() } + if let Some(w) = self.handshake_waker.take() { + w.wake() + } for (_, v) in self.writers.drain() { v.wake(); } @@ -696,6 +716,7 @@ impl Muxer { drop(sender.send(())) } self.connectors.truncate(0); + self.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); if !self.connection.is_drained() { self.connection .close(Instant::now(), Default::default(), Default::default()); @@ -783,9 +804,15 @@ impl Muxer { drop(sender.send(())) } } - // These are separate events, but are handled the same way. - Event::StreamOpened { dir: Dir::Bi } | Event::Connected => { - debug!("connected or stream opened!"); + Event::Connected => { + debug!("connected!"); + assert!(!self.connection.is_handshaking(), "quinn-proto bug"); + if let Some(w) = self.handshake_waker.take() { + w.wake() + } + } + Event::StreamOpened { dir: Dir::Bi } => { + debug!("stream opened!"); if let Some(w) = self.accept_waker.take() { w.wake() } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index ee99951a6f8..f7fa9e3114a 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -185,7 +185,9 @@ fn communicating_between_dialer_and_listener() { ready_tx.take().unwrap().send(listen_addr).unwrap(); } ListenerEvent::Upgrade { upgrade, .. } => { + log::debug!("got a connection upgrade!"); let mut muxer: QuicMuxer = upgrade.await.expect("upgrade failed"); + log::debug!("got a new muxer!"); let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); let mut buf = [0u8; 3]; @@ -238,7 +240,6 @@ fn communicating_between_dialer_and_listener() { log::debug!("checking for EOF!"); assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); - connection.await.unwrap(); log::debug!("awaiting handle!"); }); async_std::task::block_on(_handle); From 5e5693456c4c0d70001505c8382aab02c07a4b87 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 17 Jan 2020 11:39:03 -0500 Subject: [PATCH 058/202] Another failed attempt to fix the hang! This is almost certainly a but in a third-party library. --- transports/quic/src/endpoint.rs | 157 +++++++++++++++++++------------- transports/quic/src/lib.rs | 141 ++++++++++++++-------------- transports/quic/src/tests.rs | 2 +- 3 files changed, 164 insertions(+), 136 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 18ea800d25c..69f0a48991e 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -17,7 +17,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::QuicConfig; +use crate::{EndpointMessage, QuicConfig}; use async_macros::ready; use async_std::net::{SocketAddr, UdpSocket}; use futures::{channel::mpsc, prelude::*}; @@ -26,7 +26,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, trace, warn}; +use log::{debug, error, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionHandle}; use std::{ @@ -38,7 +38,7 @@ use std::{ Context, Poll::{self, Pending, Ready}, }, - time::Instant, + time::{Duration, Instant}, }; #[derive(Debug)] @@ -49,7 +49,9 @@ pub(super) struct EndpointInner { /// Pending packet outgoing_packet: Option, /// Used to receive events from connections - event_receiver: mpsc::Receiver, + event_receiver: mpsc::Receiver, + /// Bad timer + bad_timer: futures_timer::Delay, } impl EndpointInner { @@ -64,8 +66,25 @@ impl EndpointInner { self.inner.handle_event(handle, event) } - pub(super) fn set_pending_packet(&mut self, packet: quinn_proto::Transmit) { - assert!(std::mem::replace(&mut self.outgoing_packet, Some(packet)).is_none()) + fn drive_events(&mut self, cx: &mut Context) { + while let Ready(e) = self.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { + match e { + EndpointMessage::ConnectionAccepted => { + debug!("accepting connection!"); + self.inner.accept(); + } + EndpointMessage::EndpointEvent { handle, event } => { + debug!("we have an event from connection {:?}", handle); + match self.muxers.get(&handle).and_then(|e| e.upgrade()) { + None => drop(self.muxers.remove(&handle)), + Some(connection) => match self.inner.handle_event(handle, event) { + None => connection.lock().process_endpoint_communication(self), + Some(event) => connection.lock().process_connection_events(self, event), + }, + } + } + } + } } fn drive_receive( @@ -101,10 +120,13 @@ impl EndpointInner { .process_connection_events(self, connection_event), None => debug!("lost our connection!"), } - ready!(self.poll_transmit_pending(socket, cx))? } - DatagramEvent::NewConnection(connection) => break Ready(Ok((handle, connection))), + DatagramEvent::NewConnection(connection) => { + debug!("new connection detected!"); + break Ready(Ok((handle, connection))); + } } + trace!("event processed!") } } @@ -112,14 +134,29 @@ impl EndpointInner { &mut self, socket: &UdpSocket, cx: &mut Context, - ) -> Poll> { + ) -> Result<(), io::Error> { + self.poll_transmit_pending_raw(socket, cx).map_err(|e| { + error!("I/O error: {:?}", e); + e + }) + } + + fn poll_transmit_pending_raw( + &mut self, + socket: &UdpSocket, + cx: &mut Context, + ) -> Result<(), io::Error> { if let Some(tx) = self.outgoing_packet.take() { - ready!(self.poll_transmit(socket, cx, tx))? + if self.poll_transmit(socket, cx, tx)? { + return Ok(()); + } } while let Some(tx) = self.inner.poll_transmit() { - ready!(self.poll_transmit(socket, cx, tx))? + if self.poll_transmit(socket, cx, tx)? { + break; + } } - Ready(Ok(())) + Ok(()) } fn poll_transmit( @@ -127,25 +164,20 @@ impl EndpointInner { socket: &UdpSocket, cx: &mut Context, packet: quinn_proto::Transmit, - ) -> Poll> { - match socket.poll_send_to(cx, &packet.contents, &packet.destination) { - Pending => { - if self.outgoing_packet.is_some() { - warn!("Discarding packet!") + ) -> Result { + loop { + break match socket.poll_send_to(cx, &packet.contents, &packet.destination) { + Pending => { + self.outgoing_packet = Some(packet); + Ok(true) } - self.outgoing_packet = Some(packet); - return Ready(Ok(())); - } - Ready(Ok(size)) => { - debug_assert_eq!(size, packet.contents.len()); - trace!( - "sent packet of length {} to {}", - packet.contents.len(), - packet.destination - ); - Ready(Ok(())) - } - Ready(Err(e)) => return Ready(Err(e)), + Ready(Ok(size)) => { + trace!("sent packet of length {} to {}", size, packet.destination); + Ok(false) + } + Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, + Ready(Err(e)) => Err(e), + }; } } } @@ -164,7 +196,7 @@ struct EndpointData { Option, io::Error>>>, >, /// Connections send their events to this - event_channel: mpsc::Sender, + event_channel: mpsc::Sender, /// The `Multiaddr` address: Multiaddr, /// The configuration @@ -190,10 +222,13 @@ pub struct Endpoint(Arc); impl Endpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { - self.0.inner.lock() + trace!("acquiring lock!"); + let q = self.0.inner.lock(); + trace!("lock acquired!"); + q } - pub(super) fn event_channel(&self) -> mpsc::Sender { + pub(super) fn event_channel(&self) -> mpsc::Sender { self.0.event_channel.clone() } @@ -220,9 +255,6 @@ impl Endpoint { let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (new_connections, receive_connections) = mpsc::unbounded(); let (event_channel, event_receiver) = mpsc::channel(0); - new_connections - .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) - .expect("we have a reference to the peer, so this will not fail; qed"); Ok(Self(Arc::new(EndpointData { socket, inner: Mutex::new(EndpointInner { @@ -234,6 +266,7 @@ impl Endpoint { driver: None, event_receiver, outgoing_packet: None, + bad_timer: futures_timer::Delay::new(Duration::new(1, 0)), }), address, receive_connections: Mutex::new(Some(receive_connections)), @@ -280,37 +313,27 @@ impl Endpoint { impl Future for Endpoint { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - use super::EndpointMessage; let this = self.get_mut(); - let mut inner = this.inner(); - 'a: loop { - ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; - while let Ready(e) = inner.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { - match e { - EndpointMessage::ConnectionAccepted => { - debug!("accepting connection!"); - inner.inner.accept(); - } - EndpointMessage::EndpointEvent { handle, event } => { - debug!("we have an event from connection {:?}", handle); - match inner.muxers.get(&handle).and_then(|e| e.upgrade()) { - None => drop(inner.muxers.remove(&handle)), - Some(connection) => match inner.inner.handle_event(handle, event) { - None => { - connection.lock().process_endpoint_communication(&mut inner) - } - Some(event) => connection - .lock() - .process_connection_events(&mut inner, event), - }, - } - } + loop { + let mut inner = this.inner(); + inner.drive_events(cx); + while inner.bad_timer.poll_unpin(cx).is_ready() { + inner.bad_timer = futures_timer::Delay::new(Duration::new(1, 0)); + } + match inner.drive_receive(&this.0.socket, cx) { + Pending => { + inner.poll_transmit_pending(&this.0.socket, cx)?; + break Pending; + } + Ready(Ok((handle, connection))) => { + this.accept_muxer(connection, handle, &mut *inner); + inner.poll_transmit_pending(&this.0.socket, cx)?; + } + Ready(Err(e)) => { + log::error!("I/O error: {:?}", e); + return Ready(Err(e)); } - ready!(inner.poll_transmit_pending(&this.0.socket, cx))?; } - let (handle, connection) = ready!(inner.drive_receive(&this.0.socket, cx))?; - this.accept_muxer(connection, handle, &mut *inner); - ready!(inner.poll_transmit_pending(&this.0.socket, cx))? } } } @@ -334,7 +357,11 @@ impl Transport for &Endpoint { .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); let mut inner = self.inner(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(self.clone())) + inner.driver = Some(async_std::task::spawn(self.clone())); + self.0 + .new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(addr))) + .expect("we have a reference to the peer, so this will not fail; qed"); } res } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9800b4cd3fd..8231e87c231 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -72,7 +72,7 @@ use futures::{ prelude::*, }; use libp2p_core::StreamMuxer; -use log::{debug, trace, warn}; +use log::{debug, error, trace}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ @@ -85,7 +85,7 @@ use std::{ Context, Poll::{self, Pending, Ready}, }, - time::Instant, + time::{Duration, Instant}, }; #[derive(Debug)] pub struct QuicSubstream { @@ -378,7 +378,10 @@ impl StreamMuxer for QuicMuxer { inner.readers.insert(substream.id(), cx.waker().clone()); Pending } - Err(ReadError::UnknownStream) => Ready(Ok(0)), + Err(ReadError::UnknownStream) => { + error!("you used a stream that was already closed!"); + Ready(Ok(0)) + } Err(ReadError::Reset(_)) => Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -516,12 +519,14 @@ pub struct Muxer { pending: Option, /// The timer being used by this connection timer: Option, + /// Bad delay used as a temporary hack + bad_timer: futures_timer::Delay, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver waker: Option, /// Channel for endpoint events - endpoint_channel: mpsc::Sender, + endpoint_channel: mpsc::UnboundedSender, /// Last timeout last_timeout: Option, /// Join handle for the driver @@ -608,6 +613,7 @@ impl Muxer { endpoint: endpoint, pending: None, timer: None, + bad_timer: futures_timer::Delay::new(Duration::new(1, 0)), close_reason: None, waker: None, driver: None, @@ -625,77 +631,71 @@ impl Muxer { } /// Call when I/O is done by the application. `bytes` is the number of bytes of I/O done. - fn on_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { - let now = Instant::now(); - let mut needs_polling = false; - while let Some(event) = self.connection.poll_endpoint_events() { - needs_polling = true; - self.endpoint_channel - .start_send(EndpointMessage::EndpointEvent { - handle: self.handle, - event, - }) - .expect( - "we checked in `pre_application_io` that this channel had space; \ - that is always called first, and \ - there is a lock preventing concurrency problems; qed", - ); - ready!(self.endpoint_channel.poll_ready(cx)) - .expect("we have a reference to the peer; qed"); - } - while let Some(transmit) = self.connection.poll_transmit(now) { - ready!(self.poll_transmit(cx, transmit))?; - needs_polling = true; + fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { + loop { + match self.endpoint_channel.poll_ready(cx) { + Pending => break, + Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), + Ready(Ok(())) => {} + } + if let Some(event) = self.connection.poll_endpoint_events() { + self.endpoint_channel + .start_send(EndpointMessage::EndpointEvent { + handle: self.handle, + event, + }) + .expect( + "we checked in `pre_application_io` that this channel had space; \ + that is always called first, and there is a lock preventing concurrency \ + problems; qed", + ) + } else { + break; + } } - Ready(Ok(needs_polling)) } - fn pre_application_io(&mut self, cx: &mut Context<'_>) -> Poll> { + fn pre_application_io( + &mut self, + now: Instant, + cx: &mut Context<'_>, + ) -> Result { if let Some(transmit) = self.pending.take() { - ready!(self.poll_transmit(cx, transmit))?; + if self.poll_transmit(cx, transmit)? { + return Ok(false); + } } - ready!(self.endpoint_channel.poll_ready(cx)).expect("we have a reference to the peer; qed"); - let mut keep_going = false; - while let Some(transmit) = self.connection.poll_transmit(Instant::now()) { - keep_going = true; - ready!(self.poll_transmit(cx, transmit))?; + let mut needs_timer_update = false; + while let Some(transmit) = self.connection.poll_transmit(now) { + needs_timer_update = true; + if self.poll_transmit(cx, transmit)? { + break; + } } - Ready(Ok(keep_going)) + Ok(needs_timer_update) } fn poll_transmit( &mut self, cx: &mut Context<'_>, transmit: quinn_proto::Transmit, - ) -> Poll> { - trace!( - "sending packet of length {} to {}", - transmit.contents.len(), - transmit.destination - ); - match self - .endpoint - .poll_send_to(cx, &transmit.contents, &transmit.destination) - { - Pending => { - if let Some(packet) = self.pending.take() { - warn!( - "dropping packet of length {} to {} on the floor!", - packet.contents.len(), - packet.destination - ); + ) -> Result { + loop { + let res = self + .endpoint + .poll_send_to(cx, &transmit.contents, &transmit.destination); + break match res { + Pending => { + self.pending = Some(transmit); + Ok(true) } - self.pending = Some(transmit); - Pending - } - r @ Ready(_) => { - trace!( - "sent packet of length {} to {}", - transmit.contents.len(), - transmit.destination - ); - r - } + Ready(Ok(size)) => { + trace!("sent packet of length {} to {}", size, transmit.destination); + Ok(false) + } + Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, + Ready(Err(e)) => Err(e), + }; } } @@ -740,14 +740,11 @@ impl Muxer { self.wake_driver(); assert!(self.connection.poll_endpoint_events().is_none()); assert!(self.connection.poll().is_none()); - if let Some(tx) = self.connection.poll_transmit(Instant::now()) { - endpoint.set_pending_packet(tx) - } } pub fn process_app_events(&mut self) { use quinn_proto::Event; - while let Some(event) = self.connection.poll() { + 'a: while let Some(event) = self.connection.poll() { match event { Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { panic!("we disabled incoming unidirectional streams and datagrams") @@ -775,7 +772,7 @@ impl Muxer { // no task to wake up continue; } - debug_assert!( + assert!( self.pending_stream.is_none(), "we cannot have both pending tasks and a pending stream; qed" ); @@ -783,7 +780,7 @@ impl Muxer { .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); while let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { - Ok(()) => break, + Ok(()) => continue 'a, Err(_) => {} } } @@ -849,6 +846,7 @@ impl ConnectionDriver { impl Future for ConnectionDriver { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + // cx.waker().wake_by_ref(); let this = self.get_mut(); debug!("being polled for timer!"); let mut inner = this.inner.lock(); @@ -857,9 +855,12 @@ impl Future for ConnectionDriver { loop { let mut needs_timer_update = false; needs_timer_update |= inner.drive_timer(cx, now); - needs_timer_update |= ready!(inner.pre_application_io(cx))?; - needs_timer_update |= ready!(inner.on_application_io(cx))?; + needs_timer_update |= inner.pre_application_io(now, cx)?; + inner.poll_endpoint_events(cx); inner.process_app_events(); + while inner.bad_timer.poll_unpin(cx).is_ready() { + inner.bad_timer = futures_timer::Delay::new(Duration::new(1, 0)) + } if inner.connection.is_drained() { break Ready( match inner diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index f7fa9e3114a..346121982a8 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -229,7 +229,7 @@ fn communicating_between_dialer_and_listener() { muxer: connection.clone(), shutdown: false, }; - log::warn!("have a new stream!"); + log::debug!("have a new stream!"); stream.write_all(&[4u8, 5, 6]).await.unwrap(); let mut buf = [0u8; 3]; log::debug!("reading data!"); From e684c1e8218573de58f1c2a0e391a02a4bc7e562 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 17 Jan 2020 18:48:15 -0500 Subject: [PATCH 059/202] Hopefully fix the hangs --- transports/quic/Cargo.toml | 4 + transports/quic/src/endpoint.rs | 8 +- transports/quic/src/lib.rs | 148 +++++++++++++++++++++++--------- transports/quic/src/tests.rs | 30 +++++-- 4 files changed, 137 insertions(+), 53 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 13c78f61188..e7aabef9e8e 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -44,3 +44,7 @@ protobuf = "2.8.1" yasna = { version = "0.3.1", features = ["num-bigint"] } ring = "0.16.9" webpki = "0.21.0" + +[dev-dependencies] +tracing-subscriber = "0.1.6" +tracing = "0.1.12" diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 69f0a48991e..36021718ceb 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -38,7 +38,7 @@ use std::{ Context, Poll::{self, Pending, Ready}, }, - time::{Duration, Instant}, + time::Instant, }; #[derive(Debug)] @@ -50,8 +50,6 @@ pub(super) struct EndpointInner { outgoing_packet: Option, /// Used to receive events from connections event_receiver: mpsc::Receiver, - /// Bad timer - bad_timer: futures_timer::Delay, } impl EndpointInner { @@ -266,7 +264,6 @@ impl Endpoint { driver: None, event_receiver, outgoing_packet: None, - bad_timer: futures_timer::Delay::new(Duration::new(1, 0)), }), address, receive_connections: Mutex::new(Some(receive_connections)), @@ -317,9 +314,6 @@ impl Future for Endpoint { loop { let mut inner = this.inner(); inner.drive_events(cx); - while inner.bad_timer.poll_unpin(cx).is_ready() { - inner.bad_timer = futures_timer::Delay::new(Duration::new(1, 0)); - } match inner.drive_receive(&this.0.socket, cx) { Pending => { inner.poll_transmit_pending(&this.0.socket, cx)?; diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 0202563603d..42459ffb17b 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -85,7 +85,7 @@ use std::{ Context, Poll::{self, Pending, Ready}, }, - time::{Duration, Instant}, + time::Instant, }; #[derive(Debug)] pub struct QuicSubstream { @@ -252,13 +252,7 @@ impl StreamMuxer for QuicMuxer { io::ErrorKind::ConnectionAborted, e.clone(), )))) - } else if let Some(id) = inner.pending_stream.take() { - // mandatory ― otherwise we will fail an assertion above - inner.wake_driver(); - Outbound(OutboundInner::Complete(Ok(id))) - } else if let Some(id) = inner.connection.open(Dir::Bi) { - // optimization: if we can complete synchronously, do so. - inner.wake_driver(); + } else if let Some(id) = inner.get_pending_stream() { Outbound(OutboundInner::Complete(Ok(id))) } else { let (sender, receiver) = oneshot::channel(); @@ -290,10 +284,14 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context) -> Poll> { debug!("being polled for inbound connections!"); let mut inner = self.inner(); - if let Some(ref e) = inner.close_reason { + if inner.connection.is_drained() { return Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, - e.clone(), + inner + .close_reason + .as_ref() + .expect("closed connections always have a reason; qed") + .clone(), ))); } inner.wake_driver(); @@ -304,7 +302,10 @@ impl StreamMuxer for QuicMuxer { } Pending } - Some(id) => Ready(Ok(QuicSubstream::new(id))), + Some(id) => { + inner.finishers.insert(id, None); + Ready(Ok(QuicSubstream::new(id))) + } } } @@ -367,10 +368,6 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::ReadError; let mut inner = self.inner(); inner.wake_driver(); - if inner.close_reason.is_some() { - // FIXME!!! - return Ready(Ok(0)); - } match inner.connection.read(substream.id(), buf) { Ok(Some(bytes)) => Ready(Ok(bytes)), Ok(None) => Ready(Ok(0)), @@ -408,6 +405,10 @@ impl StreamMuxer for QuicMuxer { .finish(substream.id()) .map_err(|e| match e { quinn_proto::FinishError::UnknownStream => { + io::Error::new( + io::ErrorKind::ConnectionAborted, + quinn_proto::FinishError::UnknownStream, + ); unreachable!("we checked for this above!") } quinn_proto::FinishError::Stopped { .. } => { @@ -420,7 +421,11 @@ impl StreamMuxer for QuicMuxer { "we haven’t written to the peer yet" ); substream.status = SubstreamStatus::Finishing(receiver); - assert!(inner.finishers.insert(substream.id(), sender).is_none()); + assert!(inner + .finishers + .insert(substream.id(), Some(sender)) + .unwrap() + .is_none()); Pending } @@ -437,9 +442,34 @@ impl StreamMuxer for QuicMuxer { } fn close(&self, cx: &mut Context) -> Poll> { + trace!("close() called"); let mut inner = self.inner(); - inner.shutdown(); - inner.driver().poll_unpin(cx) + if inner.connection.is_closed() { + return Ready(Ok(())); + } else if inner.finishers.is_empty() { + inner.shutdown(); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + drop(inner.driver().poll_unpin(cx)); + return Ready(Ok(())); + } else if inner.close_waker.is_some() { + inner.close_waker = Some(cx.waker().clone()); + return Pending; + } else { + inner.close_waker = Some(cx.waker().clone()) + } + let Muxer { + ref mut finishers, + ref mut connection, + .. + } = *inner; + finishers.retain(|id, channel| { + if channel.is_some() { + true + } else { + connection.finish(*id).is_ok() + } + }); + Pending } } @@ -466,10 +496,17 @@ impl Future for QuicUpgrade { trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if let Some(ref e) = inner.close_reason { - Err(io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())) + if inner.connection.is_closed() { + Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + inner + .close_reason + .as_ref() + .expect("closed connections always have a reason; qed") + .clone(), + )) } else if inner.connection.is_handshaking() { - assert!(!inner.connection.is_drained(), "deadlock"); + assert!(!inner.connection.is_closed(), "deadlock"); inner.handshake_waker = Some(cx.waker().clone()); return Pending; } else if inner.connection.side().is_server() { @@ -508,7 +545,7 @@ pub struct Muxer { /// Tasks blocked on reading readers: HashMap, /// Tasks blocked on finishing - finishers: HashMap>, + finishers: HashMap>>, /// Task waiting for new connections handshake_waker: Option, /// Task waiting for new connections @@ -519,8 +556,6 @@ pub struct Muxer { pending: Option, /// The timer being used by this connection timer: Option, - /// Bad delay used as a temporary hack - bad_timer: futures_timer::Delay, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver @@ -531,6 +566,8 @@ pub struct Muxer { last_timeout: Option, /// Join handle for the driver driver: Option>>, + /// Close waker + close_waker: Option, } impl Drop for Muxer { @@ -599,6 +636,7 @@ impl Muxer { fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { + close_waker: None, last_timeout: None, pending_stream: None, connection, @@ -613,7 +651,6 @@ impl Muxer { endpoint: endpoint, pending: None, timer: None, - bad_timer: futures_timer::Delay::new(Duration::new(1, 0)), close_reason: None, waker: None, driver: None, @@ -630,11 +667,12 @@ impl Muxer { } } - /// Call when I/O is done by the application. `bytes` is the number of bytes of I/O done. - fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { + /// Send endpoint events. Returns true if and only if there are endpoint events remaining to + /// be sent. + fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { loop { match self.endpoint_channel.poll_ready(cx) { - Pending => break, + Pending => break true, Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), Ready(Ok(())) => {} } @@ -650,7 +688,7 @@ impl Muxer { problems; qed", ) } else { - break; + break false; } } } @@ -700,6 +738,7 @@ impl Muxer { } fn shutdown(&mut self) { + debug!("shutting connection down!"); if let Some(w) = self.accept_waker.take() { w.wake() } @@ -712,12 +751,11 @@ impl Muxer { for (_, v) in self.readers.drain() { v.wake(); } - for (_, sender) in self.finishers.drain() { + for sender in self.finishers.drain().filter_map(|x| x.1) { drop(sender.send(())) } self.connectors.truncate(0); - self.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - if !self.connection.is_drained() { + if !self.connection.is_closed() { self.connection .close(Instant::now(), Default::default(), Default::default()); self.process_app_events(); @@ -732,7 +770,7 @@ impl Muxer { } fn process_endpoint_communication(&mut self, endpoint: &mut EndpointInner) { - if self.close_reason.is_some() { + if self.connection.is_drained() { return; } self.send_to_endpoint(endpoint); @@ -742,9 +780,24 @@ impl Muxer { assert!(self.connection.poll().is_none()); } - pub fn process_app_events(&mut self) { + fn get_pending_stream(&mut self) -> Option { + self.wake_driver(); + if let Some(id) = self.pending_stream.take() { + Some(id) + } else { + self.connection.open(Dir::Bi) + } + .map(|id| { + self.finishers.insert(id, None); + id + }) + } + + pub fn process_app_events(&mut self) -> bool { use quinn_proto::Event; + let mut keep_going = false; 'a: while let Some(event) = self.connection.poll() { + keep_going = true; match event { Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { panic!("we disabled incoming unidirectional streams and datagrams") @@ -788,7 +841,9 @@ impl Muxer { } Event::ConnectionLost { reason } => { debug!("lost connection due to {:?}", reason); + assert!(self.connection.is_closed()); self.close_reason = Some(reason); + self.close_waker.take().map(|e| e.wake()); self.shutdown(); } Event::StreamFinished { stream, .. } => { @@ -797,9 +852,15 @@ impl Muxer { if let Some((_, waker)) = self.writers.remove_entry(&stream) { waker.wake() } - if let Some((_, sender)) = self.finishers.remove_entry(&stream) { + if let (_, Some(sender)) = self.finishers.remove_entry(&stream).expect( + "every write stream is placed in this map, and entries are removed \ + exactly once; qed", + ) { drop(sender.send(())) } + if self.finishers.is_empty() { + self.close_waker.take().map(|e| e.wake()); + } } Event::Connected => { debug!("connected!"); @@ -816,6 +877,7 @@ impl Muxer { } } } + keep_going } } @@ -856,11 +918,8 @@ impl Future for ConnectionDriver { let mut needs_timer_update = false; needs_timer_update |= inner.drive_timer(cx, now); needs_timer_update |= inner.pre_application_io(now, cx)?; - inner.poll_endpoint_events(cx); - inner.process_app_events(); - while inner.bad_timer.poll_unpin(cx).is_ready() { - inner.bad_timer = futures_timer::Delay::new(Duration::new(1, 0)) - } + needs_timer_update |= inner.process_app_events(); + let needs_to_send_endpoint_events = inner.poll_endpoint_events(cx); if inner.connection.is_drained() { break Ready( match inner @@ -893,7 +952,14 @@ impl Future for ConnectionDriver { }, ); } else if !needs_timer_update { - break Pending; + break if inner.close_reason.is_none() { + Pending + } else if needs_to_send_endpoint_events { + debug!("still have endpoint events to send!"); + Pending + } else { + Ready(Ok(())) + }; } } } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 346121982a8..99ffacbfbf8 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -47,12 +47,13 @@ impl AsyncWrite for QuicStream { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - debug!("trying to close"); self.shutdown = true; let inner = self.get_mut(); + debug!("trying to close {:?}", inner.id); ready!(inner .muxer .shutdown_substream(cx, inner.id.as_mut().unwrap()))?; + debug!("closed {:?}", inner.id); Ready(Ok(())) } @@ -96,7 +97,11 @@ impl futures::Stream for QuicMuxer { } pub(crate) fn init() { - drop(env_logger::try_init()); + use tracing_subscriber::{fmt::Subscriber, EnvFilter}; + Subscriber::builder() + .with_env_filter(EnvFilter::try_from_default_env().expect("test suite is run incorrectly")) + .try_init() + .expect("error") } impl Future for QuicMuxer { @@ -192,7 +197,12 @@ fn communicating_between_dialer_and_listener() { let mut buf = [0u8; 3]; log::debug!("reading data from accepted stream!"); - socket.read_exact(&mut buf).await.unwrap(); + { + let mut count = 0; + while count < buf.len() { + count += socket.read(&mut buf[count..]).await.unwrap(); + } + } assert_eq!(buf, [4, 5, 6]); log::debug!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); @@ -202,6 +212,7 @@ fn communicating_between_dialer_and_listener() { drop(socket); log::debug!("end of stream"); muxer.await.unwrap(); + log::debug!("finished!"); break; } _ => unreachable!(), @@ -231,15 +242,24 @@ fn communicating_between_dialer_and_listener() { }; log::debug!("have a new stream!"); stream.write_all(&[4u8, 5, 6]).await.unwrap(); + stream.close().await.unwrap(); let mut buf = [0u8; 3]; log::debug!("reading data!"); - stream.read_exact(&mut buf).await.unwrap(); + { + let mut count = 0; + while count < buf.len() { + let read = stream.read(&mut buf[count..]).await.unwrap(); + assert_ne!(read, 0usize); + count += read; + } + } assert_eq!(buf, [1u8, 2, 3]); log::debug!("data read!"); - stream.close().await.unwrap(); log::debug!("checking for EOF!"); assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); + log::debug!("have EOF!"); + connection.await; log::debug!("awaiting handle!"); }); async_std::task::block_on(_handle); From 522cb336847a42869414100dd217c0cc06809257 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 18 Jan 2020 17:59:13 -0500 Subject: [PATCH 060/202] Fix the hangs for real This version has, unless I made a huge mistake, run over 30,000 times in a row without hanging. --- transports/quic/src/endpoint.rs | 201 ++++++++++++++++---------------- transports/quic/src/lib.rs | 73 ++++++++---- transports/quic/src/socket.rs | 109 +++++++++++++++++ transports/quic/src/tests.rs | 17 +-- 4 files changed, 270 insertions(+), 130 deletions(-) create mode 100644 transports/quic/src/socket.rs diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 36021718ceb..675eb7d50de 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -17,6 +17,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +// use crate::{EndpointMessage, QuicConfig}; use async_macros::ready; use async_std::net::{SocketAddr, UdpSocket}; @@ -26,7 +27,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, error, trace, warn}; +use log::{debug, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionHandle}; use std::{ @@ -46,10 +47,10 @@ pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, driver: Option>>, - /// Pending packet - outgoing_packet: Option, + socket: crate::socket::Socket, /// Used to receive events from connections event_receiver: mpsc::Receiver, + refcount: u64, } impl EndpointInner { @@ -59,9 +60,14 @@ impl EndpointInner { event: quinn_proto::EndpointEvent, ) -> Option { if event.is_drained() { - self.muxers.remove_entry(&handle); + let res = self.inner.handle_event(handle, event); + self.muxers + .remove_entry(&handle) + .expect("cannot close a connection that did not exist"); + res + } else { + self.inner.handle_event(handle, event) } - self.inner.handle_event(handle, event) } fn drive_events(&mut self, cx: &mut Context) { @@ -92,10 +98,8 @@ impl EndpointInner { ) -> Poll> { use quinn_proto::DatagramEvent; let mut buf = vec![0; 65535]; - trace!("endpoint polling for incoming packets!"); loop { let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; - trace!("got a packet of length {} from {}!", bytes, peer); let (handle, event) = match self .inner @@ -107,12 +111,7 @@ impl EndpointInner { trace!("have an event!"); match event { DatagramEvent::ConnectionEvent(connection_event) => { - match self - .muxers - .get(&handle) - .expect("received a ConnectionEvent for an unknown Connection") - .upgrade() - { + match self.muxers.get(&handle).and_then(|e| e.upgrade()) { Some(connection) => connection .lock() .process_connection_events(self, connection_event), @@ -132,56 +131,14 @@ impl EndpointInner { &mut self, socket: &UdpSocket, cx: &mut Context, - ) -> Result<(), io::Error> { - self.poll_transmit_pending_raw(socket, cx).map_err(|e| { - error!("I/O error: {:?}", e); - e - }) - } - - fn poll_transmit_pending_raw( - &mut self, - socket: &UdpSocket, - cx: &mut Context, - ) -> Result<(), io::Error> { - if let Some(tx) = self.outgoing_packet.take() { - if self.poll_transmit(socket, cx, tx)? { - return Ok(()); - } - } - while let Some(tx) = self.inner.poll_transmit() { - if self.poll_transmit(socket, cx, tx)? { - break; - } - } - Ok(()) - } - - fn poll_transmit( - &mut self, - socket: &UdpSocket, - cx: &mut Context, - packet: quinn_proto::Transmit, - ) -> Result { - loop { - break match socket.poll_send_to(cx, &packet.contents, &packet.destination) { - Pending => { - self.outgoing_packet = Some(packet); - Ok(true) - } - Ready(Ok(size)) => { - trace!("sent packet of length {} to {}", size, packet.destination); - Ok(false) - } - Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, - Ready(Err(e)) => Err(e), - }; - } + ) -> Poll> { + self.socket + .send_packet(cx, socket, &mut self.inner, Instant::now()) } } #[derive(Debug)] -struct EndpointData { +pub(super) struct EndpointData { /// The single UDP socket used for I/O socket: UdpSocket, /// A `Mutex` protecting the QUIC state machine. @@ -201,6 +158,16 @@ struct EndpointData { config: QuicConfig, } +impl EndpointData { + pub(super) fn event_channel(&self) -> mpsc::Sender { + self.event_channel.clone() + } + + pub(super) fn socket(&self) -> &UdpSocket { + &self.socket + } +} + /// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// /// You generally need only one of these per process. Endpoints are `Send` and `Sync`, so you @@ -218,6 +185,23 @@ struct EndpointData { #[derive(Debug, Clone)] pub struct Endpoint(Arc); +struct EndpointDriver(Arc); + +impl Drop for Endpoint { + fn drop(&mut self) { + let mut inner = self.inner(); + let decrement = if self.0.receive_connections.lock().is_some() { + 2 + } else { + 1 + }; + inner.refcount = inner + .refcount + .checked_sub(decrement) + .expect("we do accurate reference counting; qed"); + } +} + impl Endpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { trace!("acquiring lock!"); @@ -226,19 +210,6 @@ impl Endpoint { q } - pub(super) fn event_channel(&self) -> mpsc::Sender { - self.0.event_channel.clone() - } - - pub(super) fn poll_send_to( - &self, - cx: &mut Context, - transmit: &[u8], - destination: &SocketAddr, - ) -> Poll> { - self.0.socket.poll_send_to(cx, transmit, destination) - } - /// Construct a `Endpoint` with the given `QuicConfig` and `Multiaddr`. pub fn new( config: QuicConfig, @@ -253,7 +224,7 @@ impl Endpoint { let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); let (new_connections, receive_connections) = mpsc::unbounded(); let (event_channel, event_receiver) = mpsc::channel(0); - Ok(Self(Arc::new(EndpointData { + let return_value = Self(Arc::new(EndpointData { socket, inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( @@ -261,67 +232,95 @@ impl Endpoint { Some(config.server_config.clone()), ), muxers: HashMap::new(), + socket: Default::default(), driver: None, event_receiver, - outgoing_packet: None, + refcount: 2, }), - address, + address: address.clone(), receive_connections: Mutex::new(Some(receive_connections)), new_connections, event_channel, config, - }))) + })); + return_value.inner().driver = Some(async_std::task::spawn(EndpointDriver( + return_value.0.clone(), + ))); + return_value + .0 + .new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address))) + .expect("we have a reference to the peer, so this will not fail; qed"); + Ok(return_value) } +} - fn create_muxer( - &self, - connection: Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, - ) -> super::QuicMuxer { - let (driver, muxer) = - super::ConnectionDriver::new(super::Muxer::new(self.clone(), connection, handle)); - inner.muxers.insert(handle, Arc::downgrade(&muxer)); - (*muxer).lock().driver = Some(async_std::task::spawn(driver)); - super::QuicMuxer(muxer) - } +fn create_muxer( + endpoint: Arc, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, +) -> super::QuicMuxer { + let (driver, muxer) = + super::ConnectionDriver::new(super::Muxer::new(endpoint, connection, handle)); + inner.muxers.insert(handle, Arc::downgrade(&muxer)); + (*muxer).lock().driver = Some(async_std::task::spawn(driver)); + super::QuicMuxer(muxer) +} +impl EndpointDriver { fn accept_muxer( &self, connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, ) { - let muxer = self.create_muxer(connection, handle, &mut *inner); - self.0 + let muxer = create_muxer(self.0.clone(), connection, handle, &mut *inner); + if self + .0 .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { upgrade: super::QuicUpgrade { muxer: Some(muxer) }, local_addr: self.0.address.clone(), remote_addr: self.0.address.clone(), })) - .expect( - "this is an unbounded channel, and we have an instance \ - of the peer, so this will never fail; qed", - ); + .is_err() + { + inner.inner.accept(); + inner.inner.reject_new_connections(); + inner.refcount = inner + .refcount + .checked_sub(1) + .expect("attempt to decrement below zero"); + } } } -impl Future for Endpoint { +impl Future for EndpointDriver { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.get_mut(); loop { - let mut inner = this.inner(); + let mut inner = this.0.inner.lock(); + trace!("driving events"); inner.drive_events(cx); + trace!("driving incoming packets"); match inner.drive_receive(&this.0.socket, cx) { Pending => { - inner.poll_transmit_pending(&this.0.socket, cx)?; + debug!("no new connections"); + drop(inner.poll_transmit_pending(&this.0.socket, cx)?); + trace!("returning Pending"); break Pending; } Ready(Ok((handle, connection))) => { + trace!("have a new connection"); this.accept_muxer(connection, handle, &mut *inner); - inner.poll_transmit_pending(&this.0.socket, cx)?; + trace!("connection accepted"); + match inner.poll_transmit_pending(&this.0.socket, cx)? { + Pending => break Pending, + Ready(()) if inner.refcount == 0 => break Ready(Ok(())), + Ready(()) => break Pending, + } } Ready(Err(e)) => { log::error!("I/O error: {:?}", e); @@ -351,7 +350,7 @@ impl Transport for &Endpoint { .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); let mut inner = self.inner(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(self.clone())); + inner.driver = Some(async_std::task::spawn(EndpointDriver(self.0.clone()))); self.0 .new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(addr))) @@ -374,7 +373,7 @@ impl Transport for &Endpoint { }; let mut inner = self.inner(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(self.clone())) + inner.driver = Some(async_std::task::spawn(EndpointDriver(self.0.clone()))) } let s: Result<(_, Connection), _> = inner @@ -389,7 +388,7 @@ impl Transport for &Endpoint { TransportError::Other(io::ErrorKind::InvalidInput.into()) }); let (handle, conn) = s?; - let muxer = self.create_muxer(conn, handle, &mut inner); + let muxer = create_muxer(self.0.clone(), conn, handle, &mut inner); Ok(super::QuicUpgrade { muxer: Some(muxer) }) } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 42459ffb17b..48b685bcdb3 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -60,6 +60,7 @@ #![deny(trivial_casts)] mod certificate; mod endpoint; +mod socket; #[cfg(test)] mod tests; mod verifier; @@ -139,6 +140,11 @@ fn make_client_config( let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); + use std::time::Duration; + // transport + // .idle_timeout(None) + // .expect("None is a valid timeout"); + transport.keep_alive_interval(Some(Duration::new(0, 100))); let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; @@ -320,6 +326,10 @@ impl StreamMuxer for QuicMuxer { return Ready(Err(io::ErrorKind::ConnectionAborted.into())); } let mut inner = self.inner(); + debug_assert!( + inner.finishers.get(&substream.id()).is_some(), + "no entry in finishers map for write stream" + ); inner.wake_driver(); if let Some(ref e) = inner.close_reason { return Ready(Err(io::Error::new( @@ -371,6 +381,10 @@ impl StreamMuxer for QuicMuxer { match inner.connection.read(substream.id(), buf) { Ok(Some(bytes)) => Ready(Ok(bytes)), Ok(None) => Ready(Ok(0)), + Err(ReadError::Blocked) if inner.connection_lost => { + // Ready(Err(io::ErrorKind::ConnectionReset.into())) + Ready(Ok(0)) + } Err(ReadError::Blocked) => { inner.readers.insert(substream.id(), cx.waker().clone()); Pending @@ -446,6 +460,8 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); if inner.connection.is_closed() { return Ready(Ok(())); + } else if inner.close_reason.is_some() { + return Ready(Ok(())); } else if inner.finishers.is_empty() { inner.shutdown(); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); @@ -466,7 +482,8 @@ impl StreamMuxer for QuicMuxer { if channel.is_some() { true } else { - connection.finish(*id).is_ok() + drop(connection.finish(*id)); + true } }); Pending @@ -535,7 +552,7 @@ pub struct Muxer { /// The pending stream, if any. pending_stream: Option, /// The associated endpoint - endpoint: Endpoint, + endpoint: Arc, /// The `quinn_proto::Connection` struct. connection: Connection, /// Connection handle @@ -568,11 +585,15 @@ pub struct Muxer { driver: Option>>, /// Close waker close_waker: Option, + /// Have we gotten a connection lost event? + connection_lost: bool, } impl Drop for Muxer { fn drop(&mut self) { - self.shutdown() + if self.close_reason.is_none() { + self.shutdown() + } } } @@ -604,7 +625,7 @@ impl Muxer { fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { let mut keep_going = false; - loop { + 'outer: loop { match self.connection.poll_timeout() { None => { self.timer = None; @@ -621,21 +642,26 @@ impl Muxer { self.timer = Some(futures_timer::Delay::new(delay)) } } + if let Some(ref mut timer) = self.timer { + if timer.poll_unpin(cx).is_ready() { + self.connection.handle_timeout(now); + keep_going = true; + continue 'outer; + } + } break; } - while let Some(ref mut timer) = self.timer { - if timer.poll_unpin(cx).is_pending() { - break; - } - self.connection.handle_timeout(now); - keep_going = true - } keep_going } - fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { + fn new( + endpoint: Arc, + connection: Connection, + handle: ConnectionHandle, + ) -> Self { Muxer { + connection_lost: false, close_waker: None, last_timeout: None, pending_stream: None, @@ -698,17 +724,22 @@ impl Muxer { now: Instant, cx: &mut Context<'_>, ) -> Result { + let mut needs_timer_update = false; if let Some(transmit) = self.pending.take() { + trace!("trying to send packet!"); + needs_timer_update = true; if self.poll_transmit(cx, transmit)? { return Ok(false); } + trace!("packet sent!"); } - let mut needs_timer_update = false; while let Some(transmit) = self.connection.poll_transmit(now) { + trace!("trying to send packet!"); needs_timer_update = true; if self.poll_transmit(cx, transmit)? { break; } + trace!("packet sent!"); } Ok(needs_timer_update) } @@ -719,18 +750,16 @@ impl Muxer { transmit: quinn_proto::Transmit, ) -> Result { loop { - let res = self - .endpoint - .poll_send_to(cx, &transmit.contents, &transmit.destination); + let res = + self.endpoint + .socket() + .poll_send_to(cx, &transmit.contents, &transmit.destination); break match res { Pending => { self.pending = Some(transmit); Ok(true) } - Ready(Ok(size)) => { - trace!("sent packet of length {} to {}", size, transmit.destination); - Ok(false) - } + Ready(Ok(_)) => Ok(false), Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, Ready(Err(e)) => Err(e), }; @@ -843,6 +872,7 @@ impl Muxer { debug!("lost connection due to {:?}", reason); assert!(self.connection.is_closed()); self.close_reason = Some(reason); + self.connection_lost = true; self.close_waker.take().map(|e| e.wake()); self.shutdown(); } @@ -884,7 +914,7 @@ impl Muxer { #[derive(Debug)] struct ConnectionDriver { inner: Arc>, - endpoint: Endpoint, + endpoint: Arc, outgoing_packet: Option, } @@ -952,6 +982,7 @@ impl Future for ConnectionDriver { }, ); } else if !needs_timer_update { + assert!(inner.connection.poll_transmit(now).is_none()); break if inner.close_reason.is_none() { Pending } else if needs_to_send_endpoint_events { diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs new file mode 100644 index 00000000000..db010012a25 --- /dev/null +++ b/transports/quic/src/socket.rs @@ -0,0 +1,109 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! I/O for libp2p-quic. +//! +//! This provides a central location for socket I/O, and logs all incoming and outgoing packets. + +use async_std::net::UdpSocket; +use log::{debug, trace}; +use quinn_proto::{Connection, Endpoint, Transmit}; +use std::{io::Result, task::Context, task::Poll, time::Instant}; + +mod private { + use super::Instant; + pub(crate) struct Dummy; + /// A trait for packet generators + pub(crate) trait PacketGen { + /// Get the next packet to be sent, if any. + fn get_packet(&mut self, dummy: Dummy, now: Instant) -> Option; + } +} + +use private::Dummy; +pub(crate) use private::PacketGen; + +impl PacketGen for Endpoint { + fn get_packet(&mut self, Dummy: Dummy, _now: Instant) -> Option { + self.poll_transmit() + } +} + +impl PacketGen for Connection { + fn get_packet(&mut self, Dummy: Dummy, now: Instant) -> Option { + self.poll_transmit(now) + } +} + +/// A socket wrapper for libp2p-quic +#[derive(Debug, Default)] +pub(crate) struct Socket { + pending: Option, +} + +impl Socket { + pub fn send_packet( + &mut self, + cx: &mut Context, + socket: &UdpSocket, + source: &mut dyn PacketGen, + now: Instant, + ) -> Poll> { + if let Some(ref mut transmit) = self.pending { + trace!("trying to send packet!"); + match socket.poll_send_to(cx, &transmit.contents, &transmit.destination) { + Poll::Pending => { + debug!("not able to send packet right away"); + return Poll::Pending; + } + Poll::Ready(Ok(_)) => {} + // FIXME is this even possible? + Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => {} + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + trace!( + "sent packet of length {} to {}", + transmit.contents.len(), + transmit.destination + ); + } + self.pending = None; + while let Some(transmit) = source.get_packet(Dummy, now) { + trace!("trying to send packet!"); + match socket.poll_send_to(cx, &transmit.contents, &transmit.destination) { + Poll::Pending => debug!("not able to send packet right away"), + Poll::Ready(Ok(_)) => { + trace!( + "sent packet of length {} to {}", + transmit.contents.len(), + transmit.destination + ); + continue; + } + // FIXME is this even possible? + Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => continue, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + self.pending = Some(transmit); + return Poll::Pending; + } + Poll::Ready(Ok(())) + } +} diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 99ffacbfbf8..956dd6309c6 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -72,7 +72,6 @@ impl AsyncRead for QuicStream { inner .muxer .read_substream(cx, inner.id.as_mut().unwrap(), buf) - .map_err(|e| panic!("unexpected error {:?}", e)) } } @@ -98,10 +97,11 @@ impl futures::Stream for QuicMuxer { pub(crate) fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; - Subscriber::builder() - .with_env_filter(EnvFilter::try_from_default_env().expect("test suite is run incorrectly")) - .try_init() - .expect("error") + drop( + Subscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(), + ) } impl Future for QuicMuxer { @@ -208,9 +208,10 @@ fn communicating_between_dialer_and_listener() { socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); log::debug!("data written!"); socket.close().await.unwrap(); + log::debug!("socket closed!"); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); - drop(socket); log::debug!("end of stream"); + drop(socket); muxer.await.unwrap(); log::debug!("finished!"); break; @@ -258,8 +259,8 @@ fn communicating_between_dialer_and_listener() { log::debug!("checking for EOF!"); assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); - log::debug!("have EOF!"); - connection.await; + log::debug!("have EOF!"); + connection.await.expect("closed successfully"); log::debug!("awaiting handle!"); }); async_std::task::block_on(_handle); From a5a4cd0ff1adbfa278b6c94eee566ddbd5f98d8a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 18 Jan 2020 18:22:18 -0500 Subject: [PATCH 061/202] Fix wildcard_expansion test case --- transports/quic/src/endpoint.rs | 4 --- transports/quic/src/tests.rs | 46 ++++++++++++++------------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 675eb7d50de..89476cc530c 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -351,10 +351,6 @@ impl Transport for &Endpoint { let mut inner = self.inner(); if inner.driver.is_none() { inner.driver = Some(async_std::task::spawn(EndpointDriver(self.0.clone()))); - self.0 - .new_connections - .unbounded_send(Ok(ListenerEvent::NewAddress(addr))) - .expect("we have a reference to the peer, so this will not fail; qed"); } res } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 956dd6309c6..0792c11947f 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -115,40 +115,32 @@ impl Future for QuicMuxer { fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let listener = Endpoint::new(QuicConfig::default(), addr.clone()) + let mut listener = Endpoint::new(QuicConfig::default(), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); - let addr: Multiaddr = "/ip4/127.0.0.1/udp/1236/quic".parse().unwrap(); - let client = Endpoint::new(QuicConfig::default(), addr.clone()) - .expect("endpoint") - .dial(addr) - .expect("dialer"); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. - let server = listener - .take_while(|event| match event.as_ref().unwrap() { - ListenerEvent::NewAddress(a) => { - let mut iter = a.iter(); - match iter.next().expect("ip address") { - Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), - Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), - other => panic!("Unexpected protocol: {}", other), - } - if let Protocol::Udp(port) = iter.next().expect("port") { - assert_ne!(0, port) - } else { - panic!("No UDP port in address: {}", a) - } - futures::future::ready(true) + match futures::executor::block_on(listener.next()) + .unwrap() + .unwrap() + { + ListenerEvent::NewAddress(a) => { + let mut iter = a.iter(); + match iter.next().expect("ip address") { + Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), + Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), + other => panic!("Unexpected protocol: {}", other), } - _ => futures::future::ready(false), - }) - .for_each(|_| futures::future::ready(())); - - async_std::task::spawn(server); - futures::executor::block_on(client).unwrap(); + if let Protocol::Udp(port) = iter.next().expect("port") { + assert_ne!(0, port) + } else { + panic!("No UDP port in address: {}", a) + } + } + _ => panic!("NewAddress is the first event"), + } } #[test] From 6ab6114f2b86d47b34e58b9af0c959ed91410590 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 18 Jan 2020 21:04:22 -0500 Subject: [PATCH 062/202] =?UTF-8?q?Don=E2=80=99t=20use=20keep=20alives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They caused rare hangs that consumed 100% CPU. This version has run for over 528,000 successful runs. --- transports/quic/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 48b685bcdb3..6338c641d09 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -140,11 +140,11 @@ fn make_client_config( let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); - use std::time::Duration; + // use std::time::Duration; // transport // .idle_timeout(None) // .expect("None is a valid timeout"); - transport.keep_alive_interval(Some(Duration::new(0, 100))); + // transport.keep_alive_interval(Some(Duration::new(0, 100))); let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; From 7d39812a4dd6bf6f2e6c01c3b1ffbe28c019e511 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 22 Jan 2020 13:12:30 -0500 Subject: [PATCH 063/202] Add a keep-alive interval to prevent spurious timeouts --- transports/quic/src/lib.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 6338c641d09..188015ef3e8 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -140,11 +140,8 @@ fn make_client_config( let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); - // use std::time::Duration; - // transport - // .idle_timeout(None) - // .expect("None is a valid timeout"); - // transport.keep_alive_interval(Some(Duration::new(0, 100))); + use std::time::Duration; + transport.keep_alive_interval(Some(Duration::from_millis(1000))); let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; From d7bbbe1a9222db0aaa01bbf02ec2b8e6762a6cab Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 22 Jan 2020 15:09:27 -0500 Subject: [PATCH 064/202] =?UTF-8?q?Don=E2=80=99t=20use=20Poll::{self,=20Pe?= =?UTF-8?q?nding,=20Ready}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- transports/quic/src/endpoint.rs | 25 +++++----- transports/quic/src/lib.rs | 87 ++++++++++++++++----------------- transports/quic/src/tests.rs | 6 +-- 3 files changed, 57 insertions(+), 61 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 89476cc530c..d025ee5dfad 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -35,10 +35,7 @@ use std::{ io, pin::Pin, sync::{Arc, Weak}, - task::{ - Context, - Poll::{self, Pending, Ready}, - }, + task::{Context, Poll}, time::Instant, }; @@ -71,7 +68,7 @@ impl EndpointInner { } fn drive_events(&mut self, cx: &mut Context) { - while let Ready(e) = self.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { + while let Poll::Ready(e) = self.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { match e { EndpointMessage::ConnectionAccepted => { debug!("accepting connection!"); @@ -120,7 +117,7 @@ impl EndpointInner { } DatagramEvent::NewConnection(connection) => { debug!("new connection detected!"); - break Ready(Ok((handle, connection))); + break Poll::Ready(Ok((handle, connection))); } } trace!("event processed!") @@ -306,25 +303,25 @@ impl Future for EndpointDriver { inner.drive_events(cx); trace!("driving incoming packets"); match inner.drive_receive(&this.0.socket, cx) { - Pending => { + Poll::Pending => { debug!("no new connections"); drop(inner.poll_transmit_pending(&this.0.socket, cx)?); trace!("returning Pending"); - break Pending; + break Poll::Pending; } - Ready(Ok((handle, connection))) => { + Poll::Ready(Ok((handle, connection))) => { trace!("have a new connection"); this.accept_muxer(connection, handle, &mut *inner); trace!("connection accepted"); match inner.poll_transmit_pending(&this.0.socket, cx)? { - Pending => break Pending, - Ready(()) if inner.refcount == 0 => break Ready(Ok(())), - Ready(()) => break Pending, + Poll::Pending => break Poll::Pending, + Poll::Ready(()) if inner.refcount == 0 => break Poll::Ready(Ok(())), + Poll::Ready(()) => break Poll::Pending, } } - Ready(Err(e)) => { + Poll::Ready(Err(e)) => { log::error!("I/O error: {:?}", e); - return Ready(Err(e)); + return Poll::Ready(Err(e)); } } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 188015ef3e8..dd054acb1fe 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -82,10 +82,7 @@ use std::{ mem::replace, pin::Pin, sync::Arc, - task::{ - Context, - Poll::{self, Pending, Ready}, - }, + task::{Context, Poll}, time::Instant, }; #[derive(Debug)] @@ -229,7 +226,7 @@ impl Future for Outbound { let this = &mut *self; match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Ready(e.map(QuicSubstream::new)), + OutboundInner::Complete(e) => Poll::Ready(e.map(QuicSubstream::new)), _ => unreachable!(), }, OutboundInner::Pending(ref mut receiver) => { @@ -237,7 +234,7 @@ impl Future for Outbound { .map(QuicSubstream::new) .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); this.0 = OutboundInner::Done; - Ready(result) + Poll::Ready(result) } OutboundInner::Done => panic!("polled after yielding Ready"), } @@ -288,7 +285,7 @@ impl StreamMuxer for QuicMuxer { debug!("being polled for inbound connections!"); let mut inner = self.inner(); if inner.connection.is_drained() { - return Ready(Err(io::Error::new( + return Poll::Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, inner .close_reason @@ -303,11 +300,11 @@ impl StreamMuxer for QuicMuxer { if let Some(waker) = replace(&mut inner.accept_waker, Some(cx.waker().clone())) { waker.wake() } - Pending + Poll::Pending } Some(id) => { inner.finishers.insert(id, None); - Ready(Ok(QuicSubstream::new(id))) + Poll::Ready(Ok(QuicSubstream::new(id))) } } } @@ -320,7 +317,7 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::WriteError; if !substream.is_live() { - return Ready(Err(io::ErrorKind::ConnectionAborted.into())); + return Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())); } let mut inner = self.inner(); debug_assert!( @@ -329,7 +326,7 @@ impl StreamMuxer for QuicMuxer { ); inner.wake_driver(); if let Some(ref e) = inner.close_reason { - return Ready(Err(io::Error::new( + return Poll::Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); @@ -340,21 +337,23 @@ impl StreamMuxer for QuicMuxer { "attempting to write to a drained connection" ); match inner.connection.write(substream.id(), buf) { - Ok(bytes) => Ready(Ok(bytes)), + Ok(bytes) => Poll::Ready(Ok(bytes)), Err(WriteError::Blocked) => { if let Some(ref e) = inner.close_reason { - return Ready(Err(io::Error::new( + return Poll::Ready(Err(io::Error::new( io::ErrorKind::ConnectionAborted, e.clone(), ))); } inner.writers.insert(substream.id(), cx.waker().clone()); - Pending + Poll::Pending } Err(WriteError::UnknownStream) => { panic!("libp2p never uses a closed stream, so this cannot happen; qed") } - Err(WriteError::Stopped(_)) => Ready(Err(io::ErrorKind::ConnectionAborted.into())), + Err(WriteError::Stopped(_)) => { + Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + } } } @@ -376,21 +375,21 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); inner.wake_driver(); match inner.connection.read(substream.id(), buf) { - Ok(Some(bytes)) => Ready(Ok(bytes)), - Ok(None) => Ready(Ok(0)), + Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), + Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) if inner.connection_lost => { - // Ready(Err(io::ErrorKind::ConnectionReset.into())) - Ready(Ok(0)) + // Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) + Poll::Ready(Ok(0)) } Err(ReadError::Blocked) => { inner.readers.insert(substream.id(), cx.waker().clone()); - Pending + Poll::Pending } Err(ReadError::UnknownStream) => { error!("you used a stream that was already closed!"); - Ready(Ok(0)) + Poll::Ready(Ok(0)) } - Err(ReadError::Reset(_)) => Ready(Err(io::ErrorKind::ConnectionReset.into())), + Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -400,7 +399,7 @@ impl StreamMuxer for QuicMuxer { substream: &mut Self::Substream, ) -> Poll> { match substream.status { - SubstreamStatus::Finished => return Ready(Ok(())), + SubstreamStatus::Finished => return Poll::Ready(Ok(())), SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); return channel @@ -437,7 +436,7 @@ impl StreamMuxer for QuicMuxer { .insert(substream.id(), Some(sender)) .unwrap() .is_none()); - Pending + Poll::Pending } fn flush_substream( @@ -445,28 +444,28 @@ impl StreamMuxer for QuicMuxer { _cx: &mut Context, _substream: &mut Self::Substream, ) -> Poll> { - Ready(Ok(())) + Poll::Ready(Ok(())) } fn flush_all(&self, _cx: &mut Context) -> Poll> { - Ready(Ok(())) + Poll::Ready(Ok(())) } fn close(&self, cx: &mut Context) -> Poll> { trace!("close() called"); let mut inner = self.inner(); if inner.connection.is_closed() { - return Ready(Ok(())); + return Poll::Ready(Ok(())); } else if inner.close_reason.is_some() { - return Ready(Ok(())); + return Poll::Ready(Ok(())); } else if inner.finishers.is_empty() { inner.shutdown(); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); drop(inner.driver().poll_unpin(cx)); - return Ready(Ok(())); + return Poll::Ready(Ok(())); } else if inner.close_waker.is_some() { inner.close_waker = Some(cx.waker().clone()); - return Pending; + return Poll::Pending; } else { inner.close_waker = Some(cx.waker().clone()) } @@ -483,7 +482,7 @@ impl StreamMuxer for QuicMuxer { true } }); - Pending + Poll::Pending } } @@ -522,7 +521,7 @@ impl Future for QuicUpgrade { } else if inner.connection.is_handshaking() { assert!(!inner.connection.is_closed(), "deadlock"); inner.handshake_waker = Some(cx.waker().clone()); - return Pending; + return Poll::Pending; } else if inner.connection.side().is_server() { ready!(inner.endpoint_channel.poll_ready(cx)) .expect("we have a reference to the peer; qed"); @@ -538,7 +537,7 @@ impl Future for QuicUpgrade { } }; let muxer = muxer.take().expect("polled after yielding Ready"); - Ready(res.map(|()| muxer)) + Poll::Ready(res.map(|()| muxer)) } } @@ -695,9 +694,9 @@ impl Muxer { fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { loop { match self.endpoint_channel.poll_ready(cx) { - Pending => break true, - Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), - Ready(Ok(())) => {} + Poll::Pending => break true, + Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), + Poll::Ready(Ok(())) => {} } if let Some(event) = self.connection.poll_endpoint_events() { self.endpoint_channel @@ -752,13 +751,13 @@ impl Muxer { .socket() .poll_send_to(cx, &transmit.contents, &transmit.destination); break match res { - Pending => { + Poll::Pending => { self.pending = Some(transmit); Ok(true) } - Ready(Ok(_)) => Ok(false), - Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, - Ready(Err(e)) => Err(e), + Poll::Ready(Ok(_)) => Ok(false), + Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, + Poll::Ready(Err(e)) => Err(e), }; } } @@ -948,7 +947,7 @@ impl Future for ConnectionDriver { needs_timer_update |= inner.process_app_events(); let needs_to_send_endpoint_events = inner.poll_endpoint_events(cx); if inner.connection.is_drained() { - break Ready( + break Poll::Ready( match inner .close_reason .clone() @@ -981,12 +980,12 @@ impl Future for ConnectionDriver { } else if !needs_timer_update { assert!(inner.connection.poll_transmit(now).is_none()); break if inner.close_reason.is_none() { - Pending + Poll::Pending } else if needs_to_send_endpoint_events { debug!("still have endpoint events to send!"); - Pending + Poll::Pending } else { - Ready(Ok(())) + Poll::Ready(Ok(())) }; } } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 0792c11947f..597713b40b4 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -54,11 +54,11 @@ impl AsyncWrite for QuicStream { .muxer .shutdown_substream(cx, inner.id.as_mut().unwrap()))?; debug!("closed {:?}", inner.id); - Ready(Ok(())) + Poll::Ready(Ok(())) } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Ready(Ok(())) + Poll::Ready(Ok(())) } } @@ -87,7 +87,7 @@ impl Drop for QuicStream { impl futures::Stream for QuicMuxer { type Item = QuicStream; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Ready(Some(QuicStream { + Poll::Ready(Some(QuicStream { id: Some(ready!(self.poll_inbound(cx)).expect("bug")), muxer: self.get_mut().clone(), shutdown: false, From 9b98ad83da51f094511f34a4c371b74a6c0691d7 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 22 Jan 2020 15:10:48 -0500 Subject: [PATCH 065/202] Do not check for ECONNRESET on transmit It can only happen on receive. --- transports/quic/src/lib.rs | 25 +++++++++++-------------- transports/quic/src/socket.rs | 4 ---- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index dd054acb1fe..eb9cc909cb0 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -745,20 +745,17 @@ impl Muxer { cx: &mut Context<'_>, transmit: quinn_proto::Transmit, ) -> Result { - loop { - let res = - self.endpoint - .socket() - .poll_send_to(cx, &transmit.contents, &transmit.destination); - break match res { - Poll::Pending => { - self.pending = Some(transmit); - Ok(true) - } - Poll::Ready(Ok(_)) => Ok(false), - Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::ConnectionReset => continue, - Poll::Ready(Err(e)) => Err(e), - }; + let res = + self.endpoint + .socket() + .poll_send_to(cx, &transmit.contents, &transmit.destination); + match res { + Poll::Pending => { + self.pending = Some(transmit); + Ok(true) + } + Poll::Ready(Ok(_)) => Ok(false), + Poll::Ready(Err(e)) => Err(e), } } diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index db010012a25..f3b5bfe64c9 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -74,8 +74,6 @@ impl Socket { return Poll::Pending; } Poll::Ready(Ok(_)) => {} - // FIXME is this even possible? - Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => {} Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } trace!( @@ -97,8 +95,6 @@ impl Socket { ); continue; } - // FIXME is this even possible? - Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => continue, Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } self.pending = Some(transmit); From a30fe7687583b52db1ee626b19ba424cb2af7843 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 22 Jan 2020 16:54:12 -0500 Subject: [PATCH 066/202] Refactor networking code --- transports/quic/src/endpoint.rs | 2 +- transports/quic/src/socket.rs | 68 ++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index d025ee5dfad..cfdd964a795 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -96,7 +96,7 @@ impl EndpointInner { use quinn_proto::DatagramEvent; let mut buf = vec![0; 65535]; loop { - let (bytes, peer) = ready!(socket.poll_recv_from(cx, &mut buf[..]))?; + let (bytes, peer) = ready!(crate::socket::receive_from(cx, socket, &mut buf[..])?); let (handle, event) = match self .inner diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index f3b5bfe64c9..0dfbd53caf6 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -23,7 +23,7 @@ //! This provides a central location for socket I/O, and logs all incoming and outgoing packets. use async_std::net::UdpSocket; -use log::{debug, trace}; +use log::trace; use quinn_proto::{Connection, Endpoint, Transmit}; use std::{io::Result, task::Context, task::Poll, time::Instant}; @@ -58,6 +58,42 @@ pub(crate) struct Socket { pending: Option, } +pub fn raw_send(cx: &mut Context, socket: &UdpSocket, packet: &Transmit) -> Poll> { + match socket.poll_send_to(cx, &packet.contents, &packet.destination) { + Poll::Pending => { + trace!("not able to send packet right away"); + return Poll::Pending; + } + Poll::Ready(Ok(e)) => { + trace!("sent packet of length {} to {}", e, packet.destination); + return Poll::Ready(Ok(())); + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } +} + +pub fn receive_from( + cx: &mut Context, + socket: &UdpSocket, + buf: &mut [u8], +) -> Poll> { + loop { + match socket.poll_recv_from(cx, buf) { + Poll::Pending => { + trace!("no packets available yet"); + break Poll::Pending; + } + Poll::Ready(Ok(e)) => { + trace!("received packet of length {} from {}", e.0, e.1); + break Poll::Ready(Ok(e)); + } + // May be injected by a malicious ICMP packet + Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => {} + Poll::Ready(Err(e)) => break Poll::Ready(Err(e)), + } + } +} + impl Socket { pub fn send_packet( &mut self, @@ -68,37 +104,23 @@ impl Socket { ) -> Poll> { if let Some(ref mut transmit) = self.pending { trace!("trying to send packet!"); - match socket.poll_send_to(cx, &transmit.contents, &transmit.destination) { - Poll::Pending => { - debug!("not able to send packet right away"); - return Poll::Pending; - } - Poll::Ready(Ok(_)) => {} + match raw_send(cx, socket, &transmit) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } - trace!( - "sent packet of length {} to {}", - transmit.contents.len(), - transmit.destination - ); } self.pending = None; while let Some(transmit) = source.get_packet(Dummy, now) { trace!("trying to send packet!"); - match socket.poll_send_to(cx, &transmit.contents, &transmit.destination) { - Poll::Pending => debug!("not able to send packet right away"), - Poll::Ready(Ok(_)) => { - trace!( - "sent packet of length {} to {}", - transmit.contents.len(), - transmit.destination - ); - continue; + match raw_send(cx, socket, &transmit) { + Poll::Ready(Ok(())) => {} + Poll::Pending => { + self.pending = Some(transmit); + return Poll::Pending; } Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } - self.pending = Some(transmit); - return Poll::Pending; } Poll::Ready(Ok(())) } From 0aba2f27b6a2969ce477570206d449d4bba71894 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 23 Jan 2020 17:26:40 -0500 Subject: [PATCH 067/202] Cleanup I/O paths This cleans up the I/O paths to unify logging and retry on ECONNRESET. It also prepares for using a different mechanism to determine when an endpoint can be shut down. --- transports/quic/src/endpoint.rs | 61 +++++++++------- transports/quic/src/lib.rs | 8 +- transports/quic/src/socket.rs | 125 ++++++++++++++++---------------- transports/quic/src/verifier.rs | 20 ++--- 4 files changed, 111 insertions(+), 103 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index cfdd964a795..3439d1361a8 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,9 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{EndpointMessage, QuicConfig}; +use crate::{socket, EndpointMessage, QuicConfig}; use async_macros::ready; -use async_std::net::{SocketAddr, UdpSocket}; +use async_std::net::SocketAddr; use futures::{channel::mpsc, prelude::*}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, @@ -44,7 +44,7 @@ pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, driver: Option>>, - socket: crate::socket::Socket, + pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, refcount: u64, @@ -90,13 +90,13 @@ impl EndpointInner { fn drive_receive( &mut self, - socket: &UdpSocket, + socket: &socket::Socket, cx: &mut Context, ) -> Poll> { use quinn_proto::DatagramEvent; let mut buf = vec![0; 65535]; loop { - let (bytes, peer) = ready!(crate::socket::receive_from(cx, socket, &mut buf[..])?); + let (bytes, peer) = ready!(socket.recv_from(cx, &mut buf[..])?); let (handle, event) = match self .inner @@ -126,18 +126,18 @@ impl EndpointInner { fn poll_transmit_pending( &mut self, - socket: &UdpSocket, + socket: &socket::Socket, cx: &mut Context, ) -> Poll> { - self.socket - .send_packet(cx, socket, &mut self.inner, Instant::now()) + let Self { inner, pending, .. } = self; + pending.send_packet(cx, socket, &mut || inner.poll_transmit()) } } #[derive(Debug)] pub(super) struct EndpointData { /// The single UDP socket used for I/O - socket: UdpSocket, + socket: socket::Socket, /// A `Mutex` protecting the QUIC state machine. inner: Mutex, /// The channel on which new connections are sent. This is bounded in practice by the accept @@ -160,7 +160,7 @@ impl EndpointData { self.event_channel.clone() } - pub(super) fn socket(&self) -> &UdpSocket { + pub(super) fn socket(&self) -> &socket::Socket { &self.socket } } @@ -222,16 +222,16 @@ impl Endpoint { let (new_connections, receive_connections) = mpsc::unbounded(); let (event_channel, event_receiver) = mpsc::channel(0); let return_value = Self(Arc::new(EndpointData { - socket, + socket: socket::Socket::new(socket), inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( config.endpoint_config.clone(), Some(config.server_config.clone()), ), muxers: HashMap::new(), - socket: Default::default(), driver: None, event_receiver, + pending: Default::default(), refcount: 2, }), address: address.clone(), @@ -302,14 +302,13 @@ impl Future for EndpointDriver { trace!("driving events"); inner.drive_events(cx); trace!("driving incoming packets"); - match inner.drive_receive(&this.0.socket, cx) { + match inner.drive_receive(&this.0.socket, cx)? { Poll::Pending => { - debug!("no new connections"); drop(inner.poll_transmit_pending(&this.0.socket, cx)?); trace!("returning Pending"); break Poll::Pending; } - Poll::Ready(Ok((handle, connection))) => { + Poll::Ready((handle, connection)) => { trace!("have a new connection"); this.accept_muxer(connection, handle, &mut *inner); trace!("connection accepted"); @@ -319,20 +318,31 @@ impl Future for EndpointDriver { Poll::Ready(()) => break Poll::Pending, } } - Poll::Ready(Err(e)) => { - log::error!("I/O error: {:?}", e); - return Poll::Ready(Err(e)); - } } } } } +/// A QUIC listener +#[derive(Debug)] +pub struct Listener { + reference: Arc, + channel: mpsc::UnboundedReceiver, io::Error>>, +} + +impl Unpin for Listener {} + +impl Stream for Listener { + type Item = Result, io::Error>; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.get_mut().channel.poll_next_unpin(cx) + } +} + impl Transport for &Endpoint { type Output = super::QuicMuxer; type Error = io::Error; - type Listener = - mpsc::UnboundedReceiver, Self::Error>>; + type Listener = Listener; type ListenerUpgrade = super::QuicUpgrade; type Dial = super::QuicUpgrade; @@ -340,16 +350,17 @@ impl Transport for &Endpoint { if addr != self.0.address { return Err(TransportError::MultiaddrNotSupported(addr)); } - let res = (self.0) + let channel = (self.0) .receive_connections .lock() .take() - .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into())); + .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into()))?; let mut inner = self.inner(); + let reference = self.0.clone(); if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(EndpointDriver(self.0.clone()))); + inner.driver = Some(async_std::task::spawn(EndpointDriver(reference.clone()))); } - res + Ok(Listener { channel, reference }) } fn dial(self, addr: Multiaddr) -> Result> { diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index eb9cc909cb0..21b663f6e93 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -66,8 +66,8 @@ mod tests; mod verifier; use async_macros::ready; pub use certificate::make_cert; -pub use endpoint::Endpoint; use endpoint::EndpointInner; +pub use endpoint::{Endpoint, Listener}; use futures::{ channel::{mpsc, oneshot}, prelude::*, @@ -723,7 +723,6 @@ impl Muxer { let mut needs_timer_update = false; if let Some(transmit) = self.pending.take() { trace!("trying to send packet!"); - needs_timer_update = true; if self.poll_transmit(cx, transmit)? { return Ok(false); } @@ -745,10 +744,7 @@ impl Muxer { cx: &mut Context<'_>, transmit: quinn_proto::Transmit, ) -> Result { - let res = - self.endpoint - .socket() - .poll_send_to(cx, &transmit.contents, &transmit.destination); + let res = self.endpoint.socket().poll_send_to(cx, &transmit); match res { Poll::Pending => { self.pending = Some(transmit); diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 0dfbd53caf6..8f3bb2af2b3 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -23,97 +23,98 @@ //! This provides a central location for socket I/O, and logs all incoming and outgoing packets. use async_std::net::UdpSocket; -use log::trace; -use quinn_proto::{Connection, Endpoint, Transmit}; -use std::{io::Result, task::Context, task::Poll, time::Instant}; +use log::{trace, warn}; +use quinn_proto::Transmit; +use std::{io::Result, task::Context, task::Poll}; -mod private { - use super::Instant; - pub(crate) struct Dummy; - /// A trait for packet generators - pub(crate) trait PacketGen { - /// Get the next packet to be sent, if any. - fn get_packet(&mut self, dummy: Dummy, now: Instant) -> Option; - } -} - -use private::Dummy; -pub(crate) use private::PacketGen; - -impl PacketGen for Endpoint { - fn get_packet(&mut self, Dummy: Dummy, _now: Instant) -> Option { - self.poll_transmit() - } -} - -impl PacketGen for Connection { - fn get_packet(&mut self, Dummy: Dummy, now: Instant) -> Option { - self.poll_transmit(now) - } -} - -/// A socket wrapper for libp2p-quic +/// A pending packet for libp2p-quic #[derive(Debug, Default)] -pub(crate) struct Socket { +pub(crate) struct Pending { pending: Option, } -pub fn raw_send(cx: &mut Context, socket: &UdpSocket, packet: &Transmit) -> Poll> { - match socket.poll_send_to(cx, &packet.contents, &packet.destination) { - Poll::Pending => { - trace!("not able to send packet right away"); - return Poll::Pending; - } - Poll::Ready(Ok(e)) => { - trace!("sent packet of length {} to {}", e, packet.destination); - return Poll::Ready(Ok(())); - } - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - } +/// A socket wrapper for libp2p-quic +#[derive(Debug)] +pub(crate) struct Socket { + socket: UdpSocket, } -pub fn receive_from( - cx: &mut Context, - socket: &UdpSocket, - buf: &mut [u8], -) -> Poll> { - loop { - match socket.poll_recv_from(cx, buf) { +impl Socket { + /// Transmit a packet if possible, with appropriate logging. + pub fn poll_send_to(&self, cx: &mut Context, packet: &Transmit) -> Poll> { + match self + .socket + .poll_send_to(cx, &packet.contents, &packet.destination) + { Poll::Pending => { - trace!("no packets available yet"); - break Poll::Pending; + trace!("not able to send packet right away"); + return Poll::Pending; } Poll::Ready(Ok(e)) => { - trace!("received packet of length {} from {}", e.0, e.1); - break Poll::Ready(Ok(e)); + trace!("sent packet of length {} to {}", e, packet.destination); + return Poll::Ready(Ok(())); + } + Poll::Ready(Err(e)) => { + warn!("Fatal I/O error: {:?}", e); + return Poll::Ready(Err(e)); + } + } + } + + /// A wrapper around `recv_from` that handles ECONNRESET and logging + pub fn recv_from( + &self, + cx: &mut Context, + buf: &mut [u8], + ) -> Poll> { + loop { + match self.socket.poll_recv_from(cx, buf) { + Poll::Pending => { + trace!("no packets available yet"); + break Poll::Pending; + } + Poll::Ready(Ok(e)) => { + trace!("received packet of length {} from {}", e.0, e.1); + break Poll::Ready(Ok(e)); + } + // May be injected by a malicious ICMP packet + Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => { + warn!("Got ECONNRESET from recv_from() - possible MITM attack") + } + Poll::Ready(Err(e)) => { + warn!("Fatal I/O error: {:?}", e); + break Poll::Ready(Err(e)); + } } - // May be injected by a malicious ICMP packet - Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => {} - Poll::Ready(Err(e)) => break Poll::Ready(Err(e)), } } } impl Socket { + pub fn new(socket: UdpSocket) -> Self { + Self { socket } + } +} + +impl Pending { pub fn send_packet( &mut self, cx: &mut Context, - socket: &UdpSocket, - source: &mut dyn PacketGen, - now: Instant, + socket: &Socket, + source: &mut dyn FnMut() -> Option, ) -> Poll> { if let Some(ref mut transmit) = self.pending { trace!("trying to send packet!"); - match raw_send(cx, socket, &transmit) { + match socket.poll_send_to(cx, &transmit) { Poll::Pending => return Poll::Pending, Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } } self.pending = None; - while let Some(transmit) = source.get_packet(Dummy, now) { + while let Some(transmit) = source() { trace!("trying to send packet!"); - match raw_send(cx, socket, &transmit) { + match socket.poll_send_to(cx, &transmit) { Poll::Ready(Ok(())) => {} Poll::Pending => { self.pending = Some(transmit); diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index a822747c135..3aa1f3457a8 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -46,11 +46,11 @@ impl rustls::ServerCertVerifier for VeryInsecureAllowAllCertificatesWithoutCheck _dns_name: webpki::DNSNameRef, _ocsp_response: &[u8], ) -> Result { - if presented_certs.len() > 0 { - Ok(rustls::ServerCertVerified::assertion()) - } else { - Err(rustls::TLSError::NoCertificatesPresented) - } + if presented_certs.len() > 0 { + Ok(rustls::ServerCertVerified::assertion()) + } else { + Err(rustls::TLSError::NoCertificatesPresented) + } } } @@ -67,10 +67,10 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireClientCertificateButDoNot &self, presented_certs: &[rustls::Certificate], ) -> Result { - if presented_certs.len() > 0 { - Ok(rustls::ClientCertVerified::assertion()) - } else { - Err(rustls::TLSError::NoCertificatesPresented) - } + if presented_certs.len() > 0 { + Ok(rustls::ClientCertVerified::assertion()) + } else { + Err(rustls::TLSError::NoCertificatesPresented) + } } } From 7c10638f76989113726613ff5f33abb7b9ae05a0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 24 Jan 2020 11:42:01 -0500 Subject: [PATCH 068/202] Return peer IDs from Transport impl This is necessary for use in higher-level protocols. --- core/src/lib.rs | 15 ++++++++-- src/lib.rs | 3 ++ transports/quic/src/certificate.rs | 12 ++++---- transports/quic/src/endpoint.rs | 2 +- transports/quic/src/lib.rs | 46 +++++++++++++++++++----------- transports/quic/src/tests.rs | 39 +++++++++++++++---------- transports/quic/src/verifier.rs | 12 ++++---- 7 files changed, 81 insertions(+), 48 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index beb0ffbbf9a..b93c93fd4be 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,17 +23,26 @@ //! The main concepts of libp2p-core are: //! //! - A [`PeerId`] is a unique global identifier for a node on the network. -//! Each node must have a different `PeerId`. Normally, a `PeerId` is the +//! Each node must have a different [`PeerId`]. Normally, a [`PeerId`] is the //! hash of the public key used to negotiate encryption on the //! communication channel, thereby guaranteeing that they cannot be spoofed. //! - The [`Transport`] trait defines how to reach a remote node or listen for -//! incoming remote connections. See the `transport` module. +//! incoming remote connections. See the [`transport`] module. //! - The [`StreamMuxer`] trait is implemented on structs that hold a connection //! to a remote and can subdivide this connection into multiple substreams. -//! See the `muxing` module. +//! See the [`muxing`] module. //! - The [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] traits //! define how to upgrade each individual substream to use a protocol. //! See the `upgrade` module. +//! +//! [`PeerId`]: +//! [`UpgradeInfo`]: +//! [`InboundUpgrade`]: +//! [`OutboundUpgrade`]: +//! [`Transport`]: +//! [`transport`]: +//! [`StreamMuxer`]: +//! [`muxing`]: mod keys_proto { include!(concat!(env!("OUT_DIR"), "/keys_proto.rs")); diff --git a/src/lib.rs b/src/lib.rs index 6aee5fc7d28..abc1d353223 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,6 +185,9 @@ pub use libp2p_noise as noise; pub use libp2p_ping as ping; #[doc(inline)] pub use libp2p_plaintext as plaintext; +#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] +#[doc(inline)] +pub use libp2p_quic as quic; #[doc(inline)] pub use libp2p_secio as secio; #[doc(inline)] diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 56b865a754c..ef1d1885b7c 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -323,7 +323,7 @@ fn parse_certificate( /// We just check that its self-signature is valid, and that its public key is suitably signed. pub fn verify_libp2p_certificate( certificate: &[u8], -) -> Result { +) -> Result { #![cfg_attr(not(test), allow(dead_code))] let (raw_certificate, certificate_key, identity_key): (_, Vec, _) = parse_certificate(certificate).map_err(|e| { @@ -339,7 +339,7 @@ pub fn verify_libp2p_certificate( &raw_certificate.tbs_certificate, &raw_certificate.signature_value, )?; - Ok(identity_key) + Ok(identity_key.into()) } #[cfg(test)] @@ -351,7 +351,7 @@ mod test { let keypair = identity::Keypair::generate_ed25519(); assert_eq!( verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), - keypair.public() + libp2p_core::PeerId::from_public_key(keypair.public()) ); log::trace!("trying secp256k1!"); let keypair = identity::Keypair::generate_secp256k1(); @@ -361,10 +361,8 @@ mod test { assert_eq!(public, public, "key is not equal to itself?"); log::debug!("have a valid key!"); assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(),) - .unwrap() - .into_protobuf_encoding(), - keypair.public().into_protobuf_encoding() + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + libp2p_core::PeerId::from_public_key(keypair.public()) ); } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 3439d1361a8..cc19f53c419 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -340,7 +340,7 @@ impl Stream for Listener { } impl Transport for &Endpoint { - type Output = super::QuicMuxer; + type Output = (libp2p_core::PeerId, super::QuicMuxer); type Error = io::Error; type Listener = Listener; type ListenerUpgrade = super::QuicUpgrade; diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 21b663f6e93..a04ecfb390d 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -31,7 +31,8 @@ //! use libp2p_core::Multiaddr; //! //! # fn main() { -//! let quic_config = QuicConfig::default(); +//! let keypair = libp2p_core::identity::Keypair::generate_ed25519(); +//! let quic_config = QuicConfig::new(&keypair); //! let quic_endpoint = Endpoint::new( //! quic_config, //! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), @@ -143,7 +144,7 @@ fn make_client_config( crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; crypto.set_single_client_cert(vec![certificate], key); - let verifier = verifier::VeryInsecureAllowAllCertificatesWithoutChecking; + let verifier = verifier::VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; crypto .dangerous() .set_certificate_verifier(Arc::new(verifier)); @@ -161,7 +162,7 @@ fn make_server_config( transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireClientCertificateButDoNotCheckIt, + verifier::VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt, )); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto @@ -173,10 +174,9 @@ fn make_server_config( config } -impl Default for QuicConfig { +impl QuicConfig { /// Creates a new configuration object for TCP/IP. - fn default() -> Self { - let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { let cert = make_cert(&keypair); let (cert, key) = ( rustls::Certificate( @@ -503,7 +503,7 @@ impl Drop for QuicUpgrade { } impl Future for QuicUpgrade { - type Output = Result; + type Output = Result<(libp2p_core::PeerId, QuicMuxer), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); @@ -517,7 +517,7 @@ impl Future for QuicUpgrade { .as_ref() .expect("closed connections always have a reason; qed") .clone(), - )) + ))?; } else if inner.connection.is_handshaking() { assert!(!inner.connection.is_closed(), "deadlock"); inner.handshake_waker = Some(cx.waker().clone()); @@ -525,19 +525,33 @@ impl Future for QuicUpgrade { } else if inner.connection.side().is_server() { ready!(inner.endpoint_channel.poll_ready(cx)) .expect("we have a reference to the peer; qed"); - Ok(inner + inner .endpoint_channel .start_send(EndpointMessage::ConnectionAccepted) - .expect( - "we only send one datum per clone of the channel, so we have capacity \ - to send this; qed", - )) - } else { - Ok(()) + .expect("we just checked that we have capacity to send this; qed") } + + let peer_certificates: Vec = inner + .connection + .crypto_session() + .get_peer_certificates() + .expect("we have finished handshaking, so we have exactly one certificate; qed"); + certificate::verify_libp2p_certificate( + peer_certificates + .get(0) + .expect( + "our certificate verifiers require exactly one \ + certificate to be presented, so an empty certificate \ + chain would already have been rejected; qed", + ) + .as_ref(), + ) }; let muxer = muxer.take().expect("polled after yielding Ready"); - Poll::Ready(res.map(|()| muxer)) + Poll::Ready(match res { + Ok(e) => Ok((e, muxer)), + Err(ring::error::Unspecified) => Err(io::ErrorKind::InvalidData.into()), + }) } } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 597713b40b4..cda5de74276 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -115,7 +115,8 @@ impl Future for QuicMuxer { fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); - let mut listener = Endpoint::new(QuicConfig::default(), addr.clone()) + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let mut listener = Endpoint::new(QuicConfig::new(&keypair), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); @@ -167,11 +168,13 @@ fn communicating_between_dialer_and_listener() { } } - let _handle = async_std::task::spawn(async move { + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let keypair2 = keypair.clone(); + let handle = async_std::task::spawn(async move { let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" .parse() .expect("bad address?"); - let quic_config = QuicConfig::default(); + let quic_config = QuicConfig::new(&keypair2); let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); @@ -183,7 +186,7 @@ fn communicating_between_dialer_and_listener() { } ListenerEvent::Upgrade { upgrade, .. } => { log::debug!("got a connection upgrade!"); - let mut muxer: QuicMuxer = upgrade.await.expect("upgrade failed"); + let (id, mut muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); log::debug!("got a new muxer!"); let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); @@ -206,7 +209,7 @@ fn communicating_between_dialer_and_listener() { drop(socket); muxer.await.unwrap(); log::debug!("finished!"); - break; + break id; } _ => unreachable!(), } @@ -219,7 +222,7 @@ fn communicating_between_dialer_and_listener() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); - let quic_config = QuicConfig::default(); + let quic_config = QuicConfig::new(&keypair); let quic_endpoint = Endpoint::new( quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), @@ -229,8 +232,8 @@ fn communicating_between_dialer_and_listener() { let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); let mut stream = QuicStream { - id: Some(connection.open_outbound().await.expect("failed")), - muxer: connection.clone(), + id: Some(connection.1.open_outbound().await.expect("failed")), + muxer: connection.1.clone(), shutdown: false, }; log::debug!("have a new stream!"); @@ -252,22 +255,26 @@ fn communicating_between_dialer_and_listener() { assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); log::debug!("have EOF!"); - connection.await.expect("closed successfully"); + connection.1.await.expect("closed successfully"); log::debug!("awaiting handle!"); + connection.0 }); - async_std::task::block_on(_handle); - async_std::task::block_on(second_handle); + assert_eq!( + async_std::task::block_on(handle), + async_std::task::block_on(second_handle) + ); } #[test] fn replace_port_0_in_returned_multiaddr_ipv4() { init(); - let quic = QuicConfig::default(); + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let config = QuicConfig::new(&keypair); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); - let quic = Endpoint::new(quic, addr.clone()).expect("no error"); + let quic = Endpoint::new(config, addr.clone()).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -282,7 +289,8 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { #[test] fn replace_port_0_in_returned_multiaddr_ipv6() { init(); - let config = QuicConfig::default(); + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let config = QuicConfig::new(&keypair); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); @@ -301,7 +309,8 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { #[test] fn larger_addr_denied() { init(); - let config = QuicConfig::default(); + let keypair = libp2p_core::identity::Keypair::generate_ed25519(); + let config = QuicConfig::new(&keypair); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 3aa1f3457a8..c4f4d7deeb0 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -26,7 +26,7 @@ /// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic /// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key /// corresponing to its `PeerId`, unless that endpoint has done something insecure. -pub struct VeryInsecureAllowAllCertificatesWithoutChecking; +pub struct VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; /// A ClientCertVerifier that requires client authentication, but considers any certificate to be /// valid, and does no checking whatsoever. @@ -36,9 +36,9 @@ pub struct VeryInsecureAllowAllCertificatesWithoutChecking; /// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic /// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key /// corresponing to its `PeerId`, unless that endpoint has done something insecure. -pub struct VeryInsecureRequireClientCertificateButDoNotCheckIt; +pub struct VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt; -impl rustls::ServerCertVerifier for VeryInsecureAllowAllCertificatesWithoutChecking { +impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt { fn verify_server_cert( &self, _roots: &rustls::RootCertStore, @@ -46,7 +46,7 @@ impl rustls::ServerCertVerifier for VeryInsecureAllowAllCertificatesWithoutCheck _dns_name: webpki::DNSNameRef, _ocsp_response: &[u8], ) -> Result { - if presented_certs.len() > 0 { + if presented_certs.len() == 1 { Ok(rustls::ServerCertVerified::assertion()) } else { Err(rustls::TLSError::NoCertificatesPresented) @@ -54,7 +54,7 @@ impl rustls::ServerCertVerifier for VeryInsecureAllowAllCertificatesWithoutCheck } } -impl rustls::ClientCertVerifier for VeryInsecureRequireClientCertificateButDoNotCheckIt { +impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt { fn offer_client_auth(&self) -> bool { true } @@ -67,7 +67,7 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireClientCertificateButDoNot &self, presented_certs: &[rustls::Certificate], ) -> Result { - if presented_certs.len() > 0 { + if presented_certs.len() == 1 { Ok(rustls::ClientCertVerified::assertion()) } else { Err(rustls::TLSError::NoCertificatesPresented) From 30c2a1a7832e91b9faa98a3fb7be33ebd2d3a5e0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 24 Jan 2020 19:35:11 -0500 Subject: [PATCH 069/202] Sending endpoint events could require checking for timers --- transports/quic/src/endpoint.rs | 7 ++++++- transports/quic/src/lib.rs | 28 +++++++++------------------- transports/quic/src/tests.rs | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index cc19f53c419..46e82ceb075 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -112,7 +112,12 @@ impl EndpointInner { Some(connection) => connection .lock() .process_connection_events(self, connection_event), - None => debug!("lost our connection!"), + None => { + debug!("lost our connection!"); + assert!(self + .handle_event(handle, quinn_proto::EndpointEvent::drained()) + .is_none()) + } } } DatagramEvent::NewConnection(connection) => { diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index a04ecfb390d..c74081018fb 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -635,7 +635,7 @@ impl Muxer { fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { let mut keep_going = false; - 'outer: loop { + loop { match self.connection.poll_timeout() { None => { self.timer = None; @@ -656,7 +656,7 @@ impl Muxer { if timer.poll_unpin(cx).is_ready() { self.connection.handle_timeout(now); keep_going = true; - continue 'outer; + continue; } } break; @@ -706,25 +706,23 @@ impl Muxer { /// Send endpoint events. Returns true if and only if there are endpoint events remaining to /// be sent. fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { + let mut keep_going = false; loop { match self.endpoint_channel.poll_ready(cx) { - Poll::Pending => break true, + Poll::Pending => break keep_going, Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), Poll::Ready(Ok(())) => {} } if let Some(event) = self.connection.poll_endpoint_events() { + keep_going = true; self.endpoint_channel .start_send(EndpointMessage::EndpointEvent { handle: self.handle, event, }) - .expect( - "we checked in `pre_application_io` that this channel had space; \ - that is always called first, and there is a lock preventing concurrency \ - problems; qed", - ) + .expect("we just checked that we have capacity; qed"); } else { - break false; + break keep_going; } } } @@ -952,7 +950,7 @@ impl Future for ConnectionDriver { needs_timer_update |= inner.drive_timer(cx, now); needs_timer_update |= inner.pre_application_io(now, cx)?; needs_timer_update |= inner.process_app_events(); - let needs_to_send_endpoint_events = inner.poll_endpoint_events(cx); + needs_timer_update |= inner.poll_endpoint_events(cx); if inner.connection.is_drained() { break Poll::Ready( match inner @@ -985,15 +983,7 @@ impl Future for ConnectionDriver { }, ); } else if !needs_timer_update { - assert!(inner.connection.poll_transmit(now).is_none()); - break if inner.close_reason.is_none() { - Poll::Pending - } else if needs_to_send_endpoint_events { - debug!("still have endpoint events to send!"); - Poll::Pending - } else { - Poll::Ready(Ok(())) - }; + break Poll::Pending; } } } diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index cda5de74276..46a759230e9 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -245,7 +245,7 @@ fn communicating_between_dialer_and_listener() { let mut count = 0; while count < buf.len() { let read = stream.read(&mut buf[count..]).await.unwrap(); - assert_ne!(read, 0usize); + assert_ne!(read, 0usize, "premature end of file"); count += read; } } From 62b1f7e3dc4aaf38672b15f685181591c5dfc735 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 27 Jan 2020 17:15:23 -0500 Subject: [PATCH 070/202] Move the connection code to its own module This forced several other refactors, which resulted in a cleaner API. --- transports/quic/src/connection.rs | 944 ++++++++++++++++++++++++++++++ transports/quic/src/endpoint.rs | 79 +-- transports/quic/src/lib.rs | 936 +---------------------------- transports/quic/src/tests.rs | 32 +- 4 files changed, 1000 insertions(+), 991 deletions(-) create mode 100644 transports/quic/src/connection.rs diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs new file mode 100644 index 00000000000..df5ff9fff20 --- /dev/null +++ b/transports/quic/src/connection.rs @@ -0,0 +1,944 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +use super::{ + certificate, + endpoint::{EndpointData, EndpointInner}, + verifier, +}; +use async_macros::ready; +use futures::{ + channel::{mpsc, oneshot}, + prelude::*, +}; +use libp2p_core::StreamMuxer; +use log::{debug, error, trace}; +use parking_lot::{Mutex, MutexGuard}; +use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; +use std::{ + collections::HashMap, + io, + mem::replace, + pin::Pin, + sync::{Arc, Weak}, + task::{Context, Poll}, + time::Instant, +}; +#[derive(Debug)] +pub struct QuicSubstream { + id: StreamId, + status: SubstreamStatus, +} + +#[derive(Debug)] +enum SubstreamStatus { + Live, + Finishing(oneshot::Receiver<()>), + Finished, +} + +impl QuicSubstream { + fn new(id: StreamId) -> Self { + let status = SubstreamStatus::Live; + Self { id, status } + } + + fn id(&self) -> StreamId { + self.id + } + + fn is_live(&self) -> bool { + match self.status { + SubstreamStatus::Live => true, + SubstreamStatus::Finishing(_) | SubstreamStatus::Finished => false, + } + } +} + +/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. +/// +/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams +/// obtained by libp2p through the tokio reactor. +#[derive(Debug, Clone)] +pub struct QuicConfig { + /// The client configuration. Quinn provides functions for making one. + pub client_config: quinn_proto::ClientConfig, + /// The server configuration. Quinn provides functions for making one. + pub server_config: Arc, + /// The endpoint configuration + pub endpoint_config: Arc, +} + +fn make_client_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ClientConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); + use std::time::Duration; + transport.keep_alive_interval(Some(Duration::from_millis(1000))); + let mut crypto = rustls::ClientConfig::new(); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto.enable_early_data = true; + crypto.set_single_client_cert(vec![certificate], key); + let verifier = verifier::VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; + crypto + .dangerous() + .set_certificate_verifier(Arc::new(verifier)); + quinn_proto::ClientConfig { + transport: Arc::new(transport), + crypto: Arc::new(crypto), + } +} + +fn make_server_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ServerConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); + let mut crypto = rustls::ServerConfig::new(Arc::new( + verifier::VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt, + )); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto + .set_single_cert(vec![certificate], key) + .expect("we are given a valid cert; qed"); + let mut config = quinn_proto::ServerConfig::default(); + config.transport = Arc::new(transport); + config.crypto = Arc::new(crypto); + config +} + +impl QuicConfig { + /// Creates a new configuration object for TCP/IP. + pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { + let cert = super::make_cert(&keypair); + let (cert, key) = ( + rustls::Certificate( + cert.serialize_der() + .expect("serialization of a valid cert will succeed; qed"), + ), + rustls::PrivateKey(cert.serialize_private_key_der()), + ); + Self { + client_config: make_client_config(cert.clone(), key.clone()), + server_config: Arc::new(make_server_config(cert, key)), + endpoint_config: Default::default(), + } + } +} + +#[derive(Debug)] +pub(super) enum EndpointMessage { + ConnectionAccepted, + EndpointEvent { + handle: ConnectionHandle, + event: quinn_proto::EndpointEvent, + }, +} + +#[derive(Debug, Clone)] +pub struct QuicMuxer(Arc>); + +impl QuicMuxer { + fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { + self.0.lock() + } +} + +#[derive(Debug)] +enum OutboundInner { + Complete(Result), + Pending(oneshot::Receiver), + Done, +} + +pub struct Outbound(OutboundInner); + +impl Future for Outbound { + type Output = Result; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = &mut *self; + match this.0 { + OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { + OutboundInner::Complete(e) => Poll::Ready(e.map(QuicSubstream::new)), + _ => unreachable!(), + }, + OutboundInner::Pending(ref mut receiver) => { + let result = ready!(receiver.poll_unpin(cx)) + .map(QuicSubstream::new) + .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); + this.0 = OutboundInner::Done; + Poll::Ready(result) + } + OutboundInner::Done => panic!("polled after yielding Ready"), + } + } +} + +impl StreamMuxer for QuicMuxer { + type OutboundSubstream = Outbound; + type Substream = QuicSubstream; + type Error = io::Error; + fn open_outbound(&self) -> Self::OutboundSubstream { + let mut inner = self.inner(); + if let Some(ref e) = inner.close_reason { + Outbound(OutboundInner::Complete(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + )))) + } else if let Some(id) = inner.get_pending_stream() { + Outbound(OutboundInner::Complete(Ok(id))) + } else { + let (sender, receiver) = oneshot::channel(); + inner.connectors.push_front(sender); + inner.wake_driver(); + Outbound(OutboundInner::Pending(receiver)) + } + } + fn destroy_outbound(&self, _: Outbound) {} + fn destroy_substream(&self, substream: Self::Substream) { + let mut inner = self.inner(); + if let Some(waker) = inner.writers.remove(&substream.id()) { + waker.wake(); + } + if let Some(waker) = inner.readers.remove(&substream.id()) { + waker.wake(); + } + drop(inner.connection.finish(substream.id())); + drop( + inner + .connection + .stop_sending(substream.id(), Default::default()), + ) + } + fn is_remote_acknowledged(&self) -> bool { + true + } + + fn poll_inbound(&self, cx: &mut Context) -> Poll> { + debug!("being polled for inbound connections!"); + let mut inner = self.inner(); + if inner.connection.is_drained() { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + inner + .close_reason + .as_ref() + .expect("closed connections always have a reason; qed") + .clone(), + ))); + } + inner.wake_driver(); + match inner.connection.accept(quinn_proto::Dir::Bi) { + None => { + if let Some(waker) = replace(&mut inner.accept_waker, Some(cx.waker().clone())) { + waker.wake() + } + Poll::Pending + } + Some(id) => { + inner.finishers.insert(id, None); + Poll::Ready(Ok(QuicSubstream::new(id))) + } + } + } + + fn write_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + buf: &[u8], + ) -> Poll> { + use quinn_proto::WriteError; + if !substream.is_live() { + return Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())); + } + let mut inner = self.inner(); + debug_assert!( + inner.finishers.get(&substream.id()).is_some(), + "no entry in finishers map for write stream" + ); + inner.wake_driver(); + if let Some(ref e) = inner.close_reason { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + + assert!( + !inner.connection.is_drained(), + "attempting to write to a drained connection" + ); + match inner.connection.write(substream.id(), buf) { + Ok(bytes) => Poll::Ready(Ok(bytes)), + Err(WriteError::Blocked) => { + if let Some(ref e) = inner.close_reason { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + e.clone(), + ))); + } + inner.writers.insert(substream.id(), cx.waker().clone()); + Poll::Pending + } + Err(WriteError::UnknownStream) => { + panic!("libp2p never uses a closed stream, so this cannot happen; qed") + } + Err(WriteError::Stopped(_)) => { + Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + } + } + } + + fn poll_outbound( + &self, + cx: &mut Context, + substream: &mut Self::OutboundSubstream, + ) -> Poll> { + substream.poll_unpin(cx) + } + + fn read_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + buf: &mut [u8], + ) -> Poll> { + use quinn_proto::ReadError; + let mut inner = self.inner(); + inner.wake_driver(); + match inner.connection.read(substream.id(), buf) { + Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), + Ok(None) => Poll::Ready(Ok(0)), + Err(ReadError::Blocked) if inner.connection_lost => { + // Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) + Poll::Ready(Ok(0)) + } + Err(ReadError::Blocked) => { + inner.readers.insert(substream.id(), cx.waker().clone()); + Poll::Pending + } + Err(ReadError::UnknownStream) => { + error!("you used a stream that was already closed!"); + Poll::Ready(Ok(0)) + } + Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), + } + } + + fn shutdown_substream( + &self, + cx: &mut Context, + substream: &mut Self::Substream, + ) -> Poll> { + match substream.status { + SubstreamStatus::Finished => return Poll::Ready(Ok(())), + SubstreamStatus::Finishing(ref mut channel) => { + self.inner().wake_driver(); + return channel + .poll_unpin(cx) + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())); + } + SubstreamStatus::Live => {} + } + let mut inner = self.inner(); + inner.wake_driver(); + inner + .connection + .finish(substream.id()) + .map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => { + io::Error::new( + io::ErrorKind::ConnectionAborted, + quinn_proto::FinishError::UnknownStream, + ); + unreachable!("we checked for this above!") + } + quinn_proto::FinishError::Stopped { .. } => { + io::Error::from(io::ErrorKind::ConnectionReset) + } + })?; + let (sender, mut receiver) = oneshot::channel(); + assert!( + receiver.poll_unpin(cx).is_pending(), + "we haven’t written to the peer yet" + ); + substream.status = SubstreamStatus::Finishing(receiver); + assert!(inner + .finishers + .insert(substream.id(), Some(sender)) + .unwrap() + .is_none()); + Poll::Pending + } + + fn flush_substream( + &self, + _cx: &mut Context, + _substream: &mut Self::Substream, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn flush_all(&self, _cx: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } + + fn close(&self, cx: &mut Context) -> Poll> { + trace!("close() called"); + let mut inner = self.inner(); + if inner.connection.is_closed() { + return Poll::Ready(Ok(())); + } else if inner.close_reason.is_some() { + return Poll::Ready(Ok(())); + } else if inner.finishers.is_empty() { + inner.shutdown(); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + drop(inner.driver().poll_unpin(cx)); + return Poll::Ready(Ok(())); + } else if inner.close_waker.is_some() { + inner.close_waker = Some(cx.waker().clone()); + return Poll::Pending; + } else { + inner.close_waker = Some(cx.waker().clone()) + } + let Muxer { + ref mut finishers, + ref mut connection, + .. + } = *inner; + finishers.retain(|id, channel| { + if channel.is_some() { + true + } else { + drop(connection.finish(*id)); + true + } + }); + Poll::Pending + } +} + +#[derive(Debug)] +pub struct QuicUpgrade { + muxer: Option, +} + +#[cfg(test)] +impl Drop for QuicUpgrade { + fn drop(&mut self) { + debug!("dropping upgrade!"); + assert!( + self.muxer.is_none(), + "dropped before being polled to completion" + ); + } +} + +impl Future for QuicUpgrade { + type Output = Result<(libp2p_core::PeerId, QuicMuxer), io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let muxer = &mut self.get_mut().muxer; + trace!("outbound polling!"); + let res = { + let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); + if inner.connection.is_closed() { + Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + inner + .close_reason + .as_ref() + .expect("closed connections always have a reason; qed") + .clone(), + ))?; + } else if inner.connection.is_handshaking() { + assert!(!inner.connection.is_closed(), "deadlock"); + inner.handshake_waker = Some(cx.waker().clone()); + return Poll::Pending; + } else if inner.connection.side().is_server() { + ready!(inner.endpoint_channel.poll_ready(cx)) + .expect("we have a reference to the peer; qed"); + inner + .endpoint_channel + .start_send(EndpointMessage::ConnectionAccepted) + .expect("we just checked that we have capacity to send this; qed") + } + + let peer_certificates: Vec = inner + .connection + .crypto_session() + .get_peer_certificates() + .expect("we have finished handshaking, so we have exactly one certificate; qed"); + certificate::verify_libp2p_certificate( + peer_certificates + .get(0) + .expect( + "our certificate verifiers require exactly one \ + certificate to be presented, so an empty certificate \ + chain would already have been rejected; qed", + ) + .as_ref(), + ) + }; + let muxer = muxer.take().expect("polled after yielding Ready"); + Poll::Ready(match res { + Ok(e) => Ok((e, muxer)), + Err(ring::error::Unspecified) => Err(io::ErrorKind::InvalidData.into()), + }) + } +} + +type StreamSenderQueue = std::collections::VecDeque>; + +#[derive(Debug)] +pub(crate) struct Muxer { + /// The pending stream, if any. + pending_stream: Option, + /// The associated endpoint + endpoint: Arc, + /// The `quinn_proto::Connection` struct. + connection: Connection, + /// Connection handle + handle: ConnectionHandle, + /// Tasks blocked on writing + writers: HashMap, + /// Tasks blocked on reading + readers: HashMap, + /// Tasks blocked on finishing + finishers: HashMap>>, + /// Task waiting for new connections + handshake_waker: Option, + /// Task waiting for new connections + accept_waker: Option, + /// Tasks waiting to make a connection + connectors: StreamSenderQueue, + /// Pending transmit + pending: Option, + /// The timer being used by this connection + timer: Option, + /// The close reason, if this connection has been lost + close_reason: Option, + /// Waker to wake up the driver + waker: Option, + /// Channel for endpoint events + endpoint_channel: mpsc::Sender, + /// Last timeout + last_timeout: Option, + /// Join handle for the driver + driver: Option>>, + /// Close waker + close_waker: Option, + /// Have we gotten a connection lost event? + connection_lost: bool, +} + +impl Drop for Muxer { + fn drop(&mut self) { + if self.close_reason.is_none() { + self.shutdown() + } + } +} + +impl Drop for QuicMuxer { + fn drop(&mut self) { + let inner = self.inner(); + debug!("dropping muxer with side {:?}", inner.connection.side()); + #[cfg(test)] + assert!( + !inner.connection.is_handshaking(), + "dropped a connection that was still handshaking" + ); + } +} + +impl Muxer { + fn wake_driver(&mut self) { + if let Some(waker) = self.waker.take() { + debug!("driver awoken!"); + waker.wake(); + } + } + + fn driver(&mut self) -> &mut async_std::task::JoinHandle> { + self.driver + .as_mut() + .expect("we don’t call this until the driver is spawned; qed") + } + + fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { + let mut keep_going = false; + loop { + match self.connection.poll_timeout() { + None => { + self.timer = None; + self.last_timeout = None + } + Some(t) if t <= now => { + self.connection.handle_timeout(now); + keep_going = true; + continue; + } + t if t == self.last_timeout => {} + t => { + let delay = t.expect("already checked to be Some; qed") - now; + self.timer = Some(futures_timer::Delay::new(delay)) + } + } + if let Some(ref mut timer) = self.timer { + if timer.poll_unpin(cx).is_ready() { + self.connection.handle_timeout(now); + keep_going = true; + continue; + } + } + break; + } + + keep_going + } + + fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { + Muxer { + connection_lost: false, + close_waker: None, + last_timeout: None, + pending_stream: None, + connection, + handle, + writers: HashMap::new(), + readers: HashMap::new(), + finishers: HashMap::new(), + accept_waker: None, + handshake_waker: None, + connectors: Default::default(), + endpoint_channel: endpoint.event_channel(), + endpoint: endpoint, + pending: None, + timer: None, + close_reason: None, + waker: None, + driver: None, + } + } + + /// Process all endpoint-facing events for this connection. This is synchronous and will not + /// fail. + fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { + while let Some(endpoint_event) = self.connection.poll_endpoint_events() { + if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { + self.connection.handle_event(connection_event) + } + } + } + + /// Send endpoint events. Returns true if and only if there are endpoint events remaining to + /// be sent. + fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { + let mut keep_going = false; + loop { + match self.endpoint_channel.poll_ready(cx) { + Poll::Pending => break keep_going, + Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), + Poll::Ready(Ok(())) => {} + } + if let Some(event) = self.connection.poll_endpoint_events() { + keep_going = true; + self.endpoint_channel + .start_send(EndpointMessage::EndpointEvent { + handle: self.handle, + event, + }) + .expect("we just checked that we have capacity; qed"); + } else { + break keep_going; + } + } + } + + fn pre_application_io( + &mut self, + now: Instant, + cx: &mut Context<'_>, + ) -> Result { + let mut needs_timer_update = false; + if let Some(transmit) = self.pending.take() { + trace!("trying to send packet!"); + if self.poll_transmit(cx, transmit)? { + return Ok(false); + } + trace!("packet sent!"); + } + while let Some(transmit) = self.connection.poll_transmit(now) { + trace!("trying to send packet!"); + needs_timer_update = true; + if self.poll_transmit(cx, transmit)? { + break; + } + trace!("packet sent!"); + } + Ok(needs_timer_update) + } + + fn poll_transmit( + &mut self, + cx: &mut Context<'_>, + transmit: quinn_proto::Transmit, + ) -> Result { + let res = self.endpoint.socket().poll_send_to(cx, &transmit); + match res { + Poll::Pending => { + self.pending = Some(transmit); + Ok(true) + } + Poll::Ready(Ok(_)) => Ok(false), + Poll::Ready(Err(e)) => Err(e), + } + } + + fn shutdown(&mut self) { + debug!("shutting connection down!"); + if let Some(w) = self.accept_waker.take() { + w.wake() + } + if let Some(w) = self.handshake_waker.take() { + w.wake() + } + for (_, v) in self.writers.drain() { + v.wake(); + } + for (_, v) in self.readers.drain() { + v.wake(); + } + for sender in self.finishers.drain().filter_map(|x| x.1) { + drop(sender.send(())) + } + self.connectors.truncate(0); + if !self.connection.is_closed() { + self.connection + .close(Instant::now(), Default::default(), Default::default()); + self.process_app_events(); + } + self.wake_driver(); + } + + /// Process application events + pub(crate) fn process_connection_events( + &mut self, + endpoint: &mut EndpointInner, + event: Option, + ) { + if let Some(event) = event { + self.connection.handle_event(event); + } + if self.connection.is_drained() { + return; + } + self.send_to_endpoint(endpoint); + self.process_app_events(); + self.wake_driver(); + assert!(self.connection.poll_endpoint_events().is_none()); + assert!(self.connection.poll().is_none()); + } + + fn get_pending_stream(&mut self) -> Option { + self.wake_driver(); + if let Some(id) = self.pending_stream.take() { + Some(id) + } else { + self.connection.open(Dir::Bi) + } + .map(|id| { + self.finishers.insert(id, None); + id + }) + } + + pub fn process_app_events(&mut self) -> bool { + use quinn_proto::Event; + let mut keep_going = false; + 'a: while let Some(event) = self.connection.poll() { + keep_going = true; + match event { + Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { + panic!("we disabled incoming unidirectional streams and datagrams") + } + Event::StreamAvailable { dir: Dir::Uni } => { + panic!("we don’t use unidirectional streams") + } + Event::StreamReadable { stream } => { + trace!("Stream {:?} readable", stream); + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.readers.remove_entry(&stream) { + waker.wake() + } + } + Event::StreamWritable { stream } => { + trace!("Stream {:?} writable", stream); + // Wake up the task waiting on us (if any) + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + } + Event::StreamAvailable { dir: Dir::Bi } => { + trace!("Bidirectional stream available"); + if self.connectors.is_empty() { + // no task to wake up + continue; + } + assert!( + self.pending_stream.is_none(), + "we cannot have both pending tasks and a pending stream; qed" + ); + let stream = self.connection.open(Dir::Bi) + .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + while let Some(oneshot) = self.connectors.pop_front() { + match oneshot.send(stream) { + Ok(()) => continue 'a, + Err(_) => {} + } + } + self.pending_stream = Some(stream) + } + Event::ConnectionLost { reason } => { + debug!("lost connection due to {:?}", reason); + assert!(self.connection.is_closed()); + self.close_reason = Some(reason); + self.connection_lost = true; + self.close_waker.take().map(|e| e.wake()); + self.shutdown(); + } + Event::StreamFinished { stream, .. } => { + // Wake up the task waiting on us (if any) + debug!("Stream {:?} finished!", stream); + if let Some((_, waker)) = self.writers.remove_entry(&stream) { + waker.wake() + } + if let (_, Some(sender)) = self.finishers.remove_entry(&stream).expect( + "every write stream is placed in this map, and entries are removed \ + exactly once; qed", + ) { + drop(sender.send(())) + } + if self.finishers.is_empty() { + self.close_waker.take().map(|e| e.wake()); + } + } + Event::Connected => { + debug!("connected!"); + assert!(!self.connection.is_handshaking(), "quinn-proto bug"); + if let Some(w) = self.handshake_waker.take() { + w.wake() + } + } + Event::StreamOpened { dir: Dir::Bi } => { + debug!("stream opened!"); + if let Some(w) = self.accept_waker.take() { + w.wake() + } + } + } + } + keep_going + } +} + +#[derive(Debug)] +pub(super) struct ConnectionDriver { + inner: Arc>, + outgoing_packet: Option, +} + +impl ConnectionDriver { + pub(crate) fn spawn>)>( + endpoint: Arc, + connection: Connection, + handle: ConnectionHandle, + cb: T, + ) -> QuicUpgrade { + let inner = Arc::new(Mutex::new(Muxer::new(endpoint, connection, handle))); + cb(Arc::downgrade(&inner)); + let handle = async_std::task::spawn(Self { + inner: inner.clone(), + outgoing_packet: None, + }); + inner.lock().driver = Some(handle); + QuicUpgrade { + muxer: Some(QuicMuxer(inner)), + } + } +} + +impl Future for ConnectionDriver { + type Output = Result<(), io::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + // cx.waker().wake_by_ref(); + let this = self.get_mut(); + debug!("being polled for timer!"); + let mut inner = this.inner.lock(); + inner.waker = Some(cx.waker().clone()); + let now = Instant::now(); + loop { + let mut needs_timer_update = false; + needs_timer_update |= inner.drive_timer(cx, now); + needs_timer_update |= inner.pre_application_io(now, cx)?; + needs_timer_update |= inner.process_app_events(); + needs_timer_update |= inner.poll_endpoint_events(cx); + if inner.connection.is_drained() { + break Poll::Ready( + match inner + .close_reason + .clone() + .expect("we never have a closed connection with no reason; qed") + { + quinn_proto::ConnectionError::LocallyClosed => { + if needs_timer_update { + debug!("continuing until all events are finished"); + continue; + } else { + debug!("exiting driver"); + Ok(()) + } + } + e @ quinn_proto::ConnectionError::TimedOut => { + Err(io::Error::new(io::ErrorKind::TimedOut, e)) + } + e @ quinn_proto::ConnectionError::Reset => { + Err(io::Error::new(io::ErrorKind::ConnectionReset, e)) + } + e @ quinn_proto::ConnectionError::TransportError { .. } => { + Err(io::Error::new(io::ErrorKind::InvalidData, e)) + } + e @ quinn_proto::ConnectionError::ConnectionClosed { .. } => { + Err(io::Error::new(io::ErrorKind::ConnectionAborted, e)) + } + e => Err(io::Error::new(io::ErrorKind::Other, e)), + }, + ); + } else if !needs_timer_update { + break Poll::Pending; + } + } + } +} diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 46e82ceb075..e4721133be3 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{socket, EndpointMessage, QuicConfig}; +use crate::{connection::EndpointMessage, socket, Config, Upgrade}; use async_macros::ready; use async_std::net::SocketAddr; use futures::{channel::mpsc, prelude::*}; @@ -42,12 +42,11 @@ use std::{ #[derive(Debug)] pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, - muxers: HashMap>>, + muxers: HashMap>>, driver: Option>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, - refcount: u64, } impl EndpointInner { @@ -78,10 +77,10 @@ impl EndpointInner { debug!("we have an event from connection {:?}", handle); match self.muxers.get(&handle).and_then(|e| e.upgrade()) { None => drop(self.muxers.remove(&handle)), - Some(connection) => match self.inner.handle_event(handle, event) { - None => connection.lock().process_endpoint_communication(self), - Some(event) => connection.lock().process_connection_events(self, event), - }, + Some(connection) => { + let event = self.inner.handle_event(handle, event); + connection.lock().process_connection_events(self, event) + } } } } @@ -111,7 +110,7 @@ impl EndpointInner { match self.muxers.get(&handle).and_then(|e| e.upgrade()) { Some(connection) => connection .lock() - .process_connection_events(self, connection_event), + .process_connection_events(self, Some(connection_event)), None => { debug!("lost our connection!"); assert!(self @@ -147,17 +146,16 @@ pub(super) struct EndpointData { inner: Mutex, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. - new_connections: mpsc::UnboundedSender, io::Error>>, + new_connections: mpsc::UnboundedSender, io::Error>>, /// The channel used to receive new connections. - receive_connections: Mutex< - Option, io::Error>>>, - >, + receive_connections: + Mutex, io::Error>>>>, /// Connections send their events to this event_channel: mpsc::Sender, /// The `Multiaddr` address: Multiaddr, /// The configuration - config: QuicConfig, + config: Config, } impl EndpointData { @@ -189,21 +187,6 @@ pub struct Endpoint(Arc); struct EndpointDriver(Arc); -impl Drop for Endpoint { - fn drop(&mut self) { - let mut inner = self.inner(); - let decrement = if self.0.receive_connections.lock().is_some() { - 2 - } else { - 1 - }; - inner.refcount = inner - .refcount - .checked_sub(decrement) - .expect("we do accurate reference counting; qed"); - } -} - impl Endpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { trace!("acquiring lock!"); @@ -212,9 +195,9 @@ impl Endpoint { q } - /// Construct a `Endpoint` with the given `QuicConfig` and `Multiaddr`. + /// Construct a `Endpoint` with the given `Config` and `Multiaddr`. pub fn new( - config: QuicConfig, + config: Config, address: Multiaddr, ) -> Result::Error>> { let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { @@ -237,7 +220,6 @@ impl Endpoint { driver: None, event_receiver, pending: Default::default(), - refcount: 2, }), address: address.clone(), receive_connections: Mutex::new(Some(receive_connections)), @@ -262,12 +244,10 @@ fn create_muxer( connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, -) -> super::QuicMuxer { - let (driver, muxer) = - super::ConnectionDriver::new(super::Muxer::new(endpoint, connection, handle)); - inner.muxers.insert(handle, Arc::downgrade(&muxer)); - (*muxer).lock().driver = Some(async_std::task::spawn(driver)); - super::QuicMuxer(muxer) +) -> Upgrade { + super::connection::ConnectionDriver::spawn(endpoint, connection, handle, |weak| { + inner.muxers.insert(handle, weak); + }) } impl EndpointDriver { @@ -277,12 +257,12 @@ impl EndpointDriver { handle: ConnectionHandle, inner: &mut EndpointInner, ) { - let muxer = create_muxer(self.0.clone(), connection, handle, &mut *inner); + let upgrade = create_muxer(self.0.clone(), connection, handle, &mut *inner); if self .0 .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade: super::QuicUpgrade { muxer: Some(muxer) }, + upgrade, local_addr: self.0.address.clone(), remote_addr: self.0.address.clone(), })) @@ -290,10 +270,6 @@ impl EndpointDriver { { inner.inner.accept(); inner.inner.reject_new_connections(); - inner.refcount = inner - .refcount - .checked_sub(1) - .expect("attempt to decrement below zero"); } } } @@ -319,7 +295,9 @@ impl Future for EndpointDriver { trace!("connection accepted"); match inner.poll_transmit_pending(&this.0.socket, cx)? { Poll::Pending => break Poll::Pending, - Poll::Ready(()) if inner.refcount == 0 => break Poll::Ready(Ok(())), + Poll::Ready(()) if Arc::strong_count(&this.0) == 1 => { + break Poll::Ready(Ok(())) + } Poll::Ready(()) => break Poll::Pending, } } @@ -332,24 +310,24 @@ impl Future for EndpointDriver { #[derive(Debug)] pub struct Listener { reference: Arc, - channel: mpsc::UnboundedReceiver, io::Error>>, + channel: mpsc::UnboundedReceiver, io::Error>>, } impl Unpin for Listener {} impl Stream for Listener { - type Item = Result, io::Error>; + type Item = Result, io::Error>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.get_mut().channel.poll_next_unpin(cx) } } impl Transport for &Endpoint { - type Output = (libp2p_core::PeerId, super::QuicMuxer); + type Output = (libp2p_core::PeerId, super::Muxer); type Error = io::Error; type Listener = Listener; - type ListenerUpgrade = super::QuicUpgrade; - type Dial = super::QuicUpgrade; + type ListenerUpgrade = Upgrade; + type Dial = Upgrade; fn listen_on(self, addr: Multiaddr) -> Result> { if addr != self.0.address { @@ -397,8 +375,7 @@ impl Transport for &Endpoint { TransportError::Other(io::ErrorKind::InvalidInput.into()) }); let (handle, conn) = s?; - let muxer = create_muxer(self.0.clone(), conn, handle, &mut inner); - Ok(super::QuicUpgrade { muxer: Some(muxer) }) + Ok(create_muxer(self.0.clone(), conn, handle, &mut inner)) } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c74081018fb..5b68e4646ce 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -18,21 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the libp2p `Transport` trait for QUIC/UDP/IP. -//! -//! Uses [the *tokio* library](https://tokio.rs). +//! Implementation of the libp2p `Transport` and `StreamMuxer` traits for QUIC/UDP/IP. //! //! # Usage //! //! Example: //! //! ``` -//! use libp2p_quic::{QuicConfig, Endpoint}; +//! use libp2p_quic::{Config, Endpoint}; //! use libp2p_core::Multiaddr; //! //! # fn main() { //! let keypair = libp2p_core::identity::Keypair::generate_ed25519(); -//! let quic_config = QuicConfig::new(&keypair); +//! let quic_config = Config::new(&keypair); //! let quic_endpoint = Endpoint::new( //! quic_config, //! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), @@ -41,7 +39,7 @@ //! # } //! ``` //! -//! The `QuicConfig` structs implements the `Transport` trait of the `swarm` library. See the +//! The `Config` structs implements the `Transport` trait of the `swarm` library. See the //! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. //! //! Note that QUIC provides transport, security, and multiplexing in a single protocol. Therefore, @@ -60,931 +58,15 @@ #![deny(missing_copy_implementations)] #![deny(trivial_casts)] mod certificate; +mod connection; mod endpoint; mod socket; #[cfg(test)] mod tests; mod verifier; -use async_macros::ready; pub use certificate::make_cert; -use endpoint::EndpointInner; -pub use endpoint::{Endpoint, Listener}; -use futures::{ - channel::{mpsc, oneshot}, - prelude::*, +pub use connection::{ + Outbound, QuicConfig as Config, QuicMuxer as Muxer, QuicSubstream as Substream, + QuicUpgrade as Upgrade, }; -use libp2p_core::StreamMuxer; -use log::{debug, error, trace}; -use parking_lot::{Mutex, MutexGuard}; -use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; -use std::{ - collections::HashMap, - io, - mem::replace, - pin::Pin, - sync::Arc, - task::{Context, Poll}, - time::Instant, -}; -#[derive(Debug)] -pub struct QuicSubstream { - id: StreamId, - status: SubstreamStatus, -} - -#[derive(Debug)] -enum SubstreamStatus { - Live, - Finishing(oneshot::Receiver<()>), - Finished, -} - -impl QuicSubstream { - fn new(id: StreamId) -> Self { - let status = SubstreamStatus::Live; - Self { id, status } - } - - fn id(&self) -> StreamId { - self.id - } - - fn is_live(&self) -> bool { - match self.status { - SubstreamStatus::Live => true, - SubstreamStatus::Finishing(_) | SubstreamStatus::Finished => false, - } - } -} - -/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. -/// -/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams -/// obtained by libp2p through the tokio reactor. -#[derive(Debug, Clone)] -pub struct QuicConfig { - /// The client configuration. Quinn provides functions for making one. - pub client_config: quinn_proto::ClientConfig, - /// The server configuration. Quinn provides functions for making one. - pub server_config: Arc, - /// The endpoint configuration - pub endpoint_config: Arc, -} - -fn make_client_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ClientConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - use std::time::Duration; - transport.keep_alive_interval(Some(Duration::from_millis(1000))); - let mut crypto = rustls::ClientConfig::new(); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto.enable_early_data = true; - crypto.set_single_client_cert(vec![certificate], key); - let verifier = verifier::VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; - crypto - .dangerous() - .set_certificate_verifier(Arc::new(verifier)); - quinn_proto::ClientConfig { - transport: Arc::new(transport), - crypto: Arc::new(crypto), - } -} - -fn make_server_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ServerConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt, - )); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto - .set_single_cert(vec![certificate], key) - .expect("we are given a valid cert; qed"); - let mut config = quinn_proto::ServerConfig::default(); - config.transport = Arc::new(transport); - config.crypto = Arc::new(crypto); - config -} - -impl QuicConfig { - /// Creates a new configuration object for TCP/IP. - pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { - let cert = make_cert(&keypair); - let (cert, key) = ( - rustls::Certificate( - cert.serialize_der() - .expect("serialization of a valid cert will succeed; qed"), - ), - rustls::PrivateKey(cert.serialize_private_key_der()), - ); - Self { - client_config: make_client_config(cert.clone(), key.clone()), - server_config: Arc::new(make_server_config(cert, key)), - endpoint_config: Default::default(), - } - } -} - -#[derive(Debug)] -enum EndpointMessage { - ConnectionAccepted, - EndpointEvent { - handle: ConnectionHandle, - event: quinn_proto::EndpointEvent, - }, -} - -#[derive(Debug, Clone)] -pub struct QuicMuxer(Arc>); - -impl QuicMuxer { - fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { - self.0.lock() - } -} - -#[derive(Debug)] -enum OutboundInner { - Complete(Result), - Pending(oneshot::Receiver), - Done, -} - -pub struct Outbound(OutboundInner); - -impl Future for Outbound { - type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = &mut *self; - match this.0 { - OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Poll::Ready(e.map(QuicSubstream::new)), - _ => unreachable!(), - }, - OutboundInner::Pending(ref mut receiver) => { - let result = ready!(receiver.poll_unpin(cx)) - .map(QuicSubstream::new) - .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); - this.0 = OutboundInner::Done; - Poll::Ready(result) - } - OutboundInner::Done => panic!("polled after yielding Ready"), - } - } -} - -impl StreamMuxer for QuicMuxer { - type OutboundSubstream = Outbound; - type Substream = QuicSubstream; - type Error = io::Error; - fn open_outbound(&self) -> Self::OutboundSubstream { - let mut inner = self.inner(); - if let Some(ref e) = inner.close_reason { - Outbound(OutboundInner::Complete(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - )))) - } else if let Some(id) = inner.get_pending_stream() { - Outbound(OutboundInner::Complete(Ok(id))) - } else { - let (sender, receiver) = oneshot::channel(); - inner.connectors.push_front(sender); - inner.wake_driver(); - Outbound(OutboundInner::Pending(receiver)) - } - } - fn destroy_outbound(&self, _: Outbound) {} - fn destroy_substream(&self, substream: Self::Substream) { - let mut inner = self.inner(); - if let Some(waker) = inner.writers.remove(&substream.id()) { - waker.wake(); - } - if let Some(waker) = inner.readers.remove(&substream.id()) { - waker.wake(); - } - drop(inner.connection.finish(substream.id())); - drop( - inner - .connection - .stop_sending(substream.id(), Default::default()), - ) - } - fn is_remote_acknowledged(&self) -> bool { - true - } - - fn poll_inbound(&self, cx: &mut Context) -> Poll> { - debug!("being polled for inbound connections!"); - let mut inner = self.inner(); - if inner.connection.is_drained() { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - inner - .close_reason - .as_ref() - .expect("closed connections always have a reason; qed") - .clone(), - ))); - } - inner.wake_driver(); - match inner.connection.accept(quinn_proto::Dir::Bi) { - None => { - if let Some(waker) = replace(&mut inner.accept_waker, Some(cx.waker().clone())) { - waker.wake() - } - Poll::Pending - } - Some(id) => { - inner.finishers.insert(id, None); - Poll::Ready(Ok(QuicSubstream::new(id))) - } - } - } - - fn write_substream( - &self, - cx: &mut Context, - substream: &mut Self::Substream, - buf: &[u8], - ) -> Poll> { - use quinn_proto::WriteError; - if !substream.is_live() { - return Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())); - } - let mut inner = self.inner(); - debug_assert!( - inner.finishers.get(&substream.id()).is_some(), - "no entry in finishers map for write stream" - ); - inner.wake_driver(); - if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); - } - - assert!( - !inner.connection.is_drained(), - "attempting to write to a drained connection" - ); - match inner.connection.write(substream.id(), buf) { - Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(WriteError::Blocked) => { - if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); - } - inner.writers.insert(substream.id(), cx.waker().clone()); - Poll::Pending - } - Err(WriteError::UnknownStream) => { - panic!("libp2p never uses a closed stream, so this cannot happen; qed") - } - Err(WriteError::Stopped(_)) => { - Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) - } - } - } - - fn poll_outbound( - &self, - cx: &mut Context, - substream: &mut Self::OutboundSubstream, - ) -> Poll> { - substream.poll_unpin(cx) - } - - fn read_substream( - &self, - cx: &mut Context, - substream: &mut Self::Substream, - buf: &mut [u8], - ) -> Poll> { - use quinn_proto::ReadError; - let mut inner = self.inner(); - inner.wake_driver(); - match inner.connection.read(substream.id(), buf) { - Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), - Ok(None) => Poll::Ready(Ok(0)), - Err(ReadError::Blocked) if inner.connection_lost => { - // Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) - Poll::Ready(Ok(0)) - } - Err(ReadError::Blocked) => { - inner.readers.insert(substream.id(), cx.waker().clone()); - Poll::Pending - } - Err(ReadError::UnknownStream) => { - error!("you used a stream that was already closed!"); - Poll::Ready(Ok(0)) - } - Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), - } - } - - fn shutdown_substream( - &self, - cx: &mut Context, - substream: &mut Self::Substream, - ) -> Poll> { - match substream.status { - SubstreamStatus::Finished => return Poll::Ready(Ok(())), - SubstreamStatus::Finishing(ref mut channel) => { - self.inner().wake_driver(); - return channel - .poll_unpin(cx) - .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())); - } - SubstreamStatus::Live => {} - } - let mut inner = self.inner(); - inner.wake_driver(); - inner - .connection - .finish(substream.id()) - .map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => { - io::Error::new( - io::ErrorKind::ConnectionAborted, - quinn_proto::FinishError::UnknownStream, - ); - unreachable!("we checked for this above!") - } - quinn_proto::FinishError::Stopped { .. } => { - io::Error::from(io::ErrorKind::ConnectionReset) - } - })?; - let (sender, mut receiver) = oneshot::channel(); - assert!( - receiver.poll_unpin(cx).is_pending(), - "we haven’t written to the peer yet" - ); - substream.status = SubstreamStatus::Finishing(receiver); - assert!(inner - .finishers - .insert(substream.id(), Some(sender)) - .unwrap() - .is_none()); - Poll::Pending - } - - fn flush_substream( - &self, - _cx: &mut Context, - _substream: &mut Self::Substream, - ) -> Poll> { - Poll::Ready(Ok(())) - } - - fn flush_all(&self, _cx: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn close(&self, cx: &mut Context) -> Poll> { - trace!("close() called"); - let mut inner = self.inner(); - if inner.connection.is_closed() { - return Poll::Ready(Ok(())); - } else if inner.close_reason.is_some() { - return Poll::Ready(Ok(())); - } else if inner.finishers.is_empty() { - inner.shutdown(); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - drop(inner.driver().poll_unpin(cx)); - return Poll::Ready(Ok(())); - } else if inner.close_waker.is_some() { - inner.close_waker = Some(cx.waker().clone()); - return Poll::Pending; - } else { - inner.close_waker = Some(cx.waker().clone()) - } - let Muxer { - ref mut finishers, - ref mut connection, - .. - } = *inner; - finishers.retain(|id, channel| { - if channel.is_some() { - true - } else { - drop(connection.finish(*id)); - true - } - }); - Poll::Pending - } -} - -#[derive(Debug)] -pub struct QuicUpgrade { - muxer: Option, -} - -#[cfg(test)] -impl Drop for QuicUpgrade { - fn drop(&mut self) { - debug!("dropping upgrade!"); - assert!( - self.muxer.is_none(), - "dropped before being polled to completion" - ); - } -} - -impl Future for QuicUpgrade { - type Output = Result<(libp2p_core::PeerId, QuicMuxer), io::Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let muxer = &mut self.get_mut().muxer; - trace!("outbound polling!"); - let res = { - let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if inner.connection.is_closed() { - Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - inner - .close_reason - .as_ref() - .expect("closed connections always have a reason; qed") - .clone(), - ))?; - } else if inner.connection.is_handshaking() { - assert!(!inner.connection.is_closed(), "deadlock"); - inner.handshake_waker = Some(cx.waker().clone()); - return Poll::Pending; - } else if inner.connection.side().is_server() { - ready!(inner.endpoint_channel.poll_ready(cx)) - .expect("we have a reference to the peer; qed"); - inner - .endpoint_channel - .start_send(EndpointMessage::ConnectionAccepted) - .expect("we just checked that we have capacity to send this; qed") - } - - let peer_certificates: Vec = inner - .connection - .crypto_session() - .get_peer_certificates() - .expect("we have finished handshaking, so we have exactly one certificate; qed"); - certificate::verify_libp2p_certificate( - peer_certificates - .get(0) - .expect( - "our certificate verifiers require exactly one \ - certificate to be presented, so an empty certificate \ - chain would already have been rejected; qed", - ) - .as_ref(), - ) - }; - let muxer = muxer.take().expect("polled after yielding Ready"); - Poll::Ready(match res { - Ok(e) => Ok((e, muxer)), - Err(ring::error::Unspecified) => Err(io::ErrorKind::InvalidData.into()), - }) - } -} - -type StreamSenderQueue = std::collections::VecDeque>; - -#[derive(Debug)] -pub struct Muxer { - /// The pending stream, if any. - pending_stream: Option, - /// The associated endpoint - endpoint: Arc, - /// The `quinn_proto::Connection` struct. - connection: Connection, - /// Connection handle - handle: ConnectionHandle, - /// Tasks blocked on writing - writers: HashMap, - /// Tasks blocked on reading - readers: HashMap, - /// Tasks blocked on finishing - finishers: HashMap>>, - /// Task waiting for new connections - handshake_waker: Option, - /// Task waiting for new connections - accept_waker: Option, - /// Tasks waiting to make a connection - connectors: StreamSenderQueue, - /// Pending transmit - pending: Option, - /// The timer being used by this connection - timer: Option, - /// The close reason, if this connection has been lost - close_reason: Option, - /// Waker to wake up the driver - waker: Option, - /// Channel for endpoint events - endpoint_channel: mpsc::Sender, - /// Last timeout - last_timeout: Option, - /// Join handle for the driver - driver: Option>>, - /// Close waker - close_waker: Option, - /// Have we gotten a connection lost event? - connection_lost: bool, -} - -impl Drop for Muxer { - fn drop(&mut self) { - if self.close_reason.is_none() { - self.shutdown() - } - } -} - -impl Drop for QuicMuxer { - fn drop(&mut self) { - let inner = self.inner(); - debug!("dropping muxer with side {:?}", inner.connection.side()); - #[cfg(test)] - assert!( - !inner.connection.is_handshaking(), - "dropped a connection that was still handshaking" - ); - } -} - -impl Muxer { - fn wake_driver(&mut self) { - if let Some(waker) = self.waker.take() { - debug!("driver awoken!"); - waker.wake(); - } - } - - fn driver(&mut self) -> &mut async_std::task::JoinHandle> { - self.driver - .as_mut() - .expect("we don’t call this until the driver is spawned; qed") - } - - fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { - let mut keep_going = false; - loop { - match self.connection.poll_timeout() { - None => { - self.timer = None; - self.last_timeout = None - } - Some(t) if t <= now => { - self.connection.handle_timeout(now); - keep_going = true; - continue; - } - t if t == self.last_timeout => {} - t => { - let delay = t.expect("already checked to be Some; qed") - now; - self.timer = Some(futures_timer::Delay::new(delay)) - } - } - if let Some(ref mut timer) = self.timer { - if timer.poll_unpin(cx).is_ready() { - self.connection.handle_timeout(now); - keep_going = true; - continue; - } - } - break; - } - - keep_going - } - - fn new( - endpoint: Arc, - connection: Connection, - handle: ConnectionHandle, - ) -> Self { - Muxer { - connection_lost: false, - close_waker: None, - last_timeout: None, - pending_stream: None, - connection, - handle, - writers: HashMap::new(), - readers: HashMap::new(), - finishers: HashMap::new(), - accept_waker: None, - handshake_waker: None, - connectors: Default::default(), - endpoint_channel: endpoint.event_channel(), - endpoint: endpoint, - pending: None, - timer: None, - close_reason: None, - waker: None, - driver: None, - } - } - - /// Process all endpoint-facing events for this connection. This is synchronous and will not - /// fail. - fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { - while let Some(endpoint_event) = self.connection.poll_endpoint_events() { - if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { - self.connection.handle_event(connection_event) - } - } - } - - /// Send endpoint events. Returns true if and only if there are endpoint events remaining to - /// be sent. - fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { - let mut keep_going = false; - loop { - match self.endpoint_channel.poll_ready(cx) { - Poll::Pending => break keep_going, - Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), - Poll::Ready(Ok(())) => {} - } - if let Some(event) = self.connection.poll_endpoint_events() { - keep_going = true; - self.endpoint_channel - .start_send(EndpointMessage::EndpointEvent { - handle: self.handle, - event, - }) - .expect("we just checked that we have capacity; qed"); - } else { - break keep_going; - } - } - } - - fn pre_application_io( - &mut self, - now: Instant, - cx: &mut Context<'_>, - ) -> Result { - let mut needs_timer_update = false; - if let Some(transmit) = self.pending.take() { - trace!("trying to send packet!"); - if self.poll_transmit(cx, transmit)? { - return Ok(false); - } - trace!("packet sent!"); - } - while let Some(transmit) = self.connection.poll_transmit(now) { - trace!("trying to send packet!"); - needs_timer_update = true; - if self.poll_transmit(cx, transmit)? { - break; - } - trace!("packet sent!"); - } - Ok(needs_timer_update) - } - - fn poll_transmit( - &mut self, - cx: &mut Context<'_>, - transmit: quinn_proto::Transmit, - ) -> Result { - let res = self.endpoint.socket().poll_send_to(cx, &transmit); - match res { - Poll::Pending => { - self.pending = Some(transmit); - Ok(true) - } - Poll::Ready(Ok(_)) => Ok(false), - Poll::Ready(Err(e)) => Err(e), - } - } - - fn shutdown(&mut self) { - debug!("shutting connection down!"); - if let Some(w) = self.accept_waker.take() { - w.wake() - } - if let Some(w) = self.handshake_waker.take() { - w.wake() - } - for (_, v) in self.writers.drain() { - v.wake(); - } - for (_, v) in self.readers.drain() { - v.wake(); - } - for sender in self.finishers.drain().filter_map(|x| x.1) { - drop(sender.send(())) - } - self.connectors.truncate(0); - if !self.connection.is_closed() { - self.connection - .close(Instant::now(), Default::default(), Default::default()); - self.process_app_events(); - } - self.wake_driver(); - } - - /// Process application events - fn process_connection_events(&mut self, endpoint: &mut EndpointInner, event: ConnectionEvent) { - self.connection.handle_event(event); - self.process_endpoint_communication(endpoint) - } - - fn process_endpoint_communication(&mut self, endpoint: &mut EndpointInner) { - if self.connection.is_drained() { - return; - } - self.send_to_endpoint(endpoint); - self.process_app_events(); - self.wake_driver(); - assert!(self.connection.poll_endpoint_events().is_none()); - assert!(self.connection.poll().is_none()); - } - - fn get_pending_stream(&mut self) -> Option { - self.wake_driver(); - if let Some(id) = self.pending_stream.take() { - Some(id) - } else { - self.connection.open(Dir::Bi) - } - .map(|id| { - self.finishers.insert(id, None); - id - }) - } - - pub fn process_app_events(&mut self) -> bool { - use quinn_proto::Event; - let mut keep_going = false; - 'a: while let Some(event) = self.connection.poll() { - keep_going = true; - match event { - Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { - panic!("we disabled incoming unidirectional streams and datagrams") - } - Event::StreamAvailable { dir: Dir::Uni } => { - panic!("we don’t use unidirectional streams") - } - Event::StreamReadable { stream } => { - trace!("Stream {:?} readable", stream); - // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.readers.remove_entry(&stream) { - waker.wake() - } - } - Event::StreamWritable { stream } => { - trace!("Stream {:?} writable", stream); - // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.writers.remove_entry(&stream) { - waker.wake() - } - } - Event::StreamAvailable { dir: Dir::Bi } => { - trace!("Bidirectional stream available"); - if self.connectors.is_empty() { - // no task to wake up - continue; - } - assert!( - self.pending_stream.is_none(), - "we cannot have both pending tasks and a pending stream; qed" - ); - let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); - while let Some(oneshot) = self.connectors.pop_front() { - match oneshot.send(stream) { - Ok(()) => continue 'a, - Err(_) => {} - } - } - self.pending_stream = Some(stream) - } - Event::ConnectionLost { reason } => { - debug!("lost connection due to {:?}", reason); - assert!(self.connection.is_closed()); - self.close_reason = Some(reason); - self.connection_lost = true; - self.close_waker.take().map(|e| e.wake()); - self.shutdown(); - } - Event::StreamFinished { stream, .. } => { - // Wake up the task waiting on us (if any) - debug!("Stream {:?} finished!", stream); - if let Some((_, waker)) = self.writers.remove_entry(&stream) { - waker.wake() - } - if let (_, Some(sender)) = self.finishers.remove_entry(&stream).expect( - "every write stream is placed in this map, and entries are removed \ - exactly once; qed", - ) { - drop(sender.send(())) - } - if self.finishers.is_empty() { - self.close_waker.take().map(|e| e.wake()); - } - } - Event::Connected => { - debug!("connected!"); - assert!(!self.connection.is_handshaking(), "quinn-proto bug"); - if let Some(w) = self.handshake_waker.take() { - w.wake() - } - } - Event::StreamOpened { dir: Dir::Bi } => { - debug!("stream opened!"); - if let Some(w) = self.accept_waker.take() { - w.wake() - } - } - } - } - keep_going - } -} - -#[derive(Debug)] -struct ConnectionDriver { - inner: Arc>, - endpoint: Arc, - outgoing_packet: Option, -} - -impl Unpin for ConnectionDriver {} - -impl ConnectionDriver { - fn new(muxer: Muxer) -> (Self, Arc>) { - let endpoint = muxer.endpoint.clone(); - let inner = Arc::new(Mutex::new(muxer)); - ( - Self { - inner: inner.clone(), - endpoint, - outgoing_packet: None, - }, - inner, - ) - } -} - -impl Future for ConnectionDriver { - type Output = Result<(), io::Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - // cx.waker().wake_by_ref(); - let this = self.get_mut(); - debug!("being polled for timer!"); - let mut inner = this.inner.lock(); - inner.waker = Some(cx.waker().clone()); - let now = Instant::now(); - loop { - let mut needs_timer_update = false; - needs_timer_update |= inner.drive_timer(cx, now); - needs_timer_update |= inner.pre_application_io(now, cx)?; - needs_timer_update |= inner.process_app_events(); - needs_timer_update |= inner.poll_endpoint_events(cx); - if inner.connection.is_drained() { - break Poll::Ready( - match inner - .close_reason - .clone() - .expect("we never have a closed connection with no reason; qed") - { - quinn_proto::ConnectionError::LocallyClosed => { - if needs_timer_update { - debug!("continuing until all events are finished"); - continue; - } else { - debug!("exiting driver"); - Ok(()) - } - } - e @ quinn_proto::ConnectionError::TimedOut => { - Err(io::Error::new(io::ErrorKind::TimedOut, e)) - } - e @ quinn_proto::ConnectionError::Reset => { - Err(io::Error::new(io::ErrorKind::ConnectionReset, e)) - } - e @ quinn_proto::ConnectionError::TransportError { .. } => { - Err(io::Error::new(io::ErrorKind::InvalidData, e)) - } - e @ quinn_proto::ConnectionError::ConnectionClosed { .. } => { - Err(io::Error::new(io::ErrorKind::ConnectionAborted, e)) - } - e => Err(io::Error::new(io::ErrorKind::Other, e)), - }, - ); - } else if !needs_timer_update { - break Poll::Pending; - } - } - } -} +pub use endpoint::{Endpoint, Listener}; diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 46a759230e9..3b917a6b0da 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -19,17 +19,24 @@ // DEALINGS IN THE SOFTWARE. use super::*; +use async_macros::ready; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, transport::ListenerEvent, - Transport, + StreamMuxer, Transport, +}; +use log::{debug, trace}; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, }; #[derive(Debug)] pub struct QuicStream { - id: Option, - muxer: QuicMuxer, + id: Option, + muxer: Muxer, shutdown: bool, } @@ -84,7 +91,7 @@ impl Drop for QuicStream { } } -impl futures::Stream for QuicMuxer { +impl futures::Stream for Muxer { type Item = QuicStream; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Poll::Ready(Some(QuicStream { @@ -104,7 +111,7 @@ pub(crate) fn init() { ) } -impl Future for QuicMuxer { +impl Future for Muxer { type Output = Result<(), io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { self.get_mut().close(cx) @@ -116,7 +123,7 @@ fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let mut listener = Endpoint::new(QuicConfig::new(&keypair), addr.clone()) + let mut listener = Endpoint::new(Config::new(&keypair), addr.clone()) .expect("endpoint") .listen_on(addr) .expect("listener"); @@ -146,7 +153,6 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { - use super::{trace, StreamMuxer}; init(); let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); @@ -174,7 +180,7 @@ fn communicating_between_dialer_and_listener() { let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" .parse() .expect("bad address?"); - let quic_config = QuicConfig::new(&keypair2); + let quic_config = Config::new(&keypair2); let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); @@ -186,7 +192,7 @@ fn communicating_between_dialer_and_listener() { } ListenerEvent::Upgrade { upgrade, .. } => { log::debug!("got a connection upgrade!"); - let (id, mut muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); + let (id, mut muxer): (_, Muxer) = upgrade.await.expect("upgrade failed"); log::debug!("got a new muxer!"); let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); @@ -222,7 +228,7 @@ fn communicating_between_dialer_and_listener() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); - let quic_config = QuicConfig::new(&keypair); + let quic_config = Config::new(&keypair); let quic_endpoint = Endpoint::new( quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), @@ -269,7 +275,7 @@ fn communicating_between_dialer_and_listener() { fn replace_port_0_in_returned_multiaddr_ipv4() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = QuicConfig::new(&keypair); + let config = Config::new(&keypair); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); @@ -290,7 +296,7 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { fn replace_port_0_in_returned_multiaddr_ipv6() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = QuicConfig::new(&keypair); + let config = Config::new(&keypair); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); @@ -310,7 +316,7 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { fn larger_addr_denied() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = QuicConfig::new(&keypair); + let config = Config::new(&keypair); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); From b2564d454f80f7df61b6973da817153ee41e5e5b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 28 Jan 2020 10:58:21 -0500 Subject: [PATCH 071/202] =?UTF-8?q?Don=E2=80=99t=20rely=20on=20a=20fork=20?= =?UTF-8?q?of=20`async-std`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fork won’t be merged upstream, and `futures::pin_mut` makes it unnecessary. --- Cargo.toml | 2 +- core/Cargo.toml | 2 +- misc/mdns/Cargo.toml | 2 +- misc/rw-stream-sink/Cargo.toml | 2 +- muxers/mplex/Cargo.toml | 2 +- protocols/deflate/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/ping/Cargo.toml | 2 +- protocols/secio/Cargo.toml | 2 +- transports/quic/Cargo.toml | 2 +- transports/quic/src/lib.rs | 2 +- transports/quic/src/socket.rs | 17 +++++++++++------ transports/tcp/Cargo.toml | 2 +- transports/uds/Cargo.toml | 2 +- 14 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b44b8d56b74..192ad2866e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ libp2p-tcp = { version = "0.14.0-alpha.1", path = "transports/tcp" } libp2p-websocket = { version = "0.14.0-alpha.1", path = "transports/websocket", optional = true } [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" env_logger = "0.7.1" [workspace] diff --git a/core/Cargo.toml b/core/Cargo.toml index 68201658fe3..bbb5c5fa370 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -40,7 +40,7 @@ untrusted = "0.7.0" [dev-dependencies] assert_matches = "1.3" -async-std = "1.0" +async-std = "1.4.0" libp2p-mplex = { version = "0.14.0-alpha.1", path = "../muxers/mplex" } libp2p-secio = { version = "0.14.0-alpha.1", path = "../protocols/secio" } libp2p-swarm = { version = "0.4.0-alpha.1", path = "../swarm" } diff --git a/misc/mdns/Cargo.toml b/misc/mdns/Cargo.toml index 9b9a6836e12..30616d91c9c 100644 --- a/misc/mdns/Cargo.toml +++ b/misc/mdns/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" data-encoding = "2.0" dns-parser = "0.8" either = "1.5.3" diff --git a/misc/rw-stream-sink/Cargo.toml b/misc/rw-stream-sink/Cargo.toml index bc21624856f..0fa345aab5d 100644 --- a/misc/rw-stream-sink/Cargo.toml +++ b/misc/rw-stream-sink/Cargo.toml @@ -15,4 +15,4 @@ pin-project = "0.4.6" static_assertions = "1" [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index a5396599de3..2b3efe08b70 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -20,5 +20,5 @@ parking_lot = "0.10" unsigned-varint = { version = "0.3", features = ["futures-codec"] } [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" libp2p-tcp = { version = "0.14.0-alpha.1", path = "../../transports/tcp" } diff --git a/protocols/deflate/Cargo.toml b/protocols/deflate/Cargo.toml index fa061195e54..34554db74be 100644 --- a/protocols/deflate/Cargo.toml +++ b/protocols/deflate/Cargo.toml @@ -15,7 +15,7 @@ libp2p-core = { version = "0.14.0-alpha.1", path = "../../core" } flate2 = "1.0" [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" env_logger = "0.7.1" libp2p-tcp = { version = "0.14.0-alpha.1", path = "../../transports/tcp" } rand = "0.7" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index cf2e7124c7d..9428c97a171 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -19,7 +19,7 @@ smallvec = "1.0" wasm-timer = "0.2" [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" libp2p-mplex = { version = "0.14.0-alpha.1", path = "../../muxers/mplex" } libp2p-secio = { version = "0.14.0-alpha.1", path = "../../protocols/secio" } libp2p-tcp = { version = "0.14.0-alpha.1", path = "../../transports/tcp" } diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index d8a6321a176..71823e170aa 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -19,7 +19,7 @@ void = "1.0" wasm-timer = "0.2" [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" libp2p-tcp = { version = "0.14.0-alpha.1", path = "../../transports/tcp" } libp2p-secio = { version = "0.14.0-alpha.1", path = "../../protocols/secio" } libp2p-yamux = { version = "0.14.0-alpha.1", path = "../../muxers/yamux" } diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index 54516ba451d..702d4a5ec10 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -46,7 +46,7 @@ secp256k1 = [] aes-all = ["aesni"] [dev-dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" criterion = "0.3" libp2p-mplex = { version = "0.14.0-alpha.1", path = "../../muxers/mplex" } libp2p-tcp = { version = "0.14.0-alpha.1", path = "../../transports/tcp" } diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index e7aabef9e8e..08842761a15 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -34,7 +34,7 @@ err-derive = "0.2.1" futures = { version = "0.3.1", package = "futures", features = ["compat"] } futures-core = "0.3.1" quinn-proto = { git = "https://github.com/djc/quinn" } -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" async-macros = "2.0.0" futures-timer = "2.0.2" env_logger = "0.7.1" diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 5b68e4646ce..5b3aaab626b 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -54,7 +54,7 @@ //! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid(unused_must_use, unstable_features, warnings)] +#![forbid(unused_must_use, unstable_features, warnings, unsafe_code)] #![deny(missing_copy_implementations)] #![deny(trivial_casts)] mod certificate; diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 8f3bb2af2b3..5db07287743 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -25,7 +25,7 @@ use async_std::net::UdpSocket; use log::{trace, warn}; use quinn_proto::Transmit; -use std::{io::Result, task::Context, task::Poll}; +use std::{future::Future, io::Result, task::Context, task::Poll}; /// A pending packet for libp2p-quic #[derive(Debug, Default)] @@ -42,10 +42,11 @@ pub(crate) struct Socket { impl Socket { /// Transmit a packet if possible, with appropriate logging. pub fn poll_send_to(&self, cx: &mut Context, packet: &Transmit) -> Poll> { - match self - .socket - .poll_send_to(cx, &packet.contents, &packet.destination) - { + match { + let fut = self.socket.send_to(&packet.contents, &packet.destination); + futures::pin_mut!(fut); + fut.poll(cx) + } { Poll::Pending => { trace!("not able to send packet right away"); return Poll::Pending; @@ -68,7 +69,11 @@ impl Socket { buf: &mut [u8], ) -> Poll> { loop { - match self.socket.poll_recv_from(cx, buf) { + match { + let fut = self.socket.recv_from(buf); + futures::pin_mut!(fut); + fut.poll(cx) + } { Poll::Pending => { trace!("no packets available yet"); break Poll::Pending; diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 8cdf0b27c26..e687f8ee3c5 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std = "1.0" +async-std = "1.4.0" futures = "0.3.1" futures-timer = "2.0.2" get_if_addrs = "0.5.3" diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index 075ba160c01..2710ee8f53b 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [target.'cfg(all(unix, not(any(target_os = "emscripten", target_os = "unknown"))))'.dependencies] -async-std = { git = "https://github.com/paritytech/async-std", branch = "poll-recv-send-udp" } +async-std = "1.4.0" libp2p-core = { version = "0.14.0-alpha.1", path = "../../core" } log = "0.4.1" futures = "0.3.1" From 3d73033d370e38bb60f0ac49006014080ea5b13a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 13:26:45 -0500 Subject: [PATCH 072/202] Avoid a panic and work around a quinn-proto bug It is possible to get multiple `drained` events, so the endpoint must not panic in that case. Furthermore, due to https://github.com/djc/quinn-proto/issues/604, quinn-proto can spuriously return `Err(ReadError::Blocked)` instead of `Ok(None)` when reading from a finished stream in a closed connection. Work around that. --- transports/quic/Cargo.toml | 2 +- transports/quic/src/connection.rs | 276 ++++++++++++++++-------------- transports/quic/src/endpoint.rs | 43 +++-- transports/quic/src/lib.rs | 2 + transports/quic/src/tests.rs | 8 +- 5 files changed, 177 insertions(+), 154 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 08842761a15..a6386f85e61 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -30,7 +30,7 @@ rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.14.0-alpha.1" } log = "0.4.8" ipnet = "2.1.0" -err-derive = "0.2.1" +err-derive = "0.2.2" futures = { version = "0.3.1", package = "futures", features = ["compat"] } futures-core = "0.3.1" quinn-proto = { git = "https://github.com/djc/quinn" } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index df5ff9fff20..792ff7f1bc6 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -20,6 +20,7 @@ use super::{ certificate, endpoint::{EndpointData, EndpointInner}, + error::Error, verifier, }; use async_macros::ready; @@ -28,7 +29,7 @@ use futures::{ prelude::*, }; use libp2p_core::StreamMuxer; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; use std::{ @@ -59,10 +60,6 @@ impl QuicSubstream { Self { id, status } } - fn id(&self) -> StreamId { - self.id - } - fn is_live(&self) -> bool { match self.status { SubstreamStatus::Live => true, @@ -167,7 +164,7 @@ impl QuicMuxer { #[derive(Debug)] enum OutboundInner { - Complete(Result), + Complete(Result), Pending(oneshot::Receiver), Done, } @@ -175,7 +172,7 @@ enum OutboundInner { pub struct Outbound(OutboundInner); impl Future for Outbound { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = &mut *self; match this.0 { @@ -186,7 +183,7 @@ impl Future for Outbound { OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) .map(QuicSubstream::new) - .map_err(|oneshot::Canceled| io::ErrorKind::ConnectionAborted.into()); + .map_err(|oneshot::Canceled| Error::ConnectionLost); this.0 = OutboundInner::Done; Poll::Ready(result) } @@ -198,12 +195,11 @@ impl Future for Outbound { impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; type Substream = QuicSubstream; - type Error = io::Error; + type Error = crate::error::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { - Outbound(OutboundInner::Complete(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, + Outbound(OutboundInner::Complete(Err(Error::ConnectionError( e.clone(), )))) } else if let Some(id) = inner.get_pending_stream() { @@ -218,17 +214,21 @@ impl StreamMuxer for QuicMuxer { fn destroy_outbound(&self, _: Outbound) {} fn destroy_substream(&self, substream: Self::Substream) { let mut inner = self.inner(); - if let Some(waker) = inner.writers.remove(&substream.id()) { + if let Some(waker) = inner.writers.remove(&substream.id) { waker.wake(); } - if let Some(waker) = inner.readers.remove(&substream.id()) { + if let Some(waker) = inner.readers.remove(&substream.id) { waker.wake(); } - drop(inner.connection.finish(substream.id())); + if substream.is_live() && inner.close_reason.is_none() { + if let Err(e) = inner.connection.finish(substream.id) { + warn!("Error closing stream: {}", e); + } + } drop( inner .connection - .stop_sending(substream.id(), Default::default()), + .stop_sending(substream.id, Default::default()), ) } fn is_remote_acknowledged(&self) -> bool { @@ -239,13 +239,11 @@ impl StreamMuxer for QuicMuxer { debug!("being polled for inbound connections!"); let mut inner = self.inner(); if inner.connection.is_drained() { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, + return Poll::Ready(Err(Error::ConnectionError( inner .close_reason - .as_ref() - .expect("closed connections always have a reason; qed") - .clone(), + .clone() + .expect("closed connections always have a reason; qed"), ))); } inner.wake_driver(); @@ -271,42 +269,49 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::WriteError; if !substream.is_live() { - return Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())); + error!( + "The application used stream {:?} after it was no longer live", + substream.id + ); + return Poll::Ready(Err(Error::ExpiredStream)); } let mut inner = self.inner(); debug_assert!( - inner.finishers.get(&substream.id()).is_some(), + inner.finishers.get(&substream.id).is_some(), "no entry in finishers map for write stream" ); inner.wake_driver(); if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); + return Poll::Ready(Err(Error::ConnectionError(e.clone()))); } - assert!( !inner.connection.is_drained(), "attempting to write to a drained connection" ); - match inner.connection.write(substream.id(), buf) { + match inner.connection.write(substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(WriteError::Blocked) => { if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::ConnectionAborted, - e.clone(), - ))); + return Poll::Ready(Err(Error::ConnectionError(e.clone()))); + } + if let Some(w) = inner.writers.insert(substream.id, cx.waker().clone()) { + w.wake(); } - inner.writers.insert(substream.id(), cx.waker().clone()); Poll::Pending } Err(WriteError::UnknownStream) => { - panic!("libp2p never uses a closed stream, so this cannot happen; qed") + error!( + "The application used a stream that has already been closed. This is a bug." + ); + Poll::Ready(Err(Error::ExpiredStream)) } - Err(WriteError::Stopped(_)) => { - Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + Err(WriteError::Stopped(e)) => { + inner.finishers.remove(&substream.id); + if let Some(w) = inner.writers.remove(&substream.id) { + w.wake() + } + substream.status = SubstreamStatus::Finished; + Poll::Ready(Err(Error::Stopped(e))) } } } @@ -328,22 +333,45 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::ReadError; let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.read(substream.id(), buf) { + match inner.connection.read(substream.id, buf) { Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), Ok(None) => Poll::Ready(Ok(0)), - Err(ReadError::Blocked) if inner.connection_lost => { - // Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())) - Poll::Ready(Ok(0)) - } - Err(ReadError::Blocked) => { - inner.readers.insert(substream.id(), cx.waker().clone()); - Poll::Pending - } + Err(ReadError::Blocked) => match &inner.close_reason { + None => { + trace!( + "Blocked on reading stream {:?} with side {:?}", + substream.id, + inner.connection.side() + ); + if let Some(w) = inner.readers.insert(substream.id, cx.waker().clone()) { + w.wake() + } + Poll::Pending + } + // KLUDGE: this is a workaround for https://github.com/djc/quinn/issues/604. + Some(quinn_proto::ConnectionError::ApplicationClosed( + quinn_proto::ApplicationClose { error_code, reason }, + )) if error_code.into_inner() == 0 && reason.is_empty() => { + warn!("This should not happen, but a quinn-proto bug causes it to happen"); + if let Some(w) = inner.readers.remove(&substream.id) { + w.wake() + } + Poll::Ready(Ok(0)) + } + Some(error) => Poll::Ready(Err(Error::ConnectionError(error.clone()))), + }, Err(ReadError::UnknownStream) => { - error!("you used a stream that was already closed!"); - Poll::Ready(Ok(0)) + error!( + "The application used a stream that has already been closed. This is a bug." + ); + Poll::Ready(Err(Error::ExpiredStream)) + } + Err(ReadError::Reset(e)) => { + if let Some(w) = inner.readers.remove(&substream.id) { + w.wake() + } + Poll::Ready(Err(Error::Reset(e))) } - Err(ReadError::Reset(_)) => Poll::Ready(Err(io::ErrorKind::ConnectionReset.into())), } } @@ -356,40 +384,30 @@ impl StreamMuxer for QuicMuxer { SubstreamStatus::Finished => return Poll::Ready(Ok(())), SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); - return channel - .poll_unpin(cx) - .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())); + return channel.poll_unpin(cx).map_err(|e| { + Error::IO(io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())) + }); } SubstreamStatus::Live => {} } let mut inner = self.inner(); inner.wake_driver(); - inner - .connection - .finish(substream.id()) - .map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => { - io::Error::new( - io::ErrorKind::ConnectionAborted, - quinn_proto::FinishError::UnknownStream, - ); - unreachable!("we checked for this above!") - } - quinn_proto::FinishError::Stopped { .. } => { - io::Error::from(io::ErrorKind::ConnectionReset) - } - })?; + inner.connection.finish(substream.id).map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => unreachable!("we checked for this above!"), + quinn_proto::FinishError::Stopped(e) => Error::Stopped(e), + })?; let (sender, mut receiver) = oneshot::channel(); assert!( receiver.poll_unpin(cx).is_pending(), "we haven’t written to the peer yet" ); substream.status = SubstreamStatus::Finishing(receiver); - assert!(inner - .finishers - .insert(substream.id(), Some(sender)) - .unwrap() - .is_none()); + match inner.finishers.insert(substream.id, Some(sender)) { + Some(None) => {} + _ => unreachable!( + "We inserted a None value earlier; and haven’t touched this entry since; qed" + ), + } Poll::Pending } @@ -413,7 +431,7 @@ impl StreamMuxer for QuicMuxer { } else if inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.finishers.is_empty() { - inner.shutdown(); + inner.shutdown(0); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); drop(inner.driver().poll_unpin(cx)); return Poll::Ready(Ok(())); @@ -424,18 +442,18 @@ impl StreamMuxer for QuicMuxer { inner.close_waker = Some(cx.waker().clone()) } let Muxer { - ref mut finishers, - ref mut connection, + finishers, + connection, .. - } = *inner; - finishers.retain(|id, channel| { - if channel.is_some() { - true - } else { - drop(connection.finish(*id)); - true + } = &mut *inner; + for (id, channel) in finishers { + if channel.is_none() { + match connection.finish(*id) { + Ok(()) => {} + Err(error) => warn!("Finishing stream {:?} failed: {}", id, error), + } } - }); + } Poll::Pending } } @@ -457,21 +475,19 @@ impl Drop for QuicUpgrade { } impl Future for QuicUpgrade { - type Output = Result<(libp2p_core::PeerId, QuicMuxer), io::Error>; + type Output = Result<(libp2p_core::PeerId, QuicMuxer), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); if inner.connection.is_closed() { - Err(io::Error::new( - io::ErrorKind::ConnectionAborted, + return Poll::Ready(Err(Error::ConnectionError( inner .close_reason - .as_ref() - .expect("closed connections always have a reason; qed") - .clone(), - ))?; + .clone() + .expect("closed connections always have a reason; qed"), + ))); } else if inner.connection.is_handshaking() { assert!(!inner.connection.is_closed(), "deadlock"); inner.handshake_waker = Some(cx.waker().clone()); @@ -504,7 +520,7 @@ impl Future for QuicUpgrade { let muxer = muxer.take().expect("polled after yielding Ready"); Poll::Ready(match res { Ok(e) => Ok((e, muxer)), - Err(ring::error::Unspecified) => Err(io::ErrorKind::InvalidData.into()), + Err(ring::error::Unspecified) => Err(Error::BadCertificate(ring::error::Unspecified)), }) } } @@ -546,17 +562,19 @@ pub(crate) struct Muxer { /// Last timeout last_timeout: Option, /// Join handle for the driver - driver: Option>>, + driver: Option>>, /// Close waker close_waker: Option, /// Have we gotten a connection lost event? connection_lost: bool, } +const RESET: u32 = 1; + impl Drop for Muxer { fn drop(&mut self) { if self.close_reason.is_none() { - self.shutdown() + self.shutdown(RESET) } } } @@ -581,7 +599,7 @@ impl Muxer { } } - fn driver(&mut self) -> &mut async_std::task::JoinHandle> { + fn driver(&mut self) -> &mut async_std::task::JoinHandle> { self.driver .as_mut() .expect("we don’t call this until the driver is spawned; qed") @@ -677,11 +695,7 @@ impl Muxer { } } - fn pre_application_io( - &mut self, - now: Instant, - cx: &mut Context<'_>, - ) -> Result { + fn pre_application_io(&mut self, now: Instant, cx: &mut Context<'_>) -> Result { let mut needs_timer_update = false; if let Some(transmit) = self.pending.take() { trace!("trying to send packet!"); @@ -705,7 +719,7 @@ impl Muxer { &mut self, cx: &mut Context<'_>, transmit: quinn_proto::Transmit, - ) -> Result { + ) -> Result { let res = self.endpoint.socket().poll_send_to(cx, &transmit); match res { Poll::Pending => { @@ -713,11 +727,11 @@ impl Muxer { Ok(true) } Poll::Ready(Ok(_)) => Ok(false), - Poll::Ready(Err(e)) => Err(e), + Poll::Ready(Err(e)) => Err(e.into()), } } - fn shutdown(&mut self) { + fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); if let Some(w) = self.accept_waker.take() { w.wake() @@ -736,8 +750,11 @@ impl Muxer { } self.connectors.truncate(0); if !self.connection.is_closed() { - self.connection - .close(Instant::now(), Default::default(), Default::default()); + self.connection.close( + Instant::now(), + quinn_proto::VarInt::from_u32(error_code), + Default::default(), + ); self.process_app_events(); } self.wake_driver(); @@ -788,21 +805,32 @@ impl Muxer { panic!("we don’t use unidirectional streams") } Event::StreamReadable { stream } => { - trace!("Stream {:?} readable", stream); + trace!( + "Stream {:?} readable for side {:?}", + stream, + self.connection.side() + ); // Wake up the task waiting on us (if any) if let Some((_, waker)) = self.readers.remove_entry(&stream) { waker.wake() } } Event::StreamWritable { stream } => { - trace!("Stream {:?} writable", stream); + trace!( + "Stream {:?} writable for side {:?}", + stream, + self.connection.side() + ); // Wake up the task waiting on us (if any) if let Some((_, waker)) = self.writers.remove_entry(&stream) { waker.wake() } } Event::StreamAvailable { dir: Dir::Bi } => { - trace!("Bidirectional stream available"); + trace!( + "Bidirectional stream available for side {:?}", + self.connection.side() + ); if self.connectors.is_empty() { // no task to wake up continue; @@ -827,15 +855,23 @@ impl Muxer { self.close_reason = Some(reason); self.connection_lost = true; self.close_waker.take().map(|e| e.wake()); - self.shutdown(); + self.shutdown(0); } - Event::StreamFinished { stream, .. } => { + Event::StreamFinished { + stream, + stop_reason, + } => { // Wake up the task waiting on us (if any) - debug!("Stream {:?} finished!", stream); - if let Some((_, waker)) = self.writers.remove_entry(&stream) { + trace!( + "Stream {:?} finished for side {:?} because of {:?}", + stream, + self.connection.side(), + stop_reason + ); + if let Some(waker) = self.writers.remove(&stream) { waker.wake() } - if let (_, Some(sender)) = self.finishers.remove_entry(&stream).expect( + if let Some(sender) = self.finishers.remove(&stream).expect( "every write stream is placed in this map, and entries are removed \ exactly once; qed", ) { @@ -853,7 +889,7 @@ impl Muxer { } } Event::StreamOpened { dir: Dir::Bi } => { - debug!("stream opened!"); + debug!("stream opened for side {:?}", self.connection.side()); if let Some(w) = self.accept_waker.take() { w.wake() } @@ -891,7 +927,7 @@ impl ConnectionDriver { } impl Future for ConnectionDriver { - type Output = Result<(), io::Error>; + type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { // cx.waker().wake_by_ref(); let this = self.get_mut(); @@ -921,19 +957,7 @@ impl Future for ConnectionDriver { Ok(()) } } - e @ quinn_proto::ConnectionError::TimedOut => { - Err(io::Error::new(io::ErrorKind::TimedOut, e)) - } - e @ quinn_proto::ConnectionError::Reset => { - Err(io::Error::new(io::ErrorKind::ConnectionReset, e)) - } - e @ quinn_proto::ConnectionError::TransportError { .. } => { - Err(io::Error::new(io::ErrorKind::InvalidData, e)) - } - e @ quinn_proto::ConnectionError::ConnectionClosed { .. } => { - Err(io::Error::new(io::ErrorKind::ConnectionAborted, e)) - } - e => Err(io::Error::new(io::ErrorKind::Other, e)), + e => Err(e.into()), }, ); } else if !needs_timer_update { diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index e4721133be3..a4942b14a53 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{connection::EndpointMessage, socket, Config, Upgrade}; +use crate::{connection::EndpointMessage, error::Error, socket, Config, Upgrade}; use async_macros::ready; use async_std::net::SocketAddr; use futures::{channel::mpsc, prelude::*}; @@ -32,7 +32,6 @@ use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionHandle}; use std::{ collections::HashMap, - io, pin::Pin, sync::{Arc, Weak}, task::{Context, Poll}, @@ -43,7 +42,7 @@ use std::{ pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, - driver: Option>>, + driver: Option>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, @@ -57,9 +56,7 @@ impl EndpointInner { ) -> Option { if event.is_drained() { let res = self.inner.handle_event(handle, event); - self.muxers - .remove_entry(&handle) - .expect("cannot close a connection that did not exist"); + self.muxers.remove(&handle); res } else { self.inner.handle_event(handle, event) @@ -91,7 +88,7 @@ impl EndpointInner { &mut self, socket: &socket::Socket, cx: &mut Context, - ) -> Poll> { + ) -> Poll> { use quinn_proto::DatagramEvent; let mut buf = vec![0; 65535]; loop { @@ -132,9 +129,11 @@ impl EndpointInner { &mut self, socket: &socket::Socket, cx: &mut Context, - ) -> Poll> { + ) -> Poll> { let Self { inner, pending, .. } = self; - pending.send_packet(cx, socket, &mut || inner.poll_transmit()) + pending + .send_packet(cx, socket, &mut || inner.poll_transmit()) + .map_err(Error::IO) } } @@ -146,10 +145,10 @@ pub(super) struct EndpointData { inner: Mutex, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. - new_connections: mpsc::UnboundedSender, io::Error>>, + new_connections: mpsc::UnboundedSender, Error>>, /// The channel used to receive new connections. receive_connections: - Mutex, io::Error>>>>, + Mutex, Error>>>>, /// Connections send their events to this event_channel: mpsc::Sender, /// The `Multiaddr` @@ -206,7 +205,9 @@ impl Endpoint { return Err(TransportError::MultiaddrNotSupported(address)); }; // NOT blocking, as per man:bind(2), as we pass an IP address. - let socket = std::net::UdpSocket::bind(&socket_addr)?.into(); + let socket = std::net::UdpSocket::bind(&socket_addr) + .map_err(Error::IO)? + .into(); let (new_connections, receive_connections) = mpsc::unbounded(); let (event_channel, event_receiver) = mpsc::channel(0); let return_value = Self(Arc::new(EndpointData { @@ -275,8 +276,8 @@ impl EndpointDriver { } impl Future for EndpointDriver { - type Output = Result<(), io::Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + type Output = Result<(), Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.get_mut(); loop { let mut inner = this.0.inner.lock(); @@ -310,13 +311,13 @@ impl Future for EndpointDriver { #[derive(Debug)] pub struct Listener { reference: Arc, - channel: mpsc::UnboundedReceiver, io::Error>>, + channel: mpsc::UnboundedReceiver, Error>>, } impl Unpin for Listener {} impl Stream for Listener { - type Item = Result, io::Error>; + type Item = Result, Error>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.get_mut().channel.poll_next_unpin(cx) } @@ -324,7 +325,7 @@ impl Stream for Listener { impl Transport for &Endpoint { type Output = (libp2p_core::PeerId, super::Muxer); - type Error = io::Error; + type Error = Error; type Listener = Listener; type ListenerUpgrade = Upgrade; type Dial = Upgrade; @@ -337,7 +338,7 @@ impl Transport for &Endpoint { .receive_connections .lock() .take() - .ok_or_else(|| TransportError::Other(io::ErrorKind::AlreadyExists.into()))?; + .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; let mut inner = self.inner(); let reference = self.0.clone(); if inner.driver.is_none() { @@ -350,9 +351,7 @@ impl Transport for &Endpoint { let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::Other( - io::ErrorKind::ConnectionRefused.into(), - )); + return Err(TransportError::MultiaddrNotSupported(addr)); } socket_addr } else { @@ -372,7 +371,7 @@ impl Transport for &Endpoint { ) .map_err(|e| { warn!("Connection error: {:?}", e); - TransportError::Other(io::ErrorKind::InvalidInput.into()) + TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; Ok(create_muxer(self.0.clone(), conn, handle, &mut inner)) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 5b3aaab626b..0d2490007bd 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -60,9 +60,11 @@ mod certificate; mod connection; mod endpoint; +mod error; mod socket; #[cfg(test)] mod tests; +pub use error::Error; mod verifier; pub use certificate::make_cert; pub use connection::{ diff --git a/transports/quic/src/tests.rs b/transports/quic/src/tests.rs index 3b917a6b0da..2b6c031f673 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/src/tests.rs @@ -51,6 +51,7 @@ impl AsyncWrite for QuicStream { inner .muxer .write_substream(cx, inner.id.as_mut().unwrap(), buf) + .map_err(From::from) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -79,6 +80,7 @@ impl AsyncRead for QuicStream { inner .muxer .read_substream(cx, inner.id.as_mut().unwrap(), buf) + .map_err(From::from) } } @@ -112,7 +114,7 @@ pub(crate) fn init() { } impl Future for Muxer { - type Output = Result<(), io::Error>; + type Output = Result<(), crate::error::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { self.get_mut().close(cx) } @@ -221,10 +223,6 @@ fn communicating_between_dialer_and_listener() { } } }); - #[cfg(any())] - let _join = BlockJoin { - handle: Some(_handle), - }; let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); From 5e479bd27308231c755c00b44f8e53705b5b2c47 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 13:50:21 -0500 Subject: [PATCH 073/202] Remove unnecessary #[allow(dead_code)] --- transports/quic/src/certificate.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index ef1d1885b7c..dfb986182f8 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -105,20 +105,14 @@ fn read_algid(reader: &mut yasna::BERReaderSeq) -> Result>, } -#[cfg_attr(not(test), allow(dead_code))] #[derive(Debug)] pub struct X509Certificate { - #[cfg_attr(test, allow(dead_code))] tbs_certificate: Vec, - #[cfg_attr(test, allow(dead_code))] algorithm: AlgorithmIdentifier, - #[cfg_attr(test, allow(dead_code))] signature_value: Vec, } @@ -211,7 +205,6 @@ fn compute_signature_algorithm( key: &[u8], alg: &AlgorithmIdentifier, ) -> yasna::ASN1Result<&'static dyn ring::signature::VerificationAlgorithm> { - #![cfg_attr(not(test), allow(dead_code))] Ok(match (key.len(), &**alg.algorithm.components()) { (32, &[1, 3, 101, 111]) => &ring::signature::ED25519, (33, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P256_SHA256_ASN1, @@ -259,7 +252,6 @@ fn compute_signature_algorithm( }) } -#[cfg_attr(not(test), allow(dead_code))] fn parse_certificate( certificate: &[u8], ) -> yasna::ASN1Result<(X509Certificate, Vec, identity::PublicKey)> { @@ -324,7 +316,6 @@ fn parse_certificate( pub fn verify_libp2p_certificate( certificate: &[u8], ) -> Result { - #![cfg_attr(not(test), allow(dead_code))] let (raw_certificate, certificate_key, identity_key): (_, Vec, _) = parse_certificate(certificate).map_err(|e| { log::debug!("error in parsing: {:?}", e); From 01dca1d51df7e40beeba5a82a83dbd71472e4688 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 14:22:07 -0500 Subject: [PATCH 074/202] Handle a critical Basic Constraints extension We require certificates used in libp2p to not be certificate authorities. --- transports/quic/src/certificate.rs | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index dfb986182f8..152c3f1733d 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -26,8 +26,9 @@ //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. use log::{debug, trace, warn}; -pub const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; -pub const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +const BASIC_CONSTRAINTS_OID: &[u64] = &[2, 5, 29, 19]; +const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; +const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &'static rcgen::SignatureAlgorithm = @@ -139,6 +140,7 @@ fn parse_x509_extension( certificate_key: &[u8], oids_seen: &mut std::collections::HashSet, ) -> Result, yasna::ASN1Error> { + enum Void {} reader.read_sequence(|reader| { let oid = reader.next().read_oid()?; trace!("read extensions with oid {:?}", oid); @@ -159,15 +161,27 @@ fn parse_x509_extension( })?; trace!("certificate critical? {}", is_critical); let contents = reader.next().read_bytes()?; - if *oid.components() != LIBP2P_OID { - if is_critical { - // unknown critical extension - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } else { - Ok(None) + match &**oid.components() { + LIBP2P_OID => Ok(Some(verify_libp2p_extension(&contents, certificate_key)?)), + BASIC_CONSTRAINTS_OID => yasna::parse_der(&contents, |reader| { + reader.read_sequence(|reader| { + reader.read_optional(|_| { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + }) + }) + }) + .map(|e: Option| match e { + Some(bad) => match bad {}, + None => None, + }), + _ => { + if is_critical { + // unknown critical extension + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) + } else { + Ok(None) + } } - } else { - Ok(Some(verify_libp2p_extension(&contents, certificate_key)?)) } }) } From 2f33051832b7fa1af2f9aebb12134150a6100c92 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 16:08:20 -0500 Subject: [PATCH 075/202] Let WebPKI verify the certificate self-signature This adds support for verifying notBefore and notAfter fields on the certificate, and also removes a bunch of ugly code. --- transports/quic/src/certificate.rs | 239 +++++++++++++---------------- transports/quic/src/connection.rs | 27 +++- 2 files changed, 125 insertions(+), 141 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 152c3f1733d..9a183a077ad 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -26,6 +26,23 @@ //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. use log::{debug, trace, warn}; +use quinn_proto::Side; +static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &'static [&'static webpki::SignatureAlgorithm] = { + &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::ED25519, + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + ] +}; const BASIC_CONSTRAINTS_OID: &[u64] = &[2, 5, 29, 19]; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -140,7 +157,6 @@ fn parse_x509_extension( certificate_key: &[u8], oids_seen: &mut std::collections::HashSet, ) -> Result, yasna::ASN1Error> { - enum Void {} reader.read_sequence(|reader| { let oid = reader.next().read_oid()?; trace!("read extensions with oid {:?}", oid); @@ -164,16 +180,9 @@ fn parse_x509_extension( match &**oid.components() { LIBP2P_OID => Ok(Some(verify_libp2p_extension(&contents, certificate_key)?)), BASIC_CONSTRAINTS_OID => yasna::parse_der(&contents, |reader| { - reader.read_sequence(|reader| { - reader.read_optional(|_| { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - }) - }) + reader.read_sequence(|reader| reader.read_optional(|reader| reader.read_bool())) }) - .map(|e: Option| match e { - Some(bad) => match bad {}, - None => None, - }), + .map(|_| None), _ => { if is_critical { // unknown critical extension @@ -208,67 +217,7 @@ fn verify_libp2p_extension( }) } -/// Compute the signature algorithm corresponding to an algorithm identifier. -/// These OIDs come from various RFCs. -/// -/// Potential future optimization: operate directly on serialized data. -/// -/// Note that this is NOT a validating parser! We rely on webpki to do that for us. We just do -/// the bare minimum. -fn compute_signature_algorithm( - key: &[u8], - alg: &AlgorithmIdentifier, -) -> yasna::ASN1Result<&'static dyn ring::signature::VerificationAlgorithm> { - Ok(match (key.len(), &**alg.algorithm.components()) { - (32, &[1, 3, 101, 111]) => &ring::signature::ED25519, - (33, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P256_SHA256_ASN1, - (65, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P256_SHA256_ASN1, - (33, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P256_SHA384_ASN1, - (65, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P256_SHA384_ASN1, - (49, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P384_SHA256_ASN1, - (97, &[1, 2, 840, 10045, 4, 3, 2]) => &ring::signature::ECDSA_P384_SHA256_ASN1, - (49, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P384_SHA384_ASN1, - (97, &[1, 2, 840, 10045, 4, 3, 3]) => &ring::signature::ECDSA_P384_SHA384_ASN1, - (_, &[1, 2, 840, 113549, 1, 1, 11]) => &ring::signature::RSA_PKCS1_2048_8192_SHA256, - (_, &[1, 2, 840, 113549, 1, 1, 12]) => &ring::signature::RSA_PKCS1_2048_8192_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 13]) => &ring::signature::RSA_PKCS1_2048_8192_SHA512, - (_, &[1, 2, 840, 113549, 1, 1, 14]) => &ring::signature::RSA_PKCS1_3072_8192_SHA384, - (_, &[1, 2, 840, 113549, 1, 1, 10]) => match alg.parameters { - None => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), - Some(ref e) => yasna::parse_der(e, |reader| { - reader.read_sequence(|reader| { - let keytype = reader.next().read_sequence(|reader| { - let keytype = match &**reader.next().read_oid()?.components() { - &[2, 16, 840, 1, 101, 3, 4, 2, 1] => { - &ring::signature::RSA_PSS_2048_8192_SHA256 - } - &[2, 16, 840, 1, 101, 3, 4, 2, 2] => { - &ring::signature::RSA_PSS_2048_8192_SHA384 - } - &[2, 16, 840, 1, 101, 3, 4, 2, 3] => { - &ring::signature::RSA_PSS_2048_8192_SHA512 - } - _ => { - warn!("unsupported signature algorithm, rejecting!"); - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); - } - }; - reader.read_optional(|reader| reader.read_null())?; - Ok(keytype) - })?; - reader.next().read_der()?; // ignore maskGenAlgorithm - reader.next().read_der()?; // ignore saltLength - Ok(keytype) - }) - })?, - }, - _ => return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)), - }) -} - -fn parse_certificate( - certificate: &[u8], -) -> yasna::ASN1Result<(X509Certificate, Vec, identity::PublicKey)> { +fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result { trace!("parsing certificate"); let raw_certificate = yasna::parse_der(certificate, |reader| { reader.read_sequence(|mut reader| { @@ -282,69 +231,82 @@ fn parse_certificate( }) }) })?; - let (certificate_key, identity_key) = - yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { - trace!("parsing TBScertificate"); - reader.read_sequence(|mut reader| { - trace!("getting X509 version"); - let version = reader.next().read_der()?; - // this is the encoding of 2 with context 0 - if version != [160, 3, 2, 1, 2] { - warn!("got invalid version {:?}", version); - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; - } - reader.next().read_biguint()?; // ignore the serial number - if read_algid(&mut reader)? != raw_certificate.algorithm { - debug!( - "expected algid to be {:?}, but it is not", - raw_certificate.algorithm - ); - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? - } - reader.next().read_der()?; // we don’t care about the issuer - trace!("reading validity"); - reader.next().read_der()?; // we don’t care about the validity - trace!("reading subject"); - reader.next().read_der()?; // we don’t care about the subject - trace!("reading subjectPublicKeyInfo"); - let key = reader.next().read_sequence(|mut reader| { - trace!("reading subject key algorithm"); - reader.next().read_der()?; - trace!("reading subject key"); - read_bitvec(&mut reader) - })?; - trace!("reading issuerUniqueId"); - reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the issuerUniqueId - trace!("reading subjectUniqueId"); - reader.read_optional(|reader| reader.read_bitvec_bytes())?; // we don’t care about the subjectUniqueId - trace!("reading extensions"); - let identity_key = parse_x509_extensions(reader, &key)?; - Ok((key, identity_key)) - }) - })?; - Ok((raw_certificate, certificate_key, identity_key)) + yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { + trace!("parsing TBScertificate"); + reader.read_sequence(|mut reader| { + trace!("getting X509 version"); + let version = reader.next().read_der()?; + // this is the encoding of 2 with context 0 + if version != [160, 3, 2, 1, 2] { + warn!("got invalid version {:?}", version); + return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; + } + // Skip the serial number + reader.next().read_der()?; + if read_algid(&mut reader)? != raw_certificate.algorithm { + debug!( + "expected algid to be {:?}, but it is not", + raw_certificate.algorithm + ); + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? + } + // Skip the issuer + reader.next().read_der()?; + // Skip validity + reader.next().read_der()?; + // Skip subject + reader.next().read_der()?; + trace!("reading subjectPublicKeyInfo"); + let key = reader.next().read_sequence(|mut reader| { + trace!("reading subject key algorithm"); + reader.next().read_der()?; + trace!("reading subject key"); + read_bitvec(&mut reader) + })?; + // skip issuerUniqueId + reader.read_optional(|reader| reader.read_bitvec_bytes())?; + // skip subjectUniqueId + reader.read_optional(|reader| reader.read_bitvec_bytes())?; + trace!("reading extensions"); + parse_x509_extensions(reader, &key) + }) + }) } /// The name is a misnomer. We don’t bother checking if the certificate is actually well-formed. /// We just check that its self-signature is valid, and that its public key is suitably signed. pub fn verify_libp2p_certificate( certificate: &[u8], -) -> Result { - let (raw_certificate, certificate_key, identity_key): (_, Vec, _) = - parse_certificate(certificate).map_err(|e| { - log::debug!("error in parsing: {:?}", e); - ring::error::Unspecified - })?; - let algorithm = compute_signature_algorithm(&certificate_key, &raw_certificate.algorithm) + side: Side, +) -> Result { + let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(certificate)?; + let cert = webpki::EndEntityCert::from(certificate)?; + let time = webpki::Time::try_from(std::time::SystemTime::now()).expect( + "we assume the system clock is not set to before the UNIX epoch; \ + if it is not, then the system is hopelessly misconfigured, and many \ + other things will break; if this is not set to before the UNIX epoch, \ + this will succeed; qed", + ); + match side { + Side::Server => cert.verify_is_valid_tls_server_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSServerTrustAnchors(&[trust_anchor]), + &[], + time, + ), + Side::Client => cert.verify_is_valid_tls_client_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSClientTrustAnchors(&[trust_anchor]), + &[], + time, + ), + }?; + parse_certificate(certificate) .map_err(|e| { - log::debug!("error getting signature algorithm: {:?}", e); - ring::error::Unspecified - })?; - ring::signature::UnparsedPublicKey::new(algorithm, &certificate_key).verify( - &raw_certificate.tbs_certificate, - &raw_certificate.signature_value, - )?; - Ok(identity_key.into()) + log::debug!("error in parsing: {:?}", e); + webpki::Error::InvalidSignatureForPublicKey + }) + .map(From::from) } #[cfg(test)] @@ -352,12 +314,16 @@ mod test { use super::*; #[test] fn can_make_a_certificate() { + use Side::{Client, Server}; drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); - assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); + for side in &[Client, Server] { + assert_eq!( + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(), *side) + .unwrap(), + libp2p_core::PeerId::from_public_key(keypair.public()) + ); + } log::trace!("trying secp256k1!"); let keypair = identity::Keypair::generate_secp256k1(); log::trace!("have a key!"); @@ -365,9 +331,12 @@ mod test { log::trace!("have a public key!"); assert_eq!(public, public, "key is not equal to itself?"); log::debug!("have a valid key!"); - assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); + for side in &[Client, Server] { + assert_eq!( + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(), *side) + .unwrap(), + libp2p_core::PeerId::from_public_key(keypair.public()) + ); + } } } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 792ff7f1bc6..b2c72d01cf2 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -352,11 +352,25 @@ impl StreamMuxer for QuicMuxer { Some(quinn_proto::ConnectionError::ApplicationClosed( quinn_proto::ApplicationClose { error_code, reason }, )) if error_code.into_inner() == 0 && reason.is_empty() => { - warn!("This should not happen, but a quinn-proto bug causes it to happen"); - if let Some(w) = inner.readers.remove(&substream.id) { - w.wake() - } - Poll::Ready(Ok(0)) + let error_code = *error_code; + warn!("This should not happen, but a quinn-proto bug causes it to happen for side {:?}", inner.connection.side()); + + Poll::Ready(if !cfg!(test) { + if let Some(w) = inner.readers.remove(&substream.id) { + w.wake() + } + (Ok(0)) + } else { + let reason = reason.clone(); + if let Some(w) = inner.readers.remove(&substream.id) { + w.wake() + } + Err(Error::ConnectionError( + quinn_proto::ConnectionError::ApplicationClosed( + quinn_proto::ApplicationClose { error_code, reason }, + ), + )) + }) } Some(error) => Poll::Ready(Err(Error::ConnectionError(error.clone()))), }, @@ -515,12 +529,13 @@ impl Future for QuicUpgrade { chain would already have been rejected; qed", ) .as_ref(), + inner.connection.side(), ) }; let muxer = muxer.take().expect("polled after yielding Ready"); Poll::Ready(match res { Ok(e) => Ok((e, muxer)), - Err(ring::error::Unspecified) => Err(Error::BadCertificate(ring::error::Unspecified)), + Err(_) => Err(Error::BadCertificate(ring::error::Unspecified)), }) } } From 28b889f5479df39673c1fd8b408808cff907fec1 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 17:45:28 -0500 Subject: [PATCH 076/202] Add error file --- transports/quic/src/error.rs | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 transports/quic/src/error.rs diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs new file mode 100644 index 00000000000..f627d5304f3 --- /dev/null +++ b/transports/quic/src/error.rs @@ -0,0 +1,64 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use err_derive::Error; +use io::ErrorKind; +use ring::error::Unspecified; +use std::io; +#[derive(Error, Debug)] +/// An error that can be returned by libp2p-quic. +pub enum Error { + #[error(display = "Fatal I/O error {}", _0)] + IO(#[error(source)] std::io::Error), + #[error(display = "Peer sent a malformed certificate")] + BadCertificate(#[error(source)] ring::error::Unspecified), + #[error(display = "QUIC protocol error: {}", _0)] + ConnectionError(#[error(source)] quinn_proto::ConnectionError), + #[error(display = "Cannot establish connection: {}", _0)] + CannotConnect(#[error(source)] quinn_proto::ConnectError), + #[error(display = "Peer stopped receiving data: code {}", _0)] + Stopped(quinn_proto::VarInt), + #[error(display = "Connection was prematurely closed")] + ConnectionLost, + #[error(display = "Cannot listen on the same endpoint more than once")] + AlreadyListening, + #[error(display = "Peer reset stream: code {}", _0)] + Reset(quinn_proto::VarInt), + #[error( + display = "Use of a stream that is no longer valid. This is a bug in the application." + )] + ExpiredStream, +} + +impl From for io::Error { + fn from(e: Error) -> Self { + match e { + Error::IO(e) => e, + e @ Error::BadCertificate(Unspecified) => io::Error::new(ErrorKind::InvalidData, e), + Error::ConnectionError(e) => e.into(), + e @ Error::CannotConnect(_) => io::Error::new(ErrorKind::Other, e), + e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { + io::Error::new(ErrorKind::ConnectionAborted, e) + } + e @ Error::ExpiredStream => io::Error::new(ErrorKind::Other, e), + e @ Error::AlreadyListening => io::Error::new(ErrorKind::AddrInUse, e), + } + } +} From 88400d5432a28d96422c632f17277b340259705b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 29 Jan 2020 17:45:41 -0500 Subject: [PATCH 077/202] Split up certificate verification Part of it uses webpki and is now done from the rustls certificate verifier. --- transports/quic/src/certificate.rs | 67 ++++------------------- transports/quic/src/connection.rs | 5 +- transports/quic/src/verifier.rs | 85 ++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 83 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 9a183a077ad..096f5361177 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -26,23 +26,6 @@ //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. use log::{debug, trace, warn}; -use quinn_proto::Side; -static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &'static [&'static webpki::SignatureAlgorithm] = { - &[ - &webpki::ECDSA_P256_SHA256, - &webpki::ECDSA_P256_SHA384, - &webpki::ECDSA_P384_SHA256, - &webpki::ECDSA_P384_SHA384, - &webpki::ED25519, - &webpki::RSA_PKCS1_2048_8192_SHA256, - &webpki::RSA_PKCS1_2048_8192_SHA384, - &webpki::RSA_PKCS1_2048_8192_SHA512, - &webpki::RSA_PKCS1_3072_8192_SHA384, - &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, - ] -}; const BASIC_CONSTRAINTS_OID: &[u64] = &[2, 5, 29, 19]; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -275,32 +258,7 @@ fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result Result { - let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(certificate)?; - let cert = webpki::EndEntityCert::from(certificate)?; - let time = webpki::Time::try_from(std::time::SystemTime::now()).expect( - "we assume the system clock is not set to before the UNIX epoch; \ - if it is not, then the system is hopelessly misconfigured, and many \ - other things will break; if this is not set to before the UNIX epoch, \ - this will succeed; qed", - ); - match side { - Side::Server => cert.verify_is_valid_tls_server_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSServerTrustAnchors(&[trust_anchor]), - &[], - time, - ), - Side::Client => cert.verify_is_valid_tls_client_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSClientTrustAnchors(&[trust_anchor]), - &[], - time, - ), - }?; +pub fn verify_libp2p_certificate(certificate: &[u8]) -> Result { parse_certificate(certificate) .map_err(|e| { log::debug!("error in parsing: {:?}", e); @@ -314,16 +272,12 @@ mod test { use super::*; #[test] fn can_make_a_certificate() { - use Side::{Client, Server}; drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); - for side in &[Client, Server] { - assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(), *side) - .unwrap(), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); - } + assert_eq!( + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + libp2p_core::PeerId::from_public_key(keypair.public()) + ); log::trace!("trying secp256k1!"); let keypair = identity::Keypair::generate_secp256k1(); log::trace!("have a key!"); @@ -331,12 +285,9 @@ mod test { log::trace!("have a public key!"); assert_eq!(public, public, "key is not equal to itself?"); log::debug!("have a valid key!"); - for side in &[Client, Server] { - assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap(), *side) - .unwrap(), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); - } + assert_eq!( + verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + libp2p_core::PeerId::from_public_key(keypair.public()) + ); } } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index b2c72d01cf2..72875dc04fe 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -95,7 +95,7 @@ fn make_client_config( crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; crypto.set_single_client_cert(vec![certificate], key); - let verifier = verifier::VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; + let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; crypto .dangerous() .set_certificate_verifier(Arc::new(verifier)); @@ -113,7 +113,7 @@ fn make_server_config( transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt, + verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, )); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto @@ -529,7 +529,6 @@ impl Future for QuicUpgrade { chain would already have been rejected; qed", ) .as_ref(), - inner.connection.side(), ) }; let muxer = muxer.take().expect("polled after yielding Ready"); diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index c4f4d7deeb0..16d072e920c 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -18,27 +18,43 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -/// A ServerCertVerifier that considers any certificate to be valid, and does no checking -/// whatsoever. +static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &'static [&'static webpki::SignatureAlgorithm] = { + &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::ED25519, + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + ] +}; + +/// A ServerCertVerifier that considers any self-signed certificate to be valid. /// /// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! -/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any certificate -/// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic -/// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key -/// corresponing to its `PeerId`, unless that endpoint has done something insecure. -pub struct VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt; +/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any self-signed +/// certificate with a valid libp2p extension **by design**. Instead, it is the application’s job +/// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection +/// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done +/// something insecure. +pub struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; -/// A ClientCertVerifier that requires client authentication, but considers any certificate to be -/// valid, and does no checking whatsoever. +/// A ClientCertVerifier that requires client authentication, and requires the certificate to be self-signed. /// /// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! /// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any certificate /// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic /// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key /// corresponing to its `PeerId`, unless that endpoint has done something insecure. -pub struct VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt; +pub struct VeryInsecureRequireExactlyOneSelfSignedClientCertificate; -impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneServerCertificateButDoNotCheckIt { +impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServerCertificate { fn verify_server_cert( &self, _roots: &rustls::RootCertStore, @@ -46,15 +62,36 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneServerCertifica _dns_name: webpki::DNSNameRef, _ocsp_response: &[u8], ) -> Result { - if presented_certs.len() == 1 { - Ok(rustls::ServerCertVerified::assertion()) - } else { - Err(rustls::TLSError::NoCertificatesPresented) - } + let (time, cert, trust_anchor) = verify_presented_certs(presented_certs)?; + cert.verify_is_valid_tls_server_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSServerTrustAnchors(&[trust_anchor]), + &[], + time, + ) + .map(|()| rustls::ServerCertVerified::assertion()) + .map_err(rustls::TLSError::WebPKIError) + } +} + +fn verify_presented_certs( + presented_certs: &[rustls::Certificate], +) -> Result<(webpki::Time, webpki::EndEntityCert, webpki::TrustAnchor), rustls::TLSError> { + if presented_certs.len() != 1 { + Err(rustls::TLSError::NoCertificatesPresented)? } + let time = webpki::Time::try_from(std::time::SystemTime::now()) + .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; + let raw_certificate = presented_certs[0].as_ref(); + Ok(( + time, + webpki::EndEntityCert::from(raw_certificate).map_err(rustls::TLSError::WebPKIError)?, + webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) + .map_err(rustls::TLSError::WebPKIError)?, + )) } -impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneClientCertificateButDoNotCheckIt { +impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { fn offer_client_auth(&self) -> bool { true } @@ -67,10 +104,14 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneClientCertifica &self, presented_certs: &[rustls::Certificate], ) -> Result { - if presented_certs.len() == 1 { - Ok(rustls::ClientCertVerified::assertion()) - } else { - Err(rustls::TLSError::NoCertificatesPresented) - } + let (time, cert, trust_anchor) = verify_presented_certs(presented_certs)?; + cert.verify_is_valid_tls_client_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSClientTrustAnchors(&[trust_anchor]), + &[], + time, + ) + .map(|()| rustls::ClientCertVerified::assertion()) + .map_err(rustls::TLSError::WebPKIError) } } From c4a86a0ce33937887b3173223be022ceff17e400 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 30 Jan 2020 18:39:20 -0500 Subject: [PATCH 078/202] Simplify certificate verification All X.509 certificates are validated by WebPKI before being parsed by libp2p-quic. As a result, libp2p-quic does not need to do even basic validation itself. --- transports/quic/src/certificate.rs | 134 ++++++++--------------------- 1 file changed, 37 insertions(+), 97 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 096f5361177..207a0bb6e2d 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -25,8 +25,7 @@ //! This crate uses the `log` crate to emit log output. Events that will occur normally are output //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. -use log::{debug, trace, warn}; -const BASIC_CONSTRAINTS_OID: &[u64] = &[2, 5, 29, 19]; +use log::{trace, warn}; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); @@ -92,31 +91,6 @@ fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1E } } -/// Read an X.509 Algorithm Identifier -fn read_algid(reader: &mut yasna::BERReaderSeq) -> Result { - reader.next().read_sequence(|reader| { - let algorithm = reader.next().read_oid()?; - let parameters = reader.read_optional(|reader| reader.read_der())?; - Ok(AlgorithmIdentifier { - algorithm, - parameters, - }) - }) -} - -#[derive(PartialEq, Eq, Debug)] -struct AlgorithmIdentifier { - algorithm: yasna::models::ObjectIdentifier, - parameters: Option>, -} - -#[derive(Debug)] -pub struct X509Certificate { - tbs_certificate: Vec, - algorithm: AlgorithmIdentifier, - signature_value: Vec, -} - fn parse_x509_extensions( reader: &mut yasna::BERReaderSeq, certificate_key: &[u8], @@ -150,30 +124,11 @@ fn parse_x509_extension( ); return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); } - let mut is_critical = false; - reader.read_optional(|reader| { - if reader.read_bool()? { - Ok(is_critical = true) - } else { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } - })?; - trace!("certificate critical? {}", is_critical); + reader.read_optional(|reader| reader.read_bool().map(drop))?; let contents = reader.next().read_bytes()?; match &**oid.components() { - LIBP2P_OID => Ok(Some(verify_libp2p_extension(&contents, certificate_key)?)), - BASIC_CONSTRAINTS_OID => yasna::parse_der(&contents, |reader| { - reader.read_sequence(|reader| reader.read_optional(|reader| reader.read_bool())) - }) - .map(|_| None), - _ => { - if is_critical { - // unknown critical extension - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } else { - Ok(None) - } - } + LIBP2P_OID => verify_libp2p_extension(&contents, certificate_key).map(Some), + _ => Ok(None), } }) } @@ -202,60 +157,45 @@ fn verify_libp2p_extension( fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result { trace!("parsing certificate"); - let raw_certificate = yasna::parse_der(certificate, |reader| { - reader.read_sequence(|mut reader| { - let tbs_certificate = reader.next().read_der()?; - let algorithm = read_algid(&mut reader)?; - let signature_value = read_bitvec(&mut reader)?; - Ok(X509Certificate { - tbs_certificate, - algorithm, - signature_value, - }) - }) - })?; - yasna::parse_der(&raw_certificate.tbs_certificate, |reader| { - trace!("parsing TBScertificate"); - reader.read_sequence(|mut reader| { - trace!("getting X509 version"); - let version = reader.next().read_der()?; - // this is the encoding of 2 with context 0 - if version != [160, 3, 2, 1, 2] { - warn!("got invalid version {:?}", version); - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; - } - // Skip the serial number - reader.next().read_der()?; - if read_algid(&mut reader)? != raw_certificate.algorithm { - debug!( - "expected algid to be {:?}, but it is not", - raw_certificate.algorithm - ); - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? - } - // Skip the issuer - reader.next().read_der()?; - // Skip validity + yasna::parse_der(certificate, |reader| { + reader.read_sequence(|reader| { + let key = parse_tbscertificate(reader.next())?; + // skip algorithm reader.next().read_der()?; - // Skip subject + // skip signature reader.next().read_der()?; - trace!("reading subjectPublicKeyInfo"); - let key = reader.next().read_sequence(|mut reader| { - trace!("reading subject key algorithm"); - reader.next().read_der()?; - trace!("reading subject key"); - read_bitvec(&mut reader) - })?; - // skip issuerUniqueId - reader.read_optional(|reader| reader.read_bitvec_bytes())?; - // skip subjectUniqueId - reader.read_optional(|reader| reader.read_bitvec_bytes())?; - trace!("reading extensions"); - parse_x509_extensions(reader, &key) + Ok(key) }) }) } +fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result { + trace!("parsing TBScertificate"); + reader.read_sequence(|reader| { + // Skip the X.509 version + reader.next().read_der()?; + // Skip the serial number + reader.next().read_der()?; + // Skip the signature algorithm + reader.next().read_der()?; + // Skip the issuer + reader.next().read_der()?; + // Skip validity + reader.next().read_der()?; + // Skip subject + reader.next().read_der()?; + trace!("reading subjectPublicKeyInfo"); + let key = reader.next().read_sequence(|mut reader| { + // Skip the subject key algorithm + reader.next().read_der()?; + trace!("reading subject key"); + read_bitvec(&mut reader) + })?; + trace!("reading extensions"); + parse_x509_extensions(reader, &key) + }) +} + /// The name is a misnomer. We don’t bother checking if the certificate is actually well-formed. /// We just check that its self-signature is valid, and that its public key is suitably signed. pub fn verify_libp2p_certificate(certificate: &[u8]) -> Result { From 535dda5d2c426dfdb431ccae0f5a4b112b9ace64 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 31 Jan 2020 12:37:24 -0500 Subject: [PATCH 079/202] Clean up certificate verification code --- transports/quic/src/certificate.rs | 10 ++--- transports/quic/src/verifier.rs | 60 ++++++++++++++++-------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 207a0bb6e2d..94bf62b3512 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -199,12 +199,10 @@ fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result Result { - parse_certificate(certificate) - .map_err(|e| { - log::debug!("error in parsing: {:?}", e); - webpki::Error::InvalidSignatureForPublicKey - }) - .map(From::from) + parse_certificate(certificate).map_err(|e| { + log::debug!("error in parsing: {:?}", e); + webpki::Error::InvalidSignatureForPublicKey + }).map(libp2p_core::PeerId::from_public_key) } #[cfg(test)] diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 16d072e920c..3453f131fd1 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -48,10 +48,11 @@ pub struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; /// A ClientCertVerifier that requires client authentication, and requires the certificate to be self-signed. /// /// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! -/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any certificate -/// **by design**. Instead, it is the application’s job to check the peer ID that libp2p-quic -/// provides. libp2p-quic does guarantee that the connection is to a peer with the secret key -/// corresponing to its `PeerId`, unless that endpoint has done something insecure. +/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any self-signed +/// certificate with a valid libp2p extension **by design**. Instead, it is the application’s job +/// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection +/// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done +/// something insecure. pub struct VeryInsecureRequireExactlyOneSelfSignedClientCertificate; impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServerCertificate { @@ -62,33 +63,38 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe _dns_name: webpki::DNSNameRef, _ocsp_response: &[u8], ) -> Result { - let (time, cert, trust_anchor) = verify_presented_certs(presented_certs)?; - cert.verify_is_valid_tls_server_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSServerTrustAnchors(&[trust_anchor]), - &[], - time, - ) + verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { + end_entity_cert.verify_is_valid_tls_server_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSServerTrustAnchors(&[trust_anchor]), + &[], + time, + ) + }) .map(|()| rustls::ServerCertVerified::assertion()) - .map_err(rustls::TLSError::WebPKIError) } } fn verify_presented_certs( presented_certs: &[rustls::Certificate], -) -> Result<(webpki::Time, webpki::EndEntityCert, webpki::TrustAnchor), rustls::TLSError> { + cb: &dyn Fn( + webpki::Time, + webpki::EndEntityCert, + webpki::TrustAnchor, + ) -> Result<(), webpki::Error>, +) -> Result<(), rustls::TLSError> { if presented_certs.len() != 1 { Err(rustls::TLSError::NoCertificatesPresented)? } let time = webpki::Time::try_from(std::time::SystemTime::now()) .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; let raw_certificate = presented_certs[0].as_ref(); - Ok(( - time, - webpki::EndEntityCert::from(raw_certificate).map_err(rustls::TLSError::WebPKIError)?, - webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) - .map_err(rustls::TLSError::WebPKIError)?, - )) + let inner_func = || { + let parsed_cert = webpki::EndEntityCert::from(raw_certificate)?; + let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate)?; + cb(time, parsed_cert, trust_anchor) + }; + inner_func().map_err(rustls::TLSError::WebPKIError) } impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { @@ -104,14 +110,14 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien &self, presented_certs: &[rustls::Certificate], ) -> Result { - let (time, cert, trust_anchor) = verify_presented_certs(presented_certs)?; - cert.verify_is_valid_tls_client_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSClientTrustAnchors(&[trust_anchor]), - &[], - time, - ) + verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { + end_entity_cert.verify_is_valid_tls_client_cert( + ALL_SUPPORTED_SIGNATURE_ALGORITHMS, + &webpki::TLSClientTrustAnchors(&[trust_anchor]), + &[], + time, + ) + }) .map(|()| rustls::ClientCertVerified::assertion()) - .map_err(rustls::TLSError::WebPKIError) } } From 6b3ca0920863c3434c1df6a4e71b88a04f0098cf Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 31 Jan 2020 15:38:25 -0500 Subject: [PATCH 080/202] =?UTF-8?q?Don=E2=80=99t=20try=20to=20use=20`ifadd?= =?UTF-8?q?rs`=20in=20the=20browser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- misc/multiaddr/Cargo.toml | 2 ++ misc/multiaddr/src/lib.rs | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/misc/multiaddr/Cargo.toml b/misc/multiaddr/Cargo.toml index 628529ea7e1..d1734869a03 100644 --- a/misc/multiaddr/Cargo.toml +++ b/misc/multiaddr/Cargo.toml @@ -19,6 +19,8 @@ serde = "1.0.70" static_assertions = "1.1" unsigned-varint = "0.3" url = { version = "2.1.0", default-features = false } + +[target.'cfg(not(any(target_os = "emscripten", target_os = "unknown")))'.dependencies] get_if_addrs = "0.5.3" ipnet = "2.1.0" diff --git a/misc/multiaddr/src/lib.rs b/misc/multiaddr/src/lib.rs index e7443701178..ba4d21cecc0 100644 --- a/misc/multiaddr/src/lib.rs +++ b/misc/multiaddr/src/lib.rs @@ -6,8 +6,6 @@ mod protocol; mod errors; mod from_url; -use get_if_addrs::{get_if_addrs, IfAddr}; -use ipnet::{IpNet, Ipv4Net, Ipv6Net}; use serde::{ Deserialize, Deserializer, @@ -314,7 +312,10 @@ pub fn ip_to_multiaddr(ip: IpAddr, suffix: &[Protocol]) -> Multiaddr { } /// Collect all local host addresses and use the provided port number as listen port. -pub fn host_addresses(suffix: &[Protocol]) -> io::Result> { +#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] +pub fn host_addresses(suffix: &[Protocol]) -> io::Result> { + use get_if_addrs::{get_if_addrs, IfAddr}; + use ipnet::{IpNet, Ipv4Net, Ipv6Net}; let mut addrs = Vec::new(); for iface in get_if_addrs()? { let ip = iface.ip(); From 0f2269961f9453acdd6d3783b2f15108b5bac2b8 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 2 Feb 2020 15:46:49 -0500 Subject: [PATCH 081/202] Remove ugly workaround and clean up some code https://github.com/djc/quinn/issues/617 has been fixed, which allows for an ugly workaround (using the close reason to determine if a connection close was clean) to be removed. --- transports/quic/Cargo.toml | 6 +++--- transports/quic/src/certificate.rs | 18 +++++++++++------- transports/quic/src/connection.rs | 24 ------------------------ transports/quic/src/verifier.rs | 3 ++- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index db935ae8e15..f9ca0d953b0 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -39,10 +39,10 @@ futures-timer = "2.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" rcgen = "0.7.0" -protobuf = "2.8.1" +protobuf = "2.10.1" yasna = { version = "0.3.1", features = ["num-bigint"] } -ring = "0.16.9" -webpki = "0.21.0" +ring = "0.16.10" +webpki = "0.21.2" [dev-dependencies] tracing-subscriber = "0.1.6" diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 94bf62b3512..d3da30021c0 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -172,8 +172,10 @@ fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result yasna::ASN1Result { trace!("parsing TBScertificate"); reader.read_sequence(|reader| { - // Skip the X.509 version - reader.next().read_der()?; + // Check the X.509 version + if reader.next().read_der()? != [160, 3, 2, 1, 2] { + Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))? + } // Skip the serial number reader.next().read_der()?; // Skip the signature algorithm @@ -197,12 +199,14 @@ fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result Result { - parse_certificate(certificate).map_err(|e| { - log::debug!("error in parsing: {:?}", e); - webpki::Error::InvalidSignatureForPublicKey - }).map(libp2p_core::PeerId::from_public_key) + parse_certificate(certificate) + .map_err(|e| { + log::debug!("error in parsing: {:?}", e); + webpki::Error::InvalidSignatureForPublicKey + }) + .map(libp2p_core::PeerId::from_public_key) } #[cfg(test)] diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 72875dc04fe..cc1f8dd3d85 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -348,30 +348,6 @@ impl StreamMuxer for QuicMuxer { } Poll::Pending } - // KLUDGE: this is a workaround for https://github.com/djc/quinn/issues/604. - Some(quinn_proto::ConnectionError::ApplicationClosed( - quinn_proto::ApplicationClose { error_code, reason }, - )) if error_code.into_inner() == 0 && reason.is_empty() => { - let error_code = *error_code; - warn!("This should not happen, but a quinn-proto bug causes it to happen for side {:?}", inner.connection.side()); - - Poll::Ready(if !cfg!(test) { - if let Some(w) = inner.readers.remove(&substream.id) { - w.wake() - } - (Ok(0)) - } else { - let reason = reason.clone(); - if let Some(w) = inner.readers.remove(&substream.id) { - w.wake() - } - Err(Error::ConnectionError( - quinn_proto::ConnectionError::ApplicationClosed( - quinn_proto::ApplicationClose { error_code, reason }, - ), - )) - }) - } Some(error) => Poll::Ready(Err(Error::ConnectionError(error.clone()))), }, Err(ReadError::UnknownStream) => { diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 3453f131fd1..731b5576a48 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -45,7 +45,8 @@ static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &'static [&'static webpki::SignatureA /// something insecure. pub struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; -/// A ClientCertVerifier that requires client authentication, and requires the certificate to be self-signed. +/// A ClientCertVerifier that requires client authentication, and requires the certificate to be +/// self-signed. /// /// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! /// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any self-signed From 05fae6b44e7a8fb576a95af60eabe6553a03adb6 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 3 Feb 2020 17:27:17 -0500 Subject: [PATCH 082/202] Return an error if an unwritten stream is read This avoids the deadlock that would otherwise result. I also did some refactoring, mostly renaming types. --- transports/quic/src/connection.rs | 111 ++++++++++++++++-------------- transports/quic/src/error.rs | 16 +++-- transports/quic/src/lib.rs | 5 +- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index cc1f8dd3d85..bc2b3f71216 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -42,19 +42,20 @@ use std::{ time::Instant, }; #[derive(Debug)] -pub struct QuicSubstream { +pub struct Substream { id: StreamId, status: SubstreamStatus, } #[derive(Debug)] enum SubstreamStatus { + Unwritten, Live, Finishing(oneshot::Receiver<()>), Finished, } -impl QuicSubstream { +impl Substream { fn new(id: StreamId) -> Self { let status = SubstreamStatus::Live; Self { id, status } @@ -62,7 +63,7 @@ impl QuicSubstream { fn is_live(&self) -> bool { match self.status { - SubstreamStatus::Live => true, + SubstreamStatus::Live | SubstreamStatus::Unwritten => true, SubstreamStatus::Finishing(_) | SubstreamStatus::Finished => false, } } @@ -73,7 +74,7 @@ impl QuicSubstream { /// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams /// obtained by libp2p through the tokio reactor. #[derive(Debug, Clone)] -pub struct QuicConfig { +pub struct Config { /// The client configuration. Quinn provides functions for making one. pub client_config: quinn_proto::ClientConfig, /// The server configuration. Quinn provides functions for making one. @@ -125,7 +126,7 @@ fn make_server_config( config } -impl QuicConfig { +impl Config { /// Creates a new configuration object for TCP/IP. pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { let cert = super::make_cert(&keypair); @@ -172,17 +173,17 @@ enum OutboundInner { pub struct Outbound(OutboundInner); impl Future for Outbound { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = &mut *self; match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Poll::Ready(e.map(QuicSubstream::new)), + OutboundInner::Complete(e) => Poll::Ready(e.map(Substream::new)), _ => unreachable!(), }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) - .map(QuicSubstream::new) + .map(Substream::new) .map_err(|oneshot::Canceled| Error::ConnectionLost); this.0 = OutboundInner::Done; Poll::Ready(result) @@ -194,7 +195,7 @@ impl Future for Outbound { impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; - type Substream = QuicSubstream; + type Substream = Substream; type Error = crate::error::Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); @@ -225,11 +226,9 @@ impl StreamMuxer for QuicMuxer { warn!("Error closing stream: {}", e); } } - drop( - inner - .connection - .stop_sending(substream.id, Default::default()), - ) + let _ = inner + .connection + .stop_sending(substream.id, Default::default()); } fn is_remote_acknowledged(&self) -> bool { true @@ -249,14 +248,17 @@ impl StreamMuxer for QuicMuxer { inner.wake_driver(); match inner.connection.accept(quinn_proto::Dir::Bi) { None => { - if let Some(waker) = replace(&mut inner.accept_waker, Some(cx.waker().clone())) { + if let Some(waker) = replace( + &mut inner.handshake_or_accept_waker, + Some(cx.waker().clone()), + ) { waker.wake() } Poll::Pending } Some(id) => { inner.finishers.insert(id, None); - Poll::Ready(Ok(QuicSubstream::new(id))) + Poll::Ready(Ok(Substream::new(id))) } } } @@ -284,10 +286,6 @@ impl StreamMuxer for QuicMuxer { if let Some(ref e) = inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(e.clone()))); } - assert!( - !inner.connection.is_drained(), - "attempting to write to a drained connection" - ); match inner.connection.write(substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(WriteError::Blocked) => { @@ -300,10 +298,19 @@ impl StreamMuxer for QuicMuxer { Poll::Pending } Err(WriteError::UnknownStream) => { - error!( - "The application used a stream that has already been closed. This is a bug." - ); - Poll::Ready(Err(Error::ExpiredStream)) + if let Some(e) = &inner.close_reason { + error!( + "The application used a connection that is already being \ + closed. This is a bug in the application or in libp2p." + ); + Poll::Ready(Err(Error::ConnectionError(e.clone()))) + } else { + error!( + "The application used a stream that has already been \ + closed. This is a bug in the application or in libp2p." + ); + Poll::Ready(Err(Error::ExpiredStream)) + } } Err(WriteError::Stopped(e)) => { inner.finishers.remove(&substream.id); @@ -336,8 +343,12 @@ impl StreamMuxer for QuicMuxer { match inner.connection.read(substream.id, buf) { Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), Ok(None) => Poll::Ready(Ok(0)), - Err(ReadError::Blocked) => match &inner.close_reason { - None => { + Err(ReadError::Blocked) => { + if let Some(error) = &inner.close_reason { + Poll::Ready(Err(Error::ConnectionError(error.clone()))) + } else if let SubstreamStatus::Unwritten = substream.status { + Poll::Ready(Err(Error::CannotReadFromUnwrittenStream)) + } else { trace!( "Blocked on reading stream {:?} with side {:?}", substream.id, @@ -348,8 +359,7 @@ impl StreamMuxer for QuicMuxer { } Poll::Pending } - Some(error) => Poll::Ready(Err(Error::ConnectionError(error.clone()))), - }, + } Err(ReadError::UnknownStream) => { error!( "The application used a stream that has already been closed. This is a bug." @@ -378,7 +388,7 @@ impl StreamMuxer for QuicMuxer { Error::IO(io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())) }); } - SubstreamStatus::Live => {} + SubstreamStatus::Unwritten | SubstreamStatus::Live => {} } let mut inner = self.inner(); inner.wake_driver(); @@ -449,12 +459,12 @@ impl StreamMuxer for QuicMuxer { } #[derive(Debug)] -pub struct QuicUpgrade { +pub struct Upgrade { muxer: Option, } #[cfg(test)] -impl Drop for QuicUpgrade { +impl Drop for Upgrade { fn drop(&mut self) { debug!("dropping upgrade!"); assert!( @@ -464,14 +474,14 @@ impl Drop for QuicUpgrade { } } -impl Future for QuicUpgrade { +impl Future for Upgrade { type Output = Result<(libp2p_core::PeerId, QuicMuxer), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if inner.connection.is_closed() { + if inner.connection.is_drained() { return Poll::Ready(Err(Error::ConnectionError( inner .close_reason @@ -479,8 +489,11 @@ impl Future for QuicUpgrade { .expect("closed connections always have a reason; qed"), ))); } else if inner.connection.is_handshaking() { - assert!(!inner.connection.is_closed(), "deadlock"); - inner.handshake_waker = Some(cx.waker().clone()); + assert!( + !inner.connection.is_closed(), + "a closed connection cannot be handshaking; qed" + ); + inner.handshake_or_accept_waker = Some(cx.waker().clone()); return Poll::Pending; } else if inner.connection.side().is_server() { ready!(inner.endpoint_channel.poll_ready(cx)) @@ -533,10 +546,8 @@ pub(crate) struct Muxer { readers: HashMap, /// Tasks blocked on finishing finishers: HashMap>>, - /// Task waiting for new connections - handshake_waker: Option, - /// Task waiting for new connections - accept_waker: Option, + /// Task waiting for new connections, or for the connection to finish handshaking. + handshake_or_accept_waker: Option, /// Tasks waiting to make a connection connectors: StreamSenderQueue, /// Pending transmit @@ -564,6 +575,7 @@ const RESET: u32 = 1; impl Drop for Muxer { fn drop(&mut self) { if self.close_reason.is_none() { + warn!("connection uncleanly closed!"); self.shutdown(RESET) } } @@ -638,8 +650,7 @@ impl Muxer { writers: HashMap::new(), readers: HashMap::new(), finishers: HashMap::new(), - accept_waker: None, - handshake_waker: None, + handshake_or_accept_waker: None, connectors: Default::default(), endpoint_channel: endpoint.event_channel(), endpoint: endpoint, @@ -723,10 +734,7 @@ impl Muxer { fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); - if let Some(w) = self.accept_waker.take() { - w.wake() - } - if let Some(w) = self.handshake_waker.take() { + if let Some(w) = self.handshake_or_accept_waker.take() { w.wake() } for (_, v) in self.writers.drain() { @@ -841,7 +849,10 @@ impl Muxer { } Event::ConnectionLost { reason } => { debug!("lost connection due to {:?}", reason); - assert!(self.connection.is_closed()); + debug_assert!( + self.connection.is_closed(), + "connection lost without being closed" + ); self.close_reason = Some(reason); self.connection_lost = true; self.close_waker.take().map(|e| e.wake()); @@ -874,13 +885,13 @@ impl Muxer { Event::Connected => { debug!("connected!"); assert!(!self.connection.is_handshaking(), "quinn-proto bug"); - if let Some(w) = self.handshake_waker.take() { + if let Some(w) = self.handshake_or_accept_waker.take() { w.wake() } } Event::StreamOpened { dir: Dir::Bi } => { debug!("stream opened for side {:?}", self.connection.side()); - if let Some(w) = self.accept_waker.take() { + if let Some(w) = self.handshake_or_accept_waker.take() { w.wake() } } @@ -902,7 +913,7 @@ impl ConnectionDriver { connection: Connection, handle: ConnectionHandle, cb: T, - ) -> QuicUpgrade { + ) -> Upgrade { let inner = Arc::new(Mutex::new(Muxer::new(endpoint, connection, handle))); cb(Arc::downgrade(&inner)); let handle = async_std::task::spawn(Self { @@ -910,7 +921,7 @@ impl ConnectionDriver { outgoing_packet: None, }); inner.lock().driver = Some(handle); - QuicUpgrade { + Upgrade { muxer: Some(QuicMuxer(inner)), } } diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index f627d5304f3..731096c6e69 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -39,12 +39,19 @@ pub enum Error { ConnectionLost, #[error(display = "Cannot listen on the same endpoint more than once")] AlreadyListening, + /// The stream was reset by the peer. #[error(display = "Peer reset stream: code {}", _0)] Reset(quinn_proto::VarInt), - #[error( - display = "Use of a stream that is no longer valid. This is a bug in the application." - )] + /// Either an attempt was made to write to a stream that was already shut down, + /// or a previous operation on this stream failed. + #[error(display = "Use of a stream that has is no longer valid. This is a \ + bug in the application.")] ExpiredStream, + #[error(display = "Trying to ready from a stream that the peer has not been \ + notified of. For performance and complexity reasons, libp2p-quic does not \ + notify the peer that a stream is opened until at least one byte is sent. \ + Therefore, this read would deadlock.")] + CannotReadFromUnwrittenStream, } impl From for io::Error { @@ -57,8 +64,9 @@ impl From for io::Error { e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } - e @ Error::ExpiredStream => io::Error::new(ErrorKind::Other, e), + e @ Error::ExpiredStream => io::Error::new(ErrorKind::BrokenPipe, e), e @ Error::AlreadyListening => io::Error::new(ErrorKind::AddrInUse, e), + e @ Error::CannotReadFromUnwrittenStream => io::Error::new(ErrorKind::NotConnected, e), } } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 0d2490007bd..6ae663179a4 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -67,8 +67,5 @@ mod tests; pub use error::Error; mod verifier; pub use certificate::make_cert; -pub use connection::{ - Outbound, QuicConfig as Config, QuicMuxer as Muxer, QuicSubstream as Substream, - QuicUpgrade as Upgrade, -}; +pub use connection::{Config, Outbound, QuicMuxer as Muxer, Substream, Upgrade}; pub use endpoint::{Endpoint, Listener}; From 7e6029cc57e783eb8d7a78d29ff516d2134c726c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 11:27:43 -0500 Subject: [PATCH 083/202] Reading from an unwritten stream must fail Since streams are opened lazily in QUIC, reading from a stream that has never been written to cannot possibly succeed. Return an error in this case. --- transports/quic/src/connection.rs | 18 ++-- transports/quic/src/endpoint.rs | 6 +- transports/quic/src/error.rs | 2 +- transports/quic/src/lib.rs | 2 - transports/quic/{src => tests}/tests.rs | 111 ++++++++++-------------- 5 files changed, 67 insertions(+), 72 deletions(-) rename transports/quic/{src => tests}/tests.rs (80%) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index bc2b3f71216..b0633b2eabf 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -56,7 +56,12 @@ enum SubstreamStatus { } impl Substream { - fn new(id: StreamId) -> Self { + fn unwritten(id: StreamId) -> Self { + let status = SubstreamStatus::Unwritten; + Self { id, status } + } + + fn live(id: StreamId) -> Self { let status = SubstreamStatus::Live; Self { id, status } } @@ -72,7 +77,7 @@ impl Substream { /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. /// /// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams -/// obtained by libp2p through the tokio reactor. +/// obtained by libp2p through a reactor. #[derive(Debug, Clone)] pub struct Config { /// The client configuration. Quinn provides functions for making one. @@ -178,12 +183,12 @@ impl Future for Outbound { let this = &mut *self; match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Poll::Ready(e.map(Substream::new)), + OutboundInner::Complete(e) => Poll::Ready(e.map(Substream::unwritten)), _ => unreachable!(), }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) - .map(Substream::new) + .map(Substream::unwritten) .map_err(|oneshot::Canceled| Error::ConnectionLost); this.0 = OutboundInner::Done; Poll::Ready(result) @@ -258,7 +263,7 @@ impl StreamMuxer for QuicMuxer { } Some(id) => { inner.finishers.insert(id, None); - Poll::Ready(Ok(Substream::new(id))) + Poll::Ready(Ok(Substream::live(id))) } } } @@ -277,6 +282,9 @@ impl StreamMuxer for QuicMuxer { ); return Poll::Ready(Err(Error::ExpiredStream)); } + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } let mut inner = self.inner(); debug_assert!( inner.finishers.get(&substream.id).is_some(), diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index a4942b14a53..1cb4757ef94 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -400,10 +400,14 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { } } +#[cfg(test)] #[test] fn multiaddr_to_udp_conversion() { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - crate::tests::init(); + use tracing_subscriber::{fmt::Subscriber, EnvFilter}; + let _ = Subscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); assert!( multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() ); diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 731096c6e69..63edabaf0c0 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -57,7 +57,7 @@ pub enum Error { impl From for io::Error { fn from(e: Error) -> Self { match e { - Error::IO(e) => e, + Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), e @ Error::BadCertificate(Unspecified) => io::Error::new(ErrorKind::InvalidData, e), Error::ConnectionError(e) => e.into(), e @ Error::CannotConnect(_) => io::Error::new(ErrorKind::Other, e), diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 6ae663179a4..dc77cd1b017 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -62,8 +62,6 @@ mod connection; mod endpoint; mod error; mod socket; -#[cfg(test)] -mod tests; pub use error::Error; mod verifier; pub use certificate::make_cert; diff --git a/transports/quic/src/tests.rs b/transports/quic/tests/tests.rs similarity index 80% rename from transports/quic/src/tests.rs rename to transports/quic/tests/tests.rs index 2b6c031f673..a7329054366 100644 --- a/transports/quic/src/tests.rs +++ b/transports/quic/tests/tests.rs @@ -18,7 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::*; use async_macros::ready; use futures::prelude::*; use libp2p_core::{ @@ -26,60 +25,49 @@ use libp2p_core::{ transport::ListenerEvent, StreamMuxer, Transport, }; +use libp2p_quic::{Config, Endpoint, Muxer, Substream}; use log::{debug, trace}; use std::{ - io, + io::Result, pin::Pin, task::{Context, Poll}, }; #[derive(Debug)] -pub struct QuicStream { +struct QuicStream { id: Option, muxer: Muxer, shutdown: bool, } impl AsyncWrite for QuicStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &[u8], - ) -> Poll> { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { assert!(!self.shutdown, "written after close"); - let inner = self.get_mut(); - inner - .muxer - .write_substream(cx, inner.id.as_mut().unwrap(), buf) + let Self { muxer, id, .. } = self.get_mut(); + muxer + .write_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.shutdown = true; - let inner = self.get_mut(); - debug!("trying to close {:?}", inner.id); - ready!(inner - .muxer - .shutdown_substream(cx, inner.id.as_mut().unwrap()))?; - debug!("closed {:?}", inner.id); + let Self { muxer, id, .. } = self.get_mut(); + debug!("trying to close {:?}", id); + ready!(muxer.shutdown_substream(cx, id.as_mut().unwrap()))?; + debug!("closed {:?}", id); Poll::Ready(Ok(())) } - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { Poll::Ready(Ok(())) } } impl AsyncRead for QuicStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context, - buf: &mut [u8], - ) -> Poll> { - let inner = self.get_mut(); - inner - .muxer - .read_substream(cx, inner.id.as_mut().unwrap(), buf) + fn poll_read(self: Pin<&mut Self>, cx: &mut Context, buf: &mut [u8]) -> Poll> { + let Self { id, muxer, .. } = self.get_mut(); + muxer + .read_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) } } @@ -93,30 +81,32 @@ impl Drop for QuicStream { } } -impl futures::Stream for Muxer { +#[derive(Debug)] +struct Inbound<'a>(&'a mut Muxer); +impl<'a> futures::Stream for Inbound<'a> { type Item = QuicStream; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Poll::Ready(Some(QuicStream { - id: Some(ready!(self.poll_inbound(cx)).expect("bug")), - muxer: self.get_mut().clone(), + id: Some(ready!(self.0.poll_inbound(cx)).expect("bug")), + muxer: self.get_mut().0.clone(), shutdown: false, })) } } -pub(crate) fn init() { +fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; - drop( - Subscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(), - ) + let _ = Subscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); } -impl Future for Muxer { - type Output = Result<(), crate::error::Error>; +struct Closer(Muxer); + +impl Future for Closer { + type Output = Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - self.get_mut().close(cx) + self.get_mut().0.close(cx).map_err(From::from) } } @@ -155,27 +145,10 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { + use std::error::Error as _; init(); let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); - - #[cfg(any())] - async fn create_slowdown() { - futures_timer::Delay::new(std::time::Duration::new(1, 0)).await - } - - #[cfg(any())] - struct BlockJoin { - handle: Option>, - } - - #[cfg(any())] - impl Drop for BlockJoin { - fn drop(&mut self) { - drop(async_std::task::block_on(self.handle.take().unwrap())) - } - } - let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); let handle = async_std::task::spawn(async move { @@ -196,7 +169,10 @@ fn communicating_between_dialer_and_listener() { log::debug!("got a connection upgrade!"); let (id, mut muxer): (_, Muxer) = upgrade.await.expect("upgrade failed"); log::debug!("got a new muxer!"); - let mut socket: QuicStream = muxer.next().await.expect("no incoming stream"); + let mut socket: QuicStream = Inbound(&mut muxer) + .next() + .await + .expect("no incoming stream"); let mut buf = [0u8; 3]; log::debug!("reading data from accepted stream!"); @@ -215,7 +191,7 @@ fn communicating_between_dialer_and_listener() { assert_eq!(socket.read(&mut buf).await.unwrap(), 0); log::debug!("end of stream"); drop(socket); - muxer.await.unwrap(); + Closer(muxer).await.unwrap(); log::debug!("finished!"); break id; } @@ -240,7 +216,16 @@ fn communicating_between_dialer_and_listener() { muxer: connection.1.clone(), shutdown: false, }; - log::debug!("have a new stream!"); + log::debug!("opened a stream: id {:?}", stream.id); + let result = stream.read(&mut [][..]).await; + let result = result.expect_err("reading from an unwritten stream cannot succeed"); + assert_eq!(result.kind(), std::io::ErrorKind::NotConnected); + assert!(result.source().is_none()); + let wrapped = result.get_ref().unwrap().downcast_ref().unwrap(); + match wrapped { + libp2p_quic::Error::CannotReadFromUnwrittenStream => {} + e => panic!("Wrong error from reading unwritten stream: {}", e), + } stream.write_all(&[4u8, 5, 6]).await.unwrap(); stream.close().await.unwrap(); let mut buf = [0u8; 3]; @@ -259,7 +244,7 @@ fn communicating_between_dialer_and_listener() { assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); log::debug!("have EOF!"); - connection.1.await.expect("closed successfully"); + Closer(connection.1).await.expect("closed successfully"); log::debug!("awaiting handle!"); connection.0 }); From d678cc45eb23bbe0cc75cbbe5686d3aff5597d8e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 13:27:24 -0500 Subject: [PATCH 084/202] Refactor connection code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use the existing Pending machinery to handle packet transmission - Remove unnecessary looping in ‘ConnectionDriver::poll’ and ‘Muxer::drive_timer’ --- transports/quic/src/connection.rs | 152 +++++++++++------------------- 1 file changed, 54 insertions(+), 98 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index b0633b2eabf..749e1314668 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -21,6 +21,7 @@ use super::{ certificate, endpoint::{EndpointData, EndpointInner}, error::Error, + socket::Pending, verifier, }; use async_macros::ready; @@ -559,7 +560,7 @@ pub(crate) struct Muxer { /// Tasks waiting to make a connection connectors: StreamSenderQueue, /// Pending transmit - pending: Option, + pending: super::socket::Pending, /// The timer being used by this connection timer: Option, /// The close reason, if this connection has been lost @@ -616,35 +617,25 @@ impl Muxer { } fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { - let mut keep_going = false; - loop { - match self.connection.poll_timeout() { - None => { - self.timer = None; - self.last_timeout = None - } - Some(t) if t <= now => { - self.connection.handle_timeout(now); - keep_going = true; - continue; - } - t if t == self.last_timeout => {} - t => { - let delay = t.expect("already checked to be Some; qed") - now; - self.timer = Some(futures_timer::Delay::new(delay)) - } + match self.connection.poll_timeout() { + None => { + self.timer = None; + self.last_timeout = None } - if let Some(ref mut timer) = self.timer { - if timer.poll_unpin(cx).is_ready() { - self.connection.handle_timeout(now); - keep_going = true; - continue; - } + Some(t) if t <= now => { + self.connection.handle_timeout(now); + return true; } - break; + t if t == self.last_timeout => {} + Some(t) => self.timer = Some(futures_timer::Delay::new(t - now)), } - - keep_going + if let Some(ref mut timer) = self.timer { + if timer.poll_unpin(cx).is_ready() { + self.connection.handle_timeout(now); + return true; + } + } + false } fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { @@ -662,7 +653,7 @@ impl Muxer { connectors: Default::default(), endpoint_channel: endpoint.event_channel(), endpoint: endpoint, - pending: None, + pending: Pending::default(), timer: None, close_reason: None, waker: None, @@ -682,16 +673,14 @@ impl Muxer { /// Send endpoint events. Returns true if and only if there are endpoint events remaining to /// be sent. - fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) -> bool { - let mut keep_going = false; + fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { loop { match self.endpoint_channel.poll_ready(cx) { - Poll::Pending => break keep_going, + Poll::Pending => break, Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), Poll::Ready(Ok(())) => {} } if let Some(event) = self.connection.poll_endpoint_events() { - keep_going = true; self.endpoint_channel .start_send(EndpointMessage::EndpointEvent { handle: self.handle, @@ -699,45 +688,21 @@ impl Muxer { }) .expect("we just checked that we have capacity; qed"); } else { - break keep_going; - } - } - } - - fn pre_application_io(&mut self, now: Instant, cx: &mut Context<'_>) -> Result { - let mut needs_timer_update = false; - if let Some(transmit) = self.pending.take() { - trace!("trying to send packet!"); - if self.poll_transmit(cx, transmit)? { - return Ok(false); - } - trace!("packet sent!"); - } - while let Some(transmit) = self.connection.poll_transmit(now) { - trace!("trying to send packet!"); - needs_timer_update = true; - if self.poll_transmit(cx, transmit)? { break; } - trace!("packet sent!"); } - Ok(needs_timer_update) } - fn poll_transmit( - &mut self, - cx: &mut Context<'_>, - transmit: quinn_proto::Transmit, - ) -> Result { - let res = self.endpoint.socket().poll_send_to(cx, &transmit); - match res { - Poll::Pending => { - self.pending = Some(transmit); - Ok(true) - } - Poll::Ready(Ok(_)) => Ok(false), - Poll::Ready(Err(e)) => Err(e.into()), - } + fn transmit_pending(&mut self, now: Instant, cx: &mut Context<'_>) -> Result<(), Error> { + let Self { + pending, + endpoint, + connection, + .. + } = self; + let _ = + pending.send_packet(cx, endpoint.socket(), &mut || connection.poll_transmit(now))?; + Ok(()) } fn shutdown(&mut self, error_code: u32) { @@ -781,8 +746,6 @@ impl Muxer { self.send_to_endpoint(endpoint); self.process_app_events(); self.wake_driver(); - assert!(self.connection.poll_endpoint_events().is_none()); - assert!(self.connection.poll().is_none()); } fn get_pending_stream(&mut self) -> Option { @@ -798,7 +761,7 @@ impl Muxer { }) } - pub fn process_app_events(&mut self) -> bool { + fn process_app_events(&mut self) -> bool { use quinn_proto::Event; let mut keep_going = false; 'a: while let Some(event) = self.connection.poll() { @@ -845,8 +808,11 @@ impl Muxer { self.pending_stream.is_none(), "we cannot have both pending tasks and a pending stream; qed" ); - let stream = self.connection.open(Dir::Bi) - .expect("we just were told that there is a stream available; there is a mutex that prevents other threads from calling open() in the meantime; qed"); + let stream = self.connection.open(Dir::Bi).expect( + "we just were told that there is a stream available; there is \ + a mutex that prevents other threads from calling open() in the \ + meantime; qed", + ); while let Some(oneshot) = self.connectors.pop_front() { match oneshot.send(stream) { Ok(()) => continue 'a, @@ -938,40 +904,30 @@ impl ConnectionDriver { impl Future for ConnectionDriver { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - // cx.waker().wake_by_ref(); let this = self.get_mut(); debug!("being polled for timer!"); let mut inner = this.inner.lock(); inner.waker = Some(cx.waker().clone()); - let now = Instant::now(); loop { - let mut needs_timer_update = false; - needs_timer_update |= inner.drive_timer(cx, now); - needs_timer_update |= inner.pre_application_io(now, cx)?; - needs_timer_update |= inner.process_app_events(); - needs_timer_update |= inner.poll_endpoint_events(cx); - if inner.connection.is_drained() { - break Poll::Ready( - match inner - .close_reason - .clone() - .expect("we never have a closed connection with no reason; qed") - { - quinn_proto::ConnectionError::LocallyClosed => { - if needs_timer_update { - debug!("continuing until all events are finished"); - continue; - } else { - debug!("exiting driver"); - Ok(()) - } - } - e => Err(e.into()), - }, - ); - } else if !needs_timer_update { + let now = Instant::now(); + inner.transmit_pending(now, cx)?; + inner.process_app_events(); + inner.poll_endpoint_events(cx); + if inner.drive_timer(cx, now) { + continue; + } + if !inner.connection.is_drained() { break Poll::Pending; } + debug!("exiting driver"); + let close_reason = inner + .close_reason + .clone() + .expect("we never have a closed connection with no reason; qed"); + break Poll::Ready(match close_reason { + quinn_proto::ConnectionError::LocallyClosed => Ok(()), + e => Err(e.into()), + }); } } } From a2433635e7dedbb1fdfb7cb2b28a3d9111cc3c27 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 14:56:22 -0500 Subject: [PATCH 085/202] Fix clippy lints and test endpoint shutdown The code is now clippy-clean, and endpoint shutdown is tested properly. --- transports/quic/src/certificate.rs | 10 +-- transports/quic/src/connection.rs | 61 +++++++------- transports/quic/src/endpoint.rs | 130 ++++++++++++++--------------- transports/quic/src/lib.rs | 2 - transports/quic/src/socket.rs | 13 ++- transports/quic/src/verifier.rs | 4 +- transports/quic/tests/tests.rs | 52 ++++++------ 7 files changed, 138 insertions(+), 134 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index d3da30021c0..8e066d3deb2 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -30,8 +30,7 @@ const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; -static LIBP2P_SIGNATURE_ALGORITHM: &'static rcgen::SignatureAlgorithm = - &rcgen::PKCS_ECDSA_P256_SHA256; +static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; use libp2p_core::identity; fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { @@ -83,7 +82,7 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1Error> { let (value, bits) = reader.next().read_bitvec_bytes()?; // be extra careful regarding overflow - if (bits & 7) == 0 && value.len() == (bits >> 3) { + if bits.trailing_zeros() >= 3 { Ok(value) } else { warn!("value was of wrong length, sorry!"); @@ -100,7 +99,8 @@ fn parse_x509_extensions( let mut oids_seen = std::collections::HashSet::new(); reader.read_sequence_of(|reader| { trace!("reading an extension"); - Ok(public_key = parse_x509_extension(reader, &certificate_key, &mut oids_seen)?) + public_key = parse_x509_extension(reader, &certificate_key, &mut oids_seen)?; + Ok(()) })?; match public_key { Some(e) => Ok(e), @@ -174,7 +174,7 @@ fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result>); impl QuicMuxer { - fn inner<'a>(&'a self) -> MutexGuard<'a, Muxer> { + fn inner(&self) -> MutexGuard { self.0.lock() } } @@ -393,9 +393,9 @@ impl StreamMuxer for QuicMuxer { SubstreamStatus::Finished => return Poll::Ready(Ok(())), SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); - return channel.poll_unpin(cx).map_err(|e| { - Error::IO(io::Error::new(io::ErrorKind::ConnectionAborted, e.clone())) - }); + return channel + .poll_unpin(cx) + .map_err(|e| Error::IO(io::Error::new(io::ErrorKind::ConnectionAborted, e))); } SubstreamStatus::Unwritten | SubstreamStatus::Live => {} } @@ -435,20 +435,17 @@ impl StreamMuxer for QuicMuxer { fn close(&self, cx: &mut Context) -> Poll> { trace!("close() called"); let mut inner = self.inner(); - if inner.connection.is_closed() { - return Poll::Ready(Ok(())); - } else if inner.close_reason.is_some() { + if inner.connection.is_closed() || inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.finishers.is_empty() { inner.shutdown(0); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); drop(inner.driver().poll_unpin(cx)); return Poll::Ready(Ok(())); - } else if inner.close_waker.is_some() { - inner.close_waker = Some(cx.waker().clone()); + } + if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { + waker.wake(); return Poll::Pending; - } else { - inner.close_waker = Some(cx.waker().clone()) } let Muxer { finishers, @@ -610,6 +607,12 @@ impl Muxer { } } + fn wake_incoming(&mut self) { + if let Some(waker) = self.handshake_or_accept_waker.take() { + waker.wake() + } + } + fn driver(&mut self) -> &mut async_std::task::JoinHandle> { self.driver .as_mut() @@ -652,7 +655,7 @@ impl Muxer { handshake_or_accept_waker: None, connectors: Default::default(), endpoint_channel: endpoint.event_channel(), - endpoint: endpoint, + endpoint, pending: Pending::default(), timer: None, close_reason: None, @@ -707,9 +710,7 @@ impl Muxer { fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); - if let Some(w) = self.handshake_or_accept_waker.take() { - w.wake() - } + self.wake_incoming(); for (_, v) in self.writers.drain() { v.wake(); } @@ -717,7 +718,7 @@ impl Muxer { v.wake(); } for sender in self.finishers.drain().filter_map(|x| x.1) { - drop(sender.send(())) + let _ = sender.send(()); } self.connectors.truncate(0); if !self.connection.is_closed() { @@ -814,9 +815,8 @@ impl Muxer { meantime; qed", ); while let Some(oneshot) = self.connectors.pop_front() { - match oneshot.send(stream) { - Ok(()) => continue 'a, - Err(_) => {} + if let Ok(()) = oneshot.send(stream) { + continue 'a; } } self.pending_stream = Some(stream) @@ -829,7 +829,9 @@ impl Muxer { ); self.close_reason = Some(reason); self.connection_lost = true; - self.close_waker.take().map(|e| e.wake()); + if let Some(e) = self.close_waker.take() { + e.wake() + } self.shutdown(0); } Event::StreamFinished { @@ -850,24 +852,21 @@ impl Muxer { "every write stream is placed in this map, and entries are removed \ exactly once; qed", ) { - drop(sender.send(())) + let _ = sender.send(()); } if self.finishers.is_empty() { - self.close_waker.take().map(|e| e.wake()); + if let Some(e) = self.close_waker.take() { + e.wake() + } } } Event::Connected => { debug!("connected!"); - assert!(!self.connection.is_handshaking(), "quinn-proto bug"); - if let Some(w) = self.handshake_or_accept_waker.take() { - w.wake() - } + self.wake_incoming(); } Event::StreamOpened { dir: Dir::Bi } => { debug!("stream opened for side {:?}", self.connection.side()); - if let Some(w) = self.handshake_or_accept_waker.take() { - w.wake() - } + self.wake_incoming() } } } @@ -919,7 +918,7 @@ impl Future for ConnectionDriver { if !inner.connection.is_drained() { break Poll::Pending; } - debug!("exiting driver"); + info!("exiting driver"); let close_reason = inner .close_reason .clone() diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 1cb4757ef94..997d6a5c31f 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -27,7 +27,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, trace, warn}; +use log::{debug, info, trace, warn}; use parking_lot::{Mutex, MutexGuard}; use quinn_proto::{Connection, ConnectionHandle}; use std::{ @@ -42,7 +42,6 @@ use std::{ pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, muxers: HashMap>>, - driver: Option>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, @@ -136,7 +135,7 @@ impl EndpointInner { .map_err(Error::IO) } } - +type ListenerResult = Result, Error>; #[derive(Debug)] pub(super) struct EndpointData { /// The single UDP socket used for I/O @@ -145,10 +144,9 @@ pub(super) struct EndpointData { inner: Mutex, /// The channel on which new connections are sent. This is bounded in practice by the accept /// backlog. - new_connections: mpsc::UnboundedSender, Error>>, + new_connections: mpsc::UnboundedSender, /// The channel used to receive new connections. - receive_connections: - Mutex, Error>>>>, + receive_connections: Mutex>>, /// Connections send their events to this event_channel: mpsc::Sender, /// The `Multiaddr` @@ -181,15 +179,18 @@ impl EndpointData { /// The **only** valid `Multiaddr` to pass to `listen_on` or `dial` is the one used to create the /// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you /// will get `TransportError::MultiaddrNotSuppported`. -#[derive(Debug, Clone)] -pub struct Endpoint(Arc); +#[derive(Debug)] +pub struct Endpoint { + data: Arc, + join_handle: async_std::task::JoinHandle>, +} -struct EndpointDriver(Arc); +struct EndpointDriver(Option>); impl Endpoint { fn inner(&self) -> MutexGuard<'_, EndpointInner> { trace!("acquiring lock!"); - let q = self.0.inner.lock(); + let q = self.data.inner.lock(); trace!("lock acquired!"); q } @@ -210,7 +211,7 @@ impl Endpoint { .into(); let (new_connections, receive_connections) = mpsc::unbounded(); let (event_channel, event_receiver) = mpsc::channel(0); - let return_value = Self(Arc::new(EndpointData { + let data = Arc::new(EndpointData { socket: socket::Socket::new(socket), inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( @@ -218,7 +219,6 @@ impl Endpoint { Some(config.server_config.clone()), ), muxers: HashMap::new(), - driver: None, event_receiver, pending: Default::default(), }), @@ -227,16 +227,20 @@ impl Endpoint { new_connections, event_channel, config, - })); - return_value.inner().driver = Some(async_std::task::spawn(EndpointDriver( - return_value.0.clone(), - ))); - return_value - .0 - .new_connections + }); + let join_handle = async_std::task::spawn(EndpointDriver(Some(data.clone()))); + data.new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address))) .expect("we have a reference to the peer, so this will not fail; qed"); - Ok(return_value) + Ok(Self { data, join_handle }) + } + + /// Consume this `Endpoint`, and wait for it to close. + /// + /// No more connections will be accepted. The returned future will resolve when all existing + /// connections are complete or a fatal error occurs. + pub fn close(self) -> impl Future> { + self.join_handle } } @@ -251,21 +255,20 @@ fn create_muxer( }) } -impl EndpointDriver { +impl EndpointData { fn accept_muxer( - &self, + self: &Arc, connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, ) { - let upgrade = create_muxer(self.0.clone(), connection, handle, &mut *inner); + let upgrade = create_muxer(self.clone(), connection, handle, &mut *inner); if self - .0 .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { upgrade, - local_addr: self.0.address.clone(), - remote_addr: self.0.address.clone(), + local_addr: self.address.clone(), + remote_addr: self.address.clone(), })) .is_err() { @@ -278,29 +281,35 @@ impl EndpointDriver { impl Future for EndpointDriver { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let this = self.get_mut(); - loop { - let mut inner = this.0.inner.lock(); - trace!("driving events"); - inner.drive_events(cx); - trace!("driving incoming packets"); - match inner.drive_receive(&this.0.socket, cx)? { - Poll::Pending => { - drop(inner.poll_transmit_pending(&this.0.socket, cx)?); - trace!("returning Pending"); - break Poll::Pending; - } - Poll::Ready((handle, connection)) => { - trace!("have a new connection"); - this.accept_muxer(connection, handle, &mut *inner); - trace!("connection accepted"); - match inner.poll_transmit_pending(&this.0.socket, cx)? { - Poll::Pending => break Poll::Pending, - Poll::Ready(()) if Arc::strong_count(&this.0) == 1 => { - break Poll::Ready(Ok(())) - } - Poll::Ready(()) => break Poll::Pending, - } + let outer_this = &mut self.get_mut().0; + let this = outer_this.as_ref().expect("polled after yielding Ready"); + let mut inner = this.inner.lock(); + trace!("driving events"); + inner.drive_events(cx); + trace!("driving incoming packets"); + match inner.drive_receive(&this.socket, cx)? { + Poll::Pending => {} + Poll::Ready((handle, connection)) => { + trace!("have a new connection"); + this.accept_muxer(connection, handle, &mut *inner); + trace!("connection accepted"); + } + } + match inner.poll_transmit_pending(&this.socket, cx)? { + Poll::Pending | Poll::Ready(()) => { + let refcount = Arc::strong_count(&this); + trace!( + "Strong reference count is {}. Address is {:?}", + refcount, + this.address + ); + if refcount == 1 { + info!("All connections have closed. Exiting."); + drop(inner); + *outer_this = None; + Poll::Ready(Ok(())) + } else { + Poll::Pending } } } @@ -331,19 +340,15 @@ impl Transport for &Endpoint { type Dial = Upgrade; fn listen_on(self, addr: Multiaddr) -> Result> { - if addr != self.0.address { + let reference = self.data.clone(); + if addr != reference.address { return Err(TransportError::MultiaddrNotSupported(addr)); } - let channel = (self.0) + let channel = reference .receive_connections .lock() .take() .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; - let mut inner = self.inner(); - let reference = self.0.clone(); - if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(EndpointDriver(reference.clone()))); - } Ok(Listener { channel, reference }) } @@ -357,24 +362,17 @@ impl Transport for &Endpoint { } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; + let data = self.data.clone(); let mut inner = self.inner(); - if inner.driver.is_none() { - inner.driver = Some(async_std::task::spawn(EndpointDriver(self.0.clone()))) - } - let s: Result<(_, Connection), _> = inner .inner - .connect( - self.0.config.client_config.clone(), - socket_addr, - "localhost", - ) + .connect(data.config.client_config.clone(), socket_addr, "localhost") .map_err(|e| { warn!("Connection error: {:?}", e); TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; - Ok(create_muxer(self.0.clone(), conn, handle, &mut inner)) + Ok(create_muxer(data, conn, handle, &mut inner)) } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index dc77cd1b017..2398a1cef24 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -28,7 +28,6 @@ //! use libp2p_quic::{Config, Endpoint}; //! use libp2p_core::Multiaddr; //! -//! # fn main() { //! let keypair = libp2p_core::identity::Keypair::generate_ed25519(); //! let quic_config = Config::new(&keypair); //! let quic_endpoint = Endpoint::new( @@ -36,7 +35,6 @@ //! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), //! ) //! .expect("I/O error"); -//! # } //! ``` //! //! The `Config` structs implements the `Transport` trait of the `swarm` library. See the diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 5db07287743..04832d86cf6 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -41,6 +41,11 @@ pub(crate) struct Socket { impl Socket { /// Transmit a packet if possible, with appropriate logging. + /// + /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition + /// and drop it. If it is not, the connection will eventually time out. This provides a very + /// high degree of robustness. Connections will transparently resume after a transient network + /// outage, and problems that are specific to one peer will not effect other peers. pub fn poll_send_to(&self, cx: &mut Context, packet: &Transmit) -> Poll> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); @@ -49,15 +54,15 @@ impl Socket { } { Poll::Pending => { trace!("not able to send packet right away"); - return Poll::Pending; + Poll::Pending } Poll::Ready(Ok(e)) => { trace!("sent packet of length {} to {}", e, packet.destination); - return Poll::Ready(Ok(())); + Poll::Ready(Ok(())) } Poll::Ready(Err(e)) => { - warn!("Fatal I/O error: {:?}", e); - return Poll::Ready(Err(e)); + warn!("Ignoring I/O error on transmit: {:?}", e); + Poll::Ready(Ok(())) } } } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 731b5576a48..8a7f8624590 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &'static [&'static webpki::SignatureAlgorithm] = { +static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA384, @@ -85,7 +85,7 @@ fn verify_presented_certs( ) -> Result<(), webpki::Error>, ) -> Result<(), rustls::TLSError> { if presented_certs.len() != 1 { - Err(rustls::TLSError::NoCertificatesPresented)? + return Err(rustls::TLSError::NoCertificatesPresented); } let time = webpki::Time::try_from(std::time::SystemTime::now()) .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index a7329054366..9015f947c3c 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -26,7 +26,7 @@ use libp2p_core::{ StreamMuxer, Transport, }; use libp2p_quic::{Config, Endpoint, Muxer, Substream}; -use log::{debug, trace}; +use log::{debug, info, trace}; use std::{ io::Result, pin::Pin, @@ -151,31 +151,31 @@ fn communicating_between_dialer_and_listener() { let mut ready_tx = Some(ready_tx); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); - let handle = async_std::task::spawn(async move { - let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" - .parse() - .expect("bad address?"); - let quic_config = Config::new(&keypair2); - let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); - let mut listener = quic_endpoint.listen_on(addr).unwrap(); + let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" + .parse() + .expect("bad address?"); + let quic_config = Config::new(&keypair2); + let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); + let mut listener = quic_endpoint.listen_on(addr).unwrap(); - loop { + let handle = async_std::task::spawn(async move { + let key = loop { trace!("awaiting connection"); match listener.next().await.unwrap().unwrap() { ListenerEvent::NewAddress(listen_addr) => { ready_tx.take().unwrap().send(listen_addr).unwrap(); } ListenerEvent::Upgrade { upgrade, .. } => { - log::debug!("got a connection upgrade!"); + debug!("got a connection upgrade!"); let (id, mut muxer): (_, Muxer) = upgrade.await.expect("upgrade failed"); - log::debug!("got a new muxer!"); + debug!("got a new muxer!"); let mut socket: QuicStream = Inbound(&mut muxer) .next() .await .expect("no incoming stream"); let mut buf = [0u8; 3]; - log::debug!("reading data from accepted stream!"); + debug!("reading data from accepted stream!"); { let mut count = 0; while count < buf.len() { @@ -183,21 +183,24 @@ fn communicating_between_dialer_and_listener() { } } assert_eq!(buf, [4, 5, 6]); - log::debug!("writing data!"); + debug!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); - log::debug!("data written!"); + debug!("data written!"); socket.close().await.unwrap(); - log::debug!("socket closed!"); + debug!("socket closed!"); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); - log::debug!("end of stream"); + debug!("end of stream"); drop(socket); Closer(muxer).await.unwrap(); - log::debug!("finished!"); + debug!("finished!"); break id; } _ => unreachable!(), } - } + }; + drop(listener); + quic_endpoint.close().await.unwrap(); + key }); let second_handle = async_std::task::spawn(async move { @@ -216,7 +219,7 @@ fn communicating_between_dialer_and_listener() { muxer: connection.1.clone(), shutdown: false, }; - log::debug!("opened a stream: id {:?}", stream.id); + debug!("opened a stream: id {:?}", stream.id); let result = stream.read(&mut [][..]).await; let result = result.expect_err("reading from an unwritten stream cannot succeed"); assert_eq!(result.kind(), std::io::ErrorKind::NotConnected); @@ -229,7 +232,7 @@ fn communicating_between_dialer_and_listener() { stream.write_all(&[4u8, 5, 6]).await.unwrap(); stream.close().await.unwrap(); let mut buf = [0u8; 3]; - log::debug!("reading data!"); + debug!("reading data!"); { let mut count = 0; while count < buf.len() { @@ -239,13 +242,14 @@ fn communicating_between_dialer_and_listener() { } } assert_eq!(buf, [1u8, 2, 3]); - log::debug!("data read!"); - log::debug!("checking for EOF!"); + debug!("data read ― checking for EOF"); assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); - log::debug!("have EOF!"); + debug!("have EOF"); Closer(connection.1).await.expect("closed successfully"); - log::debug!("awaiting handle!"); + debug!("awaiting handle"); + quic_endpoint.close().await.unwrap(); + info!("endpoint is finished"); connection.0 }); assert_eq!( From 204b9c201504b35d688d322a33d3d7dc45cdbd70 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 15:10:13 -0500 Subject: [PATCH 086/202] Only forbid warnings when testing Future releases of Rust or Clippy may add new warnings, so a blanket `#[forbid(warnings, clippy::all)]` is a bad idea. However, the test suite is only ran by developers and CI, so `#[forbid(warnings, clippy::all)]` is fine there. --- transports/quic/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 2398a1cef24..06122dd1815 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -52,7 +52,9 @@ //! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid(unused_must_use, unstable_features, warnings, unsafe_code)] +#![forbid(unstable_features, unsafe_code)] +// Forbid warnings when testing, but don’t break other people’s code +#![cfg_attr(test, forbid(warnings, clippy::all))] #![deny(missing_copy_implementations)] #![deny(trivial_casts)] mod certificate; From 6fe90911bce015ac3c8fc5aa3e36c9311e06a187 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 15:32:16 -0500 Subject: [PATCH 087/202] Add Cargo metadata --- transports/quic/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index f9ca0d953b0..57921806af4 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -22,6 +22,10 @@ name = "libp2p-quic" version = "0.15.0" authors = ["Parity Technologies "] edition = "2018" +license = "MIT" +description = "A libp2p transport using QUIC" +keywords = ["peer-to-peer", "libp2p", "quic", "networking"] +categories = ["network-programming", "asynchronous"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 13e0eb219f06d548823cc4e9f4902927c31bc896 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 4 Feb 2020 18:09:12 -0500 Subject: [PATCH 088/202] Wake up the endpoint driver when needed When `Endpoint` or `EndpointRef` is dropped, the endpoint driver needs to be woken up. Make sure that happens. --- transports/quic/src/connection.rs | 1 + transports/quic/src/endpoint.rs | 74 ++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 550c337ee83..5ba18b3f56e 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -153,6 +153,7 @@ impl Config { #[derive(Debug)] pub(super) enum EndpointMessage { + Dummy, ConnectionAccepted, EndpointEvent { handle: ConnectionHandle, diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 997d6a5c31f..d6ae2d3898c 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -28,7 +28,7 @@ use libp2p_core::{ Transport, }; use log::{debug, info, trace, warn}; -use parking_lot::{Mutex, MutexGuard}; +use parking_lot::Mutex; use quinn_proto::{Connection, ConnectionHandle}; use std::{ collections::HashMap, @@ -79,6 +79,7 @@ impl EndpointInner { } } } + EndpointMessage::Dummy => {} } } } @@ -135,7 +136,9 @@ impl EndpointInner { .map_err(Error::IO) } } + type ListenerResult = Result, Error>; + #[derive(Debug)] pub(super) struct EndpointData { /// The single UDP socket used for I/O @@ -165,6 +168,37 @@ impl EndpointData { } } +mod endpoint_ref { + use super::{EndpointData, EndpointMessage}; + use std::sync::Arc; + #[derive(Debug, Clone)] + pub(super) struct EndpointRef(Option>); + impl EndpointRef { + pub fn new(data: Arc) -> Self { + Self(Some(data)) + } + + pub fn inner(&self) -> &Arc { + self.0 + .as_ref() + .expect("this is only set to None in the destructor") + } + } + + impl Drop for EndpointRef { + fn drop(&mut self) { + let inner = self.0.take().expect("we set this to a Some; qed"); + let mut drop_channel = inner.event_channel.clone(); + // decrement the refcount + drop(inner); + // wake the driver + let _ = drop_channel.start_send(EndpointMessage::Dummy); + } + } +} + +use endpoint_ref::EndpointRef; + /// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// /// You generally need only one of these per process. Endpoints are `Send` and `Sync`, so you @@ -181,20 +215,13 @@ impl EndpointData { /// will get `TransportError::MultiaddrNotSuppported`. #[derive(Debug)] pub struct Endpoint { - data: Arc, + data: EndpointRef, join_handle: async_std::task::JoinHandle>, } struct EndpointDriver(Option>); impl Endpoint { - fn inner(&self) -> MutexGuard<'_, EndpointInner> { - trace!("acquiring lock!"); - let q = self.data.inner.lock(); - trace!("lock acquired!"); - q - } - /// Construct a `Endpoint` with the given `Config` and `Multiaddr`. pub fn new( config: Config, @@ -232,16 +259,25 @@ impl Endpoint { data.new_connections .unbounded_send(Ok(ListenerEvent::NewAddress(address))) .expect("we have a reference to the peer, so this will not fail; qed"); - Ok(Self { data, join_handle }) + Ok(Self { + data: EndpointRef::new(data), + join_handle, + }) } /// Consume this `Endpoint`, and wait for it to close. /// - /// No more connections will be accepted. The returned future will resolve when all existing - /// connections are complete or a fatal error occurs. + /// The returned future will resolve when either + /// + /// 1. All connections are complete and [`Listener`] has been dropped. + /// 2. A fatal error occurs. pub fn close(self) -> impl Future> { self.join_handle } + + fn data(&self) -> &Arc { + self.data.inner() + } } fn create_muxer( @@ -319,12 +355,10 @@ impl Future for EndpointDriver { /// A QUIC listener #[derive(Debug)] pub struct Listener { - reference: Arc, + reference: EndpointRef, channel: mpsc::UnboundedReceiver, Error>>, } -impl Unpin for Listener {} - impl Stream for Listener { type Item = Result, Error>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { @@ -340,7 +374,7 @@ impl Transport for &Endpoint { type Dial = Upgrade; fn listen_on(self, addr: Multiaddr) -> Result> { - let reference = self.data.clone(); + let reference = self.data().clone(); if addr != reference.address { return Err(TransportError::MultiaddrNotSupported(addr)); } @@ -349,7 +383,7 @@ impl Transport for &Endpoint { .lock() .take() .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; - Ok(Listener { channel, reference }) + Ok(Listener { channel, reference: EndpointRef::new(reference) }) } fn dial(self, addr: Multiaddr) -> Result> { @@ -362,8 +396,8 @@ impl Transport for &Endpoint { } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let data = self.data.clone(); - let mut inner = self.inner(); + let data = self.data(); + let mut inner = data.inner.lock(); let s: Result<(_, Connection), _> = inner .inner .connect(data.config.client_config.clone(), socket_addr, "localhost") @@ -372,7 +406,7 @@ impl Transport for &Endpoint { TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; - Ok(create_muxer(data, conn, handle, &mut inner)) + Ok(create_muxer(data.clone(), conn, handle, &mut inner)) } } From d54f069de2c5b7e16119a65e22d530d5ec48a137 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 5 Feb 2020 20:15:28 -0500 Subject: [PATCH 089/202] Respond to code review and fix tests In addition to problems pointed out by @tomaka, three of the tests were broken. A bug in the tests meant they passed anyway. --- Cargo.toml | 4 +- core/src/lib.rs | 9 - core/src/transport.rs | 6 - misc/mdns/Cargo.toml | 4 +- misc/rw-stream-sink/Cargo.toml | 2 +- transports/quic/Cargo.toml | 21 --- transports/quic/src/certificate.rs | 8 +- transports/quic/src/connection.rs | 45 ++--- transports/quic/src/endpoint.rs | 260 ++++++++++++++++++----------- transports/quic/src/error.rs | 3 +- transports/quic/src/lib.rs | 2 +- transports/quic/src/socket.rs | 2 - transports/quic/tests/tests.rs | 70 ++++---- 13 files changed, 229 insertions(+), 207 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1465e653b18..66bd91fbd64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,10 +74,10 @@ members = [ "protocols/secio", "swarm", "transports/dns", + "transports/quic", "transports/tcp", "transports/uds", - "transports/websocket", "transports/wasm-ext", - "transports/quic", + "transports/websocket", ] diff --git a/core/src/lib.rs b/core/src/lib.rs index b93c93fd4be..7d51e460924 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -34,15 +34,6 @@ //! - The [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] traits //! define how to upgrade each individual substream to use a protocol. //! See the `upgrade` module. -//! -//! [`PeerId`]: -//! [`UpgradeInfo`]: -//! [`InboundUpgrade`]: -//! [`OutboundUpgrade`]: -//! [`Transport`]: -//! [`transport`]: -//! [`StreamMuxer`]: -//! [`muxing`]: mod keys_proto { include!(concat!(env!("OUT_DIR"), "/keys_proto.rs")); diff --git a/core/src/transport.rs b/core/src/transport.rs index 6602f434624..d9b900ab5b6 100644 --- a/core/src/transport.rs +++ b/core/src/transport.rs @@ -333,12 +333,6 @@ pub enum TransportError { Other(TErr), } -impl From for TransportError where TErr: std::error::Error { - fn from(source: TErr) -> Self { - Self::Other(source) - } -} - impl TransportError { /// Applies a function to the the error in [`TransportError::Other`]. pub fn map(self, map: impl FnOnce(TErr) -> TNewErr) -> TransportError { diff --git a/misc/mdns/Cargo.toml b/misc/mdns/Cargo.toml index c4eae2a26d2..74f44a91d63 100644 --- a/misc/mdns/Cargo.toml +++ b/misc/mdns/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std = "1.4.0" +async-std = "1.0" data-encoding = "2.0" dns-parser = "0.8" either = "1.5.3" @@ -20,7 +20,7 @@ libp2p-core = { version = "0.15.0", path = "../../core" } libp2p-swarm = { version = "0.5.0", path = "../../swarm" } log = "0.4" net2 = "0.2" -rand = "0.7.2" +rand = "0.7" smallvec = "1.0" void = "1.0" wasm-timer = "0.2.4" diff --git a/misc/rw-stream-sink/Cargo.toml b/misc/rw-stream-sink/Cargo.toml index f084f302256..07e1f81ebe9 100644 --- a/misc/rw-stream-sink/Cargo.toml +++ b/misc/rw-stream-sink/Cargo.toml @@ -15,4 +15,4 @@ pin-project = "0.4.6" static_assertions = "1" [dev-dependencies] -async-std = "1.4.0" +async-std = "1.0" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 57921806af4..ef081293649 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,22 +1,3 @@ -# Copyright 2017-2018 Parity Technologies (UK) Ltd. -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. [package] name = "libp2p-quic" version = "0.15.0" @@ -27,8 +8,6 @@ description = "A libp2p transport using QUIC" keywords = ["peer-to-peer", "libp2p", "quic", "networking"] categories = ["network-programming", "asynchronous"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.15.0" } diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 8e066d3deb2..cc84b398668 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -82,7 +82,7 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1Error> { let (value, bits) = reader.next().read_bitvec_bytes()?; // be extra careful regarding overflow - if bits.trailing_zeros() >= 3 { + if bits % 8 == 0 { Ok(value) } else { warn!("value was of wrong length, sorry!"); @@ -200,7 +200,7 @@ fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result Result { +pub fn extract_libp2p_peerid(certificate: &[u8]) -> Result { parse_certificate(certificate) .map_err(|e| { log::debug!("error in parsing: {:?}", e); @@ -217,7 +217,7 @@ mod test { drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), libp2p_core::PeerId::from_public_key(keypair.public()) ); log::trace!("trying secp256k1!"); @@ -228,7 +228,7 @@ mod test { assert_eq!(public, public, "key is not equal to itself?"); log::debug!("have a valid key!"); assert_eq!( - verify_libp2p_certificate(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), libp2p_core::PeerId::from_public_key(keypair.public()) ); } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 5ba18b3f56e..71e9e953d31 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -19,16 +19,13 @@ // DEALINGS IN THE SOFTWARE. use super::{ certificate, - endpoint::{EndpointData, EndpointInner}, + endpoint::{ConnectionEndpoint as Endpoint, EndpointInner}, error::Error, socket::Pending, verifier, }; use async_macros::ready; -use futures::{ - channel::{mpsc, oneshot}, - prelude::*, -}; +use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; use log::{debug, error, info, trace, warn}; use parking_lot::{Mutex, MutexGuard}; @@ -152,7 +149,7 @@ impl Config { } #[derive(Debug)] -pub(super) enum EndpointMessage { +pub enum EndpointMessage { Dummy, ConnectionAccepted, EndpointEvent { @@ -503,12 +500,9 @@ impl Future for Upgrade { inner.handshake_or_accept_waker = Some(cx.waker().clone()); return Poll::Pending; } else if inner.connection.side().is_server() { - ready!(inner.endpoint_channel.poll_ready(cx)) - .expect("we have a reference to the peer; qed"); - inner - .endpoint_channel - .start_send(EndpointMessage::ConnectionAccepted) - .expect("we just checked that we have capacity to send this; qed") + let endpoint = &mut inner.endpoint; + let token = ready!(endpoint.poll_ready(cx)); + endpoint.send_message(EndpointMessage::ConnectionAccepted, token) } let peer_certificates: Vec = inner @@ -516,7 +510,7 @@ impl Future for Upgrade { .crypto_session() .get_peer_certificates() .expect("we have finished handshaking, so we have exactly one certificate; qed"); - certificate::verify_libp2p_certificate( + certificate::extract_libp2p_peerid( peer_certificates .get(0) .expect( @@ -542,7 +536,7 @@ pub(crate) struct Muxer { /// The pending stream, if any. pending_stream: Option, /// The associated endpoint - endpoint: Arc, + endpoint: Endpoint, /// The `quinn_proto::Connection` struct. connection: Connection, /// Connection handle @@ -565,8 +559,6 @@ pub(crate) struct Muxer { close_reason: Option, /// Waker to wake up the driver waker: Option, - /// Channel for endpoint events - endpoint_channel: mpsc::Sender, /// Last timeout last_timeout: Option, /// Join handle for the driver @@ -642,7 +634,7 @@ impl Muxer { false } - fn new(endpoint: Arc, connection: Connection, handle: ConnectionHandle) -> Self { + fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { connection_lost: false, close_waker: None, @@ -655,7 +647,6 @@ impl Muxer { finishers: HashMap::new(), handshake_or_accept_waker: None, connectors: Default::default(), - endpoint_channel: endpoint.event_channel(), endpoint, pending: Pending::default(), timer: None, @@ -679,18 +670,18 @@ impl Muxer { /// be sent. fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { loop { - match self.endpoint_channel.poll_ready(cx) { + let token = match self.endpoint.poll_ready(cx) { Poll::Pending => break, - Poll::Ready(Err(_)) => unreachable!("we have a reference to the peer; qed"), - Poll::Ready(Ok(())) => {} - } + Poll::Ready(token) => token, + }; if let Some(event) = self.connection.poll_endpoint_events() { - self.endpoint_channel - .start_send(EndpointMessage::EndpointEvent { + self.endpoint.send_message( + EndpointMessage::EndpointEvent { handle: self.handle, event, - }) - .expect("we just checked that we have capacity; qed"); + }, + token, + ) } else { break; } @@ -883,7 +874,7 @@ pub(super) struct ConnectionDriver { impl ConnectionDriver { pub(crate) fn spawn>)>( - endpoint: Arc, + endpoint: Endpoint, connection: Connection, handle: ConnectionHandle, cb: T, diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index d6ae2d3898c..7d570ec2cdb 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -20,10 +20,10 @@ // use crate::{connection::EndpointMessage, error::Error, socket, Config, Upgrade}; use async_macros::ready; -use async_std::net::SocketAddr; +use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, + multiaddr::{host_addresses, Multiaddr, Protocol}, transport::{ListenerEvent, TransportError}, Transport, }; @@ -45,6 +45,7 @@ pub(super) struct EndpointInner { pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, + buffer: Vec, } impl EndpointInner { @@ -90,13 +91,12 @@ impl EndpointInner { cx: &mut Context, ) -> Poll> { use quinn_proto::DatagramEvent; - let mut buf = vec![0; 65535]; loop { - let (bytes, peer) = ready!(socket.recv_from(cx, &mut buf[..])?); + let (bytes, peer) = ready!(socket.recv_from(cx, &mut self.buffer[..])?); let (handle, event) = match self .inner - .handle(Instant::now(), peer, None, buf[..bytes].into()) + .handle(Instant::now(), peer, None, self.buffer[..bytes].into()) { Some(e) => e, None => continue, @@ -150,83 +150,111 @@ pub(super) struct EndpointData { new_connections: mpsc::UnboundedSender, /// The channel used to receive new connections. receive_connections: Mutex>>, - /// Connections send their events to this - event_channel: mpsc::Sender, /// The `Multiaddr` address: Multiaddr, /// The configuration config: Config, } -impl EndpointData { - pub(super) fn event_channel(&self) -> mpsc::Sender { - self.event_channel.clone() +type Sender = mpsc::Sender; + +impl ConnectionEndpoint { + pub fn socket(&self) -> &socket::Socket { + &self.0.reference.socket + } + + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll { + match self.0.channel.poll_ready(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(_)) => unreachable!( + "We have a reference to the peer; polling a \ + channel only fails when the peer has been dropped, so this will \ + not fail; qed" + ), + Poll::Ready(Ok(())) => Poll::Ready(SendToken(())), + } } - pub(super) fn socket(&self) -> &socket::Socket { - &self.socket + pub fn send_message(&mut self, event: EndpointMessage, SendToken(()): SendToken) { + self.0.channel.start_send(event).expect( + "you must check that you have capacity to get a SendToken; you cannot \ + call this method without a SendToken, so this will not fail; qed", + ) } } -mod endpoint_ref { - use super::{EndpointData, EndpointMessage}; - use std::sync::Arc; +mod channel_ref { + use super::{Arc, EndpointData, EndpointMessage::Dummy, Sender}; #[derive(Debug, Clone)] - pub(super) struct EndpointRef(Option>); - impl EndpointRef { - pub fn new(data: Arc) -> Self { - Self(Some(data)) + pub struct Channel(Sender); + + impl Channel { + pub fn new(s: Sender) -> Self { + Self(s) } + } + + impl std::ops::Deref for Channel { + type Target = Sender; + fn deref(&self) -> &Sender { + &self.0 + } + } - pub fn inner(&self) -> &Arc { - self.0 - .as_ref() - .expect("this is only set to None in the destructor") + impl std::ops::DerefMut for Channel { + fn deref_mut(&mut self) -> &mut Sender { + &mut self.0 } } - impl Drop for EndpointRef { + impl Drop for Channel { fn drop(&mut self) { - let inner = self.0.take().expect("we set this to a Some; qed"); - let mut drop_channel = inner.event_channel.clone(); - // decrement the refcount - drop(inner); - // wake the driver - let _ = drop_channel.start_send(EndpointMessage::Dummy); + let _ = self.0.start_send(Dummy); } } + + #[derive(Debug, Clone)] + pub struct EndpointRef { + pub(super) reference: Arc, + // MUST COME LATER so that the endpoint driver gets notified *after* its reference count is + // dropped. + pub(super) channel: Channel, + } } -use endpoint_ref::EndpointRef; +pub(crate) use channel_ref::{Channel, EndpointRef}; /// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// -/// You generally need only one of these per process. Endpoints are `Send` and `Sync`, so you +/// You generally need only one of these per process. Endpoints are [`Send`] and [`Sync`], so you /// can share them among as many threads as you like. However, performance may be better if you /// have one per CPU core, as this reduces lock contention. Most applications will not need to -/// worry about this. `QuicEndpoint` tries to use fine-grained locking to reduce the overhead. +/// worry about this. `Endpoint` tries to use fine-grained locking to reduce the overhead. /// -/// `QuicEndpoint` wraps the underlying data structure in an `Arc`, so cloning it just bumps the +/// `Endpoint` wraps the underlying data structure in an [`Arc`], so cloning it just bumps the /// reference count. All state is shared between the clones. For example, you can pass different -/// clones to `listen_on`. Each incoming connection will be received by exactly one of them. +/// clones to [`listen_on`]. Each incoming connection will be received by exactly one of them. /// -/// The **only** valid `Multiaddr` to pass to `listen_on` or `dial` is the one used to create the +/// The **only** valid [`Multiaddr`] to pass to `listen_on` is the one used to create the /// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you -/// will get `TransportError::MultiaddrNotSuppported`. +/// will get [`TransportError::MultiaddrNotSuppported`]. +#[derive(Debug, Clone)] +pub struct Endpoint(EndpointRef); + #[derive(Debug)] -pub struct Endpoint { - data: EndpointRef, - join_handle: async_std::task::JoinHandle>, -} +pub(crate) struct ConnectionEndpoint(EndpointRef); -struct EndpointDriver(Option>); +type JoinHandle = async_std::task::JoinHandle>; + +#[derive(Debug)] +pub(crate) struct SendToken(()); impl Endpoint { /// Construct a `Endpoint` with the given `Config` and `Multiaddr`. pub fn new( config: Config, - address: Multiaddr, - ) -> Result::Error>> { + mut address: Multiaddr, + ) -> Result<(Self, JoinHandle), TransportError<<&'static Self as Transport>::Error>> { let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { sa } else { @@ -234,12 +262,21 @@ impl Endpoint { }; // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr) - .map_err(Error::IO)? - .into(); + .map_err(|e| TransportError::Other(Error::IO(e)))?; + let port_is_zero = socket_addr.port() == 0; + let socket_addr = socket.local_addr().expect("this socket is bound; qed"); + info!("bound socket to {:?}", socket_addr); + if port_is_zero { + assert_ne!(socket_addr.port(), 0); + assert_eq!(address.pop(), Some(Protocol::Quic)); + assert_eq!(address.pop(), Some(Protocol::Udp(0))); + address.push(Protocol::Udp(socket_addr.port())); + address.push(Protocol::Quic); + } let (new_connections, receive_connections) = mpsc::unbounded(); - let (event_channel, event_receiver) = mpsc::channel(0); - let data = Arc::new(EndpointData { - socket: socket::Socket::new(socket), + let (event_sender, event_receiver) = mpsc::channel(0); + let reference = Arc::new(EndpointData { + socket: socket::Socket::new(socket.into()), inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( config.endpoint_config.clone(), @@ -248,40 +285,41 @@ impl Endpoint { muxers: HashMap::new(), event_receiver, pending: Default::default(), + buffer: vec![0; 0xFFFF], }), address: address.clone(), receive_connections: Mutex::new(Some(receive_connections)), new_connections, - event_channel, config, }); - let join_handle = async_std::task::spawn(EndpointDriver(Some(data.clone()))); - data.new_connections - .unbounded_send(Ok(ListenerEvent::NewAddress(address))) - .expect("we have a reference to the peer, so this will not fail; qed"); - Ok(Self { - data: EndpointRef::new(data), - join_handle, - }) - } - - /// Consume this `Endpoint`, and wait for it to close. - /// - /// The returned future will resolve when either - /// - /// 1. All connections are complete and [`Listener`] has been dropped. - /// 2. A fatal error occurs. - pub fn close(self) -> impl Future> { - self.join_handle - } - - fn data(&self) -> &Arc { - self.data.inner() + let channel = Channel::new(event_sender); + if socket_addr.ip().is_unspecified() { + debug!("returning all local IPs for unspecified address"); + let local_addresses = + host_addresses(&[Protocol::Udp(socket_addr.port()), Protocol::Quic]) + .map_err(|e| TransportError::Other(Error::IO(e)))?; + for i in local_addresses { + info!("sending address {:?}", i.2); + reference + .new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(i.2))) + .expect("we have a reference to the peer, so this will not fail; qed") + } + } else { + info!("sending address {:?}", address); + reference + .new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address))) + .expect("we have a reference to the peer, so this will not fail; qed"); + } + let endpoint = EndpointRef { reference, channel }; + let join_handle = spawn(endpoint.clone()); + Ok((Self(endpoint), join_handle)) } } fn create_muxer( - endpoint: Arc, + endpoint: ConnectionEndpoint, connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, @@ -297,8 +335,13 @@ impl EndpointData { connection: Connection, handle: ConnectionHandle, inner: &mut EndpointInner, + channel: Channel, ) { - let upgrade = create_muxer(self.clone(), connection, handle, &mut *inner); + let connection_endpoint = ConnectionEndpoint(EndpointRef { + reference: self.clone(), + channel, + }); + let upgrade = create_muxer(connection_endpoint, connection, handle, &mut *inner); if self .new_connections .unbounded_send(Ok(ListenerEvent::Upgrade { @@ -314,35 +357,29 @@ impl EndpointData { } } -impl Future for EndpointDriver { +impl Future for EndpointRef { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let outer_this = &mut self.get_mut().0; - let this = outer_this.as_ref().expect("polled after yielding Ready"); - let mut inner = this.inner.lock(); + let Self { reference, channel } = self.get_mut(); + let mut inner = reference.inner.lock(); trace!("driving events"); inner.drive_events(cx); trace!("driving incoming packets"); - match inner.drive_receive(&this.socket, cx)? { + match inner.drive_receive(&reference.socket, cx)? { Poll::Pending => {} Poll::Ready((handle, connection)) => { trace!("have a new connection"); - this.accept_muxer(connection, handle, &mut *inner); + reference.accept_muxer(connection, handle, &mut *inner, channel.clone()); trace!("connection accepted"); } } - match inner.poll_transmit_pending(&this.socket, cx)? { + match inner.poll_transmit_pending(&reference.socket, cx)? { Poll::Pending | Poll::Ready(()) => { - let refcount = Arc::strong_count(&this); - trace!( - "Strong reference count is {}. Address is {:?}", - refcount, - this.address - ); + let refcount = Arc::strong_count(&reference); + trace!("Strong reference count is {}", refcount); if refcount == 1 { - info!("All connections have closed. Exiting."); + debug!("All connections have closed. Closing QUIC endpoint driver."); drop(inner); - *outer_this = None; Poll::Ready(Ok(())) } else { Poll::Pending @@ -355,7 +392,9 @@ impl Future for EndpointDriver { /// A QUIC listener #[derive(Debug)] pub struct Listener { - reference: EndpointRef, + reference: Arc, + /// MUST COME AFTER `reference`! + drop_notifier: Channel, channel: mpsc::UnboundedReceiver, Error>>, } @@ -368,14 +407,26 @@ impl Stream for Listener { impl Transport for &Endpoint { type Output = (libp2p_core::PeerId, super::Muxer); - type Error = Error; + type Error = super::error::Error; type Listener = Listener; type ListenerUpgrade = Upgrade; type Dial = Upgrade; fn listen_on(self, addr: Multiaddr) -> Result> { - let reference = self.data().clone(); - if addr != reference.address { + let Endpoint(EndpointRef { + reference, + channel: drop_notifier, + }) = self; + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { + sa + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + let own_socket_addr = + multiaddr_to_socketaddr(&reference.address).expect("our own Multiaddr is valid; qed"); + if socket_addr.ip() != own_socket_addr.ip() + || (socket_addr.port() != 0 && socket_addr.port() != own_socket_addr.port()) + { return Err(TransportError::MultiaddrNotSupported(addr)); } let channel = reference @@ -383,7 +434,11 @@ impl Transport for &Endpoint { .lock() .take() .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; - Ok(Listener { channel, reference: EndpointRef::new(reference) }) + Ok(Listener { + reference: reference.clone(), + drop_notifier: drop_notifier.clone(), + channel, + }) } fn dial(self, addr: Multiaddr) -> Result> { @@ -396,17 +451,22 @@ impl Transport for &Endpoint { } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let data = self.data(); - let mut inner = data.inner.lock(); + let Endpoint(EndpointRef { reference, .. }) = self; + let mut inner = reference.inner.lock(); let s: Result<(_, Connection), _> = inner .inner - .connect(data.config.client_config.clone(), socket_addr, "localhost") + .connect(reference.config.client_config.clone(), socket_addr, "l") .map_err(|e| { warn!("Connection error: {:?}", e); TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; - Ok(create_muxer(data.clone(), conn, handle, &mut inner)) + Ok(create_muxer( + ConnectionEndpoint(self.clone().0), + conn, + handle, + &mut inner, + )) } } @@ -444,10 +504,6 @@ fn multiaddr_to_udp_conversion() { multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() ); - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/tcp/1234".parse::().unwrap()).is_err() - ); - assert_eq!( multiaddr_to_socketaddr( &"/ip4/127.0.0.1/udp/12345/quic" diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 63edabaf0c0..76cf2fcbe62 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -22,8 +22,9 @@ use err_derive::Error; use io::ErrorKind; use ring::error::Unspecified; use std::io; -#[derive(Error, Debug)] + /// An error that can be returned by libp2p-quic. +#[derive(Error, Debug)] pub enum Error { #[error(display = "Fatal I/O error {}", _0)] IO(#[error(source)] std::io::Error), diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 06122dd1815..e5d0c24ec2e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -62,8 +62,8 @@ mod connection; mod endpoint; mod error; mod socket; -pub use error::Error; mod verifier; pub use certificate::make_cert; pub use connection::{Config, Outbound, QuicMuxer as Muxer, Substream, Upgrade}; pub use endpoint::{Endpoint, Listener}; +pub use error::Error; diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 04832d86cf6..90d0de0de5b 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -53,7 +53,6 @@ impl Socket { fut.poll(cx) } { Poll::Pending => { - trace!("not able to send packet right away"); Poll::Pending } Poll::Ready(Ok(e)) => { @@ -80,7 +79,6 @@ impl Socket { fut.poll(cx) } { Poll::Pending => { - trace!("no packets available yet"); break Poll::Pending; } Poll::Ready(Ok(e)) => { diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 9015f947c3c..8b0129f9fb8 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -115,32 +115,36 @@ fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let mut listener = Endpoint::new(Config::new(&keypair), addr.clone()) - .expect("endpoint") - .listen_on(addr) - .expect("listener"); + let (listener, join) = Endpoint::new(Config::new(&keypair), addr.clone()).unwrap(); + let mut incoming = listener.listen_on(addr).unwrap(); + drop(listener); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. - match futures::executor::block_on(listener.next()) - .unwrap() - .unwrap() - { - ListenerEvent::NewAddress(a) => { - let mut iter = a.iter(); - match iter.next().expect("ip address") { - Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), - Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), - other => panic!("Unexpected protocol: {}", other), - } - if let Protocol::Udp(port) = iter.next().expect("port") { - assert_ne!(0, port) - } else { - panic!("No UDP port in address: {}", a) + futures::executor::block_on(async move { + while let Some(event) = incoming.next().await.map(|e| e.unwrap()) { + match event { + ListenerEvent::NewAddress(a) => { + let mut iter = a.iter(); + match iter.next().expect("ip address") { + Protocol::Ip4(_ip) => {} // assert!(!ip.is_unspecified()), + Protocol::Ip6(_ip) => {} // assert!(!ip.is_unspecified()), + other => panic!("Unexpected protocol: {}", other), + } + if let Protocol::Udp(port) = iter.next().expect("port") { + assert_ne!(0, port) + } else { + panic!("No UDP port in address: {}", a) + } + } + ListenerEvent::Upgrade { .. } => panic!(), + ListenerEvent::AddressExpired { .. } => panic!(), } + break; } - _ => panic!("NewAddress is the first event"), - } + drop(incoming); + join.await.unwrap() + }); } #[test] @@ -155,7 +159,7 @@ fn communicating_between_dialer_and_listener() { .parse() .expect("bad address?"); let quic_config = Config::new(&keypair2); - let quic_endpoint = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); + let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); let mut listener = quic_endpoint.listen_on(addr).unwrap(); let handle = async_std::task::spawn(async move { @@ -199,14 +203,15 @@ fn communicating_between_dialer_and_listener() { } }; drop(listener); - quic_endpoint.close().await.unwrap(); + drop(quic_endpoint); + join.await.unwrap(); key }); let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); let quic_config = Config::new(&keypair); - let quic_endpoint = Endpoint::new( + let (quic_endpoint, join) = Endpoint::new( quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), ) @@ -248,7 +253,8 @@ fn communicating_between_dialer_and_listener() { debug!("have EOF"); Closer(connection.1).await.expect("closed successfully"); debug!("awaiting handle"); - quic_endpoint.close().await.unwrap(); + drop(quic_endpoint); + join.await.unwrap(); info!("endpoint is finished"); connection.0 }); @@ -267,7 +273,7 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); - let quic = Endpoint::new(config, addr.clone()).expect("no error"); + let (quic, join) = Endpoint::new(config, addr.clone()).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -276,7 +282,11 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { .into_new_address() .expect("listen address"); - assert!(!new_addr.to_string().contains("tcp/0")); + if new_addr.to_string().contains("udp/0") { + panic!("failed to expand address ― got {}", new_addr); + } + drop(quic); + futures::executor::block_on(join).unwrap() } #[test] @@ -287,7 +297,7 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); - let quic = Endpoint::new(config, addr.clone()).expect("no error"); + let (quic, join) = Endpoint::new(config, addr.clone()).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -296,7 +306,9 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { .into_new_address() .expect("listen address"); - assert!(!new_addr.to_string().contains("tcp/0")); + assert!(!new_addr.to_string().contains("udp/0")); + drop(quic); + futures::executor::block_on(join).unwrap() } #[test] From 8ef346d36c9f564e558e7448ad5741c501f4163b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 5 Feb 2020 22:52:53 -0500 Subject: [PATCH 090/202] Move EndpointMessage to endpoint.rs where it logically belongs. --- transports/quic/src/connection.rs | 12 +----------- transports/quic/src/endpoint.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 71e9e953d31..d0a1f985af0 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use super::{ certificate, - endpoint::{ConnectionEndpoint as Endpoint, EndpointInner}, + endpoint::{ConnectionEndpoint as Endpoint, EndpointInner, EndpointMessage}, error::Error, socket::Pending, verifier, @@ -148,16 +148,6 @@ impl Config { } } -#[derive(Debug)] -pub enum EndpointMessage { - Dummy, - ConnectionAccepted, - EndpointEvent { - handle: ConnectionHandle, - event: quinn_proto::EndpointEvent, - }, -} - #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 7d570ec2cdb..bdf45e139b6 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{connection::EndpointMessage, error::Error, socket, Config, Upgrade}; +use crate::{error::Error, socket, Config, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -38,6 +38,16 @@ use std::{ time::Instant, }; +#[derive(Debug)] +pub enum EndpointMessage { + Dummy, + ConnectionAccepted, + EndpointEvent { + handle: ConnectionHandle, + event: quinn_proto::EndpointEvent, + }, +} + #[derive(Debug)] pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, From a105db56ab91edf603d5db69b5d47e661fc5be40 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 5 Feb 2020 22:54:53 -0500 Subject: [PATCH 091/202] Reformat --- transports/quic/src/socket.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 90d0de0de5b..a7621973186 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -41,20 +41,18 @@ pub(crate) struct Socket { impl Socket { /// Transmit a packet if possible, with appropriate logging. - /// - /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition - /// and drop it. If it is not, the connection will eventually time out. This provides a very - /// high degree of robustness. Connections will transparently resume after a transient network - /// outage, and problems that are specific to one peer will not effect other peers. + /// + /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition + /// and drop it. If it is not, the connection will eventually time out. This provides a very + /// high degree of robustness. Connections will transparently resume after a transient network + /// outage, and problems that are specific to one peer will not effect other peers. pub fn poll_send_to(&self, cx: &mut Context, packet: &Transmit) -> Poll> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); futures::pin_mut!(fut); fut.poll(cx) } { - Poll::Pending => { - Poll::Pending - } + Poll::Pending => Poll::Pending, Poll::Ready(Ok(e)) => { trace!("sent packet of length {} to {}", e, packet.destination); Poll::Ready(Ok(())) From ee97e31abb69cc7adafda6054d345caf2de7b619 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 6 Feb 2020 18:13:29 -0500 Subject: [PATCH 092/202] Move the configuration code into the endpoint --- transports/quic/src/connection.rs | 77 ------------------------------ transports/quic/src/endpoint.rs | 78 ++++++++++++++++++++++++++++++- transports/quic/src/lib.rs | 4 +- 3 files changed, 79 insertions(+), 80 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index d0a1f985af0..8ea4f648b1f 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -22,7 +22,6 @@ use super::{ endpoint::{ConnectionEndpoint as Endpoint, EndpointInner, EndpointMessage}, error::Error, socket::Pending, - verifier, }; use async_macros::ready; use futures::{channel::oneshot, prelude::*}; @@ -72,82 +71,6 @@ impl Substream { } } -/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. -/// -/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams -/// obtained by libp2p through a reactor. -#[derive(Debug, Clone)] -pub struct Config { - /// The client configuration. Quinn provides functions for making one. - pub client_config: quinn_proto::ClientConfig, - /// The server configuration. Quinn provides functions for making one. - pub server_config: Arc, - /// The endpoint configuration - pub endpoint_config: Arc, -} - -fn make_client_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ClientConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - use std::time::Duration; - transport.keep_alive_interval(Some(Duration::from_millis(1000))); - let mut crypto = rustls::ClientConfig::new(); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto.enable_early_data = true; - crypto.set_single_client_cert(vec![certificate], key); - let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; - crypto - .dangerous() - .set_certificate_verifier(Arc::new(verifier)); - quinn_proto::ClientConfig { - transport: Arc::new(transport), - crypto: Arc::new(crypto), - } -} - -fn make_server_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ServerConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, - )); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto - .set_single_cert(vec![certificate], key) - .expect("we are given a valid cert; qed"); - let mut config = quinn_proto::ServerConfig::default(); - config.transport = Arc::new(transport); - config.crypto = Arc::new(crypto); - config -} - -impl Config { - /// Creates a new configuration object for TCP/IP. - pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { - let cert = super::make_cert(&keypair); - let (cert, key) = ( - rustls::Certificate( - cert.serialize_der() - .expect("serialization of a valid cert will succeed; qed"), - ), - rustls::PrivateKey(cert.serialize_private_key_der()), - ); - Self { - client_config: make_client_config(cert.clone(), key.clone()), - server_config: Arc::new(make_server_config(cert, key)), - endpoint_config: Default::default(), - } - } -} - #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index bdf45e139b6..d053f6d2c2e 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{error::Error, socket, Config, Upgrade}; +use crate::{error::Error, socket, Upgrade, verifier}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -38,6 +38,82 @@ use std::{ time::Instant, }; +/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. +/// +/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams +/// obtained by libp2p through a reactor. +#[derive(Debug, Clone)] +pub struct Config { + /// The client configuration. Quinn provides functions for making one. + pub client_config: quinn_proto::ClientConfig, + /// The server configuration. Quinn provides functions for making one. + pub server_config: Arc, + /// The endpoint configuration + pub endpoint_config: Arc, +} + +fn make_client_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ClientConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); + use std::time::Duration; + transport.keep_alive_interval(Some(Duration::from_millis(1000))); + let mut crypto = rustls::ClientConfig::new(); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto.enable_early_data = true; + crypto.set_single_client_cert(vec![certificate], key); + let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; + crypto + .dangerous() + .set_certificate_verifier(Arc::new(verifier)); + quinn_proto::ClientConfig { + transport: Arc::new(transport), + crypto: Arc::new(crypto), + } +} + +fn make_server_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> quinn_proto::ServerConfig { + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); + let mut crypto = rustls::ServerConfig::new(Arc::new( + verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, + )); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto + .set_single_cert(vec![certificate], key) + .expect("we are given a valid cert; qed"); + let mut config = quinn_proto::ServerConfig::default(); + config.transport = Arc::new(transport); + config.crypto = Arc::new(crypto); + config +} + +impl Config { + /// Creates a new configuration object for TCP/IP. + pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { + let cert = super::make_cert(&keypair); + let (cert, key) = ( + rustls::Certificate( + cert.serialize_der() + .expect("serialization of a valid cert will succeed; qed"), + ), + rustls::PrivateKey(cert.serialize_private_key_der()), + ); + Self { + client_config: make_client_config(cert.clone(), key.clone()), + server_config: Arc::new(make_server_config(cert, key)), + endpoint_config: Default::default(), + } + } +} + #[derive(Debug)] pub enum EndpointMessage { Dummy, diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index e5d0c24ec2e..9b74e700c9f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -64,6 +64,6 @@ mod error; mod socket; mod verifier; pub use certificate::make_cert; -pub use connection::{Config, Outbound, QuicMuxer as Muxer, Substream, Upgrade}; -pub use endpoint::{Endpoint, Listener}; +pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; +pub use endpoint::{Endpoint, Listener, Config}; pub use error::Error; From e22d2a73a2966fadf18305c8aad6b85af45543bb Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 7 Feb 2020 09:14:21 -0500 Subject: [PATCH 093/202] Fix comment --- transports/quic/src/connection.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 8ea4f648b1f..781414e6ca7 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -579,8 +579,7 @@ impl Muxer { } } - /// Send endpoint events. Returns true if and only if there are endpoint events remaining to - /// be sent. + /// Send endpoint events. fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { loop { let token = match self.endpoint.poll_ready(cx) { From 1f51fd6b6001eac764c458636d23b3d2f5b9656e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 10 Feb 2020 10:18:29 -0500 Subject: [PATCH 094/202] Fix broken links in rustdoc --- transports/quic/src/endpoint.rs | 7 ++++--- transports/quic/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index d053f6d2c2e..047ee1ed9d8 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // -use crate::{error::Error, socket, Upgrade, verifier}; +use crate::{error::Error, socket, verifier, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -319,11 +319,12 @@ pub(crate) use channel_ref::{Channel, EndpointRef}; /// /// `Endpoint` wraps the underlying data structure in an [`Arc`], so cloning it just bumps the /// reference count. All state is shared between the clones. For example, you can pass different -/// clones to [`listen_on`]. Each incoming connection will be received by exactly one of them. +/// clones to [`Transport::listen_on`]. Each incoming connection will be received by exactly one of +/// them. /// /// The **only** valid [`Multiaddr`] to pass to `listen_on` is the one used to create the /// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you -/// will get [`TransportError::MultiaddrNotSuppported`]. +/// will get [`TransportError::MultiaddrNotSupported`]. #[derive(Debug, Clone)] pub struct Endpoint(EndpointRef); diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9b74e700c9f..998fa117fe7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -65,5 +65,5 @@ mod socket; mod verifier; pub use certificate::make_cert; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; -pub use endpoint::{Endpoint, Listener, Config}; +pub use endpoint::{Config, Endpoint, Listener}; pub use error::Error; From f87a4f2267586f0b866afc0b12273718e58593c1 Mon Sep 17 00:00:00 2001 From: Demi Obenour <48690212+DemiMarie-parity@users.noreply.github.com> Date: Wed, 12 Feb 2020 17:47:35 +0000 Subject: [PATCH 095/202] Apply suggestions from code review Co-Authored-By: Pierre Krieger --- transports/quic/src/certificate.rs | 3 +++ transports/quic/src/lib.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index cc84b398668..b794a5f1a46 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -25,6 +25,7 @@ //! This crate uses the `log` crate to emit log output. Events that will occur normally are output //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. + use log::{trace, warn}; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -68,6 +69,8 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu ) } +/// Generates a self-signed TLS certificate that includes a libp2p-specific certificate extension +/// containing the public key of the given keypair. pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { let mut params = rcgen::CertificateParams::new(vec![]); let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 998fa117fe7..c326a0e81b2 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the libp2p `Transport` and `StreamMuxer` traits for QUIC/UDP/IP. +//! Implementation of the libp2p `Transport` and `StreamMuxer` traits for QUIC. //! //! # Usage //! @@ -57,6 +57,7 @@ #![cfg_attr(test, forbid(warnings, clippy::all))] #![deny(missing_copy_implementations)] #![deny(trivial_casts)] + mod certificate; mod connection; mod endpoint; From 2d5981a496f01327cf08f65c93b26e650462ae1f Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 12:49:58 -0500 Subject: [PATCH 096/202] =?UTF-8?q?Refactor=20as=20per=20@tomaka=E2=80=99s?= =?UTF-8?q?=20suggestion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- misc/multiaddr/src/lib.rs | 15 ++++----------- transports/quic/src/certificate.rs | 6 +++--- transports/quic/src/endpoint.rs | 31 +++++++++++++++++++++--------- transports/quic/src/lib.rs | 1 - transports/quic/tests/tests.rs | 6 ++++-- transports/tcp/src/lib.rs | 8 ++++---- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/misc/multiaddr/src/lib.rs b/misc/multiaddr/src/lib.rs index 68ad3d21ec0..5dafe7e486e 100644 --- a/misc/multiaddr/src/lib.rs +++ b/misc/multiaddr/src/lib.rs @@ -303,16 +303,6 @@ impl TryFrom> for Multiaddr { } } -/// Create a [`Multiaddr`] from the given IP address and suffix. -pub fn ip_to_multiaddr(ip: IpAddr, suffix: &[Protocol]) -> Multiaddr { - let proto = match ip { - IpAddr::V4(ip) => Protocol::Ip4(ip), - IpAddr::V6(ip) => Protocol::Ip6(ip), - }; - let it = std::iter::once(proto).chain(suffix.into_iter().cloned()); - Multiaddr::from_iter(it) -} - /// Collect all local host addresses and use the provided port number as listen port. #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] pub fn host_addresses(suffix: &[Protocol]) -> io::Result> { @@ -321,7 +311,10 @@ pub fn host_addresses(suffix: &[Protocol]) -> io::Result { let prefix_len = (!u32::from_be_bytes(ip4.netmask.octets())).leading_zeros(); diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index b794a5f1a46..8fe1ed12ef4 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -26,13 +26,13 @@ //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. +use libp2p_core::identity; use log::{trace, warn}; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; -use libp2p_core::identity; fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { let public_key = public_key.into_protobuf_encoding(); @@ -201,8 +201,8 @@ fn parse_tbscertificate(reader: yasna::BERReader) -> yasna::ASN1Result Result { parse_certificate(certificate) .map_err(|e| { diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 047ee1ed9d8..0417c147559 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -35,21 +35,18 @@ use std::{ pin::Pin, sync::{Arc, Weak}, task::{Context, Poll}, - time::Instant, + time::{Duration, Instant}, }; /// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. -/// -/// The QUIC endpoints created by libp2p will need to be progressed by running the futures and streams -/// obtained by libp2p through a reactor. #[derive(Debug, Clone)] pub struct Config { /// The client configuration. Quinn provides functions for making one. - pub client_config: quinn_proto::ClientConfig, + client_config: quinn_proto::ClientConfig, /// The server configuration. Quinn provides functions for making one. - pub server_config: Arc, + server_config: Arc, /// The endpoint configuration - pub endpoint_config: Arc, + endpoint_config: Arc, } fn make_client_config( @@ -59,7 +56,6 @@ fn make_client_config( let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); - use std::time::Duration; transport.keep_alive_interval(Some(Duration::from_millis(1000))); let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; @@ -98,7 +94,7 @@ fn make_server_config( impl Config { /// Creates a new configuration object for TCP/IP. pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { - let cert = super::make_cert(&keypair); + let cert = super::certificate::make_cert(&keypair); let (cert, key) = ( rustls::Certificate( cert.serialize_der() @@ -362,6 +358,23 @@ impl Endpoint { } let (new_connections, receive_connections) = mpsc::unbounded(); let (event_sender, event_receiver) = mpsc::channel(0); + if socket_addr.ip().is_unspecified() { + info!("returning all local IPs for unspecified address"); + let suffixes = [Protocol::Udp(socket_addr.port()), Protocol::Quic]; + let local_addresses = + host_addresses(&suffixes).map_err(|e| TransportError::Other(Error::IO(e)))?; + for (_, _, address) in local_addresses { + info!("sending address {:?}", address); + new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address))) + .expect("we have a reference to the peer, so this will not fail; qed") + } + } else { + info!("sending address {:?}", address); + new_connections + .unbounded_send(Ok(ListenerEvent::NewAddress(address.clone()))) + .expect("we have a reference to the peer, so this will not fail; qed"); + } let reference = Arc::new(EndpointData { socket: socket::Socket::new(socket.into()), inner: Mutex::new(EndpointInner { diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c326a0e81b2..0a88d47e78e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -64,7 +64,6 @@ mod endpoint; mod error; mod socket; mod verifier; -pub use certificate::make_cert; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; pub use endpoint::{Config, Endpoint, Listener}; pub use error::Error; diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 8b0129f9fb8..9c9281983d2 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -155,7 +155,7 @@ fn communicating_between_dialer_and_listener() { let mut ready_tx = Some(ready_tx); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); - let addr: Multiaddr = "/ip4/127.0.0.1/udp/12345/quic" + let addr: Multiaddr = "/ip4/0.0.0.0/udp/12345/quic" .parse() .expect("bad address?"); let quic_config = Config::new(&keypair2); @@ -167,7 +167,9 @@ fn communicating_between_dialer_and_listener() { trace!("awaiting connection"); match listener.next().await.unwrap().unwrap() { ListenerEvent::NewAddress(listen_addr) => { - ready_tx.take().unwrap().send(listen_addr).unwrap(); + if let Some(channel) = ready_tx.take() { + channel.send(listen_addr).unwrap(); + } } ListenerEvent::Upgrade { upgrade, .. } => { debug!("got a connection upgrade!"); diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index b94a0929786..877c3076e67 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -34,7 +34,7 @@ use futures_timer::Delay; use ipnet::IpNet; use libp2p_core::{ Transport, - multiaddr::{Protocol, Multiaddr, host_addresses, ip_to_multiaddr}, + multiaddr::{Protocol, Multiaddr, host_addresses}, transport::{ListenerEvent, TransportError} }; use log::{debug, trace}; @@ -119,7 +119,7 @@ impl Transport for $tcp_config { debug!("Listening on {:?}", addrs.iter().map(|(_, _, ma)| ma).collect::>()); Addresses::Many(addrs) } else { - let ma = ip_to_multiaddr(local_addr.ip(), &[Protocol::Tcp(port)]); + let ma = Multiaddr::from(local_addr.ip()).with(Protocol::Tcp(port)); debug!("Listening on {:?}", ma); Addresses::One(ma) }; @@ -238,7 +238,7 @@ impl $tcp_listen_stream { return (Err(err), self); } } - ip_to_multiaddr(sock_addr.ip(), &[Protocol::Tcp(sock_addr.port())]) + Multiaddr::from(sock_addr.ip()).with(Protocol::Tcp(sock_addr.port())) } Err(err) => { debug!("Failed to get local address of incoming socket: {:?}", err); @@ -246,7 +246,7 @@ impl $tcp_listen_stream { } }; - let remote_addr = ip_to_multiaddr(sock_addr.ip(), &[Protocol::Tcp(sock_addr.port())]); + let remote_addr = Multiaddr::from(sock_addr.ip()).with(Protocol::Tcp(sock_addr.port())); match $apply_config(&self.config, &sock) { Ok(()) => { From 6db3d4955b6204a2ce8760ee15ffe288dfada1a3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 12:59:27 -0500 Subject: [PATCH 097/202] Implement Transport for Endpoint --- transports/quic/src/endpoint.rs | 20 ++++++++++++-------- transports/quic/tests/tests.rs | 7 +------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 0417c147559..3f416ec5522 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -337,7 +337,7 @@ impl Endpoint { pub fn new( config: Config, mut address: Multiaddr, - ) -> Result<(Self, JoinHandle), TransportError<<&'static Self as Transport>::Error>> { + ) -> Result<(Self, JoinHandle), TransportError<::Error>> { let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { sa } else { @@ -505,7 +505,7 @@ impl Stream for Listener { } } -impl Transport for &Endpoint { +impl Transport for Endpoint { type Output = (libp2p_core::PeerId, super::Muxer); type Error = super::error::Error; type Listener = Listener; @@ -535,8 +535,8 @@ impl Transport for &Endpoint { .take() .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; Ok(Listener { - reference: reference.clone(), - drop_notifier: drop_notifier.clone(), + reference, + drop_notifier, channel, }) } @@ -551,18 +551,22 @@ impl Transport for &Endpoint { } else { return Err(TransportError::MultiaddrNotSupported(addr)); }; - let Endpoint(EndpointRef { reference, .. }) = self; - let mut inner = reference.inner.lock(); + let Endpoint(endpoint) = self; + let mut inner = endpoint.reference.inner.lock(); let s: Result<(_, Connection), _> = inner .inner - .connect(reference.config.client_config.clone(), socket_addr, "l") + .connect( + endpoint.reference.config.client_config.clone(), + socket_addr, + "l", + ) .map_err(|e| { warn!("Connection error: {:?}", e); TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; Ok(create_muxer( - ConnectionEndpoint(self.clone().0), + ConnectionEndpoint(endpoint.clone()), conn, handle, &mut inner, diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 9c9281983d2..5e010a111c0 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -117,8 +117,6 @@ fn wildcard_expansion() { let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let (listener, join) = Endpoint::new(Config::new(&keypair), addr.clone()).unwrap(); let mut incoming = listener.listen_on(addr).unwrap(); - drop(listener); - // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. futures::executor::block_on(async move { @@ -160,7 +158,7 @@ fn communicating_between_dialer_and_listener() { .expect("bad address?"); let quic_config = Config::new(&keypair2); let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); - let mut listener = quic_endpoint.listen_on(addr).unwrap(); + let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); let handle = async_std::task::spawn(async move { let key = loop { @@ -255,7 +253,6 @@ fn communicating_between_dialer_and_listener() { debug!("have EOF"); Closer(connection.1).await.expect("closed successfully"); debug!("awaiting handle"); - drop(quic_endpoint); join.await.unwrap(); info!("endpoint is finished"); connection.0 @@ -287,7 +284,6 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { if new_addr.to_string().contains("udp/0") { panic!("failed to expand address ― got {}", new_addr); } - drop(quic); futures::executor::block_on(join).unwrap() } @@ -309,7 +305,6 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { .expect("listen address"); assert!(!new_addr.to_string().contains("udp/0")); - drop(quic); futures::executor::block_on(join).unwrap() } From f3e9fca00aafdda30050c689d00aad49c792318d Mon Sep 17 00:00:00 2001 From: Demi Obenour <48690212+DemiMarie-parity@users.noreply.github.com> Date: Wed, 12 Feb 2020 18:09:23 +0000 Subject: [PATCH 098/202] QUIC is not QUIC/UDP/IP Co-Authored-By: Pierre Krieger --- transports/quic/src/endpoint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 3f416ec5522..3706469e42b 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -38,7 +38,7 @@ use std::{ time::{Duration, Instant}, }; -/// Represents the configuration for a QUIC/UDP/IP transport capability for libp2p. +/// Represents the configuration for a QUIC transport capability for libp2p. #[derive(Debug, Clone)] pub struct Config { /// The client configuration. Quinn provides functions for making one. From 7fef35fed90ca48270e2451d24f77f6dfb4988d7 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 13:14:08 -0500 Subject: [PATCH 099/202] Avoid &Arc, as it is unstable. --- transports/quic/src/endpoint.rs | 50 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 3f416ec5522..6ed33d94577 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -429,31 +429,29 @@ fn create_muxer( }) } -impl EndpointData { - fn accept_muxer( - self: &Arc, - connection: Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, - channel: Channel, - ) { - let connection_endpoint = ConnectionEndpoint(EndpointRef { - reference: self.clone(), - channel, - }); - let upgrade = create_muxer(connection_endpoint, connection, handle, &mut *inner); - if self - .new_connections - .unbounded_send(Ok(ListenerEvent::Upgrade { - upgrade, - local_addr: self.address.clone(), - remote_addr: self.address.clone(), - })) - .is_err() - { - inner.inner.accept(); - inner.inner.reject_new_connections(); - } +fn accept_muxer( + endpoint: &Arc, + connection: Connection, + handle: ConnectionHandle, + inner: &mut EndpointInner, + channel: Channel, +) { + let connection_endpoint = ConnectionEndpoint(EndpointRef { + reference: endpoint.clone(), + channel, + }); + let upgrade = create_muxer(connection_endpoint, connection, handle, &mut *inner); + if endpoint + .new_connections + .unbounded_send(Ok(ListenerEvent::Upgrade { + upgrade, + local_addr: endpoint.address.clone(), + remote_addr: endpoint.address.clone(), + })) + .is_err() + { + inner.inner.accept(); + inner.inner.reject_new_connections(); } } @@ -469,7 +467,7 @@ impl Future for EndpointRef { Poll::Pending => {} Poll::Ready((handle, connection)) => { trace!("have a new connection"); - reference.accept_muxer(connection, handle, &mut *inner, channel.clone()); + accept_muxer(reference, connection, handle, &mut *inner, channel.clone()); trace!("connection accepted"); } } From 40895c24b398ac590883b2ca339a950913b51dac Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 16:36:25 -0500 Subject: [PATCH 100/202] Avoid references from connection to endpoint This also adds better tests and makes other improvements. --- transports/quic/src/connection.rs | 62 +++++--------- transports/quic/src/endpoint.rs | 137 +++++++++++++++++------------- transports/quic/src/error.rs | 13 ++- transports/quic/src/lib.rs | 2 +- transports/quic/tests/tests.rs | 42 ++++++--- 5 files changed, 144 insertions(+), 112 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 781414e6ca7..2f4db76e342 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use super::{ certificate, - endpoint::{ConnectionEndpoint as Endpoint, EndpointInner, EndpointMessage}, + endpoint::{ConnectionEndpoint as Endpoint, EndpointMessage}, error::Error, socket::Pending, }; @@ -28,13 +28,13 @@ use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; use log::{debug, error, info, trace, warn}; use parking_lot::{Mutex, MutexGuard}; -use quinn_proto::{Connection, ConnectionEvent, ConnectionHandle, Dir, StreamId}; +use quinn_proto::{Connection, ConnectionHandle, Dir, StreamId}; use std::{ collections::HashMap, io, mem::replace, pin::Pin, - sync::{Arc, Weak}, + sync::Arc, task::{Context, Poll}, time::Instant, }; @@ -414,8 +414,8 @@ impl Future for Upgrade { return Poll::Pending; } else if inner.connection.side().is_server() { let endpoint = &mut inner.endpoint; - let token = ready!(endpoint.poll_ready(cx)); - endpoint.send_message(EndpointMessage::ConnectionAccepted, token) + let token = ready!(endpoint.poll_ready(cx))?; + endpoint.send_message(EndpointMessage::ConnectionAccepted, token)? } let peer_certificates: Vec = inner @@ -446,7 +446,8 @@ type StreamSenderQueue = std::collections::VecDeque>; #[derive(Debug)] pub(crate) struct Muxer { - /// The pending stream, if any. + /// If this is `Some`, it is a stream that has been opened by the peer, + /// but not yet accepted by the application. Otherwise, this is `None`. pending_stream: Option, /// The associated endpoint endpoint: Endpoint, @@ -506,7 +507,7 @@ impl Drop for QuicMuxer { } impl Muxer { - fn wake_driver(&mut self) { + pub(crate) fn wake_driver(&mut self) { if let Some(waker) = self.waker.take() { debug!("driver awoken!"); waker.wake(); @@ -569,22 +570,16 @@ impl Muxer { } } - /// Process all endpoint-facing events for this connection. This is synchronous and will not - /// fail. - fn send_to_endpoint(&mut self, endpoint: &mut EndpointInner) { - while let Some(endpoint_event) = self.connection.poll_endpoint_events() { - if let Some(connection_event) = endpoint.handle_event(self.handle, endpoint_event) { - self.connection.handle_event(connection_event) - } - } + pub(crate) fn poll_endpoint_events(&mut self) -> Option { + self.connection.poll_endpoint_events() } - /// Send endpoint events. - fn poll_endpoint_events(&mut self, cx: &mut Context<'_>) { + /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. + fn send_endpoint_events(&mut self, cx: &mut Context<'_>) -> Result<(), Error> { loop { let token = match self.endpoint.poll_ready(cx) { - Poll::Pending => break, - Poll::Ready(token) => token, + Poll::Pending => break Ok(()), + Poll::Ready(token) => token?, }; if let Some(event) = self.connection.poll_endpoint_events() { self.endpoint.send_message( @@ -593,9 +588,9 @@ impl Muxer { event, }, token, - ) + )? } else { - break; + break Ok(()); } } } @@ -636,21 +631,8 @@ impl Muxer { self.wake_driver(); } - /// Process application events - pub(crate) fn process_connection_events( - &mut self, - endpoint: &mut EndpointInner, - event: Option, - ) { - if let Some(event) = event { - self.connection.handle_event(event); - } - if self.connection.is_drained() { - return; - } - self.send_to_endpoint(endpoint); - self.process_app_events(); - self.wake_driver(); + pub(crate) fn handle_event(&mut self, event: quinn_proto::ConnectionEvent) { + self.connection.handle_event(event) } fn get_pending_stream(&mut self) -> Option { @@ -666,7 +648,7 @@ impl Muxer { }) } - fn process_app_events(&mut self) -> bool { + pub(crate) fn process_app_events(&mut self) -> bool { use quinn_proto::Event; let mut keep_going = false; 'a: while let Some(event) = self.connection.poll() { @@ -785,14 +767,14 @@ pub(super) struct ConnectionDriver { } impl ConnectionDriver { - pub(crate) fn spawn>)>( + pub(crate) fn spawn>)>( endpoint: Endpoint, connection: Connection, handle: ConnectionHandle, cb: T, ) -> Upgrade { let inner = Arc::new(Mutex::new(Muxer::new(endpoint, connection, handle))); - cb(Arc::downgrade(&inner)); + cb(inner.clone()); let handle = async_std::task::spawn(Self { inner: inner.clone(), outgoing_packet: None, @@ -815,7 +797,7 @@ impl Future for ConnectionDriver { let now = Instant::now(); inner.transmit_pending(now, cx)?; inner.process_app_events(); - inner.poll_endpoint_events(cx); + inner.send_endpoint_events(cx)?; if inner.drive_timer(cx, now) { continue; } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 20d28313de7..951e5bc6fcb 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -31,9 +31,9 @@ use log::{debug, info, trace, warn}; use parking_lot::Mutex; use quinn_proto::{Connection, ConnectionHandle}; use std::{ - collections::HashMap, + collections::{hash_map::Entry, HashMap}, pin::Pin, - sync::{Arc, Weak}, + sync::Arc, task::{Context, Poll}, time::{Duration, Instant}, }; @@ -123,7 +123,7 @@ pub enum EndpointMessage { #[derive(Debug)] pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, - muxers: HashMap>>, + muxers: HashMap>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, @@ -153,13 +153,9 @@ impl EndpointInner { self.inner.accept(); } EndpointMessage::EndpointEvent { handle, event } => { - debug!("we have an event from connection {:?}", handle); - match self.muxers.get(&handle).and_then(|e| e.upgrade()) { - None => drop(self.muxers.remove(&handle)), - Some(connection) => { - let event = self.inner.handle_event(handle, event); - connection.lock().process_connection_events(self, event) - } + debug!("we have event {:?} from connection {:?}", event, handle); + if let Some(event) = self.handle_event(handle, event) { + self.send_connection_event(handle, event) } } EndpointMessage::Dummy => {} @@ -167,6 +163,35 @@ impl EndpointInner { } } + /// Send the given `ConnectionEvent` to the appropriate connection, + /// and process any events it sends back. + fn send_connection_event( + &mut self, + handle: quinn_proto::ConnectionHandle, + event: quinn_proto::ConnectionEvent, + ) { + let Self { inner, muxers, .. } = self; + let entry = match muxers.entry(handle) { + Entry::Vacant(_) => return, + Entry::Occupied(occupied) => occupied, + }; + let mut connection = entry.get().lock(); + connection.handle_event(event); + let mut is_drained = false; + while let Some(event) = connection.poll_endpoint_events() { + is_drained |= event.is_drained(); + if let Some(event) = inner.handle_event(handle, event) { + connection.handle_event(event) + } + } + connection.process_app_events(); + connection.wake_driver(); + if is_drained { + drop(connection); + entry.remove(); + } + } + fn drive_receive( &mut self, socket: &socket::Socket, @@ -185,25 +210,15 @@ impl EndpointInner { }; trace!("have an event!"); match event { - DatagramEvent::ConnectionEvent(connection_event) => { - match self.muxers.get(&handle).and_then(|e| e.upgrade()) { - Some(connection) => connection - .lock() - .process_connection_events(self, Some(connection_event)), - None => { - debug!("lost our connection!"); - assert!(self - .handle_event(handle, quinn_proto::EndpointEvent::drained()) - .is_none()) - } - } + DatagramEvent::ConnectionEvent(event) => { + self.send_connection_event(handle, event); + continue; } DatagramEvent::NewConnection(connection) => { debug!("new connection detected!"); break Poll::Ready(Ok((handle, connection))); } } - trace!("event processed!") } } @@ -224,7 +239,7 @@ type ListenerResult = Result, Error>; #[derive(Debug)] pub(super) struct EndpointData { /// The single UDP socket used for I/O - socket: socket::Socket, + socket: Arc, /// A `Mutex` protecting the QUIC state machine. inner: Mutex, /// The channel on which new connections are sent. This is bounded in practice by the accept @@ -242,26 +257,19 @@ type Sender = mpsc::Sender; impl ConnectionEndpoint { pub fn socket(&self) -> &socket::Socket { - &self.0.reference.socket + &self.socket } - pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll { - match self.0.channel.poll_ready(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Err(_)) => unreachable!( - "We have a reference to the peer; polling a \ - channel only fails when the peer has been dropped, so this will \ - not fail; qed" - ), - Poll::Ready(Ok(())) => Poll::Ready(SendToken(())), - } + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.channel.poll_ready(cx).map_ok(SendToken) } - pub fn send_message(&mut self, event: EndpointMessage, SendToken(()): SendToken) { - self.0.channel.start_send(event).expect( - "you must check that you have capacity to get a SendToken; you cannot \ - call this method without a SendToken, so this will not fail; qed", - ) + pub fn send_message( + &mut self, + event: EndpointMessage, + SendToken(()): SendToken, + ) -> Result<(), mpsc::SendError> { + self.channel.start_send(event) } } @@ -325,9 +333,12 @@ pub(crate) use channel_ref::{Channel, EndpointRef}; pub struct Endpoint(EndpointRef); #[derive(Debug)] -pub(crate) struct ConnectionEndpoint(EndpointRef); +pub(crate) struct ConnectionEndpoint { + channel: Channel, + socket: Arc, +} -type JoinHandle = async_std::task::JoinHandle>; +pub type JoinHandle = async_std::task::JoinHandle>; #[derive(Debug)] pub(crate) struct SendToken(()); @@ -376,7 +387,7 @@ impl Endpoint { .expect("we have a reference to the peer, so this will not fail; qed"); } let reference = Arc::new(EndpointData { - socket: socket::Socket::new(socket.into()), + socket: Arc::new(socket::Socket::new(socket.into())), inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( config.endpoint_config.clone(), @@ -436,10 +447,10 @@ fn accept_muxer( inner: &mut EndpointInner, channel: Channel, ) { - let connection_endpoint = ConnectionEndpoint(EndpointRef { - reference: endpoint.clone(), + let connection_endpoint = ConnectionEndpoint { + socket: endpoint.socket.clone(), channel, - }); + }; let upgrade = create_muxer(connection_endpoint, connection, handle, &mut *inner); if endpoint .new_connections @@ -472,17 +483,16 @@ impl Future for EndpointRef { } } match inner.poll_transmit_pending(&reference.socket, cx)? { - Poll::Pending | Poll::Ready(()) => { - let refcount = Arc::strong_count(&reference); - trace!("Strong reference count is {}", refcount); - if refcount == 1 { - debug!("All connections have closed. Closing QUIC endpoint driver."); - drop(inner); - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } + Poll::Pending | Poll::Ready(()) => {} + } + + if !inner.muxers.is_empty() { + Poll::Pending + } else if Arc::strong_count(&reference) == 1 { + debug!("All connections have closed. Closing QUIC endpoint driver."); + Poll::Ready(Ok(())) + } else { + Poll::Pending } } } @@ -563,11 +573,18 @@ impl Transport for Endpoint { TransportError::Other(Error::CannotConnect(e)) }); let (handle, conn) = s?; - Ok(create_muxer( - ConnectionEndpoint(endpoint.clone()), + let socket = endpoint.reference.socket.clone(); + let endpoint = ConnectionEndpoint { + socket, + channel: endpoint.channel, + }; + Ok(super::connection::ConnectionDriver::spawn( + endpoint, conn, handle, - &mut inner, + |weak| { + inner.muxers.insert(handle, weak); + }, )) } } diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 76cf2fcbe62..a3ebc7defb9 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use err_derive::Error; +use futures::channel::mpsc::SendError; use io::ErrorKind; use ring::error::Unspecified; use std::io; @@ -53,6 +54,14 @@ pub enum Error { notify the peer that a stream is opened until at least one byte is sent. \ Therefore, this read would deadlock.")] CannotReadFromUnwrittenStream, + #[error(display = "Fatal internal error or network failure")] + NetworkFailure, +} + +impl From for Error { + fn from(_: SendError) -> Error { + Error::NetworkFailure + } } impl From for io::Error { @@ -61,7 +70,9 @@ impl From for io::Error { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), e @ Error::BadCertificate(Unspecified) => io::Error::new(ErrorKind::InvalidData, e), Error::ConnectionError(e) => e.into(), - e @ Error::CannotConnect(_) => io::Error::new(ErrorKind::Other, e), + e @ Error::CannotConnect(_) | e @ Error::NetworkFailure => { + io::Error::new(ErrorKind::Other, e) + } e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 0a88d47e78e..9c30c5c4963 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -65,5 +65,5 @@ mod error; mod socket; mod verifier; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; -pub use endpoint::{Config, Endpoint, Listener}; +pub use endpoint::{Config, Endpoint, Listener, JoinHandle}; pub use error::Error; diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 5e010a111c0..b4f0a885fd4 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -22,10 +22,10 @@ use async_macros::ready; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::ListenerEvent, + transport::{ListenerEvent, TransportError}, StreamMuxer, Transport, }; -use libp2p_quic::{Config, Endpoint, Muxer, Substream}; +use libp2p_quic::{Config, Endpoint, Error, JoinHandle, Muxer, Substream}; use log::{debug, info, trace}; use std::{ io::Result, @@ -147,17 +147,40 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { - use std::error::Error as _; + use std::io::{stdout, Write as _}; init(); + let stdout = stdout(); + println!(""); + for i in 0..10u32 { + { + let mut stdout = stdout.lock(); + write!(&mut stdout, "\r{}", i).unwrap(); + (&mut stdout).flush().unwrap(); + } + do_test() + } +} + +fn retry_on_eaddrinuse(config: Config, addr: Multiaddr) -> (Endpoint, JoinHandle) { + loop { + match Endpoint::new(config.clone(), addr.clone()) { + Ok(e) => break e, + Err(TransportError::Other(Error::IO(e))) + if e.kind() == std::io::ErrorKind::AddrInUse => {} + Err(e) => panic!("Failed to create endpoint: {}", e), + } + } +} + +fn do_test() { + use std::error::Error as _; let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); - let addr: Multiaddr = "/ip4/0.0.0.0/udp/12345/quic" - .parse() - .expect("bad address?"); + let addr: Multiaddr = "/ip4/0.0.0.0/udp/12345/quic".parse().expect("bad address?"); let quic_config = Config::new(&keypair2); - let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).expect("I/O error"); + let (quic_endpoint, join) = retry_on_eaddrinuse(quic_config, addr.clone()); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); let handle = async_std::task::spawn(async move { @@ -211,11 +234,10 @@ fn communicating_between_dialer_and_listener() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); let quic_config = Config::new(&keypair); - let (quic_endpoint, join) = Endpoint::new( + let (quic_endpoint, join) = retry_on_eaddrinuse( quic_config, "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), - ) - .unwrap(); + ); // Obtain a future socket through dialing let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); From 321f7761f988ba34b16869a98c9822a600677d54 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 20:24:35 -0500 Subject: [PATCH 101/202] Avoid use of deprecated elided lifetimes --- transports/quic/src/certificate.rs | 8 ++++---- transports/quic/src/connection.rs | 26 +++++++++++++------------- transports/quic/src/endpoint.rs | 10 +++++----- transports/quic/src/socket.rs | 6 +++--- transports/quic/src/verifier.rs | 6 +++--- transports/quic/tests/tests.rs | 12 ++++++------ 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 8fe1ed12ef4..013c78fc941 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -82,7 +82,7 @@ pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { } /// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. -fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1Error> { +fn read_bitvec(reader: &mut yasna::BERReaderSeq<'_, '_>) -> Result, yasna::ASN1Error> { let (value, bits) = reader.next().read_bitvec_bytes()?; // be extra careful regarding overflow if bits % 8 == 0 { @@ -94,7 +94,7 @@ fn read_bitvec(reader: &mut yasna::BERReaderSeq) -> Result, yasna::ASN1E } fn parse_x509_extensions( - reader: &mut yasna::BERReaderSeq, + reader: &mut yasna::BERReaderSeq<'_, '_>, certificate_key: &[u8], ) -> Result { reader.next().read_tagged(yasna::Tag::context(3), |reader| { @@ -113,7 +113,7 @@ fn parse_x509_extensions( } fn parse_x509_extension( - reader: yasna::BERReader, + reader: yasna::BERReader<'_, '_>, certificate_key: &[u8], oids_seen: &mut std::collections::HashSet, ) -> Result, yasna::ASN1Error> { @@ -172,7 +172,7 @@ fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result yasna::ASN1Result { +fn parse_tbscertificate(reader: yasna::BERReader<'_, '_>) -> yasna::ASN1Result { trace!("parsing TBScertificate"); reader.read_sequence(|reader| { // Check the X.509 version diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 2f4db76e342..887be2ee719 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -75,7 +75,7 @@ impl Substream { pub struct QuicMuxer(Arc>); impl QuicMuxer { - fn inner(&self) -> MutexGuard { + fn inner(&self) -> MutexGuard<'_, Muxer> { self.0.lock() } } @@ -91,7 +91,7 @@ pub struct Outbound(OutboundInner); impl Future for Outbound { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { @@ -151,7 +151,7 @@ impl StreamMuxer for QuicMuxer { true } - fn poll_inbound(&self, cx: &mut Context) -> Poll> { + fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { debug!("being polled for inbound connections!"); let mut inner = self.inner(); if inner.connection.is_drained() { @@ -182,7 +182,7 @@ impl StreamMuxer for QuicMuxer { fn write_substream( &self, - cx: &mut Context, + cx: &mut Context<'_>, substream: &mut Self::Substream, buf: &[u8], ) -> Poll> { @@ -245,7 +245,7 @@ impl StreamMuxer for QuicMuxer { fn poll_outbound( &self, - cx: &mut Context, + cx: &mut Context<'_>, substream: &mut Self::OutboundSubstream, ) -> Poll> { substream.poll_unpin(cx) @@ -253,7 +253,7 @@ impl StreamMuxer for QuicMuxer { fn read_substream( &self, - cx: &mut Context, + cx: &mut Context<'_>, substream: &mut Self::Substream, buf: &mut [u8], ) -> Poll> { @@ -297,7 +297,7 @@ impl StreamMuxer for QuicMuxer { fn shutdown_substream( &self, - cx: &mut Context, + cx: &mut Context<'_>, substream: &mut Self::Substream, ) -> Poll> { match substream.status { @@ -333,17 +333,17 @@ impl StreamMuxer for QuicMuxer { fn flush_substream( &self, - _cx: &mut Context, + _cx: &mut Context<'_>, _substream: &mut Self::Substream, ) -> Poll> { Poll::Ready(Ok(())) } - fn flush_all(&self, _cx: &mut Context) -> Poll> { + fn flush_all(&self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn close(&self, cx: &mut Context) -> Poll> { + fn close(&self, cx: &mut Context<'_>) -> Poll> { trace!("close() called"); let mut inner = self.inner(); if inner.connection.is_closed() || inner.close_reason.is_some() { @@ -393,7 +393,7 @@ impl Drop for Upgrade { impl Future for Upgrade { type Output = Result<(libp2p_core::PeerId, QuicMuxer), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); let res = { @@ -526,7 +526,7 @@ impl Muxer { .expect("we don’t call this until the driver is spawned; qed") } - fn drive_timer(&mut self, cx: &mut Context, now: Instant) -> bool { + fn drive_timer(&mut self, cx: &mut Context<'_>, now: Instant) -> bool { match self.connection.poll_timeout() { None => { self.timer = None; @@ -788,7 +788,7 @@ impl ConnectionDriver { impl Future for ConnectionDriver { type Output = Result<(), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); debug!("being polled for timer!"); let mut inner = this.inner.lock(); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 951e5bc6fcb..bd335d5c245 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -145,7 +145,7 @@ impl EndpointInner { } } - fn drive_events(&mut self, cx: &mut Context) { + fn drive_events(&mut self, cx: &mut Context<'_>) { while let Poll::Ready(e) = self.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { match e { EndpointMessage::ConnectionAccepted => { @@ -195,7 +195,7 @@ impl EndpointInner { fn drive_receive( &mut self, socket: &socket::Socket, - cx: &mut Context, + cx: &mut Context<'_>, ) -> Poll> { use quinn_proto::DatagramEvent; loop { @@ -225,7 +225,7 @@ impl EndpointInner { fn poll_transmit_pending( &mut self, socket: &socket::Socket, - cx: &mut Context, + cx: &mut Context<'_>, ) -> Poll> { let Self { inner, pending, .. } = self; pending @@ -468,7 +468,7 @@ fn accept_muxer( impl Future for EndpointRef { type Output = Result<(), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let Self { reference, channel } = self.get_mut(); let mut inner = reference.inner.lock(); trace!("driving events"); @@ -508,7 +508,7 @@ pub struct Listener { impl Stream for Listener { type Item = Result, Error>; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.get_mut().channel.poll_next_unpin(cx) } } diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index a7621973186..1b405a82421 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -46,7 +46,7 @@ impl Socket { /// and drop it. If it is not, the connection will eventually time out. This provides a very /// high degree of robustness. Connections will transparently resume after a transient network /// outage, and problems that are specific to one peer will not effect other peers. - pub fn poll_send_to(&self, cx: &mut Context, packet: &Transmit) -> Poll> { + pub fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); futures::pin_mut!(fut); @@ -67,7 +67,7 @@ impl Socket { /// A wrapper around `recv_from` that handles ECONNRESET and logging pub fn recv_from( &self, - cx: &mut Context, + cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { loop { @@ -105,7 +105,7 @@ impl Socket { impl Pending { pub fn send_packet( &mut self, - cx: &mut Context, + cx: &mut Context<'_>, socket: &Socket, source: &mut dyn FnMut() -> Option, ) -> Poll> { diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 8a7f8624590..ec489d44099 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -61,7 +61,7 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe &self, _roots: &rustls::RootCertStore, presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef, + _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { @@ -80,8 +80,8 @@ fn verify_presented_certs( presented_certs: &[rustls::Certificate], cb: &dyn Fn( webpki::Time, - webpki::EndEntityCert, - webpki::TrustAnchor, + webpki::EndEntityCert<'_>, + webpki::TrustAnchor<'_>, ) -> Result<(), webpki::Error>, ) -> Result<(), rustls::TLSError> { if presented_certs.len() != 1 { diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index b4f0a885fd4..f5bfe33afc6 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -41,7 +41,7 @@ struct QuicStream { } impl AsyncWrite for QuicStream { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { assert!(!self.shutdown, "written after close"); let Self { muxer, id, .. } = self.get_mut(); muxer @@ -49,7 +49,7 @@ impl AsyncWrite for QuicStream { .map_err(From::from) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.shutdown = true; let Self { muxer, id, .. } = self.get_mut(); debug!("trying to close {:?}", id); @@ -58,13 +58,13 @@ impl AsyncWrite for QuicStream { Poll::Ready(Ok(())) } - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } impl AsyncRead for QuicStream { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context, buf: &mut [u8]) -> Poll> { + fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { let Self { id, muxer, .. } = self.get_mut(); muxer .read_substream(cx, id.as_mut().unwrap(), buf) @@ -85,7 +85,7 @@ impl Drop for QuicStream { struct Inbound<'a>(&'a mut Muxer); impl<'a> futures::Stream for Inbound<'a> { type Item = QuicStream; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(Some(QuicStream { id: Some(ready!(self.0.poll_inbound(cx)).expect("bug")), muxer: self.get_mut().0.clone(), @@ -105,7 +105,7 @@ struct Closer(Muxer); impl Future for Closer { type Output = Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.get_mut().0.close(cx).map_err(From::from) } } From 36c2e7bdb3d4e2292c98ec9de5726b5bf324b55c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 12 Feb 2020 20:45:51 -0500 Subject: [PATCH 102/202] Add a bunch of documentation --- transports/quic/src/certificate.rs | 6 +-- transports/quic/src/connection.rs | 65 +++++++++++++++++++++++------- transports/quic/src/endpoint.rs | 7 ++-- transports/quic/src/error.rs | 25 ++++++++---- transports/quic/src/lib.rs | 29 ++++++++++--- 5 files changed, 98 insertions(+), 34 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 013c78fc941..6438bde3106 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -173,7 +173,7 @@ fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result) -> yasna::ASN1Result { - trace!("parsing TBScertificate"); + // trace!("parsing TBScertificate"); reader.read_sequence(|reader| { // Check the X.509 version if reader.next().read_der()? != [160, 3, 2, 1, 2] { @@ -189,11 +189,11 @@ fn parse_tbscertificate(reader: yasna::BERReader<'_, '_>) -> yasna::ASN1Result), + /// The stream has been shut down. Reads are still permissible. Writes + /// will return an error. Finished, } impl Substream { + /// Construct an unwritten stream. Such a stream must be written to before + /// it can be read from. fn unwritten(id: StreamId) -> Self { let status = SubstreamStatus::Unwritten; Self { id, status } } + /// Construct a live stream, which can be read from or written to. fn live(id: StreamId) -> Self { let status = SubstreamStatus::Live; Self { id, status } } + /// Test if data can be sent on a stream. fn is_live(&self) -> bool { match self.status { SubstreamStatus::Live | SubstreamStatus::Unwritten => true, @@ -71,10 +86,20 @@ impl Substream { } } +/// A QUIC connection, either client or server. +/// +/// QUIC opens streams lazily, so the peer is not notified that a stream has +/// been opened until data is written (either on this stream, or a +/// higher-numbered one). Therefore, reading on a stream that has not been +/// written to will deadlock, unless another stream is opened and written to +/// before the first read returns. Because this is not needed in practice, and +/// to ease debugging, [`QuicMuxer::read_substream`] returns an error in this +/// case. #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); impl QuicMuxer { + /// Returns the underlying data structure, including all state. fn inner(&self) -> MutexGuard<'_, Muxer> { self.0.lock() } @@ -82,11 +107,17 @@ impl QuicMuxer { #[derive(Debug)] enum OutboundInner { + /// The substream is fully set up Complete(Result), + /// We are waiting on a stream to become available Pending(oneshot::Receiver), + /// We have already returned our substream Done, } +/// An outbound QUIC substream. This will eventually resolve to either a +/// [`Substream`] or an [`Error`]. +#[derive(Debug)] pub struct Outbound(OutboundInner); impl Future for Outbound { @@ -96,7 +127,7 @@ impl Future for Outbound { match this.0 { OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { OutboundInner::Complete(e) => Poll::Ready(e.map(Substream::unwritten)), - _ => unreachable!(), + _ => unreachable!("we just checked that we have a `Complete`; qed"), }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) @@ -113,7 +144,7 @@ impl Future for Outbound { impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; type Substream = Substream; - type Error = crate::error::Error; + type Error = Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); if let Some(ref e) = inner.close_reason { @@ -313,7 +344,7 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); inner.wake_driver(); inner.connection.finish(substream.id).map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => unreachable!("we checked for this above!"), + quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, quinn_proto::FinishError::Stopped(e) => Error::Stopped(e), })?; let (sender, mut receiver) = oneshot::channel(); @@ -331,6 +362,8 @@ impl StreamMuxer for QuicMuxer { Poll::Pending } + /// Flush pending data on this stream. libp2p-quic sends out data as soon as + /// possible, so this method does nothing. fn flush_substream( &self, _cx: &mut Context<'_>, @@ -339,10 +372,14 @@ impl StreamMuxer for QuicMuxer { Poll::Ready(Ok(())) } + /// Flush all pending data to the peer. libp2p-quic sends out data as soon + /// as possible, so this method does nothing. fn flush_all(&self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } + /// Close the connection. Once this function is called, it is a logic error + /// to call other methods on this object. fn close(&self, cx: &mut Context<'_>) -> Poll> { trace!("close() called"); let mut inner = self.inner(); @@ -375,6 +412,7 @@ impl StreamMuxer for QuicMuxer { } } +/// A QUIC connection that is #[derive(Debug)] pub struct Upgrade { muxer: Option, @@ -444,14 +482,15 @@ impl Future for Upgrade { type StreamSenderQueue = std::collections::VecDeque>; +/// A QUIC connection and its associated data. #[derive(Debug)] pub(crate) struct Muxer { /// If this is `Some`, it is a stream that has been opened by the peer, /// but not yet accepted by the application. Otherwise, this is `None`. pending_stream: Option, - /// The associated endpoint + /// A channel to communicate with the endpoint. endpoint: Endpoint, - /// The `quinn_proto::Connection` struct. + /// The QUIC state machine. connection: Connection, /// Connection handle handle: ConnectionHandle, @@ -459,28 +498,26 @@ pub(crate) struct Muxer { writers: HashMap, /// Tasks blocked on reading readers: HashMap, - /// Tasks blocked on finishing + /// Tasks that are waiting for their streams to close. finishers: HashMap>>, /// Task waiting for new connections, or for the connection to finish handshaking. handshake_or_accept_waker: Option, - /// Tasks waiting to make a connection + /// Tasks waiting to make a connection. connectors: StreamSenderQueue, - /// Pending transmit - pending: super::socket::Pending, + /// A container for a packet that is waiting to be transmitted + pending: Pending, /// The timer being used by this connection timer: Option, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver waker: Option, - /// Last timeout + /// The last timeout returned by `quinn_proto::poll_timeout`. last_timeout: Option, - /// Join handle for the driver + /// A handle to wait on the driver to exit. driver: Option>>, /// Close waker close_waker: Option, - /// Have we gotten a connection lost event? - connection_lost: bool, } const RESET: u32 = 1; @@ -550,7 +587,6 @@ impl Muxer { fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { Muxer { - connection_lost: false, close_waker: None, last_timeout: None, pending_stream: None, @@ -714,7 +750,6 @@ impl Muxer { "connection lost without being closed" ); self.close_reason = Some(reason); - self.connection_lost = true; if let Some(e) = self.close_waker.take() { e.wake() } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index bd335d5c245..67baa71a0ee 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -133,7 +133,7 @@ pub(super) struct EndpointInner { impl EndpointInner { pub(super) fn handle_event( &mut self, - handle: quinn_proto::ConnectionHandle, + handle: ConnectionHandle, event: quinn_proto::EndpointEvent, ) -> Option { if event.is_drained() { @@ -167,7 +167,7 @@ impl EndpointInner { /// and process any events it sends back. fn send_connection_event( &mut self, - handle: quinn_proto::ConnectionHandle, + handle: ConnectionHandle, event: quinn_proto::ConnectionEvent, ) { let Self { inner, muxers, .. } = self; @@ -338,6 +338,7 @@ pub(crate) struct ConnectionEndpoint { socket: Arc, } +/// A handle that can be used to wait for the endpoint driver to finish. pub type JoinHandle = async_std::task::JoinHandle>; #[derive(Debug)] @@ -515,7 +516,7 @@ impl Stream for Listener { impl Transport for Endpoint { type Output = (libp2p_core::PeerId, super::Muxer); - type Error = super::error::Error; + type Error = Error; type Listener = Listener; type ListenerUpgrade = Upgrade; type Dial = Upgrade; diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index a3ebc7defb9..bdde9e9fb0e 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -27,18 +27,25 @@ use std::io; /// An error that can be returned by libp2p-quic. #[derive(Error, Debug)] pub enum Error { + /// Fatal I/O error #[error(display = "Fatal I/O error {}", _0)] IO(#[error(source)] std::io::Error), + /// Peer sent a malformed certificate #[error(display = "Peer sent a malformed certificate")] - BadCertificate(#[error(source)] ring::error::Unspecified), + BadCertificate(#[error(source)] Unspecified), + /// QUIC protocol error #[error(display = "QUIC protocol error: {}", _0)] ConnectionError(#[error(source)] quinn_proto::ConnectionError), + /// Cannot establish connection #[error(display = "Cannot establish connection: {}", _0)] CannotConnect(#[error(source)] quinn_proto::ConnectError), + /// Peer stopped receiving data #[error(display = "Peer stopped receiving data: code {}", _0)] Stopped(quinn_proto::VarInt), + /// Connection was prematurely closed #[error(display = "Connection was prematurely closed")] ConnectionLost, + /// Cannot listen on the same endpoint more than once #[error(display = "Cannot listen on the same endpoint more than once")] AlreadyListening, /// The stream was reset by the peer. @@ -49,13 +56,15 @@ pub enum Error { #[error(display = "Use of a stream that has is no longer valid. This is a \ bug in the application.")] ExpiredStream, - #[error(display = "Trying to ready from a stream that the peer has not been \ - notified of. For performance and complexity reasons, libp2p-quic does not \ - notify the peer that a stream is opened until at least one byte is sent. \ - Therefore, this read would deadlock.")] + /// Reading from a stream that has not been written to. + #[error(display = "Reading from a stream that has not been written to.")] CannotReadFromUnwrittenStream, + /// Fatal internal error or network failure #[error(display = "Fatal internal error or network failure")] NetworkFailure, + /// Connection already being closed + #[error(display = "Connection already being closed")] + ConnectionClosing, } impl From for Error { @@ -70,9 +79,9 @@ impl From for io::Error { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), e @ Error::BadCertificate(Unspecified) => io::Error::new(ErrorKind::InvalidData, e), Error::ConnectionError(e) => e.into(), - e @ Error::CannotConnect(_) | e @ Error::NetworkFailure => { - io::Error::new(ErrorKind::Other, e) - } + e @ Error::CannotConnect(_) + | e @ Error::NetworkFailure + | e @ Error::ConnectionClosing => io::Error::new(ErrorKind::Other, e), e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9c30c5c4963..48cd0ea0cf8 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -52,11 +52,30 @@ //! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![forbid(unstable_features, unsafe_code)] // Forbid warnings when testing, but don’t break other people’s code -#![cfg_attr(test, forbid(warnings, clippy::all))] -#![deny(missing_copy_implementations)] -#![deny(trivial_casts)] +#![cfg_attr( + test, + forbid( + elided_lifetimes_in_paths, + future_incompatible, + missing_debug_implementations, + missing_docs, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unsafe_code, + unstable_features, + unused_import_braces, + warnings, + clippy::all + ) +)] +#![deny( + missing_copy_implementations, + unused, + nonstandard_style, + unused_qualifications +)] mod certificate; mod connection; @@ -65,5 +84,5 @@ mod error; mod socket; mod verifier; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; -pub use endpoint::{Config, Endpoint, Listener, JoinHandle}; +pub use endpoint::{Config, Endpoint, JoinHandle, Listener}; pub use error::Error; From 7e119f5f967f99b879d55809b895ac9c91293da4 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 13 Feb 2020 11:06:51 -0500 Subject: [PATCH 103/202] Fix broken links in documentation --- transports/quic/src/connection.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index b9a72ccc270..3d4ec829e20 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -93,8 +93,8 @@ impl Substream { /// higher-numbered one). Therefore, reading on a stream that has not been /// written to will deadlock, unless another stream is opened and written to /// before the first read returns. Because this is not needed in practice, and -/// to ease debugging, [`QuicMuxer::read_substream`] returns an error in this -/// case. +/// to ease debugging, [`::read_substream`] returns an +/// error in this case. #[derive(Debug, Clone)] pub struct QuicMuxer(Arc>); @@ -282,6 +282,8 @@ impl StreamMuxer for QuicMuxer { substream.poll_unpin(cx) } + /// Try to from a substream. This will return an error if the substream has + /// not yet been written to. fn read_substream( &self, cx: &mut Context<'_>, From dfe12a8c0cc6716a72363ba43636618d668d73d3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 18 Feb 2020 11:02:01 -0500 Subject: [PATCH 104/202] Bump dependencies of libp2p-quic (only) --- transports/quic/Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 280d89eb5fa..7c3aad2344c 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -12,21 +12,21 @@ categories = ["network-programming", "asynchronous"] rustls = { version = "0.16.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.16.0" } log = "0.4.8" -ipnet = "2.1.0" +ipnet = "2.2.0" err-derive = "0.2.2" -futures = "0.3.1" +futures = "0.3.4" quinn-proto = { git = "https://github.com/djc/quinn" } -async-std = "1.4.0" +async-std = "1.5.0" async-macros = "2.0.0" -futures-timer = "2.0.2" +futures-timer = "3.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" rcgen = "0.7.0" protobuf = "2.10.1" yasna = { version = "0.3.1", features = ["num-bigint"] } -ring = "0.16.10" +ring = "0.16.11" webpki = "0.21.2" [dev-dependencies] -tracing-subscriber = "0.1.6" +tracing-subscriber = "0.2.1" tracing = "0.1.12" From d6794aff290b2bfd538e3dc1a6c412047ed9471d Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 20 Feb 2020 13:39:30 -0500 Subject: [PATCH 105/202] Be more selective about enabling lints Instead of a blunderbuss `#[forbid(warnings)]`, this denys certain lints that are likely not to cause further problems in the future, including some that are `Allow` by default. This required fixing the code in some places, mostly replacing `pub` with `pub(crate)`. --- transports/quic/src/certificate.rs | 8 ++-- transports/quic/src/endpoint.rs | 17 ++++---- transports/quic/src/lib.rs | 65 +++++++++++++++++++++--------- transports/quic/src/socket.rs | 8 ++-- transports/quic/src/verifier.rs | 4 +- 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 40ec8ea48f0..f6ac6d3df70 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -30,7 +30,7 @@ use libp2p_core::identity; use log::{trace, warn}; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -pub const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); +pub(crate) const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; @@ -71,7 +71,7 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu /// Generates a self-signed TLS certificate that includes a libp2p-specific certificate extension /// containing the public key of the given keypair. -pub fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { +pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { let mut params = rcgen::CertificateParams::new(vec![]); let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); params.custom_extensions.push(libp2p_extension); @@ -205,7 +205,9 @@ fn parse_tbscertificate( /// Extract the peer’s public key from a libp2p certificate, and check that the certificate’s /// public key is signed by it. -pub fn extract_libp2p_peerid(certificate: &[u8]) -> Result { +pub(crate) fn extract_libp2p_peerid( + certificate: &[u8], +) -> Result { parse_certificate(certificate) .map_err(|e| { log::debug!("error in parsing: {:?}", e); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index ed2fec553bf..ceba6f494f9 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -111,7 +111,7 @@ impl Config { } #[derive(Debug)] -pub enum EndpointMessage { +pub(crate) enum EndpointMessage { Dummy, ConnectionAccepted, EndpointEvent { @@ -256,15 +256,18 @@ pub(super) struct EndpointData { type Sender = mpsc::Sender; impl ConnectionEndpoint { - pub fn socket(&self) -> &socket::Socket { + pub(crate) fn socket(&self) -> &socket::Socket { &self.socket } - pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + pub(crate) fn poll_ready( + &mut self, + cx: &mut Context<'_>, + ) -> Poll> { self.channel.poll_ready(cx).map_ok(SendToken) } - pub fn send_message( + pub(crate) fn send_message( &mut self, event: EndpointMessage, SendToken(()): SendToken, @@ -276,10 +279,10 @@ impl ConnectionEndpoint { mod channel_ref { use super::{Arc, EndpointData, EndpointMessage::Dummy, Sender}; #[derive(Debug, Clone)] - pub struct Channel(Sender); + pub(crate) struct Channel(Sender); impl Channel { - pub fn new(s: Sender) -> Self { + pub(crate) fn new(s: Sender) -> Self { Self(s) } } @@ -304,7 +307,7 @@ mod channel_ref { } #[derive(Debug, Clone)] - pub struct EndpointRef { + pub(crate) struct EndpointRef { pub(super) reference: Arc, // MUST COME LATER so that the endpoint driver gets notified *after* its reference count is // dropped. diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 48cd0ea0cf8..1f5419cdf83 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -53,29 +53,54 @@ //! `QuicConnection` also manages a background task, which handles socket output and timer polling. // Forbid warnings when testing, but don’t break other people’s code -#![cfg_attr( - test, - forbid( - elided_lifetimes_in_paths, - future_incompatible, - missing_debug_implementations, - missing_docs, - rust_2018_idioms, - trivial_casts, - trivial_numeric_casts, - unsafe_code, - unstable_features, - unused_import_braces, - warnings, - clippy::all - ) -)] #![deny( - missing_copy_implementations, + exceeding_bitshifts, + invalid_type_param_default, + missing_fragment_specifier, + mutable_transmutes, + no_mangle_const_items, + overflowing_literals, + patterns_in_fns_without_body, + pub_use_of_private_extern_crate, + unknown_crate_types, + const_err, + order_dependent_trait_objects, + illegal_floating_point_literal_pattern, + improper_ctypes, + late_bound_lifetime_arguments, + non_camel_case_types, + non_shorthand_field_patterns, + non_snake_case, + non_upper_case_globals, + no_mangle_generic_items, + path_statements, + private_in_public, + safe_packed_borrows, + stable_features, + type_alias_bounds, + tyvar_behind_raw_pointer, + unconditional_recursion, unused, - nonstandard_style, - unused_qualifications + unused_allocation, + unused_comparisons, + unused_mut, + unreachable_pub, + while_true, + anonymous_parameters, + bare_trait_objects, + elided_lifetimes_in_paths, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + clippy::all )] +#![forbid(unsafe_code)] mod certificate; mod connection; diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 1b405a82421..6196062540d 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -46,7 +46,7 @@ impl Socket { /// and drop it. If it is not, the connection will eventually time out. This provides a very /// high degree of robustness. Connections will transparently resume after a transient network /// outage, and problems that are specific to one peer will not effect other peers. - pub fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { + pub(crate) fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); futures::pin_mut!(fut); @@ -65,7 +65,7 @@ impl Socket { } /// A wrapper around `recv_from` that handles ECONNRESET and logging - pub fn recv_from( + pub(crate) fn recv_from( &self, cx: &mut Context<'_>, buf: &mut [u8], @@ -97,13 +97,13 @@ impl Socket { } impl Socket { - pub fn new(socket: UdpSocket) -> Self { + pub(crate) fn new(socket: UdpSocket) -> Self { Self { socket } } } impl Pending { - pub fn send_packet( + pub(crate) fn send_packet( &mut self, cx: &mut Context<'_>, socket: &Socket, diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index ec489d44099..c3bf7dbb041 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -43,7 +43,7 @@ static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { /// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection /// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done /// something insecure. -pub struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; +pub(crate) struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; /// A ClientCertVerifier that requires client authentication, and requires the certificate to be /// self-signed. @@ -54,7 +54,7 @@ pub struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; /// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection /// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done /// something insecure. -pub struct VeryInsecureRequireExactlyOneSelfSignedClientCertificate; +pub(crate) struct VeryInsecureRequireExactlyOneSelfSignedClientCertificate; impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServerCertificate { fn verify_server_cert( From 96aff7337373efcdd5af7e54f69fd1e9ad4f17e8 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 21 Feb 2020 11:22:51 -0500 Subject: [PATCH 106/202] Merge the connection hashmaps and split them into a separate module --- transports/quic/src/connection.rs | 205 ++++++++----------- transports/quic/src/connection/stream.rs | 97 +++++++++ transports/quic/src/connection/stream_map.rs | 99 +++++++++ transports/quic/tests/tests.rs | 10 +- 4 files changed, 289 insertions(+), 122 deletions(-) create mode 100644 transports/quic/src/connection/stream.rs create mode 100644 transports/quic/src/connection/stream_map.rs diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 77b06d47679..d74309c996e 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -28,10 +28,8 @@ use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; use log::{debug, error, info, trace, warn}; use parking_lot::{Mutex, MutexGuard}; -use quinn_proto::{Connection, ConnectionHandle, Dir, StreamId}; +use quinn_proto::{Connection, ConnectionHandle, Dir}; use std::{ - collections::HashMap, - io, mem::replace, pin::Pin, sync::Arc, @@ -39,10 +37,13 @@ use std::{ time::Instant, }; +mod stream; +mod stream_map; + /// A QUIC substream #[derive(Debug)] pub struct Substream { - id: StreamId, + id: stream_map::StreamId, status: SubstreamStatus, } @@ -66,13 +67,13 @@ enum SubstreamStatus { impl Substream { /// Construct an unwritten stream. Such a stream must be written to before /// it can be read from. - fn unwritten(id: StreamId) -> Self { + fn unwritten(id: stream_map::StreamId) -> Self { let status = SubstreamStatus::Unwritten; Self { id, status } } /// Construct a live stream, which can be read from or written to. - fn live(id: StreamId) -> Self { + fn live(id: stream_map::StreamId) -> Self { let status = SubstreamStatus::Live; Self { id, status } } @@ -108,9 +109,9 @@ impl QuicMuxer { #[derive(Debug)] enum OutboundInner { /// The substream is fully set up - Complete(Result), + Complete(Result), /// We are waiting on a stream to become available - Pending(oneshot::Receiver), + Pending(oneshot::Receiver), /// We have already returned our substream Done, } @@ -160,30 +161,49 @@ impl StreamMuxer for QuicMuxer { Outbound(OutboundInner::Pending(receiver)) } } - fn destroy_outbound(&self, _: Outbound) {} + + fn destroy_outbound(&self, outbound: Outbound) { + let mut inner = self.inner(); + let id = *match outbound.0 { + OutboundInner::Complete(Err(_)) => return, + OutboundInner::Complete(Ok(id)) => id, + // try_recv is race-free, because the lock on `self` prevents + // other tasks from sending on the other end of the channel. + OutboundInner::Pending(mut channel) => match channel.try_recv() { + Ok(Some(id)) => id, + Err(oneshot::Canceled) | Ok(None) => return, + }, + OutboundInner::Done => return, + }; + // if either of these returns an error, there is nothing we can do, so + // just ignore it. That said, an error here should never happen unless + // the connection is closed. + let _ = inner.connection.finish(id); + let _ = inner.connection.stop_sending(id, Default::default()); + } + fn destroy_substream(&self, substream: Self::Substream) { let mut inner = self.inner(); - if let Some(waker) = inner.writers.remove(&substream.id) { - waker.wake(); - } - if let Some(waker) = inner.readers.remove(&substream.id) { - waker.wake(); - } if substream.is_live() && inner.close_reason.is_none() { - if let Err(e) = inner.connection.finish(substream.id) { + if let Err(e) = inner.connection.finish(*substream.id) { warn!("Error closing stream: {}", e); } } let _ = inner .connection - .stop_sending(substream.id, Default::default()); + .stop_sending(*substream.id, Default::default()); + trace!("Removing substream {:?} from map", substream.id); + inner.streams.remove(substream.id); } + fn is_remote_acknowledged(&self) -> bool { + // we do not allow 0RTT traffic, for security reasons, so this is + // always true. true } fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { - debug!("being polled for inbound connections!"); + trace!("being polled for inbound connections!"); let mut inner = self.inner(); if inner.connection.is_drained() { return Poll::Ready(Err(Error::ConnectionError( @@ -205,8 +225,8 @@ impl StreamMuxer for QuicMuxer { Poll::Pending } Some(id) => { - inner.finishers.insert(id, None); - Poll::Ready(Ok(Substream::live(id))) + inner.outbound_streams += 1; + Poll::Ready(Ok(Substream::live(inner.streams.add_stream(id)))) } } } @@ -225,27 +245,18 @@ impl StreamMuxer for QuicMuxer { ); return Poll::Ready(Err(Error::ExpiredStream)); } - if buf.is_empty() { - return Poll::Ready(Ok(0)); - } let mut inner = self.inner(); - debug_assert!( - inner.finishers.get(&substream.id).is_some(), - "no entry in finishers map for write stream" - ); inner.wake_driver(); if let Some(ref e) = inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(e.clone()))); } - match inner.connection.write(substream.id, buf) { + match inner.connection.write(*substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(WriteError::Blocked) => { if let Some(ref e) = inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(e.clone()))); } - if let Some(w) = inner.writers.insert(substream.id, cx.waker().clone()) { - w.wake(); - } + inner.streams.set_writer(&substream.id, cx.waker().clone()); Poll::Pending } Err(WriteError::UnknownStream) => { @@ -264,10 +275,7 @@ impl StreamMuxer for QuicMuxer { } } Err(WriteError::Stopped(e)) => { - inner.finishers.remove(&substream.id); - if let Some(w) = inner.writers.remove(&substream.id) { - w.wake() - } + inner.streams.wake_writer(*substream.id); substream.status = SubstreamStatus::Finished; Poll::Ready(Err(Error::Stopped(e))) } @@ -293,7 +301,7 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::ReadError; let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.read(substream.id, buf) { + match inner.connection.read(*substream.id, buf) { Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), Ok(None) => Poll::Ready(Ok(0)), Err(ReadError::Blocked) => { @@ -307,9 +315,7 @@ impl StreamMuxer for QuicMuxer { substream.id, inner.connection.side() ); - if let Some(w) = inner.readers.insert(substream.id, cx.waker().clone()) { - w.wake() - } + inner.streams.set_reader(&substream.id, cx.waker().clone()); Poll::Pending } } @@ -320,9 +326,7 @@ impl StreamMuxer for QuicMuxer { Poll::Ready(Err(Error::ExpiredStream)) } Err(ReadError::Reset(e)) => { - if let Some(w) = inner.readers.remove(&substream.id) { - w.wake() - } + inner.streams.wake_reader(*substream.id); Poll::Ready(Err(Error::Reset(e))) } } @@ -337,30 +341,29 @@ impl StreamMuxer for QuicMuxer { SubstreamStatus::Finished => return Poll::Ready(Ok(())), SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); - return channel - .poll_unpin(cx) - .map_err(|e| Error::IO(io::Error::new(io::ErrorKind::ConnectionAborted, e))); + match channel.poll_unpin(cx) { + Poll::Ready(Ok(())) | Poll::Ready(Err(oneshot::Canceled)) => return Poll::Ready(Ok(())), + Poll::Pending => {} + } } SubstreamStatus::Unwritten | SubstreamStatus::Live => {} } let mut inner = self.inner(); inner.wake_driver(); - inner.connection.finish(substream.id).map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, - quinn_proto::FinishError::Stopped(e) => Error::Stopped(e), - })?; + inner + .connection + .finish(*substream.id) + .map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, + quinn_proto::FinishError::Stopped(e) => Error::Stopped(e), + })?; let (sender, mut receiver) = oneshot::channel(); assert!( receiver.poll_unpin(cx).is_pending(), "we haven’t written to the peer yet" ); substream.status = SubstreamStatus::Finishing(receiver); - match inner.finishers.insert(substream.id, Some(sender)) { - Some(None) => {} - _ => unreachable!( - "We inserted a None value earlier; and haven’t touched this entry since; qed" - ), - } + inner.streams.set_finisher(&substream.id, sender); Poll::Pending } @@ -383,11 +386,15 @@ impl StreamMuxer for QuicMuxer { /// Close the connection. Once this function is called, it is a logic error /// to call other methods on this object. fn close(&self, cx: &mut Context<'_>) -> Poll> { - trace!("close() called"); let mut inner = self.inner(); + trace!( + "close() called with {} outbound streams", + inner.outbound_streams + ); + inner.outbound_streams -= 1; if inner.connection.is_closed() || inner.close_reason.is_some() { return Poll::Ready(Ok(())); - } else if inner.finishers.is_empty() { + } else if inner.outbound_streams == 0 { inner.shutdown(0); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); drop(inner.driver().poll_unpin(cx)); @@ -398,17 +405,12 @@ impl StreamMuxer for QuicMuxer { return Poll::Pending; } let Muxer { - finishers, + streams, connection, .. } = &mut *inner; - for (id, channel) in finishers { - if channel.is_none() { - match connection.finish(*id) { - Ok(()) => {} - Err(error) => warn!("Finishing stream {:?} failed: {}", id, error), - } - } + for id in streams.keys() { + let _ = connection.finish(*id); } Poll::Pending } @@ -482,30 +484,28 @@ impl Future for Upgrade { } } -type StreamSenderQueue = std::collections::VecDeque>; +type StreamSenderQueue = std::collections::VecDeque>; /// A QUIC connection and its associated data. #[derive(Debug)] pub(crate) struct Muxer { /// If this is `Some`, it is a stream that has been opened by the peer, /// but not yet accepted by the application. Otherwise, this is `None`. - pending_stream: Option, + pending_stream: Option, /// A channel to communicate with the endpoint. endpoint: Endpoint, /// The QUIC state machine. connection: Connection, /// Connection handle handle: ConnectionHandle, - /// Tasks blocked on writing - writers: HashMap, - /// Tasks blocked on reading - readers: HashMap, - /// Tasks that are waiting for their streams to close. - finishers: HashMap>>, + /// The stream statuses + streams: stream_map::Streams, /// Task waiting for new connections, or for the connection to finish handshaking. handshake_or_accept_waker: Option, /// Tasks waiting to make a connection. connectors: StreamSenderQueue, + /// The number of outbound streams currently open. + outbound_streams: usize, /// A container for a packet that is waiting to be transmitted pending: Pending, /// The timer being used by this connection @@ -591,12 +591,11 @@ impl Muxer { Muxer { close_waker: None, last_timeout: None, + outbound_streams: 1, pending_stream: None, connection, handle, - writers: HashMap::new(), - readers: HashMap::new(), - finishers: HashMap::new(), + streams: Default::default(), handshake_or_accept_waker: None, connectors: Default::default(), endpoint, @@ -648,16 +647,8 @@ impl Muxer { fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); self.wake_incoming(); - for (_, v) in self.writers.drain() { - v.wake(); - } - for (_, v) in self.readers.drain() { - v.wake(); - } - for sender in self.finishers.drain().filter_map(|x| x.1) { - let _ = sender.send(()); - } - self.connectors.truncate(0); + self.streams.wake_all(); + self.connectors.clear(); if !self.connection.is_closed() { self.connection.close( Instant::now(), @@ -673,17 +664,16 @@ impl Muxer { self.connection.handle_event(event) } - fn get_pending_stream(&mut self) -> Option { + fn get_pending_stream(&mut self) -> Option { self.wake_driver(); if let Some(id) = self.pending_stream.take() { Some(id) } else { - self.connection.open(Dir::Bi) + self.connection.open(Dir::Bi).map(|e| { + self.outbound_streams += 1; + self.streams.add_stream(e) + }) } - .map(|id| { - self.finishers.insert(id, None); - id - }) } pub(crate) fn process_app_events(&mut self) -> bool { @@ -705,9 +695,7 @@ impl Muxer { self.connection.side() ); // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.readers.remove_entry(&stream) { - waker.wake() - } + self.streams.wake_reader(stream); } Event::StreamWritable { stream } => { trace!( @@ -716,9 +704,7 @@ impl Muxer { self.connection.side() ); // Wake up the task waiting on us (if any) - if let Some((_, waker)) = self.writers.remove_entry(&stream) { - waker.wake() - } + self.streams.wake_writer(stream) } Event::StreamAvailable { dir: Dir::Bi } => { trace!( @@ -738,9 +724,12 @@ impl Muxer { a mutex that prevents other threads from calling open() in the \ meantime; qed", ); + let mut stream = self.streams.add_stream(stream); + self.outbound_streams += 1; while let Some(oneshot) = self.connectors.pop_front() { - if let Ok(()) = oneshot.send(stream) { - continue 'a; + stream = match oneshot.send(stream) { + Ok(()) => continue 'a, + Err(e) => e, } } self.pending_stream = Some(stream) @@ -768,20 +757,8 @@ impl Muxer { self.connection.side(), stop_reason ); - if let Some(waker) = self.writers.remove(&stream) { - waker.wake() - } - if let Some(sender) = self.finishers.remove(&stream).expect( - "every write stream is placed in this map, and entries are removed \ - exactly once; qed", - ) { - let _ = sender.send(()); - } - if self.finishers.is_empty() { - if let Some(e) = self.close_waker.take() { - e.wake() - } - } + self.outbound_streams -= 1; + self.streams.wake_writer(stream); } Event::Connected => { debug!("connected!"); diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/connection/stream.rs new file mode 100644 index 00000000000..855d24f703c --- /dev/null +++ b/transports/quic/src/connection/stream.rs @@ -0,0 +1,97 @@ +//! The per-stream state in a QUIC connection +use futures::channel::oneshot; +use std::{mem, task}; + +/// The state of a writer. +#[derive(Debug)] +enum WriterStatus { + /// Nobody is waiting to write to or finish this stream. + Unblocked, + /// Writing to the stream is blocked. `waker` is the waker used to wake up + /// the writer task. + Blocked { waker: task::Waker }, + /// The stream is being shut down. `finisher` is the channel used to notify + /// the finishing task. + Finishing { finisher: oneshot::Sender<()> }, +} + +impl WriterStatus { + fn take(self) { + match self { + Self::Unblocked => {} + Self::Blocked { waker } => waker.wake(), + Self::Finishing { finisher } => { + let _ = finisher.send(()); + } + } + } +} + +/// The state of a stream. Dropping this will wake up any tasks blocked on it. +#[derive(Debug)] +pub(crate) struct StreamState { + /// If a task is blocked on reading this stream, this holds a waker that + /// will wake it up. Otherwise, it is `None`. + reader: Option, + /// The state of the writing portion of this stream. + writer: WriterStatus, +} + +impl Default for StreamState { + fn default() -> Self { + Self { + reader: None, + writer: WriterStatus::Unblocked, + } + } +} + +impl Drop for StreamState { + fn drop(&mut self) { + self.wake_all() + } +} + +impl StreamState { + /// Indicate that the stream is open for reading. Calling this when nobody + /// is waiting for this stream to be readable is a harmless no-op. + pub(crate) fn wake_reader(&mut self) { + if let Some(waker) = self.reader.take() { + waker.wake() + } + } + + /// If a task is waiting for this stream to be finished or written to, wake + /// it up. Otherwise, do nothing. + pub(crate) fn wake_writer(&mut self) { + mem::replace(&mut self.writer, WriterStatus::Unblocked).take() + } + + /// Set a waker that will be notified when the state becomes readable. + /// Wake up any waker that has already been registered. + pub(crate) fn set_reader(&mut self, waker: task::Waker) { + if let Some(waker) = mem::replace(&mut self.reader, Some(waker)) { + waker.wake() + } + } + + /// Set a waker that will be notified when the task becomes writable or is + /// finished, waking up any waker or channel that has already been + /// registered. + pub(crate) fn set_writer(&mut self, waker: task::Waker) { + mem::replace(&mut self.writer, WriterStatus::Blocked { waker }).take() + } + + /// Set a channel that will be notified when the task becomes writable or is + /// finished, waking up any existing registered waker or channel + pub(crate) fn set_finisher(&mut self, finisher: oneshot::Sender<()>) { + mem::replace(&mut self.writer, WriterStatus::Finishing { finisher }).take() + } + + /// Wake up both readers and writers. This is just a shorthand for calling + /// [`Self::wake_writer`] and [`Self::wake_reader`]. + pub(crate) fn wake_all(&mut self) { + self.wake_writer(); + self.wake_reader(); + } +} diff --git a/transports/quic/src/connection/stream_map.rs b/transports/quic/src/connection/stream_map.rs new file mode 100644 index 00000000000..56185576ad1 --- /dev/null +++ b/transports/quic/src/connection/stream_map.rs @@ -0,0 +1,99 @@ +//! The state of all active streams in a QUIC connection + +use super::stream::StreamState; +use futures::channel::oneshot; +use std::collections::HashMap; +use std::task; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +/// A stream ID. +pub(super) struct StreamId(quinn_proto::StreamId); + +impl std::ops::Deref for StreamId { + type Target = quinn_proto::StreamId; + fn deref(&self) -> &quinn_proto::StreamId { + &self.0 + } +} + +/// A set of streams. +#[derive(Debug, Default)] +pub(super) struct Streams { + map: HashMap, +} + +impl Streams { + pub(super) fn add_stream(&mut self, id: quinn_proto::StreamId) -> StreamId { + if self.map.insert(id, Default::default()).is_some() { + panic!( + "Internal state corrupted. \ + You probably used a Substream with the wrong StreamMuxer", + ) + } + StreamId(id) + } + + fn get(&mut self, id: &StreamId) -> &mut StreamState { + self.map.get_mut(id).expect( + "Internal state corrupted. \ + You probably used a Substream with the wrong StreamMuxer", + ) + } + + /// Indicate that the stream is open for reading. Calling this when nobody + /// is waiting for this stream to be readable is a harmless no-op. + pub(super) fn wake_reader(&mut self, id: quinn_proto::StreamId) { + if let Some(stream) = self.map.get_mut(&id) { + stream.wake_reader() + } + } + + /// If a task is waiting for this stream to be finished or written to, wake + /// it up. Otherwise, do nothing. + pub(super) fn wake_writer(&mut self, id: quinn_proto::StreamId) { + if let Some(stream) = self.map.get_mut(&id) { + stream.wake_writer() + } + } + + /// Set a waker that will be notified when the state becomes readable. + /// Wake up any waker that has already been registered. + pub(super) fn set_reader(&mut self, id: &StreamId, waker: task::Waker) { + self.get(id).set_reader(waker); + } + + /// Set a waker that will be notified when the task becomes writable or is + /// finished, waking up any waker or channel that has already been + /// registered. + pub(super) fn set_writer(&mut self, id: &StreamId, waker: task::Waker) { + self.get(id).set_writer(waker); + } + + /// Set a channel that will be notified when the task becomes writable or is + /// finished, waking up any existing registered waker or channel + pub(super) fn set_finisher(&mut self, id: &StreamId, finisher: oneshot::Sender<()>) { + self.get(id).set_finisher(finisher); + } + + /// Remove an ID from the map + pub(super) fn remove(&mut self, id: StreamId) { + if self.map.remove(&id.0).is_none() { + panic!( + "Internal state corrupted. \ + You probably used a Substream with the wrong StreamMuxer", + ); + } + } + + pub(super) fn wake_all(&mut self) { + for i in self.map.values_mut() { + i.wake_all() + } + } + + pub(super) fn keys( + &self, + ) -> std::collections::hash_map::Keys<'_, quinn_proto::StreamId, StreamState> { + self.map.keys() + } +} diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index acdee4f720b..cbdaac96caf 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -100,6 +100,7 @@ impl<'a> futures::Stream for Inbound<'a> { fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; + let _ = env_logger::try_init(); let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) .try_init(); @@ -154,16 +155,9 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { - use std::io::{stdout, Write as _}; init(); - let stdout = stdout(); println!(""); - for i in 0..10u32 { - { - let mut stdout = stdout.lock(); - write!(&mut stdout, "\r{}", i).unwrap(); - (&mut stdout).flush().unwrap(); - } + for _ in 0..100u32 { do_test() } } From c851d8ba88c93a658a96d1e650353970d07e82b2 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 21 Feb 2020 13:51:10 -0500 Subject: [PATCH 107/202] `Muxer` now uses a wrapper around `Connection` This avoids the connection needing to talk to the endpoint directly. --- transports/quic/src/connection.rs | 178 +++-------------- transports/quic/src/connection/stream.rs | 20 ++ transports/quic/src/connection/stream_map.rs | 20 ++ transports/quic/src/endpoint.rs | 198 ++++++++++++++++--- 4 files changed, 238 insertions(+), 178 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index d74309c996e..69c6ba9e913 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -17,18 +17,14 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::{ - certificate, - endpoint::{ConnectionEndpoint as Endpoint, EndpointMessage}, - error::Error, - socket::Pending, -}; + +use super::{certificate, endpoint::ConnectionEndpoint as Endpoint, error::Error}; use async_macros::ready; use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; use log::{debug, error, info, trace, warn}; use parking_lot::{Mutex, MutexGuard}; -use quinn_proto::{Connection, ConnectionHandle, Dir}; +use quinn_proto::Dir; use std::{ mem::replace, pin::Pin, @@ -175,23 +171,12 @@ impl StreamMuxer for QuicMuxer { }, OutboundInner::Done => return, }; - // if either of these returns an error, there is nothing we can do, so - // just ignore it. That said, an error here should never happen unless - // the connection is closed. - let _ = inner.connection.finish(id); - let _ = inner.connection.stop_sending(id, Default::default()); + inner.connection.destroy_stream(id) } fn destroy_substream(&self, substream: Self::Substream) { let mut inner = self.inner(); - if substream.is_live() && inner.close_reason.is_none() { - if let Err(e) = inner.connection.finish(*substream.id) { - warn!("Error closing stream: {}", e); - } - } - let _ = inner - .connection - .stop_sending(*substream.id, Default::default()); + inner.connection.destroy_stream(*substream.id); trace!("Removing substream {:?} from map", substream.id); inner.streams.remove(substream.id); } @@ -214,7 +199,7 @@ impl StreamMuxer for QuicMuxer { ))); } inner.wake_driver(); - match inner.connection.accept(quinn_proto::Dir::Bi) { + match inner.connection.accept() { None => { if let Some(waker) = replace( &mut inner.handshake_or_accept_waker, @@ -275,7 +260,6 @@ impl StreamMuxer for QuicMuxer { } } Err(WriteError::Stopped(e)) => { - inner.streams.wake_writer(*substream.id); substream.status = SubstreamStatus::Finished; Poll::Ready(Err(Error::Stopped(e))) } @@ -302,8 +286,7 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner(); inner.wake_driver(); match inner.connection.read(*substream.id, buf) { - Ok(Some(bytes)) => Poll::Ready(Ok(bytes)), - Ok(None) => Poll::Ready(Ok(0)), + Ok(bytes) => Poll::Ready(Ok(bytes)), Err(ReadError::Blocked) => { if let Some(error) = &inner.close_reason { Poll::Ready(Err(Error::ConnectionError(error.clone()))) @@ -325,10 +308,7 @@ impl StreamMuxer for QuicMuxer { ); Poll::Ready(Err(Error::ExpiredStream)) } - Err(ReadError::Reset(e)) => { - inner.streams.wake_reader(*substream.id); - Poll::Ready(Err(Error::Reset(e))) - } + Err(ReadError::Reset(e)) => Poll::Ready(Err(Error::Reset(e))), } } @@ -342,7 +322,9 @@ impl StreamMuxer for QuicMuxer { SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); match channel.poll_unpin(cx) { - Poll::Ready(Ok(())) | Poll::Ready(Err(oneshot::Canceled)) => return Poll::Ready(Ok(())), + Poll::Ready(Ok(())) | Poll::Ready(Err(oneshot::Canceled)) => { + return Poll::Ready(Ok(())) + } Poll::Pending => {} } } @@ -392,12 +374,11 @@ impl StreamMuxer for QuicMuxer { inner.outbound_streams ); inner.outbound_streams -= 1; - if inner.connection.is_closed() || inner.close_reason.is_some() { + if inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.outbound_streams == 0 { inner.shutdown(0); inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - drop(inner.driver().poll_unpin(cx)); return Poll::Ready(Ok(())); } if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { @@ -440,7 +421,7 @@ impl Future for Upgrade { trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if inner.connection.is_drained() { + let peer_certificates = if inner.connection.is_drained() { return Poll::Ready(Err(Error::ConnectionError( inner .close_reason @@ -448,23 +429,12 @@ impl Future for Upgrade { .expect("closed connections always have a reason; qed"), ))); } else if inner.connection.is_handshaking() { - assert!( - !inner.connection.is_closed(), - "a closed connection cannot be handshaking; qed" - ); inner.handshake_or_accept_waker = Some(cx.waker().clone()); return Poll::Pending; - } else if inner.connection.side().is_server() { - let endpoint = &mut inner.endpoint; - let token = ready!(endpoint.poll_ready(cx))?; - endpoint.send_message(EndpointMessage::ConnectionAccepted, token)? - } + } else { + ready!(inner.connection.notify_handshake_complete(cx))? + }; - let peer_certificates: Vec = inner - .connection - .crypto_session() - .get_peer_certificates() - .expect("we have finished handshaking, so we have exactly one certificate; qed"); certificate::extract_libp2p_peerid( peer_certificates .get(0) @@ -492,12 +462,8 @@ pub(crate) struct Muxer { /// If this is `Some`, it is a stream that has been opened by the peer, /// but not yet accepted by the application. Otherwise, this is `None`. pending_stream: Option, - /// A channel to communicate with the endpoint. - endpoint: Endpoint, - /// The QUIC state machine. - connection: Connection, - /// Connection handle - handle: ConnectionHandle, + /// The QUIC state machine and endpoint messaging. + connection: Endpoint, /// The stream statuses streams: stream_map::Streams, /// Task waiting for new connections, or for the connection to finish handshaking. @@ -506,8 +472,6 @@ pub(crate) struct Muxer { connectors: StreamSenderQueue, /// The number of outbound streams currently open. outbound_streams: usize, - /// A container for a packet that is waiting to be transmitted - pending: Pending, /// The timer being used by this connection timer: Option, /// The close reason, if this connection has been lost @@ -533,18 +497,6 @@ impl Drop for Muxer { } } -impl Drop for QuicMuxer { - fn drop(&mut self) { - let inner = self.inner(); - debug!("dropping muxer with side {:?}", inner.connection.side()); - #[cfg(test)] - assert!( - !inner.connection.is_handshaking(), - "dropped a connection that was still handshaking" - ); - } -} - impl Muxer { pub(crate) fn wake_driver(&mut self) { if let Some(waker) = self.waker.take() { @@ -553,18 +505,16 @@ impl Muxer { } } + pub(crate) fn connection(&mut self) -> &mut Endpoint { + &mut self.connection + } + fn wake_incoming(&mut self) { if let Some(waker) = self.handshake_or_accept_waker.take() { waker.wake() } } - fn driver(&mut self) -> &mut async_std::task::JoinHandle> { - self.driver - .as_mut() - .expect("we don’t call this until the driver is spawned; qed") - } - fn drive_timer(&mut self, cx: &mut Context<'_>, now: Instant) -> bool { match self.connection.poll_timeout() { None => { @@ -587,100 +537,46 @@ impl Muxer { false } - fn new(endpoint: Endpoint, connection: Connection, handle: ConnectionHandle) -> Self { + fn new(connection: Endpoint) -> Self { Muxer { close_waker: None, last_timeout: None, outbound_streams: 1, pending_stream: None, - connection, - handle, streams: Default::default(), handshake_or_accept_waker: None, connectors: Default::default(), - endpoint, - pending: Pending::default(), + connection, timer: None, close_reason: None, waker: None, driver: None, } } - - pub(crate) fn poll_endpoint_events(&mut self) -> Option { - self.connection.poll_endpoint_events() - } - - /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. - fn send_endpoint_events(&mut self, cx: &mut Context<'_>) -> Result<(), Error> { - loop { - let token = match self.endpoint.poll_ready(cx) { - Poll::Pending => break Ok(()), - Poll::Ready(token) => token?, - }; - if let Some(event) = self.connection.poll_endpoint_events() { - self.endpoint.send_message( - EndpointMessage::EndpointEvent { - handle: self.handle, - event, - }, - token, - )? - } else { - break Ok(()); - } - } - } - - fn transmit_pending(&mut self, now: Instant, cx: &mut Context<'_>) -> Result<(), Error> { - let Self { - pending, - endpoint, - connection, - .. - } = self; - let _ = - pending.send_packet(cx, endpoint.socket(), &mut || connection.poll_transmit(now))?; - Ok(()) - } - fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); self.wake_incoming(); self.streams.wake_all(); self.connectors.clear(); - if !self.connection.is_closed() { - self.connection.close( - Instant::now(), - quinn_proto::VarInt::from_u32(error_code), - Default::default(), - ); - self.process_app_events(); - } + self.connection.close(error_code); self.wake_driver(); } - pub(crate) fn handle_event(&mut self, event: quinn_proto::ConnectionEvent) { - self.connection.handle_event(event) - } - fn get_pending_stream(&mut self) -> Option { self.wake_driver(); if let Some(id) = self.pending_stream.take() { Some(id) } else { - self.connection.open(Dir::Bi).map(|e| { + self.connection.open().map(|e| { self.outbound_streams += 1; self.streams.add_stream(e) }) } } - pub(crate) fn process_app_events(&mut self) -> bool { + pub(crate) fn process_app_events(&mut self) { use quinn_proto::Event; - let mut keep_going = false; 'a: while let Some(event) = self.connection.poll() { - keep_going = true; match event { Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { panic!("we disabled incoming unidirectional streams and datagrams") @@ -719,7 +615,7 @@ impl Muxer { self.pending_stream.is_none(), "we cannot have both pending tasks and a pending stream; qed" ); - let stream = self.connection.open(Dir::Bi).expect( + let stream = self.connection.open().expect( "we just were told that there is a stream available; there is \ a mutex that prevents other threads from calling open() in the \ meantime; qed", @@ -736,10 +632,6 @@ impl Muxer { } Event::ConnectionLost { reason } => { debug!("lost connection due to {:?}", reason); - debug_assert!( - self.connection.is_closed(), - "connection lost without being closed" - ); self.close_reason = Some(reason); if let Some(e) = self.close_waker.take() { e.wake() @@ -770,7 +662,6 @@ impl Muxer { } } } - keep_going } } @@ -781,13 +672,8 @@ pub(super) struct ConnectionDriver { } impl ConnectionDriver { - pub(crate) fn spawn>)>( - endpoint: Endpoint, - connection: Connection, - handle: ConnectionHandle, - cb: T, - ) -> Upgrade { - let inner = Arc::new(Mutex::new(Muxer::new(endpoint, connection, handle))); + pub(crate) fn spawn>)>(connection: Endpoint, cb: T) -> Upgrade { + let inner = Arc::new(Mutex::new(Muxer::new(connection))); cb(inner.clone()); let handle = async_std::task::spawn(Self { inner: inner.clone(), @@ -809,9 +695,9 @@ impl Future for ConnectionDriver { inner.waker = Some(cx.waker().clone()); loop { let now = Instant::now(); - inner.transmit_pending(now, cx)?; + inner.connection.poll_transmit_pending(now, cx)?; inner.process_app_events(); - inner.send_endpoint_events(cx)?; + inner.connection.send_endpoint_events(cx)?; if inner.drive_timer(cx, now) { continue; } diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/connection/stream.rs index 855d24f703c..140bb17b892 100644 --- a/transports/quic/src/connection/stream.rs +++ b/transports/quic/src/connection/stream.rs @@ -1,3 +1,23 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + //! The per-stream state in a QUIC connection use futures::channel::oneshot; use std::{mem, task}; diff --git a/transports/quic/src/connection/stream_map.rs b/transports/quic/src/connection/stream_map.rs index 56185576ad1..0641869ff26 100644 --- a/transports/quic/src/connection/stream_map.rs +++ b/transports/quic/src/connection/stream_map.rs @@ -1,3 +1,23 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + //! The state of all active streams in a QUIC connection use super::stream::StreamState; diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index ceba6f494f9..e1ea0b11b3b 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -17,7 +17,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -// + use crate::{error::Error, socket, verifier, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; @@ -38,6 +38,8 @@ use std::{ time::{Duration, Instant}, }; +use channel_ref::Channel; + /// Represents the configuration for a QUIC transport capability for libp2p. #[derive(Debug, Clone)] pub struct Config { @@ -111,7 +113,7 @@ impl Config { } #[derive(Debug)] -pub(crate) enum EndpointMessage { +enum EndpointMessage { Dummy, ConnectionAccepted, EndpointEvent { @@ -131,7 +133,7 @@ pub(super) struct EndpointInner { } impl EndpointInner { - pub(super) fn handle_event( + fn handle_event( &mut self, handle: ConnectionHandle, event: quinn_proto::EndpointEvent, @@ -176,12 +178,15 @@ impl EndpointInner { Entry::Occupied(occupied) => occupied, }; let mut connection = entry.get().lock(); - connection.handle_event(event); let mut is_drained = false; - while let Some(event) = connection.poll_endpoint_events() { - is_drained |= event.is_drained(); - if let Some(event) = inner.handle_event(handle, event) { - connection.handle_event(event) + { + let connection = &mut connection.connection().connection; + connection.handle_event(event); + while let Some(event) = connection.poll_endpoint_events() { + is_drained |= event.is_drained(); + if let Some(event) = inner.handle_event(handle, event) { + connection.handle_event(event) + } } } connection.process_app_events(); @@ -256,33 +261,159 @@ pub(super) struct EndpointData { type Sender = mpsc::Sender; impl ConnectionEndpoint { - pub(crate) fn socket(&self) -> &socket::Socket { - &self.socket + #[inline] + pub(crate) fn is_handshaking(&mut self) -> bool { + self.connection.is_handshaking() } - pub(crate) fn poll_ready( + /// Notify the endpoint that the handshake has completed, + /// and get the certificates from it. + /// + /// # Panics + /// + /// Panics if the connection is still handshaking. + pub(crate) fn notify_handshake_complete( &mut self, cx: &mut Context<'_>, - ) -> Poll> { - self.channel.poll_ready(cx).map_ok(SendToken) + ) -> Poll, mpsc::SendError>> { + assert!(!self.is_handshaking()); + if self.connection.side().is_server() { + ready!(self.channel.poll_ready(cx))?; + self.channel + .start_send(EndpointMessage::ConnectionAccepted)? + } + Poll::Ready(Ok(self + .connection + .crypto_session() + .get_peer_certificates() + .expect( + "we have finished handshaking, so we have exactly one certificate; qed", + ))) + } + + /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. + pub(crate) fn send_endpoint_events(&mut self, cx: &mut Context<'_>) -> Result<(), Error> { + loop { + match self.channel.poll_ready(cx) { + Poll::Pending => break Ok(()), + Poll::Ready(token) => token?, + } + if let Some(event) = self.connection.poll_endpoint_events() { + self.channel.start_send(EndpointMessage::EndpointEvent { + handle: self.handle, + event, + })? + } else { + break Ok(()); + } + } + } + + /// Poll for the timeout + pub(crate) fn poll_timeout(&mut self) -> Option { + self.connection.poll_timeout() + } + + /// Handle a timeout + pub(crate) fn handle_timeout(&mut self, now: Instant) { + self.connection.handle_timeout(now) } - pub(crate) fn send_message( + /// Destroy a substream + pub(crate) fn destroy_stream(&mut self, id: quinn_proto::StreamId) { + // if either of these returns an error, there is nothing we can do, so + // just ignore it. That said, an error here should never happen unless + // the connection is closed. + let _ = self.connection.finish(id); + let _ = self.connection.stop_sending(id, Default::default()); + } + + pub(crate) fn poll_transmit_pending( &mut self, - event: EndpointMessage, - SendToken(()): SendToken, - ) -> Result<(), mpsc::SendError> { - self.channel.start_send(event) + now: Instant, + cx: &mut Context<'_>, + ) -> Result<(), Error> { + let connection = &mut self.connection; + match self + .pending + .send_packet(cx, &self.socket, &mut || connection.poll_transmit(now)) + { + Poll::Ready(Ok(())) | Poll::Pending => Ok(()), + Poll::Ready(Err(e)) => Err(Error::IO(e)), + } + } + + /// Check if this connection is drained. + pub(crate) fn is_drained(&self) -> bool { + self.connection.is_drained() + } + + /// Accept an incoming stream, if possible. + pub(crate) fn accept(&mut self) -> Option { + self.connection.accept(quinn_proto::Dir::Bi) + } + + /// Open an outgoing stream if it is possible to do so. + pub(crate) fn open(&mut self) -> Option { + self.connection.open(quinn_proto::Dir::Bi) + } + + /// Write to a stream. + pub(crate) fn write( + &mut self, + id: quinn_proto::StreamId, + buffer: &[u8], + ) -> Result { + self.connection.write(id, buffer) + } + + /// Shut down the writing side of a stream. + pub(crate) fn finish( + &mut self, + id: quinn_proto::StreamId, + ) -> Result<(), quinn_proto::FinishError> { + self.connection.finish(id) + } + + /// Get application-facing events + pub(crate) fn poll(&mut self) -> Option { + self.connection.poll() + } + + /// Get the side of the stream + pub(crate) fn side(&self) -> quinn_proto::Side { + self.connection.side() + } + + /// Read from a stream + pub(crate) fn read( + &mut self, + id: quinn_proto::StreamId, + buf: &mut [u8], + ) -> Result { + self.connection.read(id, buf).map(|size| size.unwrap_or(0)) + } + + /// Close the stream, if it is not closed already + pub(crate) fn close(&mut self, status: u32) { + if self.connection.is_closed() { + return; + } + self.connection.close( + std::time::Instant::now(), + quinn_proto::VarInt::from_u32(status), + Default::default(), + ) } } mod channel_ref { use super::{Arc, EndpointData, EndpointMessage::Dummy, Sender}; #[derive(Debug, Clone)] - pub(crate) struct Channel(Sender); + pub(super) struct Channel(Sender); impl Channel { - pub(crate) fn new(s: Sender) -> Self { + pub(super) fn new(s: Sender) -> Self { Self(s) } } @@ -315,7 +446,7 @@ mod channel_ref { } } -pub(crate) use channel_ref::{Channel, EndpointRef}; +pub(crate) use channel_ref::EndpointRef; /// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// @@ -337,6 +468,9 @@ pub struct Endpoint(EndpointRef); #[derive(Debug)] pub(crate) struct ConnectionEndpoint { + pending: socket::Pending, + connection: Connection, + handle: ConnectionHandle, channel: Channel, socket: Arc, } @@ -433,13 +567,9 @@ impl Endpoint { } } -fn create_muxer( - endpoint: ConnectionEndpoint, - connection: Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, -) -> Upgrade { - super::connection::ConnectionDriver::spawn(endpoint, connection, handle, |weak| { +fn create_muxer(endpoint: ConnectionEndpoint, inner: &mut EndpointInner) -> Upgrade { + let handle = endpoint.handle; + super::connection::ConnectionDriver::spawn(endpoint, |weak| { inner.muxers.insert(handle, weak); }) } @@ -452,10 +582,13 @@ fn accept_muxer( channel: Channel, ) { let connection_endpoint = ConnectionEndpoint { + pending: socket::Pending::default(), + connection, + handle, socket: endpoint.socket.clone(), channel, }; - let upgrade = create_muxer(connection_endpoint, connection, handle, &mut *inner); + let upgrade = create_muxer(connection_endpoint, &mut *inner); if endpoint .new_connections .unbounded_send(ListenerEvent::Upgrade { @@ -588,16 +721,17 @@ impl Transport for Endpoint { warn!("Connection error: {:?}", e); TransportError::Other(Error::CannotConnect(e)) }); - let (handle, conn) = s?; + let (handle, connection) = s?; let socket = endpoint.reference.socket.clone(); let endpoint = ConnectionEndpoint { + pending: socket::Pending::default(), + connection, + handle, socket, channel: endpoint.channel, }; Ok(super::connection::ConnectionDriver::spawn( endpoint, - conn, - handle, |weak| { inner.muxers.insert(handle, weak); }, From 984b75dd4cdad4473f17b86254939c4d617e9a60 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 24 Feb 2020 15:54:04 -0500 Subject: [PATCH 108/202] Consolidate error messages --- transports/quic/src/connection.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 69c6ba9e913..9f037d88614 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -245,17 +245,13 @@ impl StreamMuxer for QuicMuxer { Poll::Pending } Err(WriteError::UnknownStream) => { + error!( + "The application used a connection that is already being \ + closed. This is a bug in the application or in libp2p." + ); if let Some(e) = &inner.close_reason { - error!( - "The application used a connection that is already being \ - closed. This is a bug in the application or in libp2p." - ); Poll::Ready(Err(Error::ConnectionError(e.clone()))) } else { - error!( - "The application used a stream that has already been \ - closed. This is a bug in the application or in libp2p." - ); Poll::Ready(Err(Error::ExpiredStream)) } } From ba1c5fb26512781bd0516cdb5b902609045bf726 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 24 Feb 2020 16:11:06 -0500 Subject: [PATCH 109/202] Tests should not require `Outbound` to implement `Stream` --- transports/quic/tests/tests.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index cbdaac96caf..7dab7fb061d 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -85,6 +85,21 @@ impl Drop for QuicStream { } } +struct Outbound<'a>(&'a mut Muxer, libp2p_quic::Outbound); + +impl<'a> futures::Future for Outbound<'a> { + type Output = Result; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Outbound(conn, id) = &mut *self; + let id: Option = Some(ready!(conn.poll_outbound(cx, id))?); + Poll::Ready(Ok(QuicStream { + id, + muxer: self.get_mut().0.clone(), + shutdown: false, + })) + } +} + #[derive(Debug)] struct Inbound<'a>(&'a mut Muxer); impl<'a> futures::Stream for Inbound<'a> { @@ -156,7 +171,6 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { init(); - println!(""); for _ in 0..100u32 { do_test() } @@ -184,6 +198,7 @@ fn do_test() { let (quic_endpoint, join) = retry_on_eaddrinuse(quic_config, addr.clone()); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); + trace!("running tests"); let handle = async_std::task::spawn(async move { let key = loop { trace!("awaiting connection"); @@ -240,13 +255,11 @@ fn do_test() { "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), ); // Obtain a future socket through dialing - let connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); - let mut stream = QuicStream { - id: Some(connection.1.open_outbound().await.expect("failed")), - muxer: connection.1.clone(), - shutdown: false, - }; + let id = connection.1.open_outbound(); + let mut stream = Outbound(&mut connection.1, id).await.expect("failed"); + debug!("opened a stream: id {:?}", stream.id); let result = stream.read(&mut [][..]).await; let result = result.expect_err("reading from an unwritten stream cannot succeed"); From 0a2a4232b94ce8a29e4dc46ce8b641bb0af434c1 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 24 Feb 2020 16:18:16 -0500 Subject: [PATCH 110/202] Outbound should not implement Future --- transports/quic/src/connection.rs | 43 ++++++++++++++----------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 9f037d88614..1f30db98b97 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -117,27 +117,6 @@ enum OutboundInner { #[derive(Debug)] pub struct Outbound(OutboundInner); -impl Future for Outbound { - type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = &mut *self; - match this.0 { - OutboundInner::Complete(_) => match replace(&mut this.0, OutboundInner::Done) { - OutboundInner::Complete(e) => Poll::Ready(e.map(Substream::unwritten)), - _ => unreachable!("we just checked that we have a `Complete`; qed"), - }, - OutboundInner::Pending(ref mut receiver) => { - let result = ready!(receiver.poll_unpin(cx)) - .map(Substream::unwritten) - .map_err(|oneshot::Canceled| Error::ConnectionLost); - this.0 = OutboundInner::Done; - Poll::Ready(result) - } - OutboundInner::Done => panic!("polled after yielding Ready"), - } - } -} - impl StreamMuxer for QuicMuxer { type OutboundSubstream = Outbound; type Substream = Substream; @@ -160,7 +139,7 @@ impl StreamMuxer for QuicMuxer { fn destroy_outbound(&self, outbound: Outbound) { let mut inner = self.inner(); - let id = *match outbound.0 { + let id = match outbound.0 { OutboundInner::Complete(Err(_)) => return, OutboundInner::Complete(Ok(id)) => id, // try_recv is race-free, because the lock on `self` prevents @@ -171,7 +150,8 @@ impl StreamMuxer for QuicMuxer { }, OutboundInner::Done => return, }; - inner.connection.destroy_stream(id) + inner.connection.destroy_stream(*id); + inner.streams.remove(id) } fn destroy_substream(&self, substream: Self::Substream) { @@ -267,7 +247,22 @@ impl StreamMuxer for QuicMuxer { cx: &mut Context<'_>, substream: &mut Self::OutboundSubstream, ) -> Poll> { - substream.poll_unpin(cx) + let stream = match substream.0 { + OutboundInner::Complete(_) => { + match replace(&mut substream.0, OutboundInner::Done) { + OutboundInner::Complete(e) => e?, + _ => unreachable!(), + } + } + OutboundInner::Pending(ref mut receiver) => { + let result = ready!(receiver.poll_unpin(cx)) + .map_err(|oneshot::Canceled| Error::ConnectionLost)?; + substream.0 = OutboundInner::Done; + result + } + OutboundInner::Done => panic!("polled after yielding Ready"), + }; + Poll::Ready(Ok(Substream::unwritten(stream))) } /// Try to from a substream. This will return an error if the substream has From 25eb755f8be454ef38ec2a6b08a746f7447a01e7 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 24 Feb 2020 16:41:09 -0500 Subject: [PATCH 111/202] Move timer and last timeout to ConnectionDriver The Muxer never uses them. --- transports/quic/src/connection.rs | 57 ++++++++++++++----------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 1f30db98b97..da906a2572b 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -463,14 +463,11 @@ pub(crate) struct Muxer { connectors: StreamSenderQueue, /// The number of outbound streams currently open. outbound_streams: usize, - /// The timer being used by this connection - timer: Option, /// The close reason, if this connection has been lost close_reason: Option, - /// Waker to wake up the driver + /// Waker to wake up the driver. This is not a `MaybeWaker` because only the + /// driver task ever puts a waker here. waker: Option, - /// The last timeout returned by `quinn_proto::poll_timeout`. - last_timeout: Option, /// A handle to wait on the driver to exit. driver: Option>>, /// Close waker @@ -506,39 +503,15 @@ impl Muxer { } } - fn drive_timer(&mut self, cx: &mut Context<'_>, now: Instant) -> bool { - match self.connection.poll_timeout() { - None => { - self.timer = None; - self.last_timeout = None - } - Some(t) if t <= now => { - self.connection.handle_timeout(now); - return true; - } - t if t == self.last_timeout => {} - Some(t) => self.timer = Some(futures_timer::Delay::new(t - now)), - } - if let Some(ref mut timer) = self.timer { - if timer.poll_unpin(cx).is_ready() { - self.connection.handle_timeout(now); - return true; - } - } - false - } - fn new(connection: Endpoint) -> Self { Muxer { close_waker: None, - last_timeout: None, outbound_streams: 1, pending_stream: None, streams: Default::default(), handshake_or_accept_waker: None, connectors: Default::default(), connection, - timer: None, close_reason: None, waker: None, driver: None, @@ -659,7 +632,12 @@ impl Muxer { #[derive(Debug)] pub(super) struct ConnectionDriver { inner: Arc>, + /// The packet awaiting transmission, if any. outgoing_packet: Option, + /// The timer being used by this connection + timer: Option, + /// The last timeout returned by `quinn_proto::poll_timeout`. + last_timeout: Option, } impl ConnectionDriver { @@ -669,6 +647,8 @@ impl ConnectionDriver { let handle = async_std::task::spawn(Self { inner: inner.clone(), outgoing_packet: None, + timer: None, + last_timeout: None, }); inner.lock().driver = Some(handle); Upgrade { @@ -689,8 +669,23 @@ impl Future for ConnectionDriver { inner.connection.poll_transmit_pending(now, cx)?; inner.process_app_events(); inner.connection.send_endpoint_events(cx)?; - if inner.drive_timer(cx, now) { - continue; + match inner.connection.poll_timeout() { + None => { + this.timer = None; + this.last_timeout = None + } + Some(t) if t <= now => { + inner.connection.handle_timeout(now); + continue; + } + t if t == this.last_timeout => {} + Some(t) => this.timer = Some(futures_timer::Delay::new(t - now)), + } + if let Some(ref mut timer) = this.timer { + if timer.poll_unpin(cx).is_ready() { + inner.connection.handle_timeout(now); + continue; + } } if !inner.connection.is_drained() { break Poll::Pending; From 5821540cda5fcb085e5fa3a55e34a2042f19d591 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 12:04:11 -0500 Subject: [PATCH 112/202] Enhanced logging --- transports/quic/src/connection.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index da906a2572b..cf80455267c 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -248,12 +248,10 @@ impl StreamMuxer for QuicMuxer { substream: &mut Self::OutboundSubstream, ) -> Poll> { let stream = match substream.0 { - OutboundInner::Complete(_) => { - match replace(&mut substream.0, OutboundInner::Done) { - OutboundInner::Complete(e) => e?, - _ => unreachable!(), - } - } + OutboundInner::Complete(_) => match replace(&mut substream.0, OutboundInner::Done) { + OutboundInner::Complete(e) => e?, + _ => unreachable!(), + }, OutboundInner::Pending(ref mut receiver) => { let result = ready!(receiver.poll_unpin(cx)) .map_err(|oneshot::Canceled| Error::ConnectionLost)?; @@ -488,8 +486,7 @@ impl Drop for Muxer { impl Muxer { pub(crate) fn wake_driver(&mut self) { if let Some(waker) = self.waker.take() { - debug!("driver awoken!"); - waker.wake(); + waker.wake() } } @@ -595,7 +592,11 @@ impl Muxer { self.pending_stream = Some(stream) } Event::ConnectionLost { reason } => { - debug!("lost connection due to {:?}", reason); + debug!( + "lost connection due to {:?} for side {:?}", + reason, + self.connection.side() + ); self.close_reason = Some(reason); if let Some(e) = self.close_waker.take() { e.wake() @@ -617,7 +618,7 @@ impl Muxer { self.streams.wake_writer(stream); } Event::Connected => { - debug!("connected!"); + debug!("connected for side {:?}!", self.connection.side()); self.wake_incoming(); } Event::StreamOpened { dir: Dir::Bi } => { From 40e81fe50e9de87b4eff7d0d2670dd01efee50d4 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 13:12:01 -0500 Subject: [PATCH 113/202] Fix shutdown and clean up the code This avoids a reference count underflow when closing connections. It also cleans up the code and documents some internal APIs. --- transports/quic/src/connection.rs | 65 +++++++++++++------- transports/quic/src/connection/stream.rs | 11 ++++ transports/quic/src/connection/stream_map.rs | 46 +++++++++----- transports/quic/tests/tests.rs | 30 +++------ 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index cf80455267c..8a925f7c098 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -359,10 +359,19 @@ impl StreamMuxer for QuicMuxer { fn close(&self, cx: &mut Context<'_>) -> Poll> { let mut inner = self.inner(); trace!( - "close() called with {} outbound streams", - inner.outbound_streams + "close() called with {} outbound streams for side {:?}", + inner.outbound_streams, + inner.connection.side() ); - inner.outbound_streams -= 1; + if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { + waker.wake(); + } + // To avoid reference count underflow, we must only decrement the + // refcount the *first* time this is called. + if !inner.shutting_down { + inner.outbound_streams -= 1; + inner.shutting_down = true; + } if inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.outbound_streams == 0 { @@ -370,18 +379,12 @@ impl StreamMuxer for QuicMuxer { inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); return Poll::Ready(Ok(())); } - if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { - waker.wake(); - return Poll::Pending; - } let Muxer { streams, connection, .. } = &mut *inner; - for id in streams.keys() { - let _ = connection.finish(*id); - } + streams.close(|id| drop(connection.finish(id))); Poll::Pending } } @@ -461,6 +464,8 @@ pub(crate) struct Muxer { connectors: StreamSenderQueue, /// The number of outbound streams currently open. outbound_streams: usize, + /// `true` if and only if we are currently shutting down. + shutting_down: bool, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver. This is not a `MaybeWaker` because only the @@ -511,6 +516,7 @@ impl Muxer { connection, close_reason: None, waker: None, + shutting_down: false, driver: None, } } @@ -523,15 +529,19 @@ impl Muxer { self.wake_driver(); } + fn open_stream(&mut self) -> Option { + self.connection.open().map(|e| { + self.outbound_streams += 1; + self.streams.add_stream(e) + }) + } + fn get_pending_stream(&mut self) -> Option { self.wake_driver(); if let Some(id) = self.pending_stream.take() { Some(id) } else { - self.connection.open().map(|e| { - self.outbound_streams += 1; - self.streams.add_stream(e) - }) + self.open_stream() } } @@ -540,9 +550,13 @@ impl Muxer { 'a: while let Some(event) = self.connection.poll() { match event { Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { - panic!("we disabled incoming unidirectional streams and datagrams") + // This should never happen, but if it does, it is better to + // log a nasty message and recover than to tear down the + // process. + error!("we disabled incoming unidirectional streams and datagrams") } Event::StreamAvailable { dir: Dir::Uni } => { + // Ditto panic!("we don’t use unidirectional streams") } Event::StreamReadable { stream } => { @@ -560,7 +574,10 @@ impl Muxer { stream, self.connection.side() ); - // Wake up the task waiting on us (if any) + // Wake up the task waiting on us (if any). + // This will panic if quinn-proto has already emitted a + // `StreamFinished` event, but it will never emit + // `StreamWritable` after `StreamFinished`. self.streams.wake_writer(stream) } Event::StreamAvailable { dir: Dir::Bi } => { @@ -569,20 +586,19 @@ impl Muxer { self.connection.side() ); if self.connectors.is_empty() { - // no task to wake up + // Nobody is waiting on the stream. Allow quinn-proto to + // queue it, so that it can apply backpressure. continue; } assert!( self.pending_stream.is_none(), "we cannot have both pending tasks and a pending stream; qed" ); - let stream = self.connection.open().expect( + let mut stream = self.open_stream().expect( "we just were told that there is a stream available; there is \ a mutex that prevents other threads from calling open() in the \ meantime; qed", ); - let mut stream = self.streams.add_stream(stream); - self.outbound_streams += 1; while let Some(oneshot) = self.connectors.pop_front() { stream = match oneshot.send(stream) { Ok(()) => continue 'a, @@ -607,15 +623,22 @@ impl Muxer { stream, stop_reason, } => { - // Wake up the task waiting on us (if any) trace!( "Stream {:?} finished for side {:?} because of {:?}", stream, self.connection.side(), stop_reason ); + // This stream is no longer useable for outbound traffic. self.outbound_streams -= 1; + // If someone is waiting for this stream to become writable, + // wake them up, so that they can find out the stream is + // dead. self.streams.wake_writer(stream); + // Connection close could be blocked on this. + if let Some(waker) = self.close_waker.take() { + waker.wake() + } } Event::Connected => { debug!("connected for side {:?}!", self.connection.side()); diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/connection/stream.rs index 140bb17b892..d8a569b23a2 100644 --- a/transports/quic/src/connection/stream.rs +++ b/transports/quic/src/connection/stream.rs @@ -33,6 +33,8 @@ enum WriterStatus { /// The stream is being shut down. `finisher` is the channel used to notify /// the finishing task. Finishing { finisher: oneshot::Sender<()> }, + /// The stream has been finished. Further operations will panic. + Finished, } impl WriterStatus { @@ -43,6 +45,7 @@ impl WriterStatus { Self::Finishing { finisher } => { let _ = finisher.send(()); } + Self::Finished => panic!("using a finished stream"), } } } @@ -83,12 +86,20 @@ impl StreamState { /// If a task is waiting for this stream to be finished or written to, wake /// it up. Otherwise, do nothing. + /// + /// # Panics + /// + /// Panics if the stream has already been finished. pub(crate) fn wake_writer(&mut self) { mem::replace(&mut self.writer, WriterStatus::Unblocked).take() } /// Set a waker that will be notified when the state becomes readable. /// Wake up any waker that has already been registered. + /// + /// # Panics + /// + /// Panics if the stream has already been finished. pub(crate) fn set_reader(&mut self, waker: task::Waker) { if let Some(waker) = mem::replace(&mut self.reader, Some(waker)) { waker.wake() diff --git a/transports/quic/src/connection/stream_map.rs b/transports/quic/src/connection/stream_map.rs index 0641869ff26..d4ff6e9924b 100644 --- a/transports/quic/src/connection/stream_map.rs +++ b/transports/quic/src/connection/stream_map.rs @@ -85,35 +85,53 @@ impl Streams { /// Set a waker that will be notified when the task becomes writable or is /// finished, waking up any waker or channel that has already been /// registered. + /// + /// # Panics + /// + /// Panics if the stream has already been finished. pub(super) fn set_writer(&mut self, id: &StreamId, waker: task::Waker) { self.get(id).set_writer(waker); } /// Set a channel that will be notified when the task becomes writable or is - /// finished, waking up any existing registered waker or channel + /// finished, waking up any existing registered waker or channel. + /// + /// # Panics + /// + /// Panics if the stream has already been finished. pub(super) fn set_finisher(&mut self, id: &StreamId, finisher: oneshot::Sender<()>) { self.get(id).set_finisher(finisher); } - /// Remove an ID from the map + /// Remove an ID from the map. + /// + /// # Panics + /// + /// Panics if the ID has already been removed. pub(super) fn remove(&mut self, id: StreamId) { - if self.map.remove(&id.0).is_none() { - panic!( - "Internal state corrupted. \ + self.map.remove(&id.0).expect( + "Internal state corrupted. \ You probably used a Substream with the wrong StreamMuxer", - ); - } + ); } - pub(super) fn wake_all(&mut self) { - for i in self.map.values_mut() { - i.wake_all() + /// Wake all wakers and call the provided callback for each stream, + /// so as to free resources. + /// + /// # Panics + /// + /// Panics if [`Self::close`] has already been called. + pub(super) fn close(&mut self, mut cb: T) { + for (stream, value) in &mut self.map { + value.wake_all(); + cb(*stream) } } - pub(super) fn keys( - &self, - ) -> std::collections::hash_map::Keys<'_, quinn_proto::StreamId, StreamState> { - self.map.keys() + /// Wake up everything + pub(super) fn wake_all(&mut self) { + for value in self.map.values_mut() { + value.wake_all() + } } } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 7dab7fb061d..39bc9364064 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -22,10 +22,10 @@ use async_macros::ready; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{ListenerEvent, TransportError}, + transport::ListenerEvent, StreamMuxer, Transport, }; -use libp2p_quic::{Config, Endpoint, Error, JoinHandle, Muxer, Substream}; +use libp2p_quic::{Config, Endpoint, Muxer, Substream}; use log::{debug, info, trace}; use std::{ io::Result, @@ -115,7 +115,6 @@ impl<'a> futures::Stream for Inbound<'a> { fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; - let _ = env_logger::try_init(); let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) .try_init(); @@ -176,26 +175,15 @@ fn communicating_between_dialer_and_listener() { } } -fn retry_on_eaddrinuse(config: Config, addr: Multiaddr) -> (Endpoint, JoinHandle) { - loop { - match Endpoint::new(config.clone(), addr.clone()) { - Ok(e) => break e, - Err(TransportError::Other(Error::IO(e))) - if e.kind() == std::io::ErrorKind::AddrInUse => {} - Err(e) => panic!("Failed to create endpoint: {}", e), - } - } -} - fn do_test() { use std::error::Error as _; let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); - let addr: Multiaddr = "/ip4/0.0.0.0/udp/12345/quic".parse().expect("bad address?"); + let addr: Multiaddr = "/ip4/127.0.0.1/udp/0/quic".parse().expect("bad address?"); let quic_config = Config::new(&keypair2); - let (quic_endpoint, join) = retry_on_eaddrinuse(quic_config, addr.clone()); + let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).unwrap(); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); trace!("running tests"); @@ -229,8 +217,8 @@ fn do_test() { debug!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); debug!("data written!"); - socket.close().await.unwrap(); - debug!("socket closed!"); + // socket.close().await.unwrap(); + // debug!("socket closed!"); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); debug!("end of stream"); drop(socket); @@ -250,10 +238,8 @@ fn do_test() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); let quic_config = Config::new(&keypair); - let (quic_endpoint, join) = retry_on_eaddrinuse( - quic_config, - "/ip4/127.0.0.1/udp/12346/quic".parse().unwrap(), - ); + let (quic_endpoint, join) = + Endpoint::new(quic_config, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()).unwrap(); // Obtain a future socket through dialing let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); From b2815d8358896f6db42aceccee5e2ebec493447c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 15:03:12 -0500 Subject: [PATCH 114/202] Fix doc comments and bump dependencies We are stuck with relying on a (temporary) async-tls fork while we wait on https://github.com/async-std/async-tls/19 to be merged. --- transports/quic/Cargo.toml | 2 +- transports/quic/src/connection/stream.rs | 2 +- transports/quic/src/endpoint.rs | 4 +++- transports/quic/src/verifier.rs | 6 +++++- transports/websocket/Cargo.toml | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 7c3aad2344c..050d59eb1b4 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["peer-to-peer", "libp2p", "quic", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -rustls = { version = "0.16.0", features = ["dangerous_configuration"] } +rustls = { version = "0.17.0", features = ["dangerous_configuration"] } libp2p-core = { path = "../../core", version = "0.16.0" } log = "0.4.8" ipnet = "2.2.0" diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/connection/stream.rs index d8a569b23a2..ac4a26a771f 100644 --- a/transports/quic/src/connection/stream.rs +++ b/transports/quic/src/connection/stream.rs @@ -120,7 +120,7 @@ impl StreamState { } /// Wake up both readers and writers. This is just a shorthand for calling - /// [`Self::wake_writer`] and [`Self::wake_reader`]. + /// [`wake_writer`] and [`wake_reader`]. pub(crate) fn wake_all(&mut self) { self.wake_writer(); self.wake_reader(); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index e1ea0b11b3b..552f2a219b0 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -62,7 +62,9 @@ fn make_client_config( let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.enable_early_data = true; - crypto.set_single_client_cert(vec![certificate], key); + crypto + .set_single_client_cert(vec![certificate], key) + .expect("we have a valid certificate; qed"); let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; crypto .dangerous() diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index c3bf7dbb041..a5c8504dc87 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -103,13 +103,17 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien true } - fn client_auth_root_subjects(&self) -> rustls::internal::msgs::handshake::DistinguishedNames { + fn client_auth_root_subjects( + &self, + _dns_name: Option<&webpki::DNSName>, + ) -> Option { Default::default() } fn verify_client_cert( &self, presented_certs: &[rustls::Certificate], + _dns_name: Option<&webpki::DNSName>, ) -> Result { verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { end_entity_cert.verify_is_valid_tls_client_cert( diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index 6a0dfe2b335..a8c4e19040e 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -10,14 +10,14 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-tls = "0.6" +async-tls = { git = "https://github.com/DemiMarie-parity/async-tls", branch = "upgrade-webpki-rustls" } bytes = "0.5" either = "1.5.3" futures = "0.3.1" libp2p-core = { version = "0.16.0", path = "../../core" } log = "0.4.8" quicksink = "0.1" -rustls = "0.16" +rustls = "0.17.0" rw-stream-sink = "0.2.0" soketto = { version = "0.3", features = ["deflate"] } url = "2.1" From c99784d09027bd132a653974a7a1d603c0a2cd69 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 15:10:54 -0500 Subject: [PATCH 115/202] Fix compilation error in websockets --- transports/websocket/src/framed.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 830b645d496..5ac9bda87b9 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -308,14 +308,9 @@ where let dns_name = dns_name.expect("for use_tls we have checked that dns_name is some"); trace!("starting TLS handshake with {}", address); let stream = self.tls_config.client.connect(&dns_name, stream) - .map_err(|e| { - // We should never enter here as we passed a `DNSNameRef` to `connect`. - debug!("invalid domain name: {:?}", dns_name); - Error::Tls(e.into()) - })? .map_err(|e| { debug!("TLS handshake with {} failed: {}", address, e); - Error::Tls(tls::Error::from(e)) + Error::Tls(e.into()) }) .await?; From e2e75b0000acb3f0297a3c5d3b25ac383f869780 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 18:44:17 -0500 Subject: [PATCH 116/202] Adapt to changed rustls API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘ClientCertVerifier::client_auth_root_subjects()’ previously returned a ‘Vec’, but now returns an ‘Option’. As a result, ‘Default::default()’ is now ‘None’, not ‘vec![]’. This causes the connection to be aborted instead of allowing any subject. Fix this by explicitly returning ‘Some(vec![])’. There is still a bug: if the TLS handshake fails, the code will panic when it fails to obtain the nonexistent peer certificate. This will be fixed in the next commit. --- transports/quic/src/endpoint.rs | 13 ++++++------- transports/quic/src/verifier.rs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 552f2a219b0..9396349075e 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -279,18 +279,17 @@ impl ConnectionEndpoint { cx: &mut Context<'_>, ) -> Poll, mpsc::SendError>> { assert!(!self.is_handshaking()); + let certificate = self + .connection + .crypto_session() + .get_peer_certificates() + .expect("we always require the peer to present a certificate; qed"); if self.connection.side().is_server() { ready!(self.channel.poll_ready(cx))?; self.channel .start_send(EndpointMessage::ConnectionAccepted)? } - Poll::Ready(Ok(self - .connection - .crypto_session() - .get_peer_certificates() - .expect( - "we have finished handshaking, so we have exactly one certificate; qed", - ))) + Poll::Ready(Ok(certificate)) } /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index a5c8504dc87..349e52b0321 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -107,7 +107,7 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien &self, _dns_name: Option<&webpki::DNSName>, ) -> Option { - Default::default() + Some(vec![]) } fn verify_client_cert( From 557b2a8921d6f64d028f5c615f60748c037988bf Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 19:03:28 -0500 Subject: [PATCH 117/202] Fix error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A connection does not need to be drained to be closed. This could cause a panic if the TLS handshake failed: the code would expect there to be a peer certificate, but the handshake failure meant there wasn’t any. --- transports/quic/src/connection.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 8a925f7c098..5353db3deb3 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -170,13 +170,8 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { trace!("being polled for inbound connections!"); let mut inner = self.inner(); - if inner.connection.is_drained() { - return Poll::Ready(Err(Error::ConnectionError( - inner - .close_reason - .clone() - .expect("closed connections always have a reason; qed"), - ))); + if let Some(close_reason) = &inner.close_reason { + return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); } inner.wake_driver(); match inner.connection.accept() { @@ -413,13 +408,8 @@ impl Future for Upgrade { trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - let peer_certificates = if inner.connection.is_drained() { - return Poll::Ready(Err(Error::ConnectionError( - inner - .close_reason - .clone() - .expect("closed connections always have a reason; qed"), - ))); + let peer_certificates = if let Some(close_reason) = &inner.close_reason { + return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); } else if inner.connection.is_handshaking() { inner.handshake_or_accept_waker = Some(cx.waker().clone()); return Poll::Pending; From 487ae329c8854f5ee6e644bf6c8e7dbf986c9729 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 25 Feb 2020 19:07:52 -0500 Subject: [PATCH 118/202] =?UTF-8?q?Use=20the=20public=20type=20alias=20for?= =?UTF-8?q?=20=E2=80=98DistinguishedNames=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit instead of the internal one. --- transports/quic/src/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 349e52b0321..efaffef9ea1 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -106,7 +106,7 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, - ) -> Option { + ) -> Option { Some(vec![]) } From 4753706c7ebff23156624422a099c7c778500364 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 26 Feb 2020 20:07:50 -0500 Subject: [PATCH 119/202] Fix intra-rustdoc links --- transports/quic/src/connection/stream.rs | 2 +- transports/quic/src/connection/stream_map.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/connection/stream.rs index ac4a26a771f..e9f873617d9 100644 --- a/transports/quic/src/connection/stream.rs +++ b/transports/quic/src/connection/stream.rs @@ -120,7 +120,7 @@ impl StreamState { } /// Wake up both readers and writers. This is just a shorthand for calling - /// [`wake_writer`] and [`wake_reader`]. + /// [`StreamState::wake_writer`] and [`StreamState::wake_reader`]. pub(crate) fn wake_all(&mut self) { self.wake_writer(); self.wake_reader(); diff --git a/transports/quic/src/connection/stream_map.rs b/transports/quic/src/connection/stream_map.rs index d4ff6e9924b..4e3e00701f6 100644 --- a/transports/quic/src/connection/stream_map.rs +++ b/transports/quic/src/connection/stream_map.rs @@ -120,7 +120,7 @@ impl Streams { /// /// # Panics /// - /// Panics if [`Self::close`] has already been called. + /// Panics if [`Stream::close`] has already been called. pub(super) fn close(&mut self, mut cb: T) { for (stream, value) in &mut self.map { value.wake_all(); From 88d5b45bff51d4e177fdfedcff42d03044445828 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 28 Feb 2020 10:15:53 -0500 Subject: [PATCH 120/202] Use a temporary fork of webpki --- .gitmodules | 3 + Cargo.toml | 5 + transports/quic/Cargo.toml | 1 + transports/quic/src/certificate.rs | 181 +++++++---------------------- transports/quic/src/connection.rs | 6 +- transports/quic/src/verifier.rs | 66 ++++++++++- webpki | 1 + 7 files changed, 120 insertions(+), 143 deletions(-) create mode 100644 .gitmodules create mode 160000 webpki diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..1701acbc06a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "webpki"] + path = webpki + url = git+ssh://git@github.com/DemiMarie-parity/webpki diff --git a/Cargo.toml b/Cargo.toml index 7baee7c5bc5..5060a4117a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,4 +79,9 @@ members = [ "transports/wasm-ext", "transports/websocket", ] +exclude = [ + "webpki" +] +[patch.crates-io] +webpki = { path = "webpki" } diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 050d59eb1b4..b6bef52685d 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -26,6 +26,7 @@ protobuf = "2.10.1" yasna = { version = "0.3.1", features = ["num-bigint"] } ring = "0.16.11" webpki = "0.21.2" +untrusted = "0.7.0" [dev-dependencies] tracing-subscriber = "0.2.1" diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index f6ac6d3df70..2a09dd0f6d8 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -27,10 +27,9 @@ //! library) are logged at `debug` level. use libp2p_core::identity; -use log::{trace, warn}; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -pub(crate) const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); +const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; @@ -46,7 +45,9 @@ fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen .write_bitvec_bytes(signature, signature.len() * 8); }) }); - rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents) + let mut ext = rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents); + ext.set_criticality(true); + ext } fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::CustomExtension) { @@ -81,139 +82,45 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { .expect("certificate generation with valid params will succeed; qed") } -/// Read a bitvec into a vector of bytes. Requires the bitvec to be a whole number of bytes. -fn read_bitvec(reader: &mut yasna::BERReaderSeq<'_, '_>) -> Result, yasna::ASN1Error> { - let (value, bits) = reader.next().read_bitvec_bytes()?; - // be extra careful regarding overflow - if bits % 8 == 0 { - Ok(value) - } else { - warn!("value was of wrong length, sorry!"); - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) - } -} - -fn parse_x509_extensions( - reader: &mut yasna::BERReaderSeq<'_, '_>, - certificate_key: &[u8], -) -> Result { - reader.next().read_tagged(yasna::Tag::context(3), |reader| { - let mut public_key = None; - let mut oids_seen = std::collections::HashSet::new(); - reader.read_sequence_of(|reader| { - trace!("reading an extension"); - public_key = parse_x509_extension(reader, &certificate_key, &mut oids_seen)?; - Ok(()) - })?; - match public_key { - Some(e) => Ok(e), - None => Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Eof)), - } - }) -} - -fn parse_x509_extension( - reader: yasna::BERReader<'_, '_>, - certificate_key: &[u8], - oids_seen: &mut std::collections::HashSet, -) -> Result, yasna::ASN1Error> { - reader.read_sequence(|reader| { - let oid = reader.next().read_oid()?; - trace!("read extensions with oid {:?}", oid); - if !oids_seen.insert(oid.clone()) { - warn!( - "found the second extension with oid {:?} in the same certificate", - oid - ); - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); - } - reader.read_optional(|reader| reader.read_bool().map(drop))?; - let contents = reader.next().read_bytes()?; - match &**oid.components() { - LIBP2P_OID => verify_libp2p_extension(&contents, certificate_key).map(Some), - _ => Ok(None), - } - }) -} - -fn verify_libp2p_extension( - extension: &[u8], - certificate_key: &[u8], -) -> yasna::ASN1Result { - yasna::parse_der(extension, |reader| { - reader.read_sequence(|mut reader| { - let public_key = read_bitvec(&mut reader)?; - let signature = read_bitvec(&mut reader)?; - let public_key = identity::PublicKey::from_protobuf_encoding(&public_key) - .map_err(|_| yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid))?; - let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); - v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); - v.extend_from_slice(certificate_key); - if public_key.verify(&v, &signature) { - Ok(public_key) - } else { - Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)) +/// Extract the peer’s public key from a libp2p certificate. It is erroneous to +/// call this unless the certificate is known to be a valid libp2p certificate. +/// The certificate verifiers in this crate validate that a certificate is in +/// fact a valid libp2p certificate. +/// +/// # Panics +/// +/// Panics if the key could not be extracted. +pub(crate) fn extract_libp2p_peerid(certificate: &[u8]) -> libp2p_core::PeerId { + let mut id = None; + webpki::EndEntityCert::from_with_extension_cb( + certificate, + &mut |oid, value, _critical, _spki| match oid.as_slice_less_safe() { + [43, 6, 1, 4, 1, 131, 162, 90, 1, 1] => { + use ring::io::der; + value + .read_all(ring::error::Unspecified, |mut reader| { + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?.read_all( + ring::error::Unspecified, + |mut reader| { + let public_key = der::bit_string_with_no_unused_bits(&mut reader)? + .as_slice_less_safe(); + let _signature = der::bit_string_with_no_unused_bits(&mut reader)?; + id = Some( + identity::PublicKey::from_protobuf_encoding(public_key) + .map_err(|_| ring::error::Unspecified)?, + ); + Ok(()) + }, + ) + }) + .expect("we already validated the certificate"); + webpki::Understood::Yes } - }) - }) -} - -fn parse_certificate(certificate: &[u8]) -> yasna::ASN1Result { - trace!("parsing certificate"); - yasna::parse_der(certificate, |reader| { - reader.read_sequence(|reader| { - let key = parse_tbscertificate(reader.next())?; - // skip algorithm - reader.next().read_der()?; - // skip signature - reader.next().read_der()?; - Ok(key) - }) - }) -} - -fn parse_tbscertificate( - reader: yasna::BERReader<'_, '_>, -) -> yasna::ASN1Result { - // trace!("parsing TBScertificate"); - reader.read_sequence(|reader| { - // Check the X.509 version - if reader.next().read_der()? != [160, 3, 2, 1, 2] { - return Err(yasna::ASN1Error::new(yasna::ASN1ErrorKind::Invalid)); - } - // Skip the serial number - reader.next().read_der()?; - // Skip the signature algorithm - reader.next().read_der()?; - // Skip the issuer - reader.next().read_der()?; - // Skip validity - reader.next().read_der()?; - // Skip subject - reader.next().read_der()?; - // trace!("reading subjectPublicKeyInfo"); - let key = reader.next().read_sequence(|mut reader| { - // Skip the subject key algorithm - reader.next().read_der()?; - // trace!("reading subject key"); - read_bitvec(&mut reader) - })?; - trace!("reading extensions"); - parse_x509_extensions(reader, &key) - }) -} - -/// Extract the peer’s public key from a libp2p certificate, and check that the certificate’s -/// public key is signed by it. -pub(crate) fn extract_libp2p_peerid( - certificate: &[u8], -) -> Result { - parse_certificate(certificate) - .map_err(|e| { - log::debug!("error in parsing: {:?}", e); - webpki::Error::InvalidSignatureForPublicKey - }) - .map(libp2p_core::PeerId::from_public_key) + _ => webpki::Understood::No, + }, + ) + .expect("we already validated the certificate"); + id.expect("we already checked that a PeerId exists").into() } #[cfg(test)] @@ -224,7 +131,7 @@ mod test { drop(env_logger::try_init()); let keypair = identity::Keypair::generate_ed25519(); assert_eq!( - extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()), libp2p_core::PeerId::from_public_key(keypair.public()) ); log::trace!("trying secp256k1!"); @@ -235,7 +142,7 @@ mod test { assert_eq!(public, public, "key is not equal to itself?"); log::debug!("have a valid key!"); assert_eq!( - extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()).unwrap(), + extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()), libp2p_core::PeerId::from_public_key(keypair.public()) ); } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 5353db3deb3..a50b323fc6b 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -428,11 +428,7 @@ impl Future for Upgrade { .as_ref(), ) }; - let muxer = muxer.take().expect("polled after yielding Ready"); - Poll::Ready(match res { - Ok(e) => Ok((e, muxer)), - Err(_) => Err(Error::BadCertificate(ring::error::Unspecified)), - }) + Poll::Ready(Ok((res, muxer.take().expect("polled after yielding Ready")))) } } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index efaffef9ea1..e42344682a3 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -18,6 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use libp2p_core::identity; + +const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); + static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, @@ -76,6 +81,13 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe } } +#[derive(Copy, Clone)] +enum ExtensionStatus { + NotFound, + Good, + Error, +} + fn verify_presented_certs( presented_certs: &[rustls::Certificate], cb: &dyn Fn( @@ -91,13 +103,65 @@ fn verify_presented_certs( .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; let raw_certificate = presented_certs[0].as_ref(); let inner_func = || { - let parsed_cert = webpki::EndEntityCert::from(raw_certificate)?; + let mut status = ExtensionStatus::NotFound; + let parsed_cert = webpki::EndEntityCert::from_with_extension_cb( + raw_certificate, + &mut |oid, value, _critical, spki| match (oid.as_slice_less_safe(), status) { + ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], ExtensionStatus::NotFound) => { + status = if verify_libp2p_extension(value, spki).is_ok() { + ExtensionStatus::Good + } else { + ExtensionStatus::Error + }; + webpki::Understood::Yes + } + ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], _) => { + status = ExtensionStatus::Error; + webpki::Understood::Yes + } + _ => webpki::Understood::No, + }, + )?; + match status { + ExtensionStatus::Good => {} + ExtensionStatus::Error | ExtensionStatus::NotFound => { + return Err(webpki::Error::UnknownIssuer) + } + } let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate)?; cb(time, parsed_cert, trust_anchor) }; inner_func().map_err(rustls::TLSError::WebPKIError) } +fn verify_libp2p_extension( + extension: untrusted::Input<'_>, + subject_public_key_info: untrusted::Input<'_>, +) -> Result { + use ring::{error::Unspecified, io::der}; + let spki = subject_public_key_info.read_all(Unspecified, |mut reader| { + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + Ok(der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe()) + })?; + extension.read_all(Unspecified, |mut reader| { + let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + inner.read_all(Unspecified, |mut reader| { + let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + let signature = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + let public_key = + identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified)?; + let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); + v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + v.extend_from_slice(spki); + if public_key.verify(&v, signature) { + Ok(public_key) + } else { + Err(Unspecified) + } + }) + }) +} + impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { fn offer_client_auth(&self) -> bool { true diff --git a/webpki b/webpki new file mode 160000 index 00000000000..bbdf4cbe5d3 --- /dev/null +++ b/webpki @@ -0,0 +1 @@ +Subproject commit bbdf4cbe5d38c9a9d12dde2771fea428556df5c9 From 245b7462345f256f08334369979074f2a8929d1f Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 28 Feb 2020 11:49:04 -0500 Subject: [PATCH 121/202] Adapt to changed extension handling API in webpki --- transports/quic/src/certificate.rs | 48 ++++++++--------- transports/quic/src/connection.rs | 19 +++---- transports/quic/src/verifier.rs | 83 ++++++++++++++++-------------- webpki | 2 +- 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 2a09dd0f6d8..0e25fd45870 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -92,37 +92,37 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { /// Panics if the key could not be extracted. pub(crate) fn extract_libp2p_peerid(certificate: &[u8]) -> libp2p_core::PeerId { let mut id = None; - webpki::EndEntityCert::from_with_extension_cb( - certificate, - &mut |oid, value, _critical, _spki| match oid.as_slice_less_safe() { + let cb = + &mut |oid: untrusted::Input<'_>, value: untrusted::Input<'_>, _critical, _spki| match oid + .as_slice_less_safe() + { [43, 6, 1, 4, 1, 131, 162, 90, 1, 1] => { - use ring::io::der; - value - .read_all(ring::error::Unspecified, |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?.read_all( - ring::error::Unspecified, - |mut reader| { - let public_key = der::bit_string_with_no_unused_bits(&mut reader)? - .as_slice_less_safe(); - let _signature = der::bit_string_with_no_unused_bits(&mut reader)?; - id = Some( - identity::PublicKey::from_protobuf_encoding(public_key) - .map_err(|_| ring::error::Unspecified)?, - ); - Ok(()) - }, - ) - }) - .expect("we already validated the certificate"); + id = Some( + extract_libp2p_extension(value).expect("we already validated the certificate"), + ); webpki::Understood::Yes } _ => webpki::Understood::No, - }, - ) - .expect("we already validated the certificate"); + }; + webpki::EndEntityCert::from_with_extension_cb(certificate, cb) + .expect("we already validated the certificate"); id.expect("we already checked that a PeerId exists").into() } +fn extract_libp2p_extension( + extension: untrusted::Input<'_>, +) -> Result { + use ring::{error::Unspecified, io::der}; + extension.read_all(Unspecified, |mut reader| { + let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + inner.read_all(Unspecified, |mut reader| { + let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + der::bit_string_with_no_unused_bits(&mut reader)?; + identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified) + }) + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index a50b323fc6b..3323464f6f6 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -416,19 +416,14 @@ impl Future for Upgrade { } else { ready!(inner.connection.notify_handshake_complete(cx))? }; - - certificate::extract_libp2p_peerid( - peer_certificates - .get(0) - .expect( - "our certificate verifiers require exactly one \ - certificate to be presented, so an empty certificate \ - chain would already have been rejected; qed", - ) - .as_ref(), - ) + // we have already verified that there is (exactly) one peer certificate, + // and that it is valid. + certificate::extract_libp2p_peerid(peer_certificates[0].as_ref()) }; - Poll::Ready(Ok((res, muxer.take().expect("polled after yielding Ready")))) + Poll::Ready(Ok(( + res, + muxer.take().expect("polled after yielding Ready"), + ))) } } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index e42344682a3..6b07acd977b 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -69,15 +69,16 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { - verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { - end_entity_cert.verify_is_valid_tls_server_cert( + let (time, end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; + end_entity_cert + .verify_is_valid_tls_server_cert( ALL_SUPPORTED_SIGNATURE_ALGORITHMS, &webpki::TLSServerTrustAnchors(&[trust_anchor]), &[], time, ) - }) - .map(|()| rustls::ServerCertVerified::assertion()) + .map(|()| rustls::ServerCertVerified::assertion()) + .map_err(rustls::TLSError::WebPKIError) } } @@ -90,48 +91,51 @@ enum ExtensionStatus { fn verify_presented_certs( presented_certs: &[rustls::Certificate], - cb: &dyn Fn( +) -> Result< + ( webpki::Time, webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>, - ) -> Result<(), webpki::Error>, -) -> Result<(), rustls::TLSError> { + ), + rustls::TLSError, +> { if presented_certs.len() != 1 { return Err(rustls::TLSError::NoCertificatesPresented); } let time = webpki::Time::try_from(std::time::SystemTime::now()) .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; let raw_certificate = presented_certs[0].as_ref(); - let inner_func = || { - let mut status = ExtensionStatus::NotFound; - let parsed_cert = webpki::EndEntityCert::from_with_extension_cb( - raw_certificate, - &mut |oid, value, _critical, spki| match (oid.as_slice_less_safe(), status) { - ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], ExtensionStatus::NotFound) => { - status = if verify_libp2p_extension(value, spki).is_ok() { - ExtensionStatus::Good - } else { - ExtensionStatus::Error - }; - webpki::Understood::Yes - } - ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], _) => { - status = ExtensionStatus::Error; - webpki::Understood::Yes - } - _ => webpki::Understood::No, - }, - )?; - match status { - ExtensionStatus::Good => {} - ExtensionStatus::Error | ExtensionStatus::NotFound => { - return Err(webpki::Error::UnknownIssuer) - } + let mut status = ExtensionStatus::NotFound; + let cb = &mut |oid: untrusted::Input<'_>, value, _critical, spki| match ( + oid.as_slice_less_safe(), + status, + ) { + ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], ExtensionStatus::NotFound) => { + status = if verify_libp2p_extension(value, spki).is_ok() { + ExtensionStatus::Good + } else { + ExtensionStatus::Error + }; + webpki::Understood::Yes + } + ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], _) => { + status = ExtensionStatus::Error; + webpki::Understood::Yes } - let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate)?; - cb(time, parsed_cert, trust_anchor) + _ => webpki::Understood::No, }; - inner_func().map_err(rustls::TLSError::WebPKIError) + let parsed_cert = webpki::EndEntityCert::from_with_extension_cb(raw_certificate, cb) + .map_err(rustls::TLSError::WebPKIError)?; + + match status { + ExtensionStatus::Good => {} + ExtensionStatus::Error | ExtensionStatus::NotFound => { + return Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer)) + } + } + let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) + .map_err(rustls::TLSError::WebPKIError)?; + Ok((time, parsed_cert, trust_anchor)) } fn verify_libp2p_extension( @@ -179,14 +183,15 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien presented_certs: &[rustls::Certificate], _dns_name: Option<&webpki::DNSName>, ) -> Result { - verify_presented_certs(presented_certs, &|time, end_entity_cert, trust_anchor| { - end_entity_cert.verify_is_valid_tls_client_cert( + let (time, end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; + end_entity_cert + .verify_is_valid_tls_client_cert( ALL_SUPPORTED_SIGNATURE_ALGORITHMS, &webpki::TLSClientTrustAnchors(&[trust_anchor]), &[], time, ) - }) - .map(|()| rustls::ClientCertVerified::assertion()) + .map(|()| rustls::ClientCertVerified::assertion()) + .map_err(rustls::TLSError::WebPKIError) } } diff --git a/webpki b/webpki index bbdf4cbe5d3..c2362773fec 160000 --- a/webpki +++ b/webpki @@ -1 +1 @@ -Subproject commit bbdf4cbe5d38c9a9d12dde2771fea428556df5c9 +Subproject commit c2362773fec9187f735cb9939df4a6daf9aed627 From 103bd7431a326c8ad507d10b09f9cb7f7e7a2635 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 28 Feb 2020 12:57:43 -0500 Subject: [PATCH 122/202] Delete unneeded code and refactor This cleans up the code and makes it easier to understand. --- transports/quic/src/certificate.rs | 3 +- transports/quic/src/connection.rs | 65 +++++++++--------------------- transports/quic/src/endpoint.rs | 40 +++++++++--------- transports/quic/src/verifier.rs | 5 ++- transports/quic/tests/tests.rs | 3 +- 5 files changed, 46 insertions(+), 70 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 0e25fd45870..344f02831a7 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -28,6 +28,7 @@ use libp2p_core::identity; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; +pub(super) const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; @@ -96,7 +97,7 @@ pub(crate) fn extract_libp2p_peerid(certificate: &[u8]) -> libp2p_core::PeerId { &mut |oid: untrusted::Input<'_>, value: untrusted::Input<'_>, _critical, _spki| match oid .as_slice_less_safe() { - [43, 6, 1, 4, 1, 131, 162, 90, 1, 1] => { + LIBP2P_OID_BYTES => { id = Some( extract_libp2p_extension(value).expect("we already validated the certificate"), ); diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 3323464f6f6..a850263a381 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::{certificate, endpoint::ConnectionEndpoint as Endpoint, error::Error}; +use super::{endpoint::ConnectionEndpoint as Endpoint, error::Error, socket}; use async_macros::ready; use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; @@ -73,14 +73,6 @@ impl Substream { let status = SubstreamStatus::Live; Self { id, status } } - - /// Test if data can be sent on a stream. - fn is_live(&self) -> bool { - match self.status { - SubstreamStatus::Live | SubstreamStatus::Unwritten => true, - SubstreamStatus::Finishing(_) | SubstreamStatus::Finished => false, - } - } } /// A QUIC connection, either client or server. @@ -176,12 +168,7 @@ impl StreamMuxer for QuicMuxer { inner.wake_driver(); match inner.connection.accept() { None => { - if let Some(waker) = replace( - &mut inner.handshake_or_accept_waker, - Some(cx.waker().clone()), - ) { - waker.wake() - } + inner.handshake_or_accept_waker = Some(cx.waker().clone()); Poll::Pending } Some(id) => { @@ -198,13 +185,6 @@ impl StreamMuxer for QuicMuxer { buf: &[u8], ) -> Poll> { use quinn_proto::WriteError; - if !substream.is_live() { - error!( - "The application used stream {:?} after it was no longer live", - substream.id - ); - return Poll::Ready(Err(Error::ExpiredStream)); - } let mut inner = self.inner(); inner.wake_driver(); if let Some(ref e) = inner.close_reason { @@ -361,12 +341,6 @@ impl StreamMuxer for QuicMuxer { if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { waker.wake(); } - // To avoid reference count underflow, we must only decrement the - // refcount the *first* time this is called. - if !inner.shutting_down { - inner.outbound_streams -= 1; - inner.shutting_down = true; - } if inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.outbound_streams == 0 { @@ -408,17 +382,14 @@ impl Future for Upgrade { trace!("outbound polling!"); let res = { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - let peer_certificates = if let Some(close_reason) = &inner.close_reason { + if let Some(close_reason) = &inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); } else if inner.connection.is_handshaking() { inner.handshake_or_accept_waker = Some(cx.waker().clone()); return Poll::Pending; } else { ready!(inner.connection.notify_handshake_complete(cx))? - }; - // we have already verified that there is (exactly) one peer certificate, - // and that it is valid. - certificate::extract_libp2p_peerid(peer_certificates[0].as_ref()) + } }; Poll::Ready(Ok(( res, @@ -445,15 +416,11 @@ pub(crate) struct Muxer { connectors: StreamSenderQueue, /// The number of outbound streams currently open. outbound_streams: usize, - /// `true` if and only if we are currently shutting down. - shutting_down: bool, /// The close reason, if this connection has been lost close_reason: Option, /// Waker to wake up the driver. This is not a `MaybeWaker` because only the /// driver task ever puts a waker here. waker: Option, - /// A handle to wait on the driver to exit. - driver: Option>>, /// Close waker close_waker: Option, } @@ -489,7 +456,7 @@ impl Muxer { fn new(connection: Endpoint) -> Self { Muxer { close_waker: None, - outbound_streams: 1, + outbound_streams: 0, pending_stream: None, streams: Default::default(), handshake_or_accept_waker: None, @@ -497,8 +464,6 @@ impl Muxer { connection, close_reason: None, waker: None, - shutting_down: false, - driver: None, } } fn shutdown(&mut self, error_code: u32) { @@ -538,7 +503,7 @@ impl Muxer { } Event::StreamAvailable { dir: Dir::Uni } => { // Ditto - panic!("we don’t use unidirectional streams") + error!("we don’t use unidirectional streams") } Event::StreamReadable { stream } => { trace!( @@ -634,6 +599,7 @@ impl Muxer { } } +/// The connection driver #[derive(Debug)] pub(super) struct ConnectionDriver { inner: Arc>, @@ -643,19 +609,24 @@ pub(super) struct ConnectionDriver { timer: Option, /// The last timeout returned by `quinn_proto::poll_timeout`. last_timeout: Option, + /// Transmit socket + socket: Arc, } impl ConnectionDriver { - pub(crate) fn spawn>)>(connection: Endpoint, cb: T) -> Upgrade { + pub(crate) fn spawn>) -> Arc>( + connection: Endpoint, + cb: T, + ) -> Upgrade { let inner = Arc::new(Mutex::new(Muxer::new(connection))); - cb(inner.clone()); - let handle = async_std::task::spawn(Self { + let socket = cb(inner.clone()); + async_std::task::spawn(Self { inner: inner.clone(), outgoing_packet: None, timer: None, last_timeout: None, + socket, }); - inner.lock().driver = Some(handle); Upgrade { muxer: Some(QuicMuxer(inner)), } @@ -671,7 +642,9 @@ impl Future for ConnectionDriver { inner.waker = Some(cx.waker().clone()); loop { let now = Instant::now(); - inner.connection.poll_transmit_pending(now, cx)?; + inner + .connection + .poll_transmit_pending(now, cx, &this.socket)?; inner.process_app_events(); inner.connection.send_endpoint_events(cx)?; match inner.connection.poll_timeout() { diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 9396349075e..b139af7cb4b 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -277,19 +277,23 @@ impl ConnectionEndpoint { pub(crate) fn notify_handshake_complete( &mut self, cx: &mut Context<'_>, - ) -> Poll, mpsc::SendError>> { + ) -> Poll> { assert!(!self.is_handshaking()); - let certificate = self - .connection - .crypto_session() - .get_peer_certificates() - .expect("we always require the peer to present a certificate; qed"); if self.connection.side().is_server() { ready!(self.channel.poll_ready(cx))?; self.channel .start_send(EndpointMessage::ConnectionAccepted)? } - Poll::Ready(Ok(certificate)) + let certificate = self + .connection + .crypto_session() + .get_peer_certificates() + .expect("we always require the peer to present a certificate; qed"); + // we have already verified that there is (exactly) one peer certificate, + // and that it is valid. + Poll::Ready(Ok(crate::certificate::extract_libp2p_peerid( + certificate[0].as_ref(), + ))) } /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. @@ -333,11 +337,12 @@ impl ConnectionEndpoint { &mut self, now: Instant, cx: &mut Context<'_>, + socket: &socket::Socket, ) -> Result<(), Error> { let connection = &mut self.connection; match self .pending - .send_packet(cx, &self.socket, &mut || connection.poll_transmit(now)) + .send_packet(cx, socket, &mut || connection.poll_transmit(now)) { Poll::Ready(Ok(())) | Poll::Pending => Ok(()), Poll::Ready(Err(e)) => Err(Error::IO(e)), @@ -473,7 +478,6 @@ pub(crate) struct ConnectionEndpoint { connection: Connection, handle: ConnectionHandle, channel: Channel, - socket: Arc, } /// A handle that can be used to wait for the endpoint driver to finish. @@ -568,13 +572,6 @@ impl Endpoint { } } -fn create_muxer(endpoint: ConnectionEndpoint, inner: &mut EndpointInner) -> Upgrade { - let handle = endpoint.handle; - super::connection::ConnectionDriver::spawn(endpoint, |weak| { - inner.muxers.insert(handle, weak); - }) -} - fn accept_muxer( endpoint: &Arc, connection: Connection, @@ -586,10 +583,15 @@ fn accept_muxer( pending: socket::Pending::default(), connection, handle, - socket: endpoint.socket.clone(), channel, }; - let upgrade = create_muxer(connection_endpoint, &mut *inner); + + let socket = endpoint.socket.clone(); + + let upgrade = super::connection::ConnectionDriver::spawn(connection_endpoint, |weak| { + inner.muxers.insert(handle, weak); + socket + }); if endpoint .new_connections .unbounded_send(ListenerEvent::Upgrade { @@ -728,13 +730,13 @@ impl Transport for Endpoint { pending: socket::Pending::default(), connection, handle, - socket, channel: endpoint.channel, }; Ok(super::connection::ConnectionDriver::spawn( endpoint, |weak| { inner.muxers.insert(handle, weak); + socket }, )) } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 6b07acd977b..67076783cff 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -20,6 +20,7 @@ use libp2p_core::identity; +use super::certificate::LIBP2P_OID_BYTES; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); @@ -110,7 +111,7 @@ fn verify_presented_certs( oid.as_slice_less_safe(), status, ) { - ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], ExtensionStatus::NotFound) => { + (LIBP2P_OID_BYTES, ExtensionStatus::NotFound) => { status = if verify_libp2p_extension(value, spki).is_ok() { ExtensionStatus::Good } else { @@ -118,7 +119,7 @@ fn verify_presented_certs( }; webpki::Understood::Yes } - ([43, 6, 1, 4, 1, 131, 162, 90, 1, 1], _) => { + (LIBP2P_OID_BYTES, _) => { status = ExtensionStatus::Error; webpki::Understood::Yes } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 39bc9364064..c05236a25c5 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -170,7 +170,7 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { init(); - for _ in 0..100u32 { + for _ in 0..1000u32 { do_test() } } @@ -185,7 +185,6 @@ fn do_test() { let quic_config = Config::new(&keypair2); let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).unwrap(); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); - trace!("running tests"); let handle = async_std::task::spawn(async move { let key = loop { From a9197a071f4c73479c41b92891c29d8182591314 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 28 Feb 2020 14:58:03 -0500 Subject: [PATCH 123/202] Simplify code even more This does additional refactoring to make the code smaller and easier to understand. --- transports/quic/src/certificate.rs | 88 ++++++++++----------------- transports/quic/src/connection.rs | 56 ++++++++++-------- transports/quic/src/endpoint.rs | 6 +- transports/quic/src/verifier.rs | 95 ++++++++++++------------------ 4 files changed, 104 insertions(+), 141 deletions(-) diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/certificate.rs index 344f02831a7..7bde8841825 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/certificate.rs @@ -29,8 +29,8 @@ use libp2p_core::identity; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; pub(super) const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; -const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); +pub(super) const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +pub(super) const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; @@ -83,68 +83,42 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { .expect("certificate generation with valid params will succeed; qed") } -/// Extract the peer’s public key from a libp2p certificate. It is erroneous to -/// call this unless the certificate is known to be a valid libp2p certificate. -/// The certificate verifiers in this crate validate that a certificate is in -/// fact a valid libp2p certificate. +/// Extracts the `PeerId` from a certificate’s libp2p extension. It is erroneous +/// to call this unless the certificate is known to be a well-formed X.509 +/// certificate with a valid libp2p extension. The certificate verifiers in this +/// crate validate check this. /// /// # Panics /// -/// Panics if the key could not be extracted. -pub(crate) fn extract_libp2p_peerid(certificate: &[u8]) -> libp2p_core::PeerId { +/// Panics if there is no libp2p extension in the certificate, or if the +/// certificate is ill-formed. +pub(crate) fn unwrap_libp2p_certificate(certificate: &[u8]) -> libp2p_core::PeerId { let mut id = None; - let cb = - &mut |oid: untrusted::Input<'_>, value: untrusted::Input<'_>, _critical, _spki| match oid - .as_slice_less_safe() - { - LIBP2P_OID_BYTES => { - id = Some( - extract_libp2p_extension(value).expect("we already validated the certificate"), - ); - webpki::Understood::Yes - } - _ => webpki::Understood::No, - }; + let cb = &mut |oid: untrusted::Input<'_>, value, _, _| match oid.as_slice_less_safe() { + LIBP2P_OID_BYTES => { + id = Some(extract_libp2p_peerid(value)); + webpki::Understood::Yes + } + _ => webpki::Understood::No, + }; webpki::EndEntityCert::from_with_extension_cb(certificate, cb) - .expect("we already validated the certificate"); - id.expect("we already checked that a PeerId exists").into() + .expect("we already validated the certificate is well-formed"); + id.expect("we already checked that a libp2p extension exists") } -fn extract_libp2p_extension( - extension: untrusted::Input<'_>, -) -> Result { +fn extract_libp2p_peerid(extension: untrusted::Input<'_>) -> libp2p_core::PeerId { use ring::{error::Unspecified, io::der}; - extension.read_all(Unspecified, |mut reader| { - let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - inner.read_all(Unspecified, |mut reader| { - let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - der::bit_string_with_no_unused_bits(&mut reader)?; - identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified) + extension + .read_all(Unspecified, |mut reader| { + let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + inner.read_all(Unspecified, |mut reader| { + let public_key = + der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + der::bit_string_with_no_unused_bits(&mut reader)?; + Ok(identity::PublicKey::from_protobuf_encoding(public_key) + .expect("already checked") + .into()) + }) }) - }) -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn can_make_a_certificate() { - drop(env_logger::try_init()); - let keypair = identity::Keypair::generate_ed25519(); - assert_eq!( - extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); - log::trace!("trying secp256k1!"); - let keypair = identity::Keypair::generate_secp256k1(); - log::trace!("have a key!"); - let public = keypair.public(); - log::trace!("have a public key!"); - assert_eq!(public, public, "key is not equal to itself?"); - log::debug!("have a valid key!"); - assert_eq!( - extract_libp2p_peerid(&make_cert(&keypair).serialize_der().unwrap()), - libp2p_core::PeerId::from_public_key(keypair.public()) - ); - } + .expect("we already checked this in the certificate verifier") } diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index a850263a381..2669f6f5f7b 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -338,9 +338,8 @@ impl StreamMuxer for QuicMuxer { inner.outbound_streams, inner.connection.side() ); - if let Some(waker) = replace(&mut inner.close_waker, Some(cx.waker().clone())) { - waker.wake(); - } + inner.wake_closer(); + inner.close_waker = Some(cx.waker().clone()); if inner.close_reason.is_some() { return Poll::Ready(Ok(())); } else if inner.outbound_streams == 0 { @@ -380,21 +379,30 @@ impl Future for Upgrade { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); - let res = { - let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if let Some(close_reason) = &inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); - } else if inner.connection.is_handshaking() { - inner.handshake_or_accept_waker = Some(cx.waker().clone()); - return Poll::Pending; - } else { - ready!(inner.connection.notify_handshake_complete(cx))? + let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); + if let Some(close_reason) = &inner.close_reason { + return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); + } else if inner.connection.is_handshaking() { + inner.handshake_or_accept_waker = Some(cx.waker().clone()); + return Poll::Pending; + } else { + match ready!(inner.connection.notify_handshake_complete(cx)) { + Ok(res) => { + drop(inner); + Poll::Ready(Ok(( + res, + muxer.take().expect("polled after yielding Ready"), + ))) + } + Err(e) => { + inner.shutdown(RESET); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + drop(inner); + *muxer = None; + Poll::Ready(Err(e)) + } } - }; - Poll::Ready(Ok(( - res, - muxer.take().expect("polled after yielding Ready"), - ))) + } } } @@ -443,6 +451,12 @@ impl Muxer { } } + fn wake_closer(&mut self) { + if let Some(close_waker) = self.close_waker.take() { + close_waker.wake() + } + } + pub(crate) fn connection(&mut self) -> &mut Endpoint { &mut self.connection } @@ -560,9 +574,7 @@ impl Muxer { self.connection.side() ); self.close_reason = Some(reason); - if let Some(e) = self.close_waker.take() { - e.wake() - } + self.wake_closer(); self.shutdown(0); } Event::StreamFinished { @@ -582,9 +594,7 @@ impl Muxer { // dead. self.streams.wake_writer(stream); // Connection close could be blocked on this. - if let Some(waker) = self.close_waker.take() { - waker.wake() - } + self.wake_closer() } Event::Connected => { debug!("connected for side {:?}!", self.connection.side()); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index b139af7cb4b..d337a2cba07 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -277,7 +277,7 @@ impl ConnectionEndpoint { pub(crate) fn notify_handshake_complete( &mut self, cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { assert!(!self.is_handshaking()); if self.connection.side().is_server() { ready!(self.channel.poll_ready(cx))?; @@ -290,8 +290,8 @@ impl ConnectionEndpoint { .get_peer_certificates() .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, - // and that it is valid. - Poll::Ready(Ok(crate::certificate::extract_libp2p_peerid( + // and that it has a valid libp2p extension. + Poll::Ready(Ok(crate::certificate::unwrap_libp2p_certificate( certificate[0].as_ref(), ))) } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index 67076783cff..f289c69cb05 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -18,11 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::identity; - use super::certificate::LIBP2P_OID_BYTES; -const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ @@ -83,11 +79,33 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe } } -#[derive(Copy, Clone)] -enum ExtensionStatus { - NotFound, - Good, - Error, +fn verify_libp2p_extension( + extension: untrusted::Input<'_>, + subject_public_key_info: untrusted::Input<'_>, +) -> Result<(), ring::error::Unspecified> { + use crate::certificate::{LIBP2P_SIGNING_PREFIX, LIBP2P_SIGNING_PREFIX_LENGTH}; + use ring::{error::Unspecified, io::der}; + let spki = subject_public_key_info.read_all(Unspecified, |mut reader| { + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + Ok(der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe()) + })?; + extension.read_all(Unspecified, |mut reader| { + let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + inner.read_all(Unspecified, |mut reader| { + let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + let signature = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); + let public_key = libp2p_core::identity::PublicKey::from_protobuf_encoding(public_key) + .map_err(|_| Unspecified)?; + let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); + v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + v.extend_from_slice(spki); + if public_key.verify(&v, signature) { + Ok(()) + } else { + Err(Unspecified) + } + }) + }) } fn verify_presented_certs( @@ -106,67 +124,28 @@ fn verify_presented_certs( let time = webpki::Time::try_from(std::time::SystemTime::now()) .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; let raw_certificate = presented_certs[0].as_ref(); - let mut status = ExtensionStatus::NotFound; - let cb = &mut |oid: untrusted::Input<'_>, value, _critical, spki| match ( - oid.as_slice_less_safe(), - status, - ) { - (LIBP2P_OID_BYTES, ExtensionStatus::NotFound) => { - status = if verify_libp2p_extension(value, spki).is_ok() { - ExtensionStatus::Good - } else { - ExtensionStatus::Error - }; - webpki::Understood::Yes - } - (LIBP2P_OID_BYTES, _) => { - status = ExtensionStatus::Error; + let mut num_libp2p_extensions = 0usize; + let cb = &mut |oid: untrusted::Input<'_>, value, _, spki| match oid.as_slice_less_safe() { + LIBP2P_OID_BYTES => { + num_libp2p_extensions += 1; + if verify_libp2p_extension(value, spki).is_err() { + num_libp2p_extensions = 2; // this will force an error + } webpki::Understood::Yes } _ => webpki::Understood::No, }; let parsed_cert = webpki::EndEntityCert::from_with_extension_cb(raw_certificate, cb) .map_err(rustls::TLSError::WebPKIError)?; - - match status { - ExtensionStatus::Good => {} - ExtensionStatus::Error | ExtensionStatus::NotFound => { - return Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer)) - } + if num_libp2p_extensions != 1 { + // this also includes the case where an extension was not valid + return Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer)); } let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) .map_err(rustls::TLSError::WebPKIError)?; Ok((time, parsed_cert, trust_anchor)) } -fn verify_libp2p_extension( - extension: untrusted::Input<'_>, - subject_public_key_info: untrusted::Input<'_>, -) -> Result { - use ring::{error::Unspecified, io::der}; - let spki = subject_public_key_info.read_all(Unspecified, |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - Ok(der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe()) - })?; - extension.read_all(Unspecified, |mut reader| { - let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - inner.read_all(Unspecified, |mut reader| { - let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - let signature = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - let public_key = - identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified)?; - let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); - v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); - v.extend_from_slice(spki); - if public_key.verify(&v, signature) { - Ok(public_key) - } else { - Err(Unspecified) - } - }) - }) -} - impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { fn offer_client_auth(&self) -> bool { true From 212f4f9194d039ed18e6a42a5e132c52c2131d06 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 1 Mar 2020 13:01:42 -0500 Subject: [PATCH 124/202] Clean up certificate verification --- transports/quic/src/verifier.rs | 47 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/verifier.rs index f289c69cb05..a8c05f7bd70 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/verifier.rs @@ -19,7 +19,8 @@ // DEALINGS IN THE SOFTWARE. use super::certificate::LIBP2P_OID_BYTES; - +pub(super) const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, @@ -66,13 +67,13 @@ impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServe _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { - let (time, end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; + let (end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; end_entity_cert .verify_is_valid_tls_server_cert( ALL_SUPPORTED_SIGNATURE_ALGORITHMS, &webpki::TLSServerTrustAnchors(&[trust_anchor]), &[], - time, + get_time()?, ) .map(|()| rustls::ServerCertVerified::assertion()) .map_err(rustls::TLSError::WebPKIError) @@ -83,7 +84,6 @@ fn verify_libp2p_extension( extension: untrusted::Input<'_>, subject_public_key_info: untrusted::Input<'_>, ) -> Result<(), ring::error::Unspecified> { - use crate::certificate::{LIBP2P_SIGNING_PREFIX, LIBP2P_SIGNING_PREFIX_LENGTH}; use ring::{error::Unspecified, io::der}; let spki = subject_public_key_info.read_all(Unspecified, |mut reader| { der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; @@ -108,22 +108,9 @@ fn verify_libp2p_extension( }) } -fn verify_presented_certs( - presented_certs: &[rustls::Certificate], -) -> Result< - ( - webpki::Time, - webpki::EndEntityCert<'_>, - webpki::TrustAnchor<'_>, - ), - rustls::TLSError, -> { - if presented_certs.len() != 1 { - return Err(rustls::TLSError::NoCertificatesPresented); - } - let time = webpki::Time::try_from(std::time::SystemTime::now()) - .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime)?; - let raw_certificate = presented_certs[0].as_ref(); +pub(super) fn verify_single_cert( + raw_certificate: &[u8], +) -> Result<(webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>), rustls::TLSError> { let mut num_libp2p_extensions = 0usize; let cb = &mut |oid: untrusted::Input<'_>, value, _, spki| match oid.as_slice_less_safe() { LIBP2P_OID_BYTES => { @@ -143,7 +130,21 @@ fn verify_presented_certs( } let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) .map_err(rustls::TLSError::WebPKIError)?; - Ok((time, parsed_cert, trust_anchor)) + Ok((parsed_cert, trust_anchor)) +} + +fn get_time() -> Result { + webpki::Time::try_from(std::time::SystemTime::now()) + .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime) +} + +fn verify_presented_certs( + presented_certs: &[rustls::Certificate], +) -> Result<(webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>), rustls::TLSError> { + if presented_certs.len() != 1 { + return Err(rustls::TLSError::NoCertificatesPresented); + } + verify_single_cert(presented_certs[0].as_ref()) } impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { @@ -163,13 +164,13 @@ impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClien presented_certs: &[rustls::Certificate], _dns_name: Option<&webpki::DNSName>, ) -> Result { - let (time, end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; + let (end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; end_entity_cert .verify_is_valid_tls_client_cert( ALL_SUPPORTED_SIGNATURE_ALGORITHMS, &webpki::TLSClientTrustAnchors(&[trust_anchor]), &[], - time, + get_time()?, ) .map(|()| rustls::ClientCertVerified::assertion()) .map_err(rustls::TLSError::WebPKIError) From 579ce9e60a17ae27a09107ec26f2a26cf6643528 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 1 Mar 2020 18:12:31 -0500 Subject: [PATCH 125/202] Refactor TLS and connection code This prepares for the TLS code being moved into a separate crate. --- transports/quic/src/connection.rs | 72 ++++------ transports/quic/src/endpoint.rs | 141 +++++++++---------- transports/quic/src/error.rs | 7 +- transports/quic/src/lib.rs | 3 +- transports/quic/src/tls.rs | 81 +++++++++++ transports/quic/src/{ => tls}/certificate.rs | 55 +++++--- transports/quic/src/{ => tls}/verifier.rs | 9 +- 7 files changed, 215 insertions(+), 153 deletions(-) create mode 100644 transports/quic/src/tls.rs rename transports/quic/src/{ => tls}/certificate.rs (76%) rename transports/quic/src/{ => tls}/verifier.rs (95%) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 2669f6f5f7b..d1bab9227bf 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -168,7 +168,7 @@ impl StreamMuxer for QuicMuxer { inner.wake_driver(); match inner.connection.accept() { None => { - inner.handshake_or_accept_waker = Some(cx.waker().clone()); + inner.connection.set_waker(cx); Poll::Pending } Some(id) => { @@ -187,9 +187,6 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::WriteError; let mut inner = self.inner(); inner.wake_driver(); - if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(e.clone()))); - } match inner.connection.write(*substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(WriteError::Blocked) => { @@ -212,6 +209,7 @@ impl StreamMuxer for QuicMuxer { } Err(WriteError::Stopped(e)) => { substream.status = SubstreamStatus::Finished; + inner.outbound_streams -= 1; Poll::Ready(Err(Error::Stopped(e))) } } @@ -296,13 +294,12 @@ impl StreamMuxer for QuicMuxer { } let mut inner = self.inner(); inner.wake_driver(); - inner - .connection - .finish(*substream.id) - .map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, - quinn_proto::FinishError::Stopped(e) => Error::Stopped(e), - })?; + let Muxer { + connection, + outbound_streams, + .. + } = &mut *inner; + connection.finish(*substream.id, outbound_streams)?; let (sender, mut receiver) = oneshot::channel(); assert!( receiver.poll_unpin(cx).is_pending(), @@ -350,9 +347,10 @@ impl StreamMuxer for QuicMuxer { let Muxer { streams, connection, + outbound_streams, .. } = &mut *inner; - streams.close(|id| drop(connection.finish(id))); + streams.close(|id| drop(connection.finish(id, outbound_streams))); Poll::Pending } } @@ -382,25 +380,21 @@ impl Future for Upgrade { let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); if let Some(close_reason) = &inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); - } else if inner.connection.is_handshaking() { - inner.handshake_or_accept_waker = Some(cx.waker().clone()); - return Poll::Pending; - } else { - match ready!(inner.connection.notify_handshake_complete(cx)) { - Ok(res) => { - drop(inner); - Poll::Ready(Ok(( - res, - muxer.take().expect("polled after yielding Ready"), - ))) - } - Err(e) => { - inner.shutdown(RESET); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - drop(inner); - *muxer = None; - Poll::Ready(Err(e)) - } + } + match ready!(inner.connection.notify_handshake_complete(cx)) { + Ok(res) => { + drop(inner); + Poll::Ready(Ok(( + res, + muxer.take().expect("polled after yielding Ready"), + ))) + } + Err(e) => { + inner.shutdown(RESET); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + drop(inner); + *muxer = None; + Poll::Ready(Err(e)) } } } @@ -418,8 +412,6 @@ pub(crate) struct Muxer { connection: Endpoint, /// The stream statuses streams: stream_map::Streams, - /// Task waiting for new connections, or for the connection to finish handshaking. - handshake_or_accept_waker: Option, /// Tasks waiting to make a connection. connectors: StreamSenderQueue, /// The number of outbound streams currently open. @@ -461,28 +453,22 @@ impl Muxer { &mut self.connection } - fn wake_incoming(&mut self) { - if let Some(waker) = self.handshake_or_accept_waker.take() { - waker.wake() - } - } - fn new(connection: Endpoint) -> Self { Muxer { close_waker: None, outbound_streams: 0, pending_stream: None, streams: Default::default(), - handshake_or_accept_waker: None, connectors: Default::default(), connection, close_reason: None, waker: None, } } + fn shutdown(&mut self, error_code: u32) { debug!("shutting connection down!"); - self.wake_incoming(); + self.connection.wake(); self.streams.wake_all(); self.connectors.clear(); self.connection.close(error_code); @@ -598,11 +584,11 @@ impl Muxer { } Event::Connected => { debug!("connected for side {:?}!", self.connection.side()); - self.wake_incoming(); + self.connection.wake(); } Event::StreamOpened { dir: Dir::Bi } => { debug!("stream opened for side {:?}", self.connection.side()); - self.wake_incoming() + self.connection.wake() } } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index d337a2cba07..b735b4a73b3 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{error::Error, socket, verifier, Upgrade}; +use crate::{error::Error, socket, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -51,64 +51,24 @@ pub struct Config { endpoint_config: Arc, } -fn make_client_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ClientConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - transport.keep_alive_interval(Some(Duration::from_millis(1000))); - let mut crypto = rustls::ClientConfig::new(); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto.enable_early_data = true; - crypto - .set_single_client_cert(vec![certificate], key) - .expect("we have a valid certificate; qed"); - let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; - crypto - .dangerous() - .set_certificate_verifier(Arc::new(verifier)); - quinn_proto::ClientConfig { - transport: Arc::new(transport), - crypto: Arc::new(crypto), - } -} - -fn make_server_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, -) -> quinn_proto::ServerConfig { - let mut transport = quinn_proto::TransportConfig::default(); - transport.stream_window_uni(0); - transport.datagram_receive_buffer_size(None); - let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, - )); - crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto - .set_single_cert(vec![certificate], key) - .expect("we are given a valid cert; qed"); - let mut config = quinn_proto::ServerConfig::default(); - config.transport = Arc::new(transport); - config.crypto = Arc::new(crypto); - config -} - impl Config { - /// Creates a new configuration object for TCP/IP. + /// Creates a new configuration object for QUIC. pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { - let cert = super::certificate::make_cert(&keypair); - let (cert, key) = ( - rustls::Certificate( - cert.serialize_der() - .expect("serialization of a valid cert will succeed; qed"), - ), - rustls::PrivateKey(cert.serialize_private_key_der()), - ); + let mut transport = quinn_proto::TransportConfig::default(); + transport.stream_window_uni(0); + transport.datagram_receive_buffer_size(None); + transport.keep_alive_interval(Some(Duration::from_millis(1000))); + let transport = Arc::new(transport); + let (client_tls_config, server_tls_config) = super::tls::make_tls_config(keypair); + let mut server_config = quinn_proto::ServerConfig::default(); + server_config.transport = transport.clone(); + server_config.crypto = Arc::new(server_tls_config); + let mut client_config = quinn_proto::ClientConfig::default(); + client_config.transport = transport; + client_config.crypto = Arc::new(client_tls_config); Self { - client_config: make_client_config(cert.clone(), key.clone()), - server_config: Arc::new(make_server_config(cert, key)), + client_config, + server_config: Arc::new(server_config), endpoint_config: Default::default(), } } @@ -262,23 +222,31 @@ pub(super) struct EndpointData { type Sender = mpsc::Sender; -impl ConnectionEndpoint { - #[inline] - pub(crate) fn is_handshaking(&mut self) -> bool { - self.connection.is_handshaking() - } +#[derive(Debug)] +pub(crate) struct ConnectionEndpoint { + pending: socket::Pending, + connection: Connection, + handle: ConnectionHandle, + channel: Channel, + waker: Option, +} +impl ConnectionEndpoint { /// Notify the endpoint that the handshake has completed, /// and get the certificates from it. /// - /// # Panics + /// If this returns [`Poll::Pending`], the current task can be worken up by + /// a call to [`ConnectionEndpoint::wake`]. /// - /// Panics if the connection is still handshaking. + /// Calling this after it has returned `Ready` is erroneous. pub(crate) fn notify_handshake_complete( &mut self, cx: &mut Context<'_>, ) -> Poll> { - assert!(!self.is_handshaking()); + if self.connection.is_handshaking() { + self.set_waker(cx); + return Poll::Pending; + } if self.connection.side().is_server() { ready!(self.channel.poll_ready(cx))?; self.channel @@ -291,9 +259,23 @@ impl ConnectionEndpoint { .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, // and that it has a valid libp2p extension. - Poll::Ready(Ok(crate::certificate::unwrap_libp2p_certificate( - certificate[0].as_ref(), - ))) + Poll::Ready(Ok(crate::tls::extract_peerid(certificate[0].as_ref())?)) + } + + /// Wake up the last task registered by + /// [`ConnectionEndpoint::notify_handshake_complete`] or + /// [`ConnectionEndpoint::set_waker`]. + pub(crate) fn wake(&mut self) { + if let Some(waker) = self.waker.take() { + waker.wake() + } + } + + /// Register the current task to be woken up when + /// [`ConnectionEndpoint::wake`] is called, overwriting any previous + /// registration. + pub(crate) fn set_waker(&mut self, cx: &mut Context<'_>) { + self.waker = Some(cx.waker().clone()) } /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. @@ -373,12 +355,21 @@ impl ConnectionEndpoint { self.connection.write(id, buffer) } - /// Shut down the writing side of a stream. + /// Shut down the writing side of a stream. If we will not get an + /// `StreamFinished` event, decrement `outbound_streams`. pub(crate) fn finish( &mut self, id: quinn_proto::StreamId, - ) -> Result<(), quinn_proto::FinishError> { - self.connection.finish(id) + outbound_streams: &mut usize, + ) -> Result<(), Error> { + self.connection.finish(id).map_err(|e| match e { + quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, + quinn_proto::FinishError::Stopped(e) => { + // we will never get a `StreamFinished` event + *outbound_streams -= 1; + Error::Stopped(e) + } + }) } /// Get application-facing events @@ -472,14 +463,6 @@ pub(crate) use channel_ref::EndpointRef; #[derive(Debug, Clone)] pub struct Endpoint(EndpointRef); -#[derive(Debug)] -pub(crate) struct ConnectionEndpoint { - pending: socket::Pending, - connection: Connection, - handle: ConnectionHandle, - channel: Channel, -} - /// A handle that can be used to wait for the endpoint driver to finish. pub type JoinHandle = async_std::task::JoinHandle>; @@ -584,6 +567,7 @@ fn accept_muxer( connection, handle, channel, + waker: None, }; let socket = endpoint.socket.clone(); @@ -731,6 +715,7 @@ impl Transport for Endpoint { connection, handle, channel: endpoint.channel, + waker: None, }; Ok(super::connection::ConnectionDriver::spawn( endpoint, diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index bdde9e9fb0e..7825b14e8da 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -21,7 +21,6 @@ use err_derive::Error; use futures::channel::mpsc::SendError; use io::ErrorKind; -use ring::error::Unspecified; use std::io; /// An error that can be returned by libp2p-quic. @@ -31,8 +30,8 @@ pub enum Error { #[error(display = "Fatal I/O error {}", _0)] IO(#[error(source)] std::io::Error), /// Peer sent a malformed certificate - #[error(display = "Peer sent a malformed certificate")] - BadCertificate(#[error(source)] Unspecified), + #[error(display = "Peer sent a malformed certificate without it being detected ― this is a bug")] + BadCertificate(#[error(source)] webpki::Error), /// QUIC protocol error #[error(display = "QUIC protocol error: {}", _0)] ConnectionError(#[error(source)] quinn_proto::ConnectionError), @@ -77,7 +76,7 @@ impl From for io::Error { fn from(e: Error) -> Self { match e { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), - e @ Error::BadCertificate(Unspecified) => io::Error::new(ErrorKind::InvalidData, e), + e @ Error::BadCertificate(_) => io::Error::new(ErrorKind::InvalidData, e), Error::ConnectionError(e) => e.into(), e @ Error::CannotConnect(_) | e @ Error::NetworkFailure diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1f5419cdf83..7745832e284 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -102,12 +102,11 @@ )] #![forbid(unsafe_code)] -mod certificate; +mod tls; mod connection; mod endpoint; mod error; mod socket; -mod verifier; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; pub use endpoint::{Config, Endpoint, JoinHandle, Listener}; pub use error::Error; diff --git a/transports/quic/src/tls.rs b/transports/quic/src/tls.rs new file mode 100644 index 00000000000..de123a65880 --- /dev/null +++ b/transports/quic/src/tls.rs @@ -0,0 +1,81 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! TLS configuration for `libp2p-quic`. + +mod certificate; +mod verifier; + +pub(crate) use certificate::extract_peerid; +use std::sync::Arc; + +const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; +const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); +const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; + +fn make_client_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> rustls::ClientConfig { + let mut crypto = rustls::ClientConfig::new(); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto.enable_early_data = true; + crypto + .set_single_client_cert(vec![certificate], key) + .expect("we have a valid certificate; qed"); + let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; + crypto + .dangerous() + .set_certificate_verifier(Arc::new(verifier)); + crypto +} + +fn make_server_config( + certificate: rustls::Certificate, + key: rustls::PrivateKey, +) -> rustls::ServerConfig { + let mut crypto = rustls::ServerConfig::new(Arc::new( + verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, + )); + crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto + .set_single_cert(vec![certificate], key) + .expect("we have a valid certificate; qed"); + crypto +} + +/// Create TLS client and server configurations for libp2p. +pub(crate) fn make_tls_config( + keypair: &libp2p_core::identity::Keypair, +) -> (rustls::ClientConfig, rustls::ServerConfig) { + let cert = certificate::make_cert(&keypair); + let private_key = cert.serialize_private_key_der(); + let (cert, key) = ( + rustls::Certificate( + cert.serialize_der() + .expect("serialization of a valid cert will succeed; qed"), + ), + rustls::PrivateKey(private_key), + ); + ( + make_client_config(cert.clone(), key.clone()), + make_server_config(cert, key), + ) +} diff --git a/transports/quic/src/certificate.rs b/transports/quic/src/tls/certificate.rs similarity index 76% rename from transports/quic/src/certificate.rs rename to transports/quic/src/tls/certificate.rs index 7bde8841825..d2cd8a4ce1e 100644 --- a/transports/quic/src/certificate.rs +++ b/transports/quic/src/tls/certificate.rs @@ -26,11 +26,11 @@ //! at `trace` level, while “expected” error conditions (ones that can result during correct use of the //! library) are logged at `debug` level. +use super::LIBP2P_SIGNING_PREFIX_LENGTH; use libp2p_core::identity; +use log::error; + const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; -pub(super) const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; -pub(super) const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -pub(super) const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; @@ -60,9 +60,10 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu assert_eq!( public.len(), LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, - "ECDSA public keys are 65 bytes" + "ed25519 public keys are {} bytes", + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH ); - signing_buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + signing_buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); signing_buf[LIBP2P_SIGNING_PREFIX_LENGTH..].copy_from_slice(public); let signature = keypair.sign(&signing_buf).expect("signing failed"); ( @@ -88,25 +89,41 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { /// certificate with a valid libp2p extension. The certificate verifiers in this /// crate validate check this. /// -/// # Panics -/// -/// Panics if there is no libp2p extension in the certificate, or if the -/// certificate is ill-formed. -pub(crate) fn unwrap_libp2p_certificate(certificate: &[u8]) -> libp2p_core::PeerId { +/// If you get `Err` from this function, there is a bug somewhere. Either you +/// called it without checking the preconditions, or there is a bug in this +/// library or one of its dependencies. +pub(crate) fn extract_peerid(certificate: &[u8]) -> Result { let mut id = None; let cb = &mut |oid: untrusted::Input<'_>, value, _, _| match oid.as_slice_less_safe() { - LIBP2P_OID_BYTES => { - id = Some(extract_libp2p_peerid(value)); + super::LIBP2P_OID_BYTES => { + if id.is_some() { + error!( + "multiple libp2p extensions should have been detected \ + earlier; something is wrong" + ); + id = Some(Err(webpki::Error::UnknownIssuer)) + } + id = Some(match extract_libp2p_peerid(value) { + Ok(value) => Ok(value), + Err(_) => { + error!( + "bogus libp2p extension should have been detected \ + earlier; something is wrong" + ); + Err(webpki::Error::UnknownIssuer) + } + }); webpki::Understood::Yes } _ => webpki::Understood::No, }; - webpki::EndEntityCert::from_with_extension_cb(certificate, cb) - .expect("we already validated the certificate is well-formed"); - id.expect("we already checked that a libp2p extension exists") + webpki::EndEntityCert::from_with_extension_cb(certificate, cb)?; + id.unwrap_or(Err(webpki::Error::UnknownIssuer)) } -fn extract_libp2p_peerid(extension: untrusted::Input<'_>) -> libp2p_core::PeerId { +fn extract_libp2p_peerid( + extension: untrusted::Input<'_>, +) -> Result { use ring::{error::Unspecified, io::der}; extension .read_all(Unspecified, |mut reader| { @@ -115,10 +132,8 @@ fn extract_libp2p_peerid(extension: untrusted::Input<'_>) -> libp2p_core::PeerId let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); der::bit_string_with_no_unused_bits(&mut reader)?; - Ok(identity::PublicKey::from_protobuf_encoding(public_key) - .expect("already checked") - .into()) + identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified) }) }) - .expect("we already checked this in the certificate verifier") + .map(From::from) } diff --git a/transports/quic/src/verifier.rs b/transports/quic/src/tls/verifier.rs similarity index 95% rename from transports/quic/src/verifier.rs rename to transports/quic/src/tls/verifier.rs index a8c05f7bd70..e6cb4b4bbb3 100644 --- a/transports/quic/src/verifier.rs +++ b/transports/quic/src/tls/verifier.rs @@ -18,9 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::certificate::LIBP2P_OID_BYTES; -pub(super) const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; -const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, @@ -96,8 +93,8 @@ fn verify_libp2p_extension( let signature = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); let public_key = libp2p_core::identity::PublicKey::from_protobuf_encoding(public_key) .map_err(|_| Unspecified)?; - let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); - v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); + let mut v = Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); + v.extend_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); v.extend_from_slice(spki); if public_key.verify(&v, signature) { Ok(()) @@ -113,7 +110,7 @@ pub(super) fn verify_single_cert( ) -> Result<(webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>), rustls::TLSError> { let mut num_libp2p_extensions = 0usize; let cb = &mut |oid: untrusted::Input<'_>, value, _, spki| match oid.as_slice_less_safe() { - LIBP2P_OID_BYTES => { + super::LIBP2P_OID_BYTES => { num_libp2p_extensions += 1; if verify_libp2p_extension(value, spki).is_err() { num_libp2p_extensions = 2; // this will force an error From 45793ca099fdd1bc7a3ab9fe6793582ee12343b3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 1 Mar 2020 18:56:40 -0500 Subject: [PATCH 126/202] Move libp2p-tls to its own crate --- Cargo.toml | 1 + protocols/tls/Cargo.toml | 29 ++++++++++ .../tls => protocols/tls/src}/certificate.rs | 2 +- {transports/quic => protocols/tls}/src/tls.rs | 53 ++++++++++++++++++- .../src/tls => protocols/tls/src}/verifier.rs | 0 transports/quic/Cargo.toml | 1 + transports/quic/src/endpoint.rs | 4 +- transports/quic/src/lib.rs | 1 - webpki | 2 +- 9 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 protocols/tls/Cargo.toml rename {transports/quic/src/tls => protocols/tls/src}/certificate.rs (98%) rename {transports/quic => protocols/tls}/src/tls.rs (69%) rename {transports/quic/src/tls => protocols/tls/src}/verifier.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 10983426ecc..777a47ee73e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ members = [ "protocols/ping", "protocols/plaintext", "protocols/secio", + "protocols/tls", "swarm", "transports/dns", "transports/quic", diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml new file mode 100644 index 00000000000..c78549ffc58 --- /dev/null +++ b/protocols/tls/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "libp2p-tls" +version = "0.16.0" +authors = ["Parity Technologies "] +edition = "2018" +description = "TLS encryption for libp2p" +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking", "tls"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +quinn = { git = "https://github.com/djc/quinn", optional = true, package = "quinn-proto" } +rustls = "0.17.0" +ring = "0.16.11" +rcgen = "0.7.0" +webpki = "0.21.2" +untrusted = "0.7.0" +log = "0.4.8" +libp2p-core = { path = "../../core", version = "0.16.0" } +yasna = "0.3.1" + +[features] +default = ["quic"] +quic = ["quinn"] + +[lib] +name = "libp2p_tls" +path = "src/tls.rs" diff --git a/transports/quic/src/tls/certificate.rs b/protocols/tls/src/certificate.rs similarity index 98% rename from transports/quic/src/tls/certificate.rs rename to protocols/tls/src/certificate.rs index d2cd8a4ce1e..0403e864a84 100644 --- a/transports/quic/src/tls/certificate.rs +++ b/protocols/tls/src/certificate.rs @@ -92,7 +92,7 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { /// If you get `Err` from this function, there is a bug somewhere. Either you /// called it without checking the preconditions, or there is a bug in this /// library or one of its dependencies. -pub(crate) fn extract_peerid(certificate: &[u8]) -> Result { +pub fn extract_peerid(certificate: &[u8]) -> Result { let mut id = None; let cb = &mut |oid: untrusted::Input<'_>, value, _, _| match oid.as_slice_less_safe() { super::LIBP2P_OID_BYTES => { diff --git a/transports/quic/src/tls.rs b/protocols/tls/src/tls.rs similarity index 69% rename from transports/quic/src/tls.rs rename to protocols/tls/src/tls.rs index de123a65880..db545587d0e 100644 --- a/transports/quic/src/tls.rs +++ b/protocols/tls/src/tls.rs @@ -19,11 +19,60 @@ // DEALINGS IN THE SOFTWARE. //! TLS configuration for `libp2p-quic`. +// Forbid warnings when testing, but don’t break other people’s code +#![deny( + exceeding_bitshifts, + invalid_type_param_default, + missing_fragment_specifier, + mutable_transmutes, + no_mangle_const_items, + overflowing_literals, + patterns_in_fns_without_body, + pub_use_of_private_extern_crate, + unknown_crate_types, + const_err, + order_dependent_trait_objects, + illegal_floating_point_literal_pattern, + improper_ctypes, + late_bound_lifetime_arguments, + non_camel_case_types, + non_shorthand_field_patterns, + non_snake_case, + non_upper_case_globals, + no_mangle_generic_items, + path_statements, + private_in_public, + safe_packed_borrows, + stable_features, + type_alias_bounds, + tyvar_behind_raw_pointer, + unconditional_recursion, + unused, + unused_allocation, + unused_comparisons, + unused_mut, + unreachable_pub, + while_true, + anonymous_parameters, + bare_trait_objects, + elided_lifetimes_in_paths, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + clippy::all +)] +#![forbid(unsafe_code)] mod certificate; mod verifier; -pub(crate) use certificate::extract_peerid; +pub use certificate::extract_peerid; use std::sync::Arc; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -62,7 +111,7 @@ fn make_server_config( } /// Create TLS client and server configurations for libp2p. -pub(crate) fn make_tls_config( +pub fn make_tls_config( keypair: &libp2p_core::identity::Keypair, ) -> (rustls::ClientConfig, rustls::ServerConfig) { let cert = certificate::make_cert(&keypair); diff --git a/transports/quic/src/tls/verifier.rs b/protocols/tls/src/verifier.rs similarity index 100% rename from transports/quic/src/tls/verifier.rs rename to protocols/tls/src/verifier.rs diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index b6bef52685d..0da2663fb84 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -27,6 +27,7 @@ yasna = { version = "0.3.1", features = ["num-bigint"] } ring = "0.16.11" webpki = "0.21.2" untrusted = "0.7.0" +tls = { path = "../../protocols/tls", package = "libp2p-tls", version = "0.16.0" } [dev-dependencies] tracing-subscriber = "0.2.1" diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index b735b4a73b3..58e81391b46 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -59,7 +59,7 @@ impl Config { transport.datagram_receive_buffer_size(None); transport.keep_alive_interval(Some(Duration::from_millis(1000))); let transport = Arc::new(transport); - let (client_tls_config, server_tls_config) = super::tls::make_tls_config(keypair); + let (client_tls_config, server_tls_config) = tls::make_tls_config(keypair); let mut server_config = quinn_proto::ServerConfig::default(); server_config.transport = transport.clone(); server_config.crypto = Arc::new(server_tls_config); @@ -259,7 +259,7 @@ impl ConnectionEndpoint { .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, // and that it has a valid libp2p extension. - Poll::Ready(Ok(crate::tls::extract_peerid(certificate[0].as_ref())?)) + Poll::Ready(Ok(tls::extract_peerid(certificate[0].as_ref())?)) } /// Wake up the last task registered by diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 7745832e284..1bbdf3889c7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -102,7 +102,6 @@ )] #![forbid(unsafe_code)] -mod tls; mod connection; mod endpoint; mod error; diff --git a/webpki b/webpki index c2362773fec..4ac0252e770 160000 --- a/webpki +++ b/webpki @@ -1 +1 @@ -Subproject commit c2362773fec9187f735cb9939df4a6daf9aed627 +Subproject commit 4ac0252e7707bfca0966ae99eeccbcd3d5aef398 From 04edc7ee40f3547c53acf0789724406316435128 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 3 Mar 2020 13:12:22 -0500 Subject: [PATCH 127/202] Move stream_map and stream up a directory --- transports/quic/src/connection.rs | 5 +---- transports/quic/src/lib.rs | 2 ++ transports/quic/src/{connection => }/stream.rs | 0 transports/quic/src/{connection => }/stream_map.rs | 0 4 files changed, 3 insertions(+), 4 deletions(-) rename transports/quic/src/{connection => }/stream.rs (100%) rename transports/quic/src/{connection => }/stream_map.rs (100%) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index d1bab9227bf..7ee4f63af15 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::{endpoint::ConnectionEndpoint as Endpoint, error::Error, socket}; +use super::{endpoint::ConnectionEndpoint as Endpoint, error::Error, socket, stream_map}; use async_macros::ready; use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; @@ -33,9 +33,6 @@ use std::{ time::Instant, }; -mod stream; -mod stream_map; - /// A QUIC substream #[derive(Debug)] pub struct Substream { diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1bbdf3889c7..51730b4df39 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -106,6 +106,8 @@ mod connection; mod endpoint; mod error; mod socket; +mod stream; +mod stream_map; pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; pub use endpoint::{Config, Endpoint, JoinHandle, Listener}; pub use error::Error; diff --git a/transports/quic/src/connection/stream.rs b/transports/quic/src/stream.rs similarity index 100% rename from transports/quic/src/connection/stream.rs rename to transports/quic/src/stream.rs diff --git a/transports/quic/src/connection/stream_map.rs b/transports/quic/src/stream_map.rs similarity index 100% rename from transports/quic/src/connection/stream_map.rs rename to transports/quic/src/stream_map.rs From fd358e39bea46e1a9c64812afd9916d05165cd87 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 3 Mar 2020 16:45:48 -0500 Subject: [PATCH 128/202] Move task wakeup into stream_map.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code responsible for waking up tasks had previously been intermixed with the code that adapts quinn-proto’s own API to that of libp2p. This separates them, which makes the code significantly clearer and easier to understand. --- transports/quic/Cargo.toml | 1 + transports/quic/src/connection.rs | 460 +++-------------------------- transports/quic/src/endpoint.rs | 72 ++--- transports/quic/src/error.rs | 4 +- transports/quic/src/lib.rs | 3 +- transports/quic/src/stream_map.rs | 471 +++++++++++++++++++++++++++--- 6 files changed, 505 insertions(+), 506 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 0da2663fb84..ac471d4056e 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -28,6 +28,7 @@ ring = "0.16.11" webpki = "0.21.2" untrusted = "0.7.0" tls = { path = "../../protocols/tls", package = "libp2p-tls", version = "0.16.0" } +either = "1.5.3" [dev-dependencies] tracing-subscriber = "0.2.1" diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 7ee4f63af15..612e785f6b5 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -18,19 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::{endpoint::ConnectionEndpoint as Endpoint, error::Error, socket, stream_map}; +use super::{error::Error, stream_map}; use async_macros::ready; +use either::Either; use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; -use log::{debug, error, info, trace, warn}; +use log::{error, trace}; use parking_lot::{Mutex, MutexGuard}; -use quinn_proto::Dir; use std::{ mem::replace, - pin::Pin, sync::Arc, task::{Context, Poll}, - time::Instant, }; /// A QUIC substream @@ -82,11 +80,11 @@ impl Substream { /// to ease debugging, [`::read_substream`] returns an /// error in this case. #[derive(Debug, Clone)] -pub struct QuicMuxer(Arc>); +pub struct QuicMuxer(pub(crate) Arc>); impl QuicMuxer { /// Returns the underlying data structure, including all state. - fn inner(&self) -> MutexGuard<'_, Muxer> { + fn inner(&self) -> MutexGuard<'_, stream_map::Streams> { self.0.lock() } } @@ -112,18 +110,14 @@ impl StreamMuxer for QuicMuxer { type Error = Error; fn open_outbound(&self) -> Self::OutboundSubstream { let mut inner = self.inner(); - if let Some(ref e) = inner.close_reason { - Outbound(OutboundInner::Complete(Err(Error::ConnectionError( - e.clone(), - )))) - } else if let Some(id) = inner.get_pending_stream() { - Outbound(OutboundInner::Complete(Ok(id))) + Outbound(if let Err(e) = inner.close_reason() { + OutboundInner::Complete(Err(e)) } else { - let (sender, receiver) = oneshot::channel(); - inner.connectors.push_front(sender); - inner.wake_driver(); - Outbound(OutboundInner::Pending(receiver)) - } + match inner.get_pending_stream() { + Either::Left(id) => OutboundInner::Complete(Ok(id)), + Either::Right(receiver) => OutboundInner::Pending(receiver), + } + }) } fn destroy_outbound(&self, outbound: Outbound) { @@ -139,15 +133,11 @@ impl StreamMuxer for QuicMuxer { }, OutboundInner::Done => return, }; - inner.connection.destroy_stream(*id); - inner.streams.remove(id) + inner.destroy_stream(id) } fn destroy_substream(&self, substream: Self::Substream) { - let mut inner = self.inner(); - inner.connection.destroy_stream(*substream.id); - trace!("Removing substream {:?} from map", substream.id); - inner.streams.remove(substream.id); + self.inner().destroy_stream(substream.id) } fn is_remote_acknowledged(&self) -> bool { @@ -159,20 +149,10 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { trace!("being polled for inbound connections!"); let mut inner = self.inner(); - if let Some(close_reason) = &inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); - } + inner.close_reason()?; inner.wake_driver(); - match inner.connection.accept() { - None => { - inner.connection.set_waker(cx); - Poll::Pending - } - Some(id) => { - inner.outbound_streams += 1; - Poll::Ready(Ok(Substream::live(inner.streams.add_stream(id)))) - } - } + let stream = ready!(inner.accept(cx)); + Poll::Ready(Ok(Substream::live(stream))) } fn write_substream( @@ -184,29 +164,19 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::WriteError; let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.write(*substream.id, buf) { + match inner.write(cx, &substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(WriteError::Blocked) => { - if let Some(ref e) = inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(e.clone()))); - } - inner.streams.set_writer(&substream.id, cx.waker().clone()); - Poll::Pending - } + Err(WriteError::Blocked) => Poll::Pending, Err(WriteError::UnknownStream) => { error!( "The application used a connection that is already being \ closed. This is a bug in the application or in libp2p." ); - if let Some(e) = &inner.close_reason { - Poll::Ready(Err(Error::ConnectionError(e.clone()))) - } else { - Poll::Ready(Err(Error::ExpiredStream)) - } + inner.close_reason()?; + Poll::Ready(Err(Error::ExpiredStream)) } Err(WriteError::Stopped(e)) => { substream.status = SubstreamStatus::Finished; - inner.outbound_streams -= 1; Poll::Ready(Err(Error::Stopped(e))) } } @@ -244,20 +214,18 @@ impl StreamMuxer for QuicMuxer { use quinn_proto::ReadError; let mut inner = self.inner(); inner.wake_driver(); - match inner.connection.read(*substream.id, buf) { + match inner.read(cx, &substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(ReadError::Blocked) => { - if let Some(error) = &inner.close_reason { - Poll::Ready(Err(Error::ConnectionError(error.clone()))) - } else if let SubstreamStatus::Unwritten = substream.status { + inner.close_reason()?; + if let SubstreamStatus::Unwritten = substream.status { Poll::Ready(Err(Error::CannotReadFromUnwrittenStream)) } else { trace!( "Blocked on reading stream {:?} with side {:?}", substream.id, - inner.connection.side() + inner.side() ); - inner.streams.set_reader(&substream.id, cx.waker().clone()); Poll::Pending } } @@ -280,31 +248,19 @@ impl StreamMuxer for QuicMuxer { SubstreamStatus::Finished => return Poll::Ready(Ok(())), SubstreamStatus::Finishing(ref mut channel) => { self.inner().wake_driver(); - match channel.poll_unpin(cx) { - Poll::Ready(Ok(())) | Poll::Ready(Err(oneshot::Canceled)) => { - return Poll::Ready(Ok(())) - } - Poll::Pending => {} - } + ready!(channel.poll_unpin(cx)).map_err(|_| Error::NetworkFailure)?; + return Poll::Ready(Ok(())); } SubstreamStatus::Unwritten | SubstreamStatus::Live => {} } - let mut inner = self.inner(); - inner.wake_driver(); - let Muxer { - connection, - outbound_streams, - .. - } = &mut *inner; - connection.finish(*substream.id, outbound_streams)?; - let (sender, mut receiver) = oneshot::channel(); - assert!( - receiver.poll_unpin(cx).is_pending(), - "we haven’t written to the peer yet" - ); - substream.status = SubstreamStatus::Finishing(receiver); - inner.streams.set_finisher(&substream.id, sender); - Poll::Pending + match self.inner().shutdown_stream(cx, &substream.id) { + Ok(receiver) => { + substream.status = SubstreamStatus::Finishing(receiver); + Poll::Pending + } + Err(quinn_proto::FinishError::Stopped(e)) => Poll::Ready(Err(Error::Stopped(e))), + Err(quinn_proto::FinishError::UnknownStream) => Poll::Ready(Err(Error::ExpiredStream)), + } } /// Flush pending data on this stream. libp2p-quic sends out data as soon as @@ -326,350 +282,6 @@ impl StreamMuxer for QuicMuxer { /// Close the connection. Once this function is called, it is a logic error /// to call other methods on this object. fn close(&self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.inner(); - trace!( - "close() called with {} outbound streams for side {:?}", - inner.outbound_streams, - inner.connection.side() - ); - inner.wake_closer(); - inner.close_waker = Some(cx.waker().clone()); - if inner.close_reason.is_some() { - return Poll::Ready(Ok(())); - } else if inner.outbound_streams == 0 { - inner.shutdown(0); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - return Poll::Ready(Ok(())); - } - let Muxer { - streams, - connection, - outbound_streams, - .. - } = &mut *inner; - streams.close(|id| drop(connection.finish(id, outbound_streams))); - Poll::Pending - } -} - -/// A QUIC connection that is -#[derive(Debug)] -pub struct Upgrade { - muxer: Option, -} - -#[cfg(test)] -impl Drop for Upgrade { - fn drop(&mut self) { - debug!("dropping upgrade!"); - assert!( - self.muxer.is_none(), - "dropped before being polled to completion" - ); - } -} - -impl Future for Upgrade { - type Output = Result<(libp2p_core::PeerId, QuicMuxer), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let muxer = &mut self.get_mut().muxer; - trace!("outbound polling!"); - let mut inner = muxer.as_mut().expect("polled after yielding Ready").inner(); - if let Some(close_reason) = &inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); - } - match ready!(inner.connection.notify_handshake_complete(cx)) { - Ok(res) => { - drop(inner); - Poll::Ready(Ok(( - res, - muxer.take().expect("polled after yielding Ready"), - ))) - } - Err(e) => { - inner.shutdown(RESET); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - drop(inner); - *muxer = None; - Poll::Ready(Err(e)) - } - } - } -} - -type StreamSenderQueue = std::collections::VecDeque>; - -/// A QUIC connection and its associated data. -#[derive(Debug)] -pub(crate) struct Muxer { - /// If this is `Some`, it is a stream that has been opened by the peer, - /// but not yet accepted by the application. Otherwise, this is `None`. - pending_stream: Option, - /// The QUIC state machine and endpoint messaging. - connection: Endpoint, - /// The stream statuses - streams: stream_map::Streams, - /// Tasks waiting to make a connection. - connectors: StreamSenderQueue, - /// The number of outbound streams currently open. - outbound_streams: usize, - /// The close reason, if this connection has been lost - close_reason: Option, - /// Waker to wake up the driver. This is not a `MaybeWaker` because only the - /// driver task ever puts a waker here. - waker: Option, - /// Close waker - close_waker: Option, -} - -const RESET: u32 = 1; - -impl Drop for Muxer { - fn drop(&mut self) { - if self.close_reason.is_none() { - warn!("connection uncleanly closed!"); - self.shutdown(RESET) - } - } -} - -impl Muxer { - pub(crate) fn wake_driver(&mut self) { - if let Some(waker) = self.waker.take() { - waker.wake() - } - } - - fn wake_closer(&mut self) { - if let Some(close_waker) = self.close_waker.take() { - close_waker.wake() - } - } - - pub(crate) fn connection(&mut self) -> &mut Endpoint { - &mut self.connection - } - - fn new(connection: Endpoint) -> Self { - Muxer { - close_waker: None, - outbound_streams: 0, - pending_stream: None, - streams: Default::default(), - connectors: Default::default(), - connection, - close_reason: None, - waker: None, - } - } - - fn shutdown(&mut self, error_code: u32) { - debug!("shutting connection down!"); - self.connection.wake(); - self.streams.wake_all(); - self.connectors.clear(); - self.connection.close(error_code); - self.wake_driver(); - } - - fn open_stream(&mut self) -> Option { - self.connection.open().map(|e| { - self.outbound_streams += 1; - self.streams.add_stream(e) - }) - } - - fn get_pending_stream(&mut self) -> Option { - self.wake_driver(); - if let Some(id) = self.pending_stream.take() { - Some(id) - } else { - self.open_stream() - } - } - - pub(crate) fn process_app_events(&mut self) { - use quinn_proto::Event; - 'a: while let Some(event) = self.connection.poll() { - match event { - Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { - // This should never happen, but if it does, it is better to - // log a nasty message and recover than to tear down the - // process. - error!("we disabled incoming unidirectional streams and datagrams") - } - Event::StreamAvailable { dir: Dir::Uni } => { - // Ditto - error!("we don’t use unidirectional streams") - } - Event::StreamReadable { stream } => { - trace!( - "Stream {:?} readable for side {:?}", - stream, - self.connection.side() - ); - // Wake up the task waiting on us (if any) - self.streams.wake_reader(stream); - } - Event::StreamWritable { stream } => { - trace!( - "Stream {:?} writable for side {:?}", - stream, - self.connection.side() - ); - // Wake up the task waiting on us (if any). - // This will panic if quinn-proto has already emitted a - // `StreamFinished` event, but it will never emit - // `StreamWritable` after `StreamFinished`. - self.streams.wake_writer(stream) - } - Event::StreamAvailable { dir: Dir::Bi } => { - trace!( - "Bidirectional stream available for side {:?}", - self.connection.side() - ); - if self.connectors.is_empty() { - // Nobody is waiting on the stream. Allow quinn-proto to - // queue it, so that it can apply backpressure. - continue; - } - assert!( - self.pending_stream.is_none(), - "we cannot have both pending tasks and a pending stream; qed" - ); - let mut stream = self.open_stream().expect( - "we just were told that there is a stream available; there is \ - a mutex that prevents other threads from calling open() in the \ - meantime; qed", - ); - while let Some(oneshot) = self.connectors.pop_front() { - stream = match oneshot.send(stream) { - Ok(()) => continue 'a, - Err(e) => e, - } - } - self.pending_stream = Some(stream) - } - Event::ConnectionLost { reason } => { - debug!( - "lost connection due to {:?} for side {:?}", - reason, - self.connection.side() - ); - self.close_reason = Some(reason); - self.wake_closer(); - self.shutdown(0); - } - Event::StreamFinished { - stream, - stop_reason, - } => { - trace!( - "Stream {:?} finished for side {:?} because of {:?}", - stream, - self.connection.side(), - stop_reason - ); - // This stream is no longer useable for outbound traffic. - self.outbound_streams -= 1; - // If someone is waiting for this stream to become writable, - // wake them up, so that they can find out the stream is - // dead. - self.streams.wake_writer(stream); - // Connection close could be blocked on this. - self.wake_closer() - } - Event::Connected => { - debug!("connected for side {:?}!", self.connection.side()); - self.connection.wake(); - } - Event::StreamOpened { dir: Dir::Bi } => { - debug!("stream opened for side {:?}", self.connection.side()); - self.connection.wake() - } - } - } - } -} - -/// The connection driver -#[derive(Debug)] -pub(super) struct ConnectionDriver { - inner: Arc>, - /// The packet awaiting transmission, if any. - outgoing_packet: Option, - /// The timer being used by this connection - timer: Option, - /// The last timeout returned by `quinn_proto::poll_timeout`. - last_timeout: Option, - /// Transmit socket - socket: Arc, -} - -impl ConnectionDriver { - pub(crate) fn spawn>) -> Arc>( - connection: Endpoint, - cb: T, - ) -> Upgrade { - let inner = Arc::new(Mutex::new(Muxer::new(connection))); - let socket = cb(inner.clone()); - async_std::task::spawn(Self { - inner: inner.clone(), - outgoing_packet: None, - timer: None, - last_timeout: None, - socket, - }); - Upgrade { - muxer: Some(QuicMuxer(inner)), - } - } -} - -impl Future for ConnectionDriver { - type Output = Result<(), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - debug!("being polled for timer!"); - let mut inner = this.inner.lock(); - inner.waker = Some(cx.waker().clone()); - loop { - let now = Instant::now(); - inner - .connection - .poll_transmit_pending(now, cx, &this.socket)?; - inner.process_app_events(); - inner.connection.send_endpoint_events(cx)?; - match inner.connection.poll_timeout() { - None => { - this.timer = None; - this.last_timeout = None - } - Some(t) if t <= now => { - inner.connection.handle_timeout(now); - continue; - } - t if t == this.last_timeout => {} - Some(t) => this.timer = Some(futures_timer::Delay::new(t - now)), - } - if let Some(ref mut timer) = this.timer { - if timer.poll_unpin(cx).is_ready() { - inner.connection.handle_timeout(now); - continue; - } - } - if !inner.connection.is_drained() { - break Poll::Pending; - } - info!("exiting driver"); - let close_reason = inner - .close_reason - .clone() - .expect("we never have a closed connection with no reason; qed"); - break Poll::Ready(match close_reason { - quinn_proto::ConnectionError::LocallyClosed => Ok(()), - e => Err(e.into()), - }); - } + self.inner().close(cx) } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 58e81391b46..060cba3046c 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -87,7 +87,7 @@ enum EndpointMessage { #[derive(Debug)] pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, - muxers: HashMap>>, + muxers: HashMap>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, @@ -132,7 +132,7 @@ impl EndpointInner { fn send_connection_event( &mut self, handle: ConnectionHandle, - event: quinn_proto::ConnectionEvent, + mut event: quinn_proto::ConnectionEvent, ) { let Self { inner, muxers, .. } = self; let entry = match muxers.entry(handle) { @@ -141,14 +141,15 @@ impl EndpointInner { }; let mut connection = entry.get().lock(); let mut is_drained = false; - { - let connection = &mut connection.connection().connection; - connection.handle_event(event); - while let Some(event) = connection.poll_endpoint_events() { - is_drained |= event.is_drained(); - if let Some(event) = inner.handle_event(handle, event) { - connection.handle_event(event) - } + loop { + let endpoint_event = match connection.handle_event(event) { + None => break, + Some(endpoint_event) => endpoint_event, + }; + is_drained |= endpoint_event.is_drained(); + event = match inner.handle_event(handle, endpoint_event) { + Some(event) => event, + None => break, } } connection.process_app_events(); @@ -271,10 +272,7 @@ impl ConnectionEndpoint { } } - /// Register the current task to be woken up when - /// [`ConnectionEndpoint::wake`] is called, overwriting any previous - /// registration. - pub(crate) fn set_waker(&mut self, cx: &mut Context<'_>) { + fn set_waker(&mut self, cx: &mut Context<'_>) { self.waker = Some(cx.waker().clone()) } @@ -331,14 +329,28 @@ impl ConnectionEndpoint { } } + pub(crate) fn handle_event( + &mut self, + event: quinn_proto::ConnectionEvent, + ) -> Option { + self.connection.handle_event(event); + self.connection.poll_endpoint_events() + } + /// Check if this connection is drained. pub(crate) fn is_drained(&self) -> bool { self.connection.is_drained() } /// Accept an incoming stream, if possible. - pub(crate) fn accept(&mut self) -> Option { - self.connection.accept(quinn_proto::Dir::Bi) + pub(crate) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { + match self.connection.accept(quinn_proto::Dir::Bi) { + None => { + self.set_waker(cx); + Poll::Pending + } + Some(e) => Poll::Ready(e), + } } /// Open an outgoing stream if it is possible to do so. @@ -355,21 +367,12 @@ impl ConnectionEndpoint { self.connection.write(id, buffer) } - /// Shut down the writing side of a stream. If we will not get an - /// `StreamFinished` event, decrement `outbound_streams`. + /// Shut down the writing side of a stream. pub(crate) fn finish( &mut self, id: quinn_proto::StreamId, - outbound_streams: &mut usize, - ) -> Result<(), Error> { - self.connection.finish(id).map_err(|e| match e { - quinn_proto::FinishError::UnknownStream => Error::ConnectionClosing, - quinn_proto::FinishError::Stopped(e) => { - // we will never get a `StreamFinished` event - *outbound_streams -= 1; - Error::Stopped(e) - } - }) + ) -> Result<(), quinn_proto::FinishError> { + self.connection.finish(id) } /// Get application-facing events @@ -572,7 +575,7 @@ fn accept_muxer( let socket = endpoint.socket.clone(); - let upgrade = super::connection::ConnectionDriver::spawn(connection_endpoint, |weak| { + let upgrade = crate::Upgrade::spawn(connection_endpoint, |weak| { inner.muxers.insert(handle, weak); socket }); @@ -717,13 +720,10 @@ impl Transport for Endpoint { channel: endpoint.channel, waker: None, }; - Ok(super::connection::ConnectionDriver::spawn( - endpoint, - |weak| { - inner.muxers.insert(handle, weak); - socket - }, - )) + Ok(crate::Upgrade::spawn(endpoint, |weak| { + inner.muxers.insert(handle, weak); + socket + })) } } diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 7825b14e8da..03fe9dc61a7 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -30,7 +30,9 @@ pub enum Error { #[error(display = "Fatal I/O error {}", _0)] IO(#[error(source)] std::io::Error), /// Peer sent a malformed certificate - #[error(display = "Peer sent a malformed certificate without it being detected ― this is a bug")] + #[error( + display = "Peer sent a malformed certificate without it being detected ― this is a bug" + )] BadCertificate(#[error(source)] webpki::Error), /// QUIC protocol error #[error(display = "QUIC protocol error: {}", _0)] diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 51730b4df39..38ca64e170f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -108,6 +108,7 @@ mod error; mod socket; mod stream; mod stream_map; -pub use connection::{Outbound, QuicMuxer as Muxer, Substream, Upgrade}; +pub use connection::{Outbound, QuicMuxer as Muxer, Substream}; pub use endpoint::{Config, Endpoint, JoinHandle, Listener}; pub use error::Error; +pub use stream_map::Upgrade; diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index 4e3e00701f6..2dab85a248e 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -20,10 +20,23 @@ //! The state of all active streams in a QUIC connection +use super::endpoint::ConnectionEndpoint; use super::stream::StreamState; -use futures::channel::oneshot; +use super::{socket, Error}; +use async_macros::ready; +use either::Either; +use futures::{channel::oneshot, future::FutureExt}; +use log::{debug, error, info, trace}; +use parking_lot::Mutex; +use quinn_proto::Dir; use std::collections::HashMap; -use std::task; +use std::sync::Arc; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, + time::Instant, +}; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] /// A stream ID. @@ -36,14 +49,62 @@ impl std::ops::Deref for StreamId { } } +/// A QUIC connection that is +#[derive(Debug)] +pub struct Upgrade { + muxer: Option>>, +} + +#[cfg(test)] +impl Drop for Upgrade { + fn drop(&mut self) { + debug!("dropping upgrade!"); + assert!( + self.muxer.is_none(), + "dropped before being polled to completion" + ); + } +} + +type StreamSenderQueue = std::collections::VecDeque>; + /// A set of streams. -#[derive(Debug, Default)] +#[derive(Debug)] pub(super) struct Streams { + /// If this is `Some`, it is a stream that has been opened by the peer, + /// but not yet accepted by the application. Otherwise, this is `None`. + pending_stream: Option, + /// The stream statuses map: HashMap, + /// The QUIC state machine and endpoint messaging. + connection: ConnectionEndpoint, + /// The number of outbound streams currently open. + outbound_streams: usize, + /// Tasks waiting to make a connection. + connectors: StreamSenderQueue, + /// The close reason, if this connection has been lost + close_reason: Option, + /// Waker to wake up the driver. This is not a `MaybeWaker` because only the + /// driver task ever puts a waker here. + waker: Option, + /// Close waker + close_waker: Option, } impl Streams { - pub(super) fn add_stream(&mut self, id: quinn_proto::StreamId) -> StreamId { + pub(crate) fn wake_driver(&mut self) { + if let Some(waker) = self.waker.take() { + waker.wake() + } + } + + fn wake_closer(&mut self) { + if let Some(close_waker) = self.close_waker.take() { + close_waker.wake() + } + } + + fn add_stream(&mut self, id: quinn_proto::StreamId) -> StreamId { if self.map.insert(id, Default::default()).is_some() { panic!( "Internal state corrupted. \ @@ -53,6 +114,19 @@ impl Streams { StreamId(id) } + fn new(connection: ConnectionEndpoint) -> Self { + Self { + pending_stream: None, + map: Default::default(), + connection, + outbound_streams: 0, + connectors: Default::default(), + close_reason: None, + waker: None, + close_waker: None, + } + } + fn get(&mut self, id: &StreamId) -> &mut StreamState { self.map.get_mut(id).expect( "Internal state corrupted. \ @@ -76,33 +150,6 @@ impl Streams { } } - /// Set a waker that will be notified when the state becomes readable. - /// Wake up any waker that has already been registered. - pub(super) fn set_reader(&mut self, id: &StreamId, waker: task::Waker) { - self.get(id).set_reader(waker); - } - - /// Set a waker that will be notified when the task becomes writable or is - /// finished, waking up any waker or channel that has already been - /// registered. - /// - /// # Panics - /// - /// Panics if the stream has already been finished. - pub(super) fn set_writer(&mut self, id: &StreamId, waker: task::Waker) { - self.get(id).set_writer(waker); - } - - /// Set a channel that will be notified when the task becomes writable or is - /// finished, waking up any existing registered waker or channel. - /// - /// # Panics - /// - /// Panics if the stream has already been finished. - pub(super) fn set_finisher(&mut self, id: &StreamId, finisher: oneshot::Sender<()>) { - self.get(id).set_finisher(finisher); - } - /// Remove an ID from the map. /// /// # Panics @@ -111,27 +158,363 @@ impl Streams { pub(super) fn remove(&mut self, id: StreamId) { self.map.remove(&id.0).expect( "Internal state corrupted. \ - You probably used a Substream with the wrong StreamMuxer", + You probably used a Substream with the wrong StreamMuxer", ); } - /// Wake all wakers and call the provided callback for each stream, - /// so as to free resources. - /// - /// # Panics - /// - /// Panics if [`Stream::close`] has already been called. - pub(super) fn close(&mut self, mut cb: T) { - for (stream, value) in &mut self.map { - value.wake_all(); - cb(*stream) - } + /// Poll for incoming streams + pub(super) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { + self.wake_driver(); + self.connection.accept(cx).map(|e| { + self.outbound_streams += 1; + self.add_stream(e) + }) } /// Wake up everything - pub(super) fn wake_all(&mut self) { + fn wake_all(&mut self) { for value in self.map.values_mut() { value.wake_all() } } + + pub(super) fn process_app_events(&mut self) { + use quinn_proto::Event; + 'a: while let Some(event) = self.connection.poll() { + match event { + Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { + // This should never happen, but if it does, it is better to + // log a nasty message and recover than to tear down the + // process. + error!("we disabled incoming unidirectional streams and datagrams") + } + Event::StreamAvailable { dir: Dir::Uni } => { + // Ditto + error!("we don’t use unidirectional streams") + } + Event::StreamReadable { stream } => { + trace!( + "Stream {:?} readable for side {:?}", + stream, + self.connection.side() + ); + // Wake up the task waiting on us (if any) + self.wake_reader(stream); + } + Event::StreamWritable { stream } => { + trace!( + "Stream {:?} writable for side {:?}", + stream, + self.connection.side() + ); + // Wake up the task waiting on us (if any). + // This will panic if quinn-proto has already emitted a + // `StreamFinished` event, but it will never emit + // `StreamWritable` after `StreamFinished`. + self.wake_writer(stream) + } + Event::StreamAvailable { dir: Dir::Bi } => { + trace!( + "Bidirectional stream available for side {:?}", + self.connection.side() + ); + if self.connectors.is_empty() { + // Nobody is waiting on the stream. Allow quinn-proto to + // queue it, so that it can apply backpressure. + continue; + } + assert!( + self.pending_stream.is_none(), + "we cannot have both pending tasks and a pending stream; qed" + ); + let mut stream = self.open_stream().expect( + "we just were told that there is a stream available; there is \ + a mutex that prevents other threads from calling open() in the \ + meantime; qed", + ); + while let Some(oneshot) = self.connectors.pop_front() { + stream = match oneshot.send(stream) { + Ok(()) => continue 'a, + Err(e) => e, + } + } + self.pending_stream = Some(stream) + } + Event::ConnectionLost { reason } => { + debug!( + "lost connection due to {:?} for side {:?}", + reason, + self.connection.side() + ); + self.close_reason = Some(reason); + self.wake_closer(); + self.shutdown(0); + } + Event::StreamFinished { + stream, + stop_reason, + } => { + trace!( + "Stream {:?} finished for side {:?} because of {:?}", + stream, + self.connection.side(), + stop_reason + ); + // This stream is no longer useable for outbound traffic. + self.outbound_streams -= 1; + // If someone is waiting for this stream to become writable, + // wake them up, so that they can find out the stream is + // dead. + self.wake_writer(stream); + // Connection close could be blocked on this. + self.wake_closer() + } + Event::Connected => { + debug!("connected for side {:?}!", self.connection.side()); + self.connection.wake(); + } + Event::StreamOpened { dir: Dir::Bi } => { + debug!("stream opened for side {:?}", self.connection.side()); + self.connection.wake() + } + } + } + } + + fn shutdown(&mut self, error_code: u32) { + debug!("shutting connection down!"); + self.connection.wake(); + self.wake_all(); + self.connectors.clear(); + self.connection.close(error_code); + self.wake_driver(); + } + + fn open_stream(&mut self) -> Option { + self.connection.open().map(|e| { + self.outbound_streams += 1; + self.add_stream(e) + }) + } + + pub(crate) fn write( + &mut self, + cx: &mut Context<'_>, + substream: &StreamId, + buf: &[u8], + ) -> Result { + use quinn_proto::WriteError; + self.wake_driver(); + match self.connection.write(substream.0, buf) { + e @ Err(WriteError::Blocked) => { + self.get(substream).set_writer(cx.waker().clone()); + e + } + e => e, + } + } + + pub(crate) fn side(&self) -> quinn_proto::Side { + self.connection.side() + } + + pub(crate) fn read( + &mut self, + cx: &mut Context<'_>, + substream: &StreamId, + buf: &mut [u8], + ) -> Result { + use quinn_proto::ReadError; + self.wake_driver(); + match self.connection.read(substream.0, buf) { + e @ Err(ReadError::Blocked) => { + self.get(substream).set_reader(cx.waker().clone()); + e + } + e => e, + } + } + + pub(crate) fn get_pending_stream(&mut self) -> Either> { + self.wake_driver(); + let pending = std::mem::replace(&mut self.pending_stream, None); + match pending.or_else(|| self.open_stream()) { + Some(stream) => Either::Left(stream), + None => { + let (sender, receiver) = oneshot::channel(); + self.connectors.push_front(sender); + self.wake_driver(); + Either::Right(receiver) + } + } + } + + pub(crate) fn close_reason(&self) -> Result<(), Error> { + match self + .close_reason + .as_ref() + .map(|e| Error::ConnectionError(e.clone())) + { + Some(e) => Err(e), + None => Ok(()), + } + } + + pub(crate) fn destroy_stream(&mut self, stream: StreamId) { + trace!("Removing substream {:?} from map", *stream); + self.connection.destroy_stream(*stream); + self.remove(stream) + } + + pub(crate) fn close(&mut self, cx: &mut Context<'_>) -> Poll> { + trace!( + "close() called with {} outbound streams for side {:?}", + self.outbound_streams, + self.connection.side() + ); + self.wake_closer(); + self.close_waker = Some(cx.waker().clone()); + if self.close_reason.is_some() { + return Poll::Ready(Ok(())); + } else if self.outbound_streams == 0 { + self.shutdown(0); + self.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + return Poll::Ready(Ok(())); + } + for (&stream, value) in &mut self.map { + value.wake_all(); + let _ = self.connection.finish(stream); + } + Poll::Pending + } + + pub(crate) fn handle_event( + &mut self, + event: quinn_proto::ConnectionEvent, + ) -> Option { + self.connection.handle_event(event) + } + + pub(crate) fn shutdown_stream( + &mut self, + cx: &mut Context<'_>, + substream: &StreamId, + ) -> Result, quinn_proto::FinishError> { + self.wake_driver(); + self.connection.finish(**substream)?; + let (sender, mut receiver) = oneshot::channel(); + let _ = receiver.poll_unpin(cx); + self.get(substream).set_finisher(sender); + Ok(receiver) + } +} + +/// The connection driver +#[derive(Debug)] +struct ConnectionDriver { + inner: Arc>, + /// The packet awaiting transmission, if any. + outgoing_packet: Option, + /// The timer being used by this connection + timer: Option, + /// The last timeout returned by `quinn_proto::poll_timeout`. + last_timeout: Option, + /// Transmit socket + socket: Arc, +} + +impl Upgrade { + pub(crate) fn spawn>) -> Arc>( + connection: ConnectionEndpoint, + cb: T, + ) -> Upgrade { + let inner = Arc::new(Mutex::new(Streams::new(connection))); + let socket = cb(inner.clone()); + async_std::task::spawn(ConnectionDriver { + inner: inner.clone(), + outgoing_packet: None, + timer: None, + last_timeout: None, + socket, + }); + Upgrade { muxer: Some(inner) } + } +} + +impl Future for ConnectionDriver { + type Output = Result<(), Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + debug!("being polled for timer!"); + let mut inner = this.inner.lock(); + inner.waker = Some(cx.waker().clone()); + loop { + let now = Instant::now(); + inner + .connection + .poll_transmit_pending(now, cx, &this.socket)?; + inner.process_app_events(); + inner.connection.send_endpoint_events(cx)?; + match inner.connection.poll_timeout() { + None => { + this.timer = None; + this.last_timeout = None + } + Some(t) if t <= now => { + inner.connection.handle_timeout(now); + continue; + } + t if t == this.last_timeout => {} + Some(t) => this.timer = Some(futures_timer::Delay::new(t - now)), + } + if let Some(ref mut timer) = this.timer { + if timer.poll_unpin(cx).is_ready() { + inner.connection.handle_timeout(now); + continue; + } + } + if !inner.connection.is_drained() { + break Poll::Pending; + } + info!("exiting driver"); + let close_reason = inner + .close_reason + .clone() + .expect("we never have a closed connection with no reason; qed"); + break Poll::Ready(match close_reason { + quinn_proto::ConnectionError::LocallyClosed => Ok(()), + e => Err(e.into()), + }); + } + } +} + +const RESET: u32 = 1; + +impl Future for Upgrade { + type Output = Result<(libp2p_core::PeerId, crate::Muxer), Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let muxer = &mut self.get_mut().muxer; + trace!("outbound polling!"); + let mut inner = muxer.as_mut().expect("polled after yielding Ready").lock(); + if let Some(close_reason) = &inner.close_reason { + return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); + } + match ready!(inner.connection.notify_handshake_complete(cx)) { + Ok(res) => { + drop(inner); + Poll::Ready(Ok(( + res, + crate::Muxer(muxer.take().expect("polled after yielding Ready")), + ))) + } + Err(e) => { + inner.shutdown(RESET); + inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); + drop(inner); + *muxer = None; + Poll::Ready(Err(e)) + } + } + } } From b10797477b99319b48e9b010b03b471ce568d3e0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 4 Mar 2020 10:11:23 -0500 Subject: [PATCH 129/202] Inline functions used only once --- transports/quic/src/stream_map.rs | 40 +++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index 2dab85a248e..da4b41b202a 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -127,6 +127,11 @@ impl Streams { } } + /// Get the stream state for the stream. + /// + /// # Panics + /// + /// Panics if there is no stream. This indicates misuse of the API. fn get(&mut self, id: &StreamId) -> &mut StreamState { self.map.get_mut(id).expect( "Internal state corrupted. \ @@ -134,34 +139,14 @@ impl Streams { ) } - /// Indicate that the stream is open for reading. Calling this when nobody - /// is waiting for this stream to be readable is a harmless no-op. - pub(super) fn wake_reader(&mut self, id: quinn_proto::StreamId) { - if let Some(stream) = self.map.get_mut(&id) { - stream.wake_reader() - } - } - /// If a task is waiting for this stream to be finished or written to, wake /// it up. Otherwise, do nothing. - pub(super) fn wake_writer(&mut self, id: quinn_proto::StreamId) { + fn wake_writer(&mut self, id: quinn_proto::StreamId) { if let Some(stream) = self.map.get_mut(&id) { stream.wake_writer() } } - /// Remove an ID from the map. - /// - /// # Panics - /// - /// Panics if the ID has already been removed. - pub(super) fn remove(&mut self, id: StreamId) { - self.map.remove(&id.0).expect( - "Internal state corrupted. \ - You probably used a Substream with the wrong StreamMuxer", - ); - } - /// Poll for incoming streams pub(super) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { self.wake_driver(); @@ -190,7 +175,9 @@ impl Streams { } Event::StreamAvailable { dir: Dir::Uni } => { // Ditto - error!("we don’t use unidirectional streams") + error!("We don’t use unidirectional streams, but got one \ + anyway. This is a libp2p-quic bug; please report it \ + at .") } Event::StreamReadable { stream } => { trace!( @@ -199,7 +186,9 @@ impl Streams { self.connection.side() ); // Wake up the task waiting on us (if any) - self.wake_reader(stream); + if let Some(stream) = self.map.get_mut(&stream) { + stream.wake_reader() + } } Event::StreamWritable { stream } => { trace!( @@ -363,7 +352,10 @@ impl Streams { pub(crate) fn destroy_stream(&mut self, stream: StreamId) { trace!("Removing substream {:?} from map", *stream); self.connection.destroy_stream(*stream); - self.remove(stream) + self.map.remove(&stream.0).expect( + "Internal state corrupted. \ + You probably used a Substream with the wrong StreamMuxer", + ); } pub(crate) fn close(&mut self, cx: &mut Context<'_>) -> Poll> { From 6e1205dca1bd6176d5eff805ec425f20ad56cccf Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 4 Mar 2020 13:16:22 -0500 Subject: [PATCH 130/202] Remove git submodule --- .gitmodules | 3 --- Cargo.toml | 2 +- webpki | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .gitmodules delete mode 160000 webpki diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1701acbc06a..00000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "webpki"] - path = webpki - url = git+ssh://git@github.com/DemiMarie-parity/webpki diff --git a/Cargo.toml b/Cargo.toml index 777a47ee73e..36ea7175514 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,4 +85,4 @@ exclude = [ ] [patch.crates-io] -webpki = { path = "webpki" } +webpki = { git = "https://github.com/paritytech/webpki", branch = "extension-handlers" } diff --git a/webpki b/webpki deleted file mode 160000 index 4ac0252e770..00000000000 --- a/webpki +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4ac0252e7707bfca0966ae99eeccbcd3d5aef398 From 9e3ef0ca1c053ecc9070055913c4f7e07b959930 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 4 Mar 2020 13:16:50 -0500 Subject: [PATCH 131/202] More cleanups --- core/src/nodes/listeners.rs | 1 - core/src/transport/memory.rs | 2 -- transports/quic/src/connection.rs | 1 - transports/quic/src/stream_map.rs | 19 ++++++++----------- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/core/src/nodes/listeners.rs b/core/src/nodes/listeners.rs index f711ef719ec..738a1752c43 100644 --- a/core/src/nodes/listeners.rs +++ b/core/src/nodes/listeners.rs @@ -369,7 +369,6 @@ where mod tests { use super::*; use crate::transport; - use futures::prelude::*; #[test] fn incoming_event() { diff --git a/core/src/transport/memory.rs b/core/src/transport/memory.rs index 8546163caeb..2f27e126b1e 100644 --- a/core/src/transport/memory.rs +++ b/core/src/transport/memory.rs @@ -279,8 +279,6 @@ impl> Into>> for Chan { #[cfg(test)] mod tests { use super::*; - use rand::Rng; - use std::io::Write; #[test] fn parse_memory_addr_works() { diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 612e785f6b5..1cea5b9e268 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -213,7 +213,6 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); - inner.wake_driver(); match inner.read(cx, &substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(ReadError::Blocked) => { diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index da4b41b202a..cea6632b453 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -111,6 +111,7 @@ impl Streams { You probably used a Substream with the wrong StreamMuxer", ) } + self.outbound_streams += 1; StreamId(id) } @@ -150,10 +151,7 @@ impl Streams { /// Poll for incoming streams pub(super) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { self.wake_driver(); - self.connection.accept(cx).map(|e| { - self.outbound_streams += 1; - self.add_stream(e) - }) + self.connection.accept(cx).map(|e| self.add_stream(e)) } /// Wake up everything @@ -175,9 +173,11 @@ impl Streams { } Event::StreamAvailable { dir: Dir::Uni } => { // Ditto - error!("We don’t use unidirectional streams, but got one \ - anyway. This is a libp2p-quic bug; please report it \ - at .") + error!( + "We don’t use unidirectional streams, but got one \ + anyway. This is a libp2p-quic bug; please report it \ + at ." + ) } Event::StreamReadable { stream } => { trace!( @@ -280,10 +280,7 @@ impl Streams { } fn open_stream(&mut self) -> Option { - self.connection.open().map(|e| { - self.outbound_streams += 1; - self.add_stream(e) - }) + self.connection.open().map(|e| self.add_stream(e)) } pub(crate) fn write( From b5b1d10b4998adcd84909f472267b9fdeec0665c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 7 Mar 2020 17:29:18 -0500 Subject: [PATCH 132/202] Fix panic when connection closed too soon The connection may be lost before the handshake is complete. If this happens, the connection will not be handshaking, but there will not be any certificates available. Return an error in this case instead of panicing. --- transports/quic/src/endpoint.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 060cba3046c..f5092541b8b 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -248,6 +248,9 @@ impl ConnectionEndpoint { self.set_waker(cx); return Poll::Pending; } + if self.connection.is_closed() { + return Poll::Ready(Err(Error::ConnectionLost)); + } if self.connection.side().is_server() { ready!(self.channel.poll_ready(cx))?; self.channel From 9574e0188007a264c8875d23020a67c93e1fa97d Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 8 Mar 2020 14:06:59 -0400 Subject: [PATCH 133/202] Be more rigorous about send stream counts They must be decremented as soon as we know we will never write on that stream again, and they must not be decremented twice for the same stream. --- protocols/tls/src/certificate.rs | 6 +++ protocols/tls/src/tls.rs | 27 +++++----- protocols/tls/src/verifier.rs | 82 ++++++++++++++++++------------- transports/quic/src/lib.rs | 1 - transports/quic/src/stream.rs | 27 +++++----- transports/quic/src/stream_map.rs | 44 ++++++++++++----- 6 files changed, 109 insertions(+), 78 deletions(-) diff --git a/protocols/tls/src/certificate.rs b/protocols/tls/src/certificate.rs index 0403e864a84..7948dcca84e 100644 --- a/protocols/tls/src/certificate.rs +++ b/protocols/tls/src/certificate.rs @@ -33,6 +33,12 @@ use log::error; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; +// preferred, but not supported by rustls yet +//const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 32; +//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ED25519; +// same but with P-384 +//const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 97; +//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P384_SHA384; fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { let public_key = public_key.into_protobuf_encoding(); diff --git a/protocols/tls/src/tls.rs b/protocols/tls/src/tls.rs index db545587d0e..d13f230a3a3 100644 --- a/protocols/tls/src/tls.rs +++ b/protocols/tls/src/tls.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. //! TLS configuration for `libp2p-quic`. -// Forbid warnings when testing, but don’t break other people’s code #![deny( exceeding_bitshifts, invalid_type_param_default, @@ -82,6 +81,7 @@ const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; fn make_client_config( certificate: rustls::Certificate, key: rustls::PrivateKey, + verifier: Arc, ) -> rustls::ClientConfig { let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; @@ -89,20 +89,16 @@ fn make_client_config( crypto .set_single_client_cert(vec![certificate], key) .expect("we have a valid certificate; qed"); - let verifier = verifier::VeryInsecureRequireExactlyOneSelfSignedServerCertificate; - crypto - .dangerous() - .set_certificate_verifier(Arc::new(verifier)); + crypto.dangerous().set_certificate_verifier(verifier); crypto } fn make_server_config( certificate: rustls::Certificate, key: rustls::PrivateKey, + verifier: Arc, ) -> rustls::ServerConfig { - let mut crypto = rustls::ServerConfig::new(Arc::new( - verifier::VeryInsecureRequireExactlyOneSelfSignedClientCertificate, - )); + let mut crypto = rustls::ServerConfig::new(verifier); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto .set_single_cert(vec![certificate], key) @@ -116,15 +112,14 @@ pub fn make_tls_config( ) -> (rustls::ClientConfig, rustls::ServerConfig) { let cert = certificate::make_cert(&keypair); let private_key = cert.serialize_private_key_der(); - let (cert, key) = ( - rustls::Certificate( - cert.serialize_der() - .expect("serialization of a valid cert will succeed; qed"), - ), - rustls::PrivateKey(private_key), + let verifier = Arc::new(verifier::Libp2pCertificateVerifier); + let cert = rustls::Certificate( + cert.serialize_der() + .expect("serialization of a valid cert will succeed; qed"), ); + let key = rustls::PrivateKey(private_key); ( - make_client_config(cert.clone(), key.clone()), - make_server_config(cert, key), + make_client_config(cert.clone(), key.clone(), verifier.clone()), + make_server_config(cert, key, verifier), ) } diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index e6cb4b4bbb3..ceb46d89917 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -21,42 +21,39 @@ static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, - &webpki::ECDSA_P256_SHA384, - &webpki::ECDSA_P384_SHA256, - &webpki::ECDSA_P384_SHA384, &webpki::ED25519, + &webpki::ECDSA_P384_SHA384, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + // Deprecated and/or undesirable algorithms. + + // deprecated elliptic curve algorithms + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P256_SHA384, + // RSA PKCS 1.5 &webpki::RSA_PKCS1_2048_8192_SHA256, &webpki::RSA_PKCS1_2048_8192_SHA384, &webpki::RSA_PKCS1_2048_8192_SHA512, &webpki::RSA_PKCS1_3072_8192_SHA384, - &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, ] }; -/// A ServerCertVerifier that considers any self-signed certificate to be valid. -/// -/// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! -/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any self-signed -/// certificate with a valid libp2p extension **by design**. Instead, it is the application’s job -/// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection -/// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done -/// something insecure. -pub(crate) struct VeryInsecureRequireExactlyOneSelfSignedServerCertificate; +/// Libp2p client and server certificate verifier. +pub(crate) struct Libp2pCertificateVerifier; -/// A ClientCertVerifier that requires client authentication, and requires the certificate to be -/// self-signed. +/// libp2p requires the following of X.509 server certificate chains: /// -/// “Isn’t that insecure?”, you may ask. Yes, it is! That’s why this struct has the name it does! -/// This doesn’t cause a vulnerability in libp2p-quic, however. libp2p-quic accepts any self-signed -/// certificate with a valid libp2p extension **by design**. Instead, it is the application’s job -/// to check the peer ID that libp2p-quic provides. libp2p-quic does guarantee that the connection -/// is to a peer with the secret key corresponing to its `PeerId`, unless that endpoint has done -/// something insecure. -pub(crate) struct VeryInsecureRequireExactlyOneSelfSignedClientCertificate; - -impl rustls::ServerCertVerifier for VeryInsecureRequireExactlyOneSelfSignedServerCertificate { +/// * Exactly one certificate must be presented. +/// * The certificate must be self-signed. +/// * The certificate must have a valid libp2p extension that includes +/// a signature of its public key. +/// +/// The check that the [`PeerId`] matches the expected `PeerId` must be done by +/// the caller. +/// +/// [`PeerId`]: libp2p_core::PeerId +impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { fn verify_server_cert( &self, _roots: &rustls::RootCertStore, @@ -82,21 +79,24 @@ fn verify_libp2p_extension( subject_public_key_info: untrusted::Input<'_>, ) -> Result<(), ring::error::Unspecified> { use ring::{error::Unspecified, io::der}; - let spki = subject_public_key_info.read_all(Unspecified, |mut reader| { + use libp2p_core::identity::PublicKey; + let certificate_key = subject_public_key_info.read_all(Unspecified, |mut reader| { der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - Ok(der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe()) + der::bit_string_with_no_unused_bits(&mut reader) })?; extension.read_all(Unspecified, |mut reader| { let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; inner.read_all(Unspecified, |mut reader| { - let public_key = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - let signature = der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - let public_key = libp2p_core::identity::PublicKey::from_protobuf_encoding(public_key) + let public_key = der::bit_string_with_no_unused_bits(&mut reader)?; + let signature = der::bit_string_with_no_unused_bits(&mut reader)?; + // We deliberately discard the error information because this is + // either a broken peer or an attack. + let public_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) .map_err(|_| Unspecified)?; - let mut v = Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + spki.len()); + let mut v = Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); v.extend_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); - v.extend_from_slice(spki); - if public_key.verify(&v, signature) { + v.extend_from_slice(certificate_key.as_slice_less_safe()); + if public_key.verify(&v, signature.as_slice_less_safe()) { Ok(()) } else { Err(Unspecified) @@ -144,7 +144,19 @@ fn verify_presented_certs( verify_single_cert(presented_certs[0].as_ref()) } -impl rustls::ClientCertVerifier for VeryInsecureRequireExactlyOneSelfSignedClientCertificate { +/// libp2p requires the following of X.509 client certificate chains: +/// +/// * Exactly one certificate must be presented. In particular, client +/// authentication is mandatory in libp2p. +/// * The certificate must be self-signed. +/// * The certificate must have a valid libp2p extension that includes +/// a signature of its public key. +/// +/// The check that the [`PeerId`] matches the expected `PeerId` must be done by +/// the caller. +/// +/// [`PeerId`]: libp2p_core::PeerId +impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { fn offer_client_auth(&self) -> bool { true } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 38ca64e170f..2f0b8027191 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -52,7 +52,6 @@ //! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -// Forbid warnings when testing, but don’t break other people’s code #![deny( exceeding_bitshifts, invalid_type_param_default, diff --git a/transports/quic/src/stream.rs b/transports/quic/src/stream.rs index e9f873617d9..1354d02642d 100644 --- a/transports/quic/src/stream.rs +++ b/transports/quic/src/stream.rs @@ -38,15 +38,16 @@ enum WriterStatus { } impl WriterStatus { - fn take(self) { + fn take(self) -> bool { match self { Self::Unblocked => {} Self::Blocked { waker } => waker.wake(), Self::Finishing { finisher } => { let _ = finisher.send(()); } - Self::Finished => panic!("using a finished stream"), - } + Self::Finished => return false, + }; + true } } @@ -86,20 +87,12 @@ impl StreamState { /// If a task is waiting for this stream to be finished or written to, wake /// it up. Otherwise, do nothing. - /// - /// # Panics - /// - /// Panics if the stream has already been finished. pub(crate) fn wake_writer(&mut self) { - mem::replace(&mut self.writer, WriterStatus::Unblocked).take() + mem::replace(&mut self.writer, WriterStatus::Unblocked).take(); } /// Set a waker that will be notified when the state becomes readable. /// Wake up any waker that has already been registered. - /// - /// # Panics - /// - /// Panics if the stream has already been finished. pub(crate) fn set_reader(&mut self, waker: task::Waker) { if let Some(waker) = mem::replace(&mut self.reader, Some(waker)) { waker.wake() @@ -110,13 +103,13 @@ impl StreamState { /// finished, waking up any waker or channel that has already been /// registered. pub(crate) fn set_writer(&mut self, waker: task::Waker) { - mem::replace(&mut self.writer, WriterStatus::Blocked { waker }).take() + mem::replace(&mut self.writer, WriterStatus::Blocked { waker }).take(); } /// Set a channel that will be notified when the task becomes writable or is /// finished, waking up any existing registered waker or channel pub(crate) fn set_finisher(&mut self, finisher: oneshot::Sender<()>) { - mem::replace(&mut self.writer, WriterStatus::Finishing { finisher }).take() + mem::replace(&mut self.writer, WriterStatus::Finishing { finisher }).take(); } /// Wake up both readers and writers. This is just a shorthand for calling @@ -125,4 +118,10 @@ impl StreamState { self.wake_writer(); self.wake_reader(); } + + /// Mark this stream done for writing. Return `true` if this stream was not + /// already finished. + pub(crate) fn finish(&mut self) -> bool { + mem::replace(&mut self.writer, WriterStatus::Finished).take() + } } diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index cea6632b453..f6acadbfab3 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -249,12 +249,15 @@ impl Streams { self.connection.side(), stop_reason ); - // This stream is no longer useable for outbound traffic. - self.outbound_streams -= 1; // If someone is waiting for this stream to become writable, // wake them up, so that they can find out the stream is - // dead. - self.wake_writer(stream); + // dead. If this is our first indication that the stream is + // finished, decrement `outbound_streams`. + if let Some(status) = self.map.get_mut(&stream) { + if status.finish() { + self.outbound_streams -= 1 + } + } // Connection close could be blocked on this. self.wake_closer() } @@ -283,6 +286,15 @@ impl Streams { self.connection.open().map(|e| self.add_stream(e)) } + fn finish(&mut self, id: &StreamId) -> Result<(), quinn_proto::FinishError> { + self.connection.finish(id.0).map_err(|e| { + if self.get(&id).finish() { + self.outbound_streams -= 1 + } + e + }) + } + pub(crate) fn write( &mut self, cx: &mut Context<'_>, @@ -291,13 +303,18 @@ impl Streams { ) -> Result { use quinn_proto::WriteError; self.wake_driver(); - match self.connection.write(substream.0, buf) { - e @ Err(WriteError::Blocked) => { - self.get(substream).set_writer(cx.waker().clone()); - e + self.connection.write(substream.0, buf).map_err(|e| { + let stream = self.get(substream); + match e { + WriteError::Blocked => stream.set_writer(cx.waker().clone()), + WriteError::Stopped(_) | WriteError::UnknownStream => { + if stream.finish() { + self.outbound_streams -= 1 + } + } } - e => e, - } + e + }) } pub(crate) fn side(&self) -> quinn_proto::Side { @@ -372,7 +389,10 @@ impl Streams { } for (&stream, value) in &mut self.map { value.wake_all(); - let _ = self.connection.finish(stream); + if self.connection.finish(stream).is_err() && value.finish() { + // We just found that a stream is finished + self.outbound_streams -= 1 + } } Poll::Pending } @@ -390,7 +410,7 @@ impl Streams { substream: &StreamId, ) -> Result, quinn_proto::FinishError> { self.wake_driver(); - self.connection.finish(**substream)?; + self.finish(substream)?; let (sender, mut receiver) = oneshot::channel(); let _ = receiver.poll_unpin(cx); self.get(substream).set_finisher(sender); From 5cba8058d0e46d48569e8b510e6f90c92366771a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 8 Mar 2020 15:41:03 -0400 Subject: [PATCH 134/202] =?UTF-8?q?Use=20quinn-proto=E2=80=99s=20built-in?= =?UTF-8?q?=20send=20stream=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit instead of trying to count send streams manually. Furthermore, stop using a fork of async-tls that has already been merged. --- transports/quic/src/endpoint.rs | 4 ++++ transports/quic/src/stream.rs | 9 ++++----- transports/quic/src/stream_map.rs | 30 +++++++----------------------- transports/websocket/Cargo.toml | 2 +- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index f5092541b8b..9a28257b115 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -307,6 +307,10 @@ impl ConnectionEndpoint { self.connection.handle_timeout(now) } + pub(crate) fn send_streams(&self) -> usize { + self.connection.send_streams() + } + /// Destroy a substream pub(crate) fn destroy_stream(&mut self, id: quinn_proto::StreamId) { // if either of these returns an error, there is nothing we can do, so diff --git a/transports/quic/src/stream.rs b/transports/quic/src/stream.rs index 1354d02642d..e3e64750ba4 100644 --- a/transports/quic/src/stream.rs +++ b/transports/quic/src/stream.rs @@ -38,16 +38,15 @@ enum WriterStatus { } impl WriterStatus { - fn take(self) -> bool { + fn take(self) { match self { Self::Unblocked => {} Self::Blocked { waker } => waker.wake(), Self::Finishing { finisher } => { let _ = finisher.send(()); } - Self::Finished => return false, - }; - true + Self::Finished => {} + } } } @@ -121,7 +120,7 @@ impl StreamState { /// Mark this stream done for writing. Return `true` if this stream was not /// already finished. - pub(crate) fn finish(&mut self) -> bool { + pub(crate) fn finish(&mut self) { mem::replace(&mut self.writer, WriterStatus::Finished).take() } } diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index f6acadbfab3..e0391d8778c 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -78,8 +78,6 @@ pub(super) struct Streams { map: HashMap, /// The QUIC state machine and endpoint messaging. connection: ConnectionEndpoint, - /// The number of outbound streams currently open. - outbound_streams: usize, /// Tasks waiting to make a connection. connectors: StreamSenderQueue, /// The close reason, if this connection has been lost @@ -111,7 +109,6 @@ impl Streams { You probably used a Substream with the wrong StreamMuxer", ) } - self.outbound_streams += 1; StreamId(id) } @@ -120,7 +117,6 @@ impl Streams { pending_stream: None, map: Default::default(), connection, - outbound_streams: 0, connectors: Default::default(), close_reason: None, waker: None, @@ -251,12 +247,9 @@ impl Streams { ); // If someone is waiting for this stream to become writable, // wake them up, so that they can find out the stream is - // dead. If this is our first indication that the stream is - // finished, decrement `outbound_streams`. + // dead. if let Some(status) = self.map.get_mut(&stream) { - if status.finish() { - self.outbound_streams -= 1 - } + status.finish() } // Connection close could be blocked on this. self.wake_closer() @@ -288,9 +281,7 @@ impl Streams { fn finish(&mut self, id: &StreamId) -> Result<(), quinn_proto::FinishError> { self.connection.finish(id.0).map_err(|e| { - if self.get(&id).finish() { - self.outbound_streams -= 1 - } + self.get(&id).finish(); e }) } @@ -307,11 +298,7 @@ impl Streams { let stream = self.get(substream); match e { WriteError::Blocked => stream.set_writer(cx.waker().clone()), - WriteError::Stopped(_) | WriteError::UnknownStream => { - if stream.finish() { - self.outbound_streams -= 1 - } - } + WriteError::Stopped(_) | WriteError::UnknownStream => stream.finish(), } e }) @@ -375,24 +362,21 @@ impl Streams { pub(crate) fn close(&mut self, cx: &mut Context<'_>) -> Poll> { trace!( "close() called with {} outbound streams for side {:?}", - self.outbound_streams, + self.connection.send_streams(), self.connection.side() ); self.wake_closer(); self.close_waker = Some(cx.waker().clone()); if self.close_reason.is_some() { return Poll::Ready(Ok(())); - } else if self.outbound_streams == 0 { + } else if self.connection.send_streams() == 0 { self.shutdown(0); self.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); return Poll::Ready(Ok(())); } for (&stream, value) in &mut self.map { value.wake_all(); - if self.connection.finish(stream).is_err() && value.finish() { - // We just found that a stream is finished - self.outbound_streams -= 1 - } + let _ = self.connection.finish(stream); } Poll::Pending } diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index a8c4e19040e..b6cc13252a8 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-tls = { git = "https://github.com/DemiMarie-parity/async-tls", branch = "upgrade-webpki-rustls" } +async-tls = { git = "https://github.com/async-std/async-tls" } bytes = "0.5" either = "1.5.3" futures = "0.3.1" From d9c2c67731cb4d08d622feeeb0928f4b5be374f0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 8 Mar 2020 16:57:26 -0400 Subject: [PATCH 135/202] Use a patch instead of a git dependency --- Cargo.toml | 4 +--- transports/websocket/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 626876db322..03cb8d72428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,9 +79,7 @@ members = [ "transports/wasm-ext", "transports/websocket", ] -exclude = [ - "webpki" -] [patch.crates-io] webpki = { git = "https://github.com/paritytech/webpki", branch = "extension-handlers" } +async-tls = { git = "https://github.com/async-std/async-tls" } diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index b6cc13252a8..1ecd7c97abb 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-tls = { git = "https://github.com/async-std/async-tls" } +async-tls = "0.6" bytes = "0.5" either = "1.5.3" futures = "0.3.1" From 4d6c709104e710b2a7abbb5890765494b748629b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 10 Mar 2020 11:51:20 -0400 Subject: [PATCH 136/202] Apply suggestions from Max Inden --- transports/quic/src/connection.rs | 2 +- transports/quic/src/endpoint.rs | 25 +++++++++++++------------ transports/quic/src/stream_map.rs | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 1cea5b9e268..cc089a05835 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -203,7 +203,7 @@ impl StreamMuxer for QuicMuxer { Poll::Ready(Ok(Substream::unwritten(stream))) } - /// Try to from a substream. This will return an error if the substream has + /// Try to read from a substream. This will return an error if the substream has /// not yet been written to. fn read_substream( &self, diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 9a28257b115..1fe4613046a 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -236,7 +236,7 @@ impl ConnectionEndpoint { /// Notify the endpoint that the handshake has completed, /// and get the certificates from it. /// - /// If this returns [`Poll::Pending`], the current task can be worken up by + /// If this returns [`Poll::Pending`], the current task can be awoken by /// a call to [`ConnectionEndpoint::wake`]. /// /// Calling this after it has returned `Ready` is erroneous. @@ -440,6 +440,7 @@ mod channel_ref { impl Drop for Channel { fn drop(&mut self) { + // Wake up the endpoint task, to avoid deadlocks. let _ = self.0.start_send(Dummy); } } @@ -455,21 +456,21 @@ mod channel_ref { pub(crate) use channel_ref::EndpointRef; -/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. +/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. /// -/// You generally need only one of these per process. Endpoints are [`Send`] and [`Sync`], so you -/// can share them among as many threads as you like. However, performance may be better if you -/// have one per CPU core, as this reduces lock contention. Most applications will not need to -/// worry about this. `Endpoint` tries to use fine-grained locking to reduce the overhead. +/// You generally need at most one of these per thread. Endpoints are [`Send`] and [`Sync`], so you +/// can share them among as many threads as you like. However, performance may be better if you +/// have one per CPU core, as this reduces lock contention. Most applications will not need to +/// worry about this. [`Endpoint`] tries to use fine-grained locking to reduce the overhead. /// -/// `Endpoint` wraps the underlying data structure in an [`Arc`], so cloning it just bumps the -/// reference count. All state is shared between the clones. For example, you can pass different -/// clones to [`Transport::listen_on`]. Each incoming connection will be received by exactly one of +/// [`Endpoint`] wraps the underlying data structure in an [`Arc`], so cloning it just bumps the +/// reference count. All state is shared between the clones. For example, you can pass different +/// clones to [`Transport::listen_on`]. Each incoming connection will be received by exactly one of /// them. /// -/// The **only** valid [`Multiaddr`] to pass to `listen_on` is the one used to create the -/// `QuicEndpoint`. You can obtain this via the `addr` method. If you pass a different one, you -/// will get [`TransportError::MultiaddrNotSupported`]. +/// The **only** valid [`Multiaddr`] to pass to [`Endpoint::listen_on`] is the one used to create the +/// [`Endpoint`]. If you pass a different one, you will get +/// [`TransportError::MultiaddrNotSupported`]. #[derive(Debug, Clone)] pub struct Endpoint(EndpointRef); diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index e0391d8778c..b3cd93f4894 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -49,7 +49,7 @@ impl std::ops::Deref for StreamId { } } -/// A QUIC connection that is +/// A QUIC connection that is being upgraded. #[derive(Debug)] pub struct Upgrade { muxer: Option>>, @@ -72,7 +72,7 @@ type StreamSenderQueue = std::collections::VecDeque>; #[derive(Debug)] pub(super) struct Streams { /// If this is `Some`, it is a stream that has been opened by the peer, - /// but not yet accepted by the application. Otherwise, this is `None`. + /// but not yet accepted by the application. Otherwise, this is `None`. pending_stream: Option, /// The stream statuses map: HashMap, From fa935cea957233504029f3a5abef7b3fb160e183 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 10 Mar 2020 12:38:21 -0400 Subject: [PATCH 137/202] Cleanup docs and remove a callback Thank you to Max Inden for the suggestions. --- transports/quic/src/endpoint.rs | 50 +++++++++++++++---------------- transports/quic/src/lib.rs | 13 ++++---- transports/quic/src/stream_map.rs | 22 +++++++------- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 1fe4613046a..e632c6b573a 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{error::Error, socket, Upgrade}; +use crate::{error::Error, socket, Upgrade, stream_map::Streams}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -29,7 +29,7 @@ use libp2p_core::{ }; use log::{debug, info, trace, warn}; use parking_lot::Mutex; -use quinn_proto::{Connection, ConnectionHandle}; +use quinn_proto::ConnectionHandle; use std::{ collections::{hash_map::Entry, HashMap}, pin::Pin, @@ -87,7 +87,7 @@ enum EndpointMessage { #[derive(Debug)] pub(super) struct EndpointInner { inner: quinn_proto::Endpoint, - muxers: HashMap>>, + muxers: HashMap>>, pending: socket::Pending, /// Used to receive events from connections event_receiver: mpsc::Receiver, @@ -164,7 +164,7 @@ impl EndpointInner { &mut self, socket: &socket::Socket, cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { use quinn_proto::DatagramEvent; loop { let (bytes, peer) = ready!(socket.recv_from(cx, &mut self.buffer[..])?); @@ -224,20 +224,20 @@ pub(super) struct EndpointData { type Sender = mpsc::Sender; #[derive(Debug)] -pub(crate) struct ConnectionEndpoint { +pub(crate) struct Connection { pending: socket::Pending, - connection: Connection, + connection: quinn_proto::Connection, handle: ConnectionHandle, channel: Channel, waker: Option, } -impl ConnectionEndpoint { +impl Connection { /// Notify the endpoint that the handshake has completed, /// and get the certificates from it. /// /// If this returns [`Poll::Pending`], the current task can be awoken by - /// a call to [`ConnectionEndpoint::wake`]. + /// a call to [`Connection::wake`]. /// /// Calling this after it has returned `Ready` is erroneous. pub(crate) fn notify_handshake_complete( @@ -267,8 +267,8 @@ impl ConnectionEndpoint { } /// Wake up the last task registered by - /// [`ConnectionEndpoint::notify_handshake_complete`] or - /// [`ConnectionEndpoint::set_waker`]. + /// [`Connection::notify_handshake_complete`] or + /// [`Connection::set_waker`]. pub(crate) fn wake(&mut self) { if let Some(waker) = self.waker.take() { waker.wake() @@ -465,11 +465,11 @@ pub(crate) use channel_ref::EndpointRef; /// /// [`Endpoint`] wraps the underlying data structure in an [`Arc`], so cloning it just bumps the /// reference count. All state is shared between the clones. For example, you can pass different -/// clones to [`Transport::listen_on`]. Each incoming connection will be received by exactly one of -/// them. +/// clones to [`::listen_on`]. Each incoming connection will be received by +/// exactly one of them. /// -/// The **only** valid [`Multiaddr`] to pass to [`Endpoint::listen_on`] is the one used to create the -/// [`Endpoint`]. If you pass a different one, you will get +/// The **only** valid [`Multiaddr`] to pass to [`::listen_on`] is the one +/// used to create the [`Endpoint`]. If you pass a different one, you will get /// [`TransportError::MultiaddrNotSupported`]. #[derive(Debug, Clone)] pub struct Endpoint(EndpointRef); @@ -568,12 +568,12 @@ impl Endpoint { fn accept_muxer( endpoint: &Arc, - connection: Connection, + connection: quinn_proto::Connection, handle: ConnectionHandle, inner: &mut EndpointInner, channel: Channel, ) { - let connection_endpoint = ConnectionEndpoint { + let connection = Connection { pending: socket::Pending::default(), connection, handle, @@ -583,10 +583,9 @@ fn accept_muxer( let socket = endpoint.socket.clone(); - let upgrade = crate::Upgrade::spawn(connection_endpoint, |weak| { - inner.muxers.insert(handle, weak); - socket - }); + let streams = Arc::new(Mutex::new(Streams::new(connection))); + inner.muxers.insert(handle, streams.clone()); + let upgrade = crate::Upgrade::spawn(streams, socket); if endpoint .new_connections .unbounded_send(ListenerEvent::Upgrade { @@ -708,7 +707,7 @@ impl Transport for Endpoint { }; let Endpoint(endpoint) = self; let mut inner = endpoint.reference.inner.lock(); - let s: Result<(_, Connection), _> = inner + let s = inner .inner .connect( endpoint.reference.config.client_config.clone(), @@ -721,17 +720,16 @@ impl Transport for Endpoint { }); let (handle, connection) = s?; let socket = endpoint.reference.socket.clone(); - let endpoint = ConnectionEndpoint { + let connection = Connection { pending: socket::Pending::default(), connection, handle, channel: endpoint.channel, waker: None, }; - Ok(crate::Upgrade::spawn(endpoint, |weak| { - inner.muxers.insert(handle, weak); - socket - })) + let streams = Arc::new(Mutex::new(Streams::new(connection))); + inner.muxers.insert(handle, streams.clone()); + Ok(crate::Upgrade::spawn(streams.clone(), socket)) } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 2f0b8027191..c224491a6e5 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -74,7 +74,6 @@ no_mangle_generic_items, path_statements, private_in_public, - safe_packed_borrows, stable_features, type_alias_bounds, tyvar_behind_raw_pointer, @@ -84,10 +83,7 @@ unused_comparisons, unused_mut, unreachable_pub, - while_true, anonymous_parameters, - bare_trait_objects, - elided_lifetimes_in_paths, missing_copy_implementations, missing_debug_implementations, missing_docs, @@ -99,7 +95,14 @@ unused_qualifications, clippy::all )] -#![forbid(unsafe_code)] +#![forbid( + unsafe_code, + intra_doc_link_resolution_failure, + safe_packed_borrows, + while_true, + elided_lifetimes_in_paths, + bare_trait_objects +)] mod connection; mod endpoint; diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index b3cd93f4894..60b0cde880f 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -20,7 +20,7 @@ //! The state of all active streams in a QUIC connection -use super::endpoint::ConnectionEndpoint; +use super::endpoint::Connection; use super::stream::StreamState; use super::{socket, Error}; use async_macros::ready; @@ -38,8 +38,8 @@ use std::{ time::Instant, }; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] /// A stream ID. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub(super) struct StreamId(quinn_proto::StreamId); impl std::ops::Deref for StreamId { @@ -77,7 +77,7 @@ pub(super) struct Streams { /// The stream statuses map: HashMap, /// The QUIC state machine and endpoint messaging. - connection: ConnectionEndpoint, + connection: Connection, /// Tasks waiting to make a connection. connectors: StreamSenderQueue, /// The close reason, if this connection has been lost @@ -112,7 +112,7 @@ impl Streams { StreamId(id) } - fn new(connection: ConnectionEndpoint) -> Self { + pub(crate) fn new(connection: Connection) -> Self { Self { pending_stream: None, map: Default::default(), @@ -417,20 +417,20 @@ struct ConnectionDriver { } impl Upgrade { - pub(crate) fn spawn>) -> Arc>( - connection: ConnectionEndpoint, - cb: T, + pub(crate) fn spawn( + connection: Arc>, + socket: Arc, ) -> Upgrade { - let inner = Arc::new(Mutex::new(Streams::new(connection))); - let socket = cb(inner.clone()); async_std::task::spawn(ConnectionDriver { - inner: inner.clone(), + inner: connection.clone(), outgoing_packet: None, timer: None, last_timeout: None, socket, }); - Upgrade { muxer: Some(inner) } + Upgrade { + muxer: Some(connection), + } } } From 5bd1743bfb375d58bcb0aead924f26af123081cc Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 10 Mar 2020 15:03:51 -0400 Subject: [PATCH 138/202] Add a span for each test run --- transports/quic/tests/tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index c05236a25c5..9ac6a6ad74a 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -169,8 +169,11 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { + use tracing::{span, Level}; init(); for _ in 0..1000u32 { + let span = span!(Level::ERROR, "single test"); + let _span = span.enter(); do_test() } } From c2d798a2685507f85dbf844fe377a1f3e8ff53aa Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 11 Mar 2020 11:10:52 -0400 Subject: [PATCH 139/202] Switch to a working quinn-proto branch Also simplify error handling by panicing on certain impossible conditions. --- protocols/tls/Cargo.toml | 2 +- transports/quic/Cargo.toml | 2 +- transports/quic/src/endpoint.rs | 16 +++++++--------- transports/quic/src/error.rs | 10 ---------- transports/quic/tests/tests.rs | 8 ++++---- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml index c78549ffc58..e7c9aae34bb 100644 --- a/protocols/tls/Cargo.toml +++ b/protocols/tls/Cargo.toml @@ -10,7 +10,6 @@ keywords = ["peer-to-peer", "libp2p", "networking", "tls"] categories = ["network-programming", "asynchronous"] [dependencies] -quinn = { git = "https://github.com/djc/quinn", optional = true, package = "quinn-proto" } rustls = "0.17.0" ring = "0.16.11" rcgen = "0.7.0" @@ -19,6 +18,7 @@ untrusted = "0.7.0" log = "0.4.8" libp2p-core = { path = "../../core", version = "0.16.0" } yasna = "0.3.1" +quinn = { git = "git+ssh://github.com/djc/quinn", optional = true, package = "quinn-proto", branch = "cid-dedup" } [features] default = ["quic"] diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index ac471d4056e..e99939f4b7f 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4.8" ipnet = "2.2.0" err-derive = "0.2.2" futures = "0.3.4" -quinn-proto = { git = "https://github.com/djc/quinn" } +quinn-proto = { git = "https://github.com/djc/quinn", branch = "cid-dedup" } async-std = "1.5.0" async-macros = "2.0.0" futures-timer = "3.0.2" diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index e632c6b573a..8f557329365 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{error::Error, socket, Upgrade, stream_map::Streams}; +use crate::{error::Error, socket, stream_map::Streams, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; use futures::{channel::mpsc, prelude::*}; @@ -27,7 +27,7 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, info, trace, warn}; +use log::{debug, info, trace}; use parking_lot::Mutex; use quinn_proto::ConnectionHandle; use std::{ @@ -263,7 +263,9 @@ impl Connection { .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, // and that it has a valid libp2p extension. - Poll::Ready(Ok(tls::extract_peerid(certificate[0].as_ref())?)) + Poll::Ready(Ok(tls::extract_peerid(certificate[0].as_ref()).expect( + "our certificate verifiers guarantee that this will succeed; qed", + ))) } /// Wake up the last task registered by @@ -707,18 +709,14 @@ impl Transport for Endpoint { }; let Endpoint(endpoint) = self; let mut inner = endpoint.reference.inner.lock(); - let s = inner + let (handle, connection) = inner .inner .connect( endpoint.reference.config.client_config.clone(), socket_addr, "l", ) - .map_err(|e| { - warn!("Connection error: {:?}", e); - TransportError::Other(Error::CannotConnect(e)) - }); - let (handle, connection) = s?; + .expect("this function does no I/O, and we pass valid parameters, so it will succeed"); let socket = endpoint.reference.socket.clone(); let connection = Connection { pending: socket::Pending::default(), diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 03fe9dc61a7..1ae3b70e45c 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -29,17 +29,9 @@ pub enum Error { /// Fatal I/O error #[error(display = "Fatal I/O error {}", _0)] IO(#[error(source)] std::io::Error), - /// Peer sent a malformed certificate - #[error( - display = "Peer sent a malformed certificate without it being detected ― this is a bug" - )] - BadCertificate(#[error(source)] webpki::Error), /// QUIC protocol error #[error(display = "QUIC protocol error: {}", _0)] ConnectionError(#[error(source)] quinn_proto::ConnectionError), - /// Cannot establish connection - #[error(display = "Cannot establish connection: {}", _0)] - CannotConnect(#[error(source)] quinn_proto::ConnectError), /// Peer stopped receiving data #[error(display = "Peer stopped receiving data: code {}", _0)] Stopped(quinn_proto::VarInt), @@ -78,9 +70,7 @@ impl From for io::Error { fn from(e: Error) -> Self { match e { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), - e @ Error::BadCertificate(_) => io::Error::new(ErrorKind::InvalidData, e), Error::ConnectionError(e) => e.into(), - e @ Error::CannotConnect(_) | e @ Error::NetworkFailure | e @ Error::ConnectionClosing => io::Error::new(ErrorKind::Other, e), e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 9ac6a6ad74a..af3b8bbd101 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -169,11 +169,11 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { - use tracing::{span, Level}; + use tracing::info_span; init(); - for _ in 0..1000u32 { - let span = span!(Level::ERROR, "single test"); - let _span = span.enter(); + for i in 0..1000u32 { + let span = info_span!("test", id = i); + let _guard = span.enter(); do_test() } } From a20f95a9583e37cd498bfeada355c43b7d7e2965 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 11 Mar 2020 11:29:11 -0400 Subject: [PATCH 140/202] Move the multiaddr to the configuration --- protocols/tls/Cargo.toml | 13 ++----- protocols/tls/src/{tls.rs => lib.rs} | 0 protocols/tls/src/verifier.rs | 14 +++++--- src/lib.rs | 3 +- transports/quic/src/endpoint.rs | 51 +++++++++++++++++----------- 5 files changed, 46 insertions(+), 35 deletions(-) rename protocols/tls/src/{tls.rs => lib.rs} (100%) diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml index e7c9aae34bb..fb8e3ce769a 100644 --- a/protocols/tls/Cargo.toml +++ b/protocols/tls/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" description = "TLS encryption for libp2p" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking", "tls"] +keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] [dependencies] @@ -17,13 +17,4 @@ webpki = "0.21.2" untrusted = "0.7.0" log = "0.4.8" libp2p-core = { path = "../../core", version = "0.16.0" } -yasna = "0.3.1" -quinn = { git = "git+ssh://github.com/djc/quinn", optional = true, package = "quinn-proto", branch = "cid-dedup" } - -[features] -default = ["quic"] -quic = ["quinn"] - -[lib] -name = "libp2p_tls" -path = "src/tls.rs" +yasna = "0.3.1" \ No newline at end of file diff --git a/protocols/tls/src/tls.rs b/protocols/tls/src/lib.rs similarity index 100% rename from protocols/tls/src/tls.rs rename to protocols/tls/src/lib.rs diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index ceb46d89917..d53eb9fd377 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -78,11 +78,16 @@ fn verify_libp2p_extension( extension: untrusted::Input<'_>, subject_public_key_info: untrusted::Input<'_>, ) -> Result<(), ring::error::Unspecified> { - use ring::{error::Unspecified, io::der}; use libp2p_core::identity::PublicKey; + use ring::{error::Unspecified, io::der}; let certificate_key = subject_public_key_info.read_all(Unspecified, |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - der::bit_string_with_no_unused_bits(&mut reader) + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?.read_all( + Unspecified, + |mut reader| { + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + der::bit_string_with_no_unused_bits(&mut reader) + }, + ) })?; extension.read_all(Unspecified, |mut reader| { let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; @@ -93,7 +98,8 @@ fn verify_libp2p_extension( // either a broken peer or an attack. let public_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) .map_err(|_| Unspecified)?; - let mut v = Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); + let mut v = + Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); v.extend_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); v.extend_from_slice(certificate_key.as_slice_less_safe()); if public_key.verify(&v, signature.as_slice_less_safe()) { diff --git a/src/lib.rs b/src/lib.rs index cf220e32569..ca0617c09e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,8 @@ pub use libp2p_noise as noise; pub use libp2p_ping as ping; #[doc(inline)] pub use libp2p_plaintext as plaintext; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] +#[cfg(all(feature = "quic", not(any(target_os = "emscripten", target_os = "unknown"))))] +>>>>>>> Stashed changes #[doc(inline)] pub use libp2p_quic as quic; #[doc(inline)] diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 8f557329365..f7e5148d27f 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -43,17 +43,19 @@ use channel_ref::Channel; /// Represents the configuration for a QUIC transport capability for libp2p. #[derive(Debug, Clone)] pub struct Config { - /// The client configuration. Quinn provides functions for making one. + /// The client configuration client_config: quinn_proto::ClientConfig, - /// The server configuration. Quinn provides functions for making one. + /// The server configuration server_config: Arc, /// The endpoint configuration endpoint_config: Arc, + /// The [`Multiaddr`] + multiaddr: Multiaddr, } impl Config { /// Creates a new configuration object for QUIC. - pub fn new(keypair: &libp2p_core::identity::Keypair) -> Self { + pub fn new(keypair: &libp2p_core::identity::Keypair, multiaddr: Multiaddr) -> Self { let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); @@ -70,6 +72,7 @@ impl Config { client_config, server_config: Arc::new(server_config), endpoint_config: Default::default(), + multiaddr: multiaddr, } } } @@ -483,15 +486,20 @@ pub type JoinHandle = async_std::task::JoinHandle>; pub(crate) struct SendToken(()); impl Endpoint { - /// Construct a `Endpoint` with the given `Config` and `Multiaddr`. + /// Construct a [`Endpoint`] with the given `Config`. pub fn new( config: Config, - mut address: Multiaddr, ) -> Result<(Self, JoinHandle), TransportError<::Error>> { - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&address) { + let Config { + mut multiaddr, + client_config, + server_config, + endpoint_config, + } = config; + let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&multiaddr) { sa } else { - return Err(TransportError::MultiaddrNotSupported(address)); + return Err(TransportError::MultiaddrNotSupported(multiaddr)); }; // NOT blocking, as per man:bind(2), as we pass an IP address. let socket = std::net::UdpSocket::bind(&socket_addr) @@ -501,10 +509,10 @@ impl Endpoint { info!("bound socket to {:?}", socket_addr); if port_is_zero { assert_ne!(socket_addr.port(), 0); - assert_eq!(address.pop(), Some(Protocol::Quic)); - assert_eq!(address.pop(), Some(Protocol::Udp(0))); - address.push(Protocol::Udp(socket_addr.port())); - address.push(Protocol::Quic); + assert_eq!(multiaddr.pop(), Some(Protocol::Quic)); + assert_eq!(multiaddr.pop(), Some(Protocol::Udp(0))); + multiaddr.push(Protocol::Udp(socket_addr.port())); + multiaddr.push(Protocol::Quic); } let (new_connections, receive_connections) = mpsc::unbounded(); let (event_sender, event_receiver) = mpsc::channel(0); @@ -520,27 +528,32 @@ impl Endpoint { .expect("we have a reference to the peer, so this will not fail; qed") } } else { - info!("sending address {:?}", address); + info!("sending address {:?}", multiaddr); new_connections - .unbounded_send(ListenerEvent::NewAddress(address.clone())) + .unbounded_send(ListenerEvent::NewAddress(multiaddr.clone())) .expect("we have a reference to the peer, so this will not fail; qed"); } let reference = Arc::new(EndpointData { socket: Arc::new(socket::Socket::new(socket.into())), inner: Mutex::new(EndpointInner { inner: quinn_proto::Endpoint::new( - config.endpoint_config.clone(), - Some(config.server_config.clone()), + endpoint_config.clone(), + Some(server_config.clone()), ), muxers: HashMap::new(), event_receiver, pending: Default::default(), buffer: vec![0; 0xFFFF], }), - address: address.clone(), + address: multiaddr.clone(), receive_connections: Mutex::new(Some(receive_connections)), new_connections, - config, + config: Config { + endpoint_config, + client_config, + server_config, + multiaddr: multiaddr.clone(), + }, }); let channel = Channel::new(event_sender); if socket_addr.ip().is_unspecified() { @@ -556,10 +569,10 @@ impl Endpoint { .expect("we have a reference to the peer, so this will not fail; qed") } } else { - info!("sending address {:?}", address); + info!("sending address {:?}", multiaddr); reference .new_connections - .unbounded_send(ListenerEvent::NewAddress(address)) + .unbounded_send(ListenerEvent::NewAddress(multiaddr)) .expect("we have a reference to the peer, so this will not fail; qed"); } let endpoint = EndpointRef { reference, channel }; From 8f13f206c922c87cb6f019e59fc49e2ca1bb79a8 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 15 Mar 2020 13:26:46 -0400 Subject: [PATCH 141/202] Switch back to quinn-proto master --- transports/quic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index e99939f4b7f..ac471d4056e 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4.8" ipnet = "2.2.0" err-derive = "0.2.2" futures = "0.3.4" -quinn-proto = { git = "https://github.com/djc/quinn", branch = "cid-dedup" } +quinn-proto = { git = "https://github.com/djc/quinn" } async-std = "1.5.0" async-macros = "2.0.0" futures-timer = "3.0.2" From 8dc4a0a851d858ac156f4fb911ce37ce1828038a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 16 Mar 2020 10:49:12 -0400 Subject: [PATCH 142/202] Fix compilation of tests --- transports/quic/tests/tests.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index af3b8bbd101..1cea1b6ad12 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -134,7 +134,7 @@ fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let (listener, join) = Endpoint::new(Config::new(&keypair), addr.clone()).unwrap(); + let (listener, join) = Endpoint::new(Config::new(&keypair, addr.clone())).unwrap(); let mut incoming = listener.listen_on(addr).unwrap(); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. @@ -185,8 +185,8 @@ fn do_test() { let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); let addr: Multiaddr = "/ip4/127.0.0.1/udp/0/quic".parse().expect("bad address?"); - let quic_config = Config::new(&keypair2); - let (quic_endpoint, join) = Endpoint::new(quic_config, addr.clone()).unwrap(); + let quic_config = Config::new(&keypair2, addr.clone()); + let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); trace!("running tests"); let handle = async_std::task::spawn(async move { @@ -239,9 +239,9 @@ fn do_test() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); - let quic_config = Config::new(&keypair); + let quic_config = Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()); let (quic_endpoint, join) = - Endpoint::new(quic_config, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()).unwrap(); + Endpoint::new(quic_config).unwrap(); // Obtain a future socket through dialing let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); @@ -291,12 +291,12 @@ fn do_test() { fn replace_port_0_in_returned_multiaddr_ipv4() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = Config::new(&keypair); let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); - let (quic, join) = Endpoint::new(config, addr.clone()).expect("no error"); + let config = Config::new(&keypair, addr.clone()); + let (quic, join) = Endpoint::new(config).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -315,11 +315,11 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { fn replace_port_0_in_returned_multiaddr_ipv6() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = Config::new(&keypair); let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); - let (quic, join) = Endpoint::new(config, addr.clone()).expect("no error"); + let config = Config::new(&keypair, addr.clone()); + let (quic, join) = Endpoint::new(config).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -336,9 +336,9 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { fn larger_addr_denied() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let config = Config::new(&keypair); let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); - assert!(Endpoint::new(config, addr).is_err()) + let config = Config::new(&keypair, addr); + assert!(Endpoint::new(config).is_err()) } From 6c072c98ec116f751a8d74d9b84885c665e51813 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 10:59:35 -0400 Subject: [PATCH 143/202] Implement alternate X.509 verifier --- protocols/tls/src/lib.rs | 4 +- protocols/tls/src/verifier.rs | 16 +- protocols/tls/src/verifier/data/LICENSE | 22 ++ protocols/tls/src/verifier/data/README.md | 21 ++ .../tls/src/verifier/data/alg-ecdsa-p256.der | 1 + .../tls/src/verifier/data/alg-ecdsa-p384.der | Bin 0 -> 16 bytes .../src/verifier/data/alg-ecdsa-sha256.der | 1 + .../src/verifier/data/alg-ecdsa-sha384.der | 1 + .../tls/src/verifier/data/alg-ed25519.der | 1 + .../src/verifier/data/alg-rsa-encryption.der | Bin 0 -> 13 bytes .../verifier/data/alg-rsa-pkcs1-sha256.der | Bin 0 -> 13 bytes .../verifier/data/alg-rsa-pkcs1-sha384.der | Bin 0 -> 13 bytes .../verifier/data/alg-rsa-pkcs1-sha512.der | Bin 0 -> 13 bytes .../verifier/data/alg-rsa-pss-sha256-v0.der | 1 + .../verifier/data/alg-rsa-pss-sha256-v1.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha256-v2.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha256-v3.der | Bin 0 -> 54 bytes .../src/verifier/data/alg-rsa-pss-sha256.der | Bin 0 -> 65 bytes .../verifier/data/alg-rsa-pss-sha384-v0.der | 1 + .../verifier/data/alg-rsa-pss-sha384-v1.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha384-v2.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha384-v3.der | Bin 0 -> 54 bytes .../src/verifier/data/alg-rsa-pss-sha384.der | Bin 0 -> 65 bytes .../verifier/data/alg-rsa-pss-sha512-v0.der | 1 + .../verifier/data/alg-rsa-pss-sha512-v1.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha512-v2.der | Bin 0 -> 52 bytes .../verifier/data/alg-rsa-pss-sha512-v3.der | Bin 0 -> 54 bytes .../src/verifier/data/alg-rsa-pss-sha512.der | Bin 0 -> 65 bytes .../tls/src/verifier/data/alg-rsa-pss.der | 1 + protocols/tls/src/verifier/x509.rs | 274 ++++++++++++++++++ transports/quic/src/endpoint.rs | 2 +- transports/quic/src/error.rs | 5 +- transports/quic/tests/tests.rs | 7 +- 33 files changed, 342 insertions(+), 17 deletions(-) create mode 100644 protocols/tls/src/verifier/data/LICENSE create mode 100644 protocols/tls/src/verifier/data/README.md create mode 100644 protocols/tls/src/verifier/data/alg-ecdsa-p256.der create mode 100644 protocols/tls/src/verifier/data/alg-ecdsa-p384.der create mode 100644 protocols/tls/src/verifier/data/alg-ecdsa-sha256.der create mode 100644 protocols/tls/src/verifier/data/alg-ecdsa-sha384.der create mode 100644 protocols/tls/src/verifier/data/alg-ed25519.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-encryption.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha256.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha384.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha512.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v0.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v1.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v2.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v3.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha256.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v1.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v2.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v3.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha384.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v1.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v2.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v3.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss-sha512.der create mode 100644 protocols/tls/src/verifier/data/alg-rsa-pss.der create mode 100644 protocols/tls/src/verifier/x509.rs diff --git a/protocols/tls/src/lib.rs b/protocols/tls/src/lib.rs index d13f230a3a3..4cc694b77e7 100644 --- a/protocols/tls/src/lib.rs +++ b/protocols/tls/src/lib.rs @@ -85,7 +85,8 @@ fn make_client_config( ) -> rustls::ClientConfig { let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; - crypto.enable_early_data = true; + crypto.alpn_protocols = vec![b"libp2p".to_vec()]; + crypto.enable_early_data = false; crypto .set_single_client_cert(vec![certificate], key) .expect("we have a valid certificate; qed"); @@ -100,6 +101,7 @@ fn make_server_config( ) -> rustls::ServerConfig { let mut crypto = rustls::ServerConfig::new(verifier); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; + crypto.alpn_protocols = vec![b"libp2p".to_vec()]; crypto .set_single_cert(vec![certificate], key) .expect("we have a valid certificate; qed"); diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index d53eb9fd377..21f1ec272da 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod x509; static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, @@ -81,13 +82,8 @@ fn verify_libp2p_extension( use libp2p_core::identity::PublicKey; use ring::{error::Unspecified, io::der}; let certificate_key = subject_public_key_info.read_all(Unspecified, |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?.read_all( - Unspecified, - |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - der::bit_string_with_no_unused_bits(&mut reader) - }, - ) + der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; + der::bit_string_with_no_unused_bits(&mut reader) })?; extension.read_all(Unspecified, |mut reader| { let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; @@ -187,7 +183,9 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { &[], get_time()?, ) - .map(|()| rustls::ClientCertVerified::assertion()) - .map_err(rustls::TLSError::WebPKIError) + .map_err(rustls::TLSError::WebPKIError)?; + x509::verify_certificate(x509::parse_certificate(presented_certs[0].as_ref()).unwrap()) + .unwrap(); + Ok(rustls::ClientCertVerified::assertion()) } } diff --git a/protocols/tls/src/verifier/data/LICENSE b/protocols/tls/src/verifier/data/LICENSE new file mode 100644 index 00000000000..93eb6d87eff --- /dev/null +++ b/protocols/tls/src/verifier/data/LICENSE @@ -0,0 +1,22 @@ +The files in this directory are taken from webpki. The license of webpki +is below: + +Except as otherwise noted, this project is licensed under the following +(ISC-style) terms: + +Copyright 2015 Brian Smith. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +The files under third-party/chromium are licensed as described in +third-party/chromium/LICENSE. diff --git a/protocols/tls/src/verifier/data/README.md b/protocols/tls/src/verifier/data/README.md new file mode 100644 index 00000000000..78fc7788d37 --- /dev/null +++ b/protocols/tls/src/verifier/data/README.md @@ -0,0 +1,21 @@ +These files contain the binary DER encoding of the *values* of some +ASN.1 [`AlgorithmIdentifier`]s, without the outer `SEQUENCE` tag or the outer +length component. + +These files were encoded with the help of [der-ascii]. They can be decoded +using: + +```sh +go get github.com/google/der-ascii/cmd/der2ascii +der2ascii -i -o .ascii +``` + +New or modified der-ascii files can be encoded using: + +```sh +go get github.com/google/der-ascii/cmd/ascii2der +ascii2der i .ascii -o +``` + +[`AlgorithmIdentifier`]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2] +[der-ascii]: https://github.com/google/der-ascii diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-p256.der b/protocols/tls/src/verifier/data/alg-ecdsa-p256.der new file mode 100644 index 00000000000..d49c30da3df --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-ecdsa-p256.der @@ -0,0 +1 @@ +*H=*H= \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-p384.der b/protocols/tls/src/verifier/data/alg-ecdsa-p384.der new file mode 100644 index 0000000000000000000000000000000000000000..8b24916caf9bfaeaecb98407738772aa659fc65e GIT binary patch literal 16 XcmZQ$*J|@PXUoLM#;V=O!k`2I8~OtA literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-sha256.der b/protocols/tls/src/verifier/data/alg-ecdsa-sha256.der new file mode 100644 index 00000000000..b2ee1289144 --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-ecdsa-sha256.der @@ -0,0 +1 @@ +*H= \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-sha384.der b/protocols/tls/src/verifier/data/alg-ecdsa-sha384.der new file mode 100644 index 00000000000..7c61d3aabdb --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-ecdsa-sha384.der @@ -0,0 +1 @@ +*H= \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-ed25519.der b/protocols/tls/src/verifier/data/alg-ed25519.der new file mode 100644 index 00000000000..7ca46fd9522 --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-ed25519.der @@ -0,0 +1 @@ ++ep \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-rsa-encryption.der b/protocols/tls/src/verifier/data/alg-rsa-encryption.der new file mode 100644 index 0000000000000000000000000000000000000000..77d159a1c6fcc68fac95281029ab0c6ce52bb58f GIT binary patch literal 13 UcmZSM)N1o+`_9YA$jHh702QtRng9R* literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha256.der b/protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha256.der new file mode 100644 index 0000000000000000000000000000000000000000..ab52bcd80b62813edb30a9ab628a5530b2ada8eb GIT binary patch literal 13 UcmZSM)N1o+`_9YA$j!@5qwPB{BO`|aFOnQ9!y;xTMg;&5k_k}& literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v2.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v2.der new file mode 100644 index 0000000000000000000000000000000000000000..787d2d53b99857e577897d7fb12d13990e66c6fc GIT binary patch literal 52 ycmXpoTEK6>%f^||=E0cC%)-RT%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mg;%`C<#vh literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v3.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v3.der new file mode 100644 index 0000000000000000000000000000000000000000..cc80b1dcd41cc950f4f09343c9ea44f90e225dfc GIT binary patch literal 54 wcmXpoS-@|=%f^||=E0cC%)-RT%CJzzK#Gl1tIebBJ1-+62b%06W+p}j02gfuTmS$7 literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha256.der new file mode 100644 index 0000000000000000000000000000000000000000..87328f7c64bc252ea54283bb542fbe36ac98e9a8 GIT binary patch literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^7`sVWEtH6dPOx2b%06W+p}j0JRPa A`2YX_ literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der new file mode 100644 index 00000000000..3aac20fc905 --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der @@ -0,0 +1 @@ +00 0  `He0 *H 0  `He0 \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v1.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v1.der new file mode 100644 index 0000000000000000000000000000000000000000..899aed10a89c5acdfff00f5a5c464a8f91fe7dff GIT binary patch literal 52 ycmXpoTEJ_-&BmF~=E0cC%)-R9P{u%tjZ>@5qwPB{BO`|aFOnQ9!y;xTMgss3&Iw}x literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v2.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v2.der new file mode 100644 index 0000000000000000000000000000000000000000..499a319cd175cd7f8861a8f6fbbe4e423d331bba GIT binary patch literal 52 ycmXpoTEK6>%f^||=E0cC%)-RP%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mgsr^VhLdY literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v3.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v3.der new file mode 100644 index 0000000000000000000000000000000000000000..d27817aec985d905dc2fb0a9628ed5317b3744c7 GIT binary patch literal 54 wcmXpoS-@|=%f^||=E0cC%)-RP%CJzzK#Gl1tIebBJ1-+62b%06W+p}h02iwXZU6uP literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha384.der new file mode 100644 index 0000000000000000000000000000000000000000..9c3b170f2bef5721f65f49f0e3beb46f89c7ed90 GIT binary patch literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^7ukVWEtH6dPOx2b%06W+p}h0JTgE A3jhEB literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der new file mode 100644 index 00000000000..f6a490c9d01 --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der @@ -0,0 +1 @@ +00 0  `He0 *H 0  `He@ \ No newline at end of file diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v1.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v1.der new file mode 100644 index 0000000000000000000000000000000000000000..bb4418cb0db939212f0b59fb683e3c515057c0a8 GIT binary patch literal 52 ycmXpoTEJ_-&BmF~=E0cC%)-RHP{u%tjZ>@5qwPB{BO`|aFOnQ9!y;xTMh5^83JG-p literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v2.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v2.der new file mode 100644 index 0000000000000000000000000000000000000000..4612c5700519cec06536233a473d7c59ae9f71bc GIT binary patch literal 52 ycmXpoTEK6>%f^||=E0cC%)-RX%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mh5@|oC$LP literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v3.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v3.der new file mode 100644 index 0000000000000000000000000000000000000000..7f067108e1cc36f354a13369eeee9d7cd10190c5 GIT binary patch literal 54 wcmXpoS-@|=%f^||=E0cC%)-RX%CJzzK#Gl1tIebBJ1-+62b%06W+p}l02k>AfB*mh literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512.der b/protocols/tls/src/verifier/data/alg-rsa-pss-sha512.der new file mode 100644 index 0000000000000000000000000000000000000000..c0ad57d612a2bd66d003342a1d32ca975baed06d GIT binary patch literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^8J!VWEtH6dPOx2b%06W+p}l0JVw? A9RL6T literal 0 HcmV?d00001 diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss.der b/protocols/tls/src/verifier/data/alg-rsa-pss.der new file mode 100644 index 00000000000..f2267d4935a --- /dev/null +++ b/protocols/tls/src/verifier/data/alg-rsa-pss.der @@ -0,0 +1 @@ + *H  diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs new file mode 100644 index 00000000000..1afd77dcd1f --- /dev/null +++ b/protocols/tls/src/verifier/x509.rs @@ -0,0 +1,274 @@ +use libp2p_core::identity::PublicKey; +use ring::{error::Unspecified, io::der, signature}; +use webpki::Error; + +pub(super) struct X509Certificate<'a> { + libp2p_extension: Libp2pExtension<'a>, + x509_tbs: untrusted::Input<'a>, + #[allow(dead_code)] + x509_validity: untrusted::Input<'a>, + x509_spki: untrusted::Input<'a>, + x509_signature_algorithm: untrusted::Input<'a>, + x509_signature: untrusted::Input<'a>, +} + +struct Libp2pExtension<'a> { + peer_key: PublicKey, + signature: &'a [u8], +} + +fn expect_sequence<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::Sequence).map_err(|Unspecified| Error::BadDER) +} + +fn expect_oid<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::OID).map_err(|Unspecified| Error::BadDER) +} + +fn expect_octet_string<'a>( + reader: &mut untrusted::Reader<'a>, +) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::OctetString) + .map_err(|Unspecified| Error::BadDER) +} + +fn parse_libp2p_extension<'a>( + extension: untrusted::Input<'a>, +) -> Result, Error> { + untrusted::Input::read_all(&extension, Unspecified, |reader| { + der::nested(reader, der::Tag::Sequence, Unspecified, |reader| { + let public_key = der::bit_string_with_no_unused_bits(reader)?; + let signature = der::bit_string_with_no_unused_bits(reader)?; + // We deliberately discard the error information because this is + // either a broken peer or an attack. + let peer_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) + .map_err(|_| Unspecified)?; + Ok(Libp2pExtension { + signature: signature.as_slice_less_safe(), + peer_key, + }) + }) + }) + .map_err(|Unspecified| Error::ExtensionValueInvalid) +} + +/// Parse X.509 extensions +fn parse_extensions<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + let mut libp2p_extension = None; + while !reader.at_end() { + const BOOLEAN: u8 = der::Tag::Boolean as _; + const OCTET_STRING: u8 = der::Tag::OctetString as _; + der::nested(reader, der::Tag::Sequence, Error::BadDER, |reader| { + let oid = expect_oid(reader)?; + let (tag, data) = der::read_tag_and_get_value(reader).map_err(|_| Error::BadDER)?; + let (critical, extension) = match tag { + BOOLEAN => match data.as_slice_less_safe() { + [0xFF] => (true, expect_octet_string(reader)?), + [0] => (false, expect_octet_string(reader)?), + _ => return Err(Error::BadDER), + }, + OCTET_STRING => (false, data), + _ => return Err(Error::BadDER), + }; + match oid.as_slice_less_safe() { + super::super::LIBP2P_OID_BYTES if libp2p_extension.is_some() => Err(Error::BadDER), + super::super::LIBP2P_OID_BYTES => { + libp2p_extension = Some(parse_libp2p_extension(extension)?); + Ok(()) + } + _ if critical => return Err(Error::UnsupportedCriticalExtension), + _ => Ok(()), + } + })? + } + libp2p_extension.ok_or(Error::BadDER) +} + +/// Extracts the algorithm id and public key from a certificate +pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { + let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = + untrusted::Input::from(certificate) + .read_all(Error::BadDER, expect_sequence)? + .read_all(Error::BadDER, |reader| { + // tbsCertificate + let tbs = reader.read_partial(|reader| expect_sequence(reader))?; + // signatureAlgorithm + let signature_algorithm = expect_sequence(reader)?; + // signatureValue + let signature = + der::bit_string_with_no_unused_bits(reader).map_err(|_| Error::BadDER)?; + Ok((tbs, signature_algorithm, signature)) + })?; + let (x509_validity, x509_spki, libp2p_extension) = + inner_tbs.read_all(Error::BadDER, |mut reader| { + // We require extensions, which means we require version 3 + if reader + .read_bytes(5) + .map_err(|_| Error::BadDER)? + .as_slice_less_safe() + != [160, 3, 2, 1, 2] + { + return Err(Error::UnsupportedCertVersion); + } + // serialNumber + der::positive_integer(&mut reader).map_err(|_| Error::BadDER)?; + // signature + if expect_sequence(reader)? != x509_signature_algorithm { + // signature algorithms don’t match + return Err(Error::SignatureAlgorithmMismatch); + } + // issuer + expect_sequence(reader)?; + // validity + let x509_validity = expect_sequence(reader)?; + // subject + expect_sequence(reader)?; + // subjectPublicKeyInfo + let spki = expect_sequence(reader)?; + // subjectUniqueId and issuerUniqueId are unsupported + // parse the extensions + let libp2p_extension = der::nested( + reader, + der::Tag::ContextSpecificConstructed3, + Error::BadDER, + |reader| der::nested(reader, der::Tag::Sequence, Error::BadDER, parse_extensions), + )?; + Ok((x509_validity, spki, libp2p_extension)) + })?; + Ok(X509Certificate { + libp2p_extension, + x509_tbs, + x509_validity, + x509_spki, + x509_signature, + x509_signature_algorithm, + }) +} + +fn verify_libp2p_signature( + libp2p_extension: Libp2pExtension<'_>, + x509_pkey_bytes: &[u8], +) -> Result<(), Error> { + let mut v = + Vec::with_capacity(super::super::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); + v.extend_from_slice(&super::super::LIBP2P_SIGNING_PREFIX[..]); + v.extend_from_slice(x509_pkey_bytes); + if libp2p_extension + .peer_key + .verify(&v, libp2p_extension.signature) + { + Ok(()) + } else { + Err(Error::UnknownIssuer) + } +} + +pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<(), Error> { + let X509Certificate { + x509_tbs, + x509_spki, + x509_signature, + x509_signature_algorithm, + libp2p_extension, + .. + } = certificate; + + let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |reader| { + let x509_pkey_alg = expect_sequence(reader)?.as_slice_less_safe(); + let x509_pkey_bytes = der::bit_string_with_no_unused_bits(reader) + .map_err(|Unspecified| Error::BadDER)? + .as_slice_less_safe(); + Ok((x509_pkey_alg, x509_pkey_bytes)) + })?; + let pkey = verify_self_signature( + x509_pkey_alg, + x509_pkey_bytes, + x509_signature_algorithm.as_slice_less_safe(), + )?; + verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; + pkey.verify( + x509_tbs.as_slice_less_safe(), + x509_signature.as_slice_less_safe(), + ) + .map_err(|_| Error::InvalidSignatureForPublicKey) +} + +fn verify_self_signature<'a>( + x509_pkey_alg: &[u8], + x509_pkey_bytes: &'a [u8], + x509_signature_algorithm: &[u8], +) -> Result, Error> { + let algorithm: &'static dyn signature::VerificationAlgorithm = + match (x509_signature_algorithm, x509_pkey_alg) { + (include_bytes!("data/alg-rsa-pkcs1-sha256.der"), _) => { + if x509_pkey_alg == include_bytes!("data/alg-rsa-encryption.der") { + &signature::RSA_PKCS1_2048_8192_SHA256 + } else { + return Err(Error::UnsupportedSignatureAlgorithmForPublicKey); + } + } + ( + include_bytes!("data/alg-rsa-pkcs1-sha384.der"), + include_bytes!("data/alg-rsa-encryption.der"), + ) => &signature::RSA_PKCS1_2048_8192_SHA384, + ( + include_bytes!("data/alg-rsa-pkcs1-sha512.der"), + include_bytes!("data/alg-rsa-encryption.der"), + ) => &signature::RSA_PKCS1_2048_8192_SHA512, + ( + include_bytes!("data/alg-ecdsa-sha256.der"), + include_bytes!("data/alg-ecdsa-p256.der"), + ) => &signature::ECDSA_P256_SHA256_ASN1, + ( + include_bytes!("data/alg-ecdsa-sha384.der"), + include_bytes!("data/alg-ecdsa-p384.der"), + ) => &signature::ECDSA_P384_SHA384_ASN1, + ( + include_bytes!("data/alg-ecdsa-sha384.der"), + include_bytes!("data/alg-ecdsa-p256.der"), + ) => &signature::ECDSA_P256_SHA384_ASN1, + ( + include_bytes!("data/alg-ecdsa-sha256.der"), + include_bytes!("data/alg-ecdsa-p384.der"), + ) => &signature::ECDSA_P384_SHA256_ASN1, + (include_bytes!("data/alg-ed25519.der"), include_bytes!("data/alg-ed25519.der")) => { + &signature::ED25519 + } + ( + [0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, ..], + include_bytes!("data/alg-rsa-encryption.der"), + ) => parse_rsa_pss(&x509_pkey_alg[11..])?, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }; + Ok(signature::UnparsedPublicKey::new( + algorithm, + x509_pkey_bytes, + )) +} + +// While the RSA-PSS parameters are a ASN.1 SEQUENCE, it is simpler to match +// against the 12 different possibilities. The binary files are *generated* by a +// Go program. +fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters, Error> { + match data { + include_bytes!("data/alg-rsa-pss-sha256-v0.der") + | include_bytes!("data/alg-rsa-pss-sha256-v1.der") + | include_bytes!("data/alg-rsa-pss-sha256-v2.der") + | include_bytes!("data/alg-rsa-pss-sha256-v3.der") => { + Ok(&signature::RSA_PSS_2048_8192_SHA256) + } + include_bytes!("data/alg-rsa-pss-sha384-v0.der") + | include_bytes!("data/alg-rsa-pss-sha384-v1.der") + | include_bytes!("data/alg-rsa-pss-sha384-v2.der") + | include_bytes!("data/alg-rsa-pss-sha384-v3.der") => { + Ok(&signature::RSA_PSS_2048_8192_SHA384) + } + include_bytes!("data/alg-rsa-pss-sha512-v0.der") + | include_bytes!("data/alg-rsa-pss-sha512-v1.der") + | include_bytes!("data/alg-rsa-pss-sha512-v2.der") + | include_bytes!("data/alg-rsa-pss-sha512-v3.der") => { + Ok(&signature::RSA_PSS_2048_8192_SHA512) + } + _ => Err(Error::UnsupportedSignatureAlgorithm), + } +} diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index f7e5148d27f..54f9ad9baab 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -59,7 +59,7 @@ impl Config { let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); - transport.keep_alive_interval(Some(Duration::from_millis(1000))); + transport.keep_alive_interval(Some(Duration::from_millis(10))); let transport = Arc::new(transport); let (client_tls_config, server_tls_config) = tls::make_tls_config(keypair); let mut server_config = quinn_proto::ServerConfig::default(); diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 1ae3b70e45c..7abd8605261 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -71,8 +71,9 @@ impl From for io::Error { match e { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), Error::ConnectionError(e) => e.into(), - | e @ Error::NetworkFailure - | e @ Error::ConnectionClosing => io::Error::new(ErrorKind::Other, e), + e @ Error::NetworkFailure | e @ Error::ConnectionClosing => { + io::Error::new(ErrorKind::Other, e) + } e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 1cea1b6ad12..135ef4c9b3b 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -240,8 +240,7 @@ fn do_test() { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); let quic_config = Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()); - let (quic_endpoint, join) = - Endpoint::new(quic_config).unwrap(); + let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); // Obtain a future socket through dialing let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); @@ -339,6 +338,6 @@ fn larger_addr_denied() { let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); - let config = Config::new(&keypair, addr); - assert!(Endpoint::new(config).is_err()) + let config = Config::new(&keypair, addr); + assert!(Endpoint::new(config).is_err()) } From ece2ff289c45e64e89d6ac64a4fb8f1b0e966c21 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 16:54:10 -0400 Subject: [PATCH 144/202] Fix doc tests --- transports/quic/src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c224491a6e5..35dbdf3d716 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -29,12 +29,9 @@ //! use libp2p_core::Multiaddr; //! //! let keypair = libp2p_core::identity::Keypair::generate_ed25519(); -//! let quic_config = Config::new(&keypair); -//! let quic_endpoint = Endpoint::new( -//! quic_config, -//! "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"), -//! ) -//! .expect("I/O error"); +//! let addr = "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"); +//! let quic_config = Config::new(&keypair, addr); +//! let quic_endpoint = Endpoint::new(quic_config).expect("I/O error"); //! ``` //! //! The `Config` structs implements the `Transport` trait of the `swarm` library. See the From 26fc93b557a1d6410fdd06cb1b4c0a70c797d28a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 16:54:32 -0400 Subject: [PATCH 145/202] protocols/tls needs dangerous_configuration --- protocols/tls/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml index fb8e3ce769a..6ecd769468d 100644 --- a/protocols/tls/Cargo.toml +++ b/protocols/tls/Cargo.toml @@ -10,11 +10,11 @@ keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] [dependencies] -rustls = "0.17.0" +rustls = { version = "0.17.0", features = ["dangerous_configuration"] } ring = "0.16.11" rcgen = "0.7.0" webpki = "0.21.2" untrusted = "0.7.0" log = "0.4.8" libp2p-core = { path = "../../core", version = "0.16.0" } -yasna = "0.3.1" \ No newline at end of file +yasna = "0.3.1" From fc39b7b1ebb47ac3a918b195791746935353605b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 17:07:40 -0400 Subject: [PATCH 146/202] Refactor certificate verification code --- protocols/tls/src/verifier.rs | 1 + protocols/tls/src/verifier/der.rs | 57 +++++++ protocols/tls/src/verifier/x509.rs | 244 ++++++++++++++++------------- 3 files changed, 195 insertions(+), 107 deletions(-) create mode 100644 protocols/tls/src/verifier/der.rs diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index 21f1ec272da..aec938371e7 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. mod x509; +mod der; static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, diff --git a/protocols/tls/src/verifier/der.rs b/protocols/tls/src/verifier/der.rs new file mode 100644 index 00000000000..c0984adabdf --- /dev/null +++ b/protocols/tls/src/verifier/der.rs @@ -0,0 +1,57 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! A wrapper for [*ring*](https://github.com/briansmith/ring)’s DER parser. +use ring::{error::Unspecified, io::der}; +use untrusted::{Input, Reader}; +use webpki::Error; +type Result = std::result::Result; +pub(super) use der::Tag; + +#[inline] +pub(super) fn expect_tag_and_get_value<'a>(input: &mut Reader<'a>, tag: Tag) -> Result> { + der::expect_tag_and_get_value(input, tag).map_err(|Unspecified| Error::BadDER) +} + +#[inline] +pub(super) fn bit_string_with_no_unused_bits<'a>(input: &mut Reader<'a>) -> Result> { + der::bit_string_with_no_unused_bits(input).map_err(|Unspecified| Error::BadDER) +} + +#[inline] +pub(super) fn nested_sequence<'a, T, U>(input: &mut Reader<'a>, decoder: U) -> Result +where + U: FnMut(&mut Reader<'a>) -> Result, +{ + nested(input, Tag::Sequence, decoder) +} + +#[inline] +pub(super) fn nested<'a, T, U>(input: &mut Reader<'a>, tag: Tag, decoder: U) -> Result +where + U: FnMut(&mut Reader<'a>) -> Result, +{ + der::nested(input, tag, Error::BadDER, decoder) +} + +#[inline] +pub(super) fn positive_integer<'a>(input: &mut Reader<'a>) -> Result> { + der::positive_integer(input).map_err(|Unspecified| Error::BadDER) +} diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index 1afd77dcd1f..1cac44a9a19 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -1,108 +1,148 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::der; +use der::Tag; use libp2p_core::identity::PublicKey; -use ring::{error::Unspecified, io::der, signature}; +use ring::signature; +use untrusted::{Input, Reader}; use webpki::Error; pub(super) struct X509Certificate<'a> { libp2p_extension: Libp2pExtension<'a>, - x509_tbs: untrusted::Input<'a>, + x509_tbs: Input<'a>, #[allow(dead_code)] - x509_validity: untrusted::Input<'a>, - x509_spki: untrusted::Input<'a>, - x509_signature_algorithm: untrusted::Input<'a>, - x509_signature: untrusted::Input<'a>, + x509_validity: Input<'a>, + x509_spki: SubjectPublicKeyInfo<'a>, + x509_signature_algorithm: Input<'a>, + x509_signature: Input<'a>, } -struct Libp2pExtension<'a> { - peer_key: PublicKey, - signature: &'a [u8], +type Result = std::result::Result; + +/// An X.509 SubjectPublicKeyInfo +struct SubjectPublicKeyInfo<'a> { + x509_pkey_algorithm: &'a [u8], + x509_pkey_bytes: &'a [u8], } -fn expect_sequence<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::Sequence).map_err(|Unspecified| Error::BadDER) +/// Read an X.509 SubjectPublicKeyInfo +fn read_spki<'a>(input: Input<'a>) -> Result> { + input.read_all(Error::BadDER, |input| { + let algorithm = der::expect_tag_and_get_value(input, Tag::Sequence)?; + let public_key = der::bit_string_with_no_unused_bits(input)?; + Ok(SubjectPublicKeyInfo { + x509_pkey_algorithm: algorithm.as_slice_less_safe(), + x509_pkey_bytes: public_key.as_slice_less_safe(), + }) + }) } -fn expect_oid<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::OID).map_err(|Unspecified| Error::BadDER) +/// Read a single extension. If it is the libp2p extension, store it in `libp2p`, unless `libp2p`, +/// is already `Some`, which is an error. Any critical extension that is not the libp2p extension +/// is also an error. +fn read_extension<'a>( + input: &mut Reader<'a>, + libp2p: &mut Option>, +) -> Result<()> { + use super::super::LIBP2P_OID_BYTES; + der::nested_sequence(input, |input| { + let oid = der::expect_tag_and_get_value(input, Tag::OID)?; + let critical = input.peek(Tag::Boolean as _); + if critical && input.read_bytes(3) != Ok(Input::from(&[1, 1, 255][..])) { + return Err(Error::BadDER); + } + let extension = + der::expect_tag_and_get_value(input, Tag::OctetString).map_err(|_| Error::BadDER)?; + match oid.as_slice_less_safe() { + LIBP2P_OID_BYTES if libp2p.is_some() => Err(Error::BadDER), + LIBP2P_OID_BYTES => { + *libp2p = Some( + parse_libp2p_extension(extension).map_err(|_| Error::ExtensionValueInvalid)?, + ); + Ok(()) + } + _ if critical => return Err(Error::UnsupportedCriticalExtension), + _ => Ok(()), + } + }) } -fn expect_octet_string<'a>( - reader: &mut untrusted::Reader<'a>, -) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::OctetString) - .map_err(|Unspecified| Error::BadDER) +/// Read X.509 extensions. +fn read_extensions<'a>(input: Input<'a>) -> Result> { + input.read_all(Error::BadDER, |input| { + der::nested_sequence(input, |input| { + let mut libp2p = None; + while !input.at_end() { + read_extension(input, &mut libp2p)?; + } + libp2p.ok_or(Error::RequiredEKUNotFound) + }) + }) } -fn parse_libp2p_extension<'a>( - extension: untrusted::Input<'a>, -) -> Result, Error> { - untrusted::Input::read_all(&extension, Unspecified, |reader| { - der::nested(reader, der::Tag::Sequence, Unspecified, |reader| { - let public_key = der::bit_string_with_no_unused_bits(reader)?; - let signature = der::bit_string_with_no_unused_bits(reader)?; +struct Libp2pExtension<'a> { + peer_key: PublicKey, + signature: &'a [u8], +} + +#[inline] +fn expect_sequence<'a>(input: &mut Reader<'a>) -> Result> { + der::expect_tag_and_get_value(input, Tag::Sequence) +} + +fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result> { + Input::read_all(&extension, Error::ExtensionValueInvalid, |input| { + der::nested_sequence(input, |input| { + let public_key = der::bit_string_with_no_unused_bits(input)?.as_slice_less_safe(); + let signature = der::bit_string_with_no_unused_bits(input)?.as_slice_less_safe(); // We deliberately discard the error information because this is // either a broken peer or an attack. - let peer_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) - .map_err(|_| Unspecified)?; + let peer_key = + PublicKey::from_protobuf_encoding(public_key).map_err(|_| Error::BadDER)?; Ok(Libp2pExtension { - signature: signature.as_slice_less_safe(), + signature, peer_key, }) }) }) - .map_err(|Unspecified| Error::ExtensionValueInvalid) -} - -/// Parse X.509 extensions -fn parse_extensions<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { - let mut libp2p_extension = None; - while !reader.at_end() { - const BOOLEAN: u8 = der::Tag::Boolean as _; - const OCTET_STRING: u8 = der::Tag::OctetString as _; - der::nested(reader, der::Tag::Sequence, Error::BadDER, |reader| { - let oid = expect_oid(reader)?; - let (tag, data) = der::read_tag_and_get_value(reader).map_err(|_| Error::BadDER)?; - let (critical, extension) = match tag { - BOOLEAN => match data.as_slice_less_safe() { - [0xFF] => (true, expect_octet_string(reader)?), - [0] => (false, expect_octet_string(reader)?), - _ => return Err(Error::BadDER), - }, - OCTET_STRING => (false, data), - _ => return Err(Error::BadDER), - }; - match oid.as_slice_less_safe() { - super::super::LIBP2P_OID_BYTES if libp2p_extension.is_some() => Err(Error::BadDER), - super::super::LIBP2P_OID_BYTES => { - libp2p_extension = Some(parse_libp2p_extension(extension)?); - Ok(()) - } - _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => Ok(()), - } - })? - } - libp2p_extension.ok_or(Error::BadDER) } /// Extracts the algorithm id and public key from a certificate -pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { - let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = - untrusted::Input::from(certificate) - .read_all(Error::BadDER, expect_sequence)? - .read_all(Error::BadDER, |reader| { - // tbsCertificate - let tbs = reader.read_partial(|reader| expect_sequence(reader))?; - // signatureAlgorithm - let signature_algorithm = expect_sequence(reader)?; - // signatureValue - let signature = - der::bit_string_with_no_unused_bits(reader).map_err(|_| Error::BadDER)?; - Ok((tbs, signature_algorithm, signature)) - })?; +pub(super) fn parse_certificate(certificate: &[u8]) -> Result> { + let certificate = Input::from(certificate).read_all(Error::BadDER, expect_sequence)?; + let (x509_tbs, inner_tbs, x509_signature_algorithm, x509_signature) = + certificate.read_all(Error::BadDER, |input| { + // tbsCertificate + let (signed_tbs, parsed_tbs) = input.read_partial(expect_sequence)?; + // signatureAlgorithm + let signature_algorithm = expect_sequence(input)?; + // signatureValue + let signature = der::bit_string_with_no_unused_bits(input)?; + Ok((signed_tbs, parsed_tbs, signature_algorithm, signature)) + })?; + let (x509_validity, x509_spki, libp2p_extension) = - inner_tbs.read_all(Error::BadDER, |mut reader| { + inner_tbs.read_all(Error::BadDER, |mut input| { // We require extensions, which means we require version 3 - if reader + if input .read_bytes(5) .map_err(|_| Error::BadDER)? .as_slice_less_safe() @@ -111,35 +151,29 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result Result, x509_pkey_bytes: &[u8], -) -> Result<(), Error> { +) -> Result<()> { let mut v = Vec::with_capacity(super::super::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); v.extend_from_slice(&super::super::LIBP2P_SIGNING_PREFIX[..]); @@ -163,25 +197,21 @@ fn verify_libp2p_signature( } } -pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<(), Error> { +pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<()> { let X509Certificate { x509_tbs, - x509_spki, + x509_spki: + SubjectPublicKeyInfo { + x509_pkey_algorithm, + x509_pkey_bytes, + }, x509_signature, x509_signature_algorithm, libp2p_extension, .. } = certificate; - - let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |reader| { - let x509_pkey_alg = expect_sequence(reader)?.as_slice_less_safe(); - let x509_pkey_bytes = der::bit_string_with_no_unused_bits(reader) - .map_err(|Unspecified| Error::BadDER)? - .as_slice_less_safe(); - Ok((x509_pkey_alg, x509_pkey_bytes)) - })?; let pkey = verify_self_signature( - x509_pkey_alg, + x509_pkey_algorithm, x509_pkey_bytes, x509_signature_algorithm.as_slice_less_safe(), )?; @@ -197,7 +227,7 @@ fn verify_self_signature<'a>( x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], -) -> Result, Error> { +) -> Result> { let algorithm: &'static dyn signature::VerificationAlgorithm = match (x509_signature_algorithm, x509_pkey_alg) { (include_bytes!("data/alg-rsa-pkcs1-sha256.der"), _) => { @@ -249,7 +279,7 @@ fn verify_self_signature<'a>( // While the RSA-PSS parameters are a ASN.1 SEQUENCE, it is simpler to match // against the 12 different possibilities. The binary files are *generated* by a // Go program. -fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters, Error> { +fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters> { match data { include_bytes!("data/alg-rsa-pss-sha256-v0.der") | include_bytes!("data/alg-rsa-pss-sha256-v1.der") From 77fa579d7443e686e1e130220c7ee6b0d9f7af38 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 20:31:04 -0400 Subject: [PATCH 147/202] Revert "Refactor certificate verification code" This reverts commit fc39b7b1ebb47ac3a918b195791746935353605b. --- protocols/tls/src/verifier.rs | 1 - protocols/tls/src/verifier/der.rs | 57 ------- protocols/tls/src/verifier/x509.rs | 244 +++++++++++++---------------- 3 files changed, 107 insertions(+), 195 deletions(-) delete mode 100644 protocols/tls/src/verifier/der.rs diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index aec938371e7..21f1ec272da 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. mod x509; -mod der; static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { &[ &webpki::ECDSA_P256_SHA256, diff --git a/protocols/tls/src/verifier/der.rs b/protocols/tls/src/verifier/der.rs deleted file mode 100644 index c0984adabdf..00000000000 --- a/protocols/tls/src/verifier/der.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! A wrapper for [*ring*](https://github.com/briansmith/ring)’s DER parser. -use ring::{error::Unspecified, io::der}; -use untrusted::{Input, Reader}; -use webpki::Error; -type Result = std::result::Result; -pub(super) use der::Tag; - -#[inline] -pub(super) fn expect_tag_and_get_value<'a>(input: &mut Reader<'a>, tag: Tag) -> Result> { - der::expect_tag_and_get_value(input, tag).map_err(|Unspecified| Error::BadDER) -} - -#[inline] -pub(super) fn bit_string_with_no_unused_bits<'a>(input: &mut Reader<'a>) -> Result> { - der::bit_string_with_no_unused_bits(input).map_err(|Unspecified| Error::BadDER) -} - -#[inline] -pub(super) fn nested_sequence<'a, T, U>(input: &mut Reader<'a>, decoder: U) -> Result -where - U: FnMut(&mut Reader<'a>) -> Result, -{ - nested(input, Tag::Sequence, decoder) -} - -#[inline] -pub(super) fn nested<'a, T, U>(input: &mut Reader<'a>, tag: Tag, decoder: U) -> Result -where - U: FnMut(&mut Reader<'a>) -> Result, -{ - der::nested(input, tag, Error::BadDER, decoder) -} - -#[inline] -pub(super) fn positive_integer<'a>(input: &mut Reader<'a>) -> Result> { - der::positive_integer(input).map_err(|Unspecified| Error::BadDER) -} diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index 1cac44a9a19..1afd77dcd1f 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -1,148 +1,108 @@ -// Copyright 2017-2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::der; -use der::Tag; use libp2p_core::identity::PublicKey; -use ring::signature; -use untrusted::{Input, Reader}; +use ring::{error::Unspecified, io::der, signature}; use webpki::Error; pub(super) struct X509Certificate<'a> { libp2p_extension: Libp2pExtension<'a>, - x509_tbs: Input<'a>, + x509_tbs: untrusted::Input<'a>, #[allow(dead_code)] - x509_validity: Input<'a>, - x509_spki: SubjectPublicKeyInfo<'a>, - x509_signature_algorithm: Input<'a>, - x509_signature: Input<'a>, + x509_validity: untrusted::Input<'a>, + x509_spki: untrusted::Input<'a>, + x509_signature_algorithm: untrusted::Input<'a>, + x509_signature: untrusted::Input<'a>, } -type Result = std::result::Result; - -/// An X.509 SubjectPublicKeyInfo -struct SubjectPublicKeyInfo<'a> { - x509_pkey_algorithm: &'a [u8], - x509_pkey_bytes: &'a [u8], +struct Libp2pExtension<'a> { + peer_key: PublicKey, + signature: &'a [u8], } -/// Read an X.509 SubjectPublicKeyInfo -fn read_spki<'a>(input: Input<'a>) -> Result> { - input.read_all(Error::BadDER, |input| { - let algorithm = der::expect_tag_and_get_value(input, Tag::Sequence)?; - let public_key = der::bit_string_with_no_unused_bits(input)?; - Ok(SubjectPublicKeyInfo { - x509_pkey_algorithm: algorithm.as_slice_less_safe(), - x509_pkey_bytes: public_key.as_slice_less_safe(), - }) - }) +fn expect_sequence<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::Sequence).map_err(|Unspecified| Error::BadDER) } -/// Read a single extension. If it is the libp2p extension, store it in `libp2p`, unless `libp2p`, -/// is already `Some`, which is an error. Any critical extension that is not the libp2p extension -/// is also an error. -fn read_extension<'a>( - input: &mut Reader<'a>, - libp2p: &mut Option>, -) -> Result<()> { - use super::super::LIBP2P_OID_BYTES; - der::nested_sequence(input, |input| { - let oid = der::expect_tag_and_get_value(input, Tag::OID)?; - let critical = input.peek(Tag::Boolean as _); - if critical && input.read_bytes(3) != Ok(Input::from(&[1, 1, 255][..])) { - return Err(Error::BadDER); - } - let extension = - der::expect_tag_and_get_value(input, Tag::OctetString).map_err(|_| Error::BadDER)?; - match oid.as_slice_less_safe() { - LIBP2P_OID_BYTES if libp2p.is_some() => Err(Error::BadDER), - LIBP2P_OID_BYTES => { - *libp2p = Some( - parse_libp2p_extension(extension).map_err(|_| Error::ExtensionValueInvalid)?, - ); - Ok(()) - } - _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => Ok(()), - } - }) +fn expect_oid<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::OID).map_err(|Unspecified| Error::BadDER) } -/// Read X.509 extensions. -fn read_extensions<'a>(input: Input<'a>) -> Result> { - input.read_all(Error::BadDER, |input| { - der::nested_sequence(input, |input| { - let mut libp2p = None; - while !input.at_end() { - read_extension(input, &mut libp2p)?; - } - libp2p.ok_or(Error::RequiredEKUNotFound) - }) - }) +fn expect_octet_string<'a>( + reader: &mut untrusted::Reader<'a>, +) -> Result, Error> { + der::expect_tag_and_get_value(reader, der::Tag::OctetString) + .map_err(|Unspecified| Error::BadDER) } -struct Libp2pExtension<'a> { - peer_key: PublicKey, - signature: &'a [u8], -} - -#[inline] -fn expect_sequence<'a>(input: &mut Reader<'a>) -> Result> { - der::expect_tag_and_get_value(input, Tag::Sequence) -} - -fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result> { - Input::read_all(&extension, Error::ExtensionValueInvalid, |input| { - der::nested_sequence(input, |input| { - let public_key = der::bit_string_with_no_unused_bits(input)?.as_slice_less_safe(); - let signature = der::bit_string_with_no_unused_bits(input)?.as_slice_less_safe(); +fn parse_libp2p_extension<'a>( + extension: untrusted::Input<'a>, +) -> Result, Error> { + untrusted::Input::read_all(&extension, Unspecified, |reader| { + der::nested(reader, der::Tag::Sequence, Unspecified, |reader| { + let public_key = der::bit_string_with_no_unused_bits(reader)?; + let signature = der::bit_string_with_no_unused_bits(reader)?; // We deliberately discard the error information because this is // either a broken peer or an attack. - let peer_key = - PublicKey::from_protobuf_encoding(public_key).map_err(|_| Error::BadDER)?; + let peer_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) + .map_err(|_| Unspecified)?; Ok(Libp2pExtension { - signature, + signature: signature.as_slice_less_safe(), peer_key, }) }) }) + .map_err(|Unspecified| Error::ExtensionValueInvalid) } -/// Extracts the algorithm id and public key from a certificate -pub(super) fn parse_certificate(certificate: &[u8]) -> Result> { - let certificate = Input::from(certificate).read_all(Error::BadDER, expect_sequence)?; - let (x509_tbs, inner_tbs, x509_signature_algorithm, x509_signature) = - certificate.read_all(Error::BadDER, |input| { - // tbsCertificate - let (signed_tbs, parsed_tbs) = input.read_partial(expect_sequence)?; - // signatureAlgorithm - let signature_algorithm = expect_sequence(input)?; - // signatureValue - let signature = der::bit_string_with_no_unused_bits(input)?; - Ok((signed_tbs, parsed_tbs, signature_algorithm, signature)) - })?; +/// Parse X.509 extensions +fn parse_extensions<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { + let mut libp2p_extension = None; + while !reader.at_end() { + const BOOLEAN: u8 = der::Tag::Boolean as _; + const OCTET_STRING: u8 = der::Tag::OctetString as _; + der::nested(reader, der::Tag::Sequence, Error::BadDER, |reader| { + let oid = expect_oid(reader)?; + let (tag, data) = der::read_tag_and_get_value(reader).map_err(|_| Error::BadDER)?; + let (critical, extension) = match tag { + BOOLEAN => match data.as_slice_less_safe() { + [0xFF] => (true, expect_octet_string(reader)?), + [0] => (false, expect_octet_string(reader)?), + _ => return Err(Error::BadDER), + }, + OCTET_STRING => (false, data), + _ => return Err(Error::BadDER), + }; + match oid.as_slice_less_safe() { + super::super::LIBP2P_OID_BYTES if libp2p_extension.is_some() => Err(Error::BadDER), + super::super::LIBP2P_OID_BYTES => { + libp2p_extension = Some(parse_libp2p_extension(extension)?); + Ok(()) + } + _ if critical => return Err(Error::UnsupportedCriticalExtension), + _ => Ok(()), + } + })? + } + libp2p_extension.ok_or(Error::BadDER) +} +/// Extracts the algorithm id and public key from a certificate +pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { + let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = + untrusted::Input::from(certificate) + .read_all(Error::BadDER, expect_sequence)? + .read_all(Error::BadDER, |reader| { + // tbsCertificate + let tbs = reader.read_partial(|reader| expect_sequence(reader))?; + // signatureAlgorithm + let signature_algorithm = expect_sequence(reader)?; + // signatureValue + let signature = + der::bit_string_with_no_unused_bits(reader).map_err(|_| Error::BadDER)?; + Ok((tbs, signature_algorithm, signature)) + })?; let (x509_validity, x509_spki, libp2p_extension) = - inner_tbs.read_all(Error::BadDER, |mut input| { + inner_tbs.read_all(Error::BadDER, |mut reader| { // We require extensions, which means we require version 3 - if input + if reader .read_bytes(5) .map_err(|_| Error::BadDER)? .as_slice_less_safe() @@ -151,29 +111,35 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result Result, x509_pkey_bytes: &[u8], -) -> Result<()> { +) -> Result<(), Error> { let mut v = Vec::with_capacity(super::super::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); v.extend_from_slice(&super::super::LIBP2P_SIGNING_PREFIX[..]); @@ -197,21 +163,25 @@ fn verify_libp2p_signature( } } -pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<()> { +pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<(), Error> { let X509Certificate { x509_tbs, - x509_spki: - SubjectPublicKeyInfo { - x509_pkey_algorithm, - x509_pkey_bytes, - }, + x509_spki, x509_signature, x509_signature_algorithm, libp2p_extension, .. } = certificate; + + let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |reader| { + let x509_pkey_alg = expect_sequence(reader)?.as_slice_less_safe(); + let x509_pkey_bytes = der::bit_string_with_no_unused_bits(reader) + .map_err(|Unspecified| Error::BadDER)? + .as_slice_less_safe(); + Ok((x509_pkey_alg, x509_pkey_bytes)) + })?; let pkey = verify_self_signature( - x509_pkey_algorithm, + x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm.as_slice_less_safe(), )?; @@ -227,7 +197,7 @@ fn verify_self_signature<'a>( x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], -) -> Result> { +) -> Result, Error> { let algorithm: &'static dyn signature::VerificationAlgorithm = match (x509_signature_algorithm, x509_pkey_alg) { (include_bytes!("data/alg-rsa-pkcs1-sha256.der"), _) => { @@ -279,7 +249,7 @@ fn verify_self_signature<'a>( // While the RSA-PSS parameters are a ASN.1 SEQUENCE, it is simpler to match // against the 12 different possibilities. The binary files are *generated* by a // Go program. -fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters> { +fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters, Error> { match data { include_bytes!("data/alg-rsa-pss-sha256-v0.der") | include_bytes!("data/alg-rsa-pss-sha256-v1.der") From 2b701b9396584eb0377e7e9479dfadce978d3444 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 17 Mar 2020 20:31:35 -0400 Subject: [PATCH 148/202] Distinguished unsupported from mismatching signature algorithms --- protocols/tls/src/verifier/x509.rs | 82 +++++++++++++++--------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index 1afd77dcd1f..a66dc276292 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -198,48 +198,48 @@ fn verify_self_signature<'a>( x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], ) -> Result, Error> { - let algorithm: &'static dyn signature::VerificationAlgorithm = - match (x509_signature_algorithm, x509_pkey_alg) { - (include_bytes!("data/alg-rsa-pkcs1-sha256.der"), _) => { - if x509_pkey_alg == include_bytes!("data/alg-rsa-encryption.der") { - &signature::RSA_PKCS1_2048_8192_SHA256 - } else { - return Err(Error::UnsupportedSignatureAlgorithmForPublicKey); - } - } - ( - include_bytes!("data/alg-rsa-pkcs1-sha384.der"), - include_bytes!("data/alg-rsa-encryption.der"), - ) => &signature::RSA_PKCS1_2048_8192_SHA384, - ( - include_bytes!("data/alg-rsa-pkcs1-sha512.der"), - include_bytes!("data/alg-rsa-encryption.der"), - ) => &signature::RSA_PKCS1_2048_8192_SHA512, - ( - include_bytes!("data/alg-ecdsa-sha256.der"), - include_bytes!("data/alg-ecdsa-p256.der"), - ) => &signature::ECDSA_P256_SHA256_ASN1, - ( - include_bytes!("data/alg-ecdsa-sha384.der"), - include_bytes!("data/alg-ecdsa-p384.der"), - ) => &signature::ECDSA_P384_SHA384_ASN1, - ( - include_bytes!("data/alg-ecdsa-sha384.der"), - include_bytes!("data/alg-ecdsa-p256.der"), - ) => &signature::ECDSA_P256_SHA384_ASN1, - ( - include_bytes!("data/alg-ecdsa-sha256.der"), - include_bytes!("data/alg-ecdsa-p384.der"), - ) => &signature::ECDSA_P384_SHA256_ASN1, - (include_bytes!("data/alg-ed25519.der"), include_bytes!("data/alg-ed25519.der")) => { - &signature::ED25519 - } - ( - [0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, ..], - include_bytes!("data/alg-rsa-encryption.der"), - ) => parse_rsa_pss(&x509_pkey_alg[11..])?, + const BUF: &[u8] = &[ + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, + ]; + use signature::{ + RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, + }; + let algorithm: &'static dyn signature::VerificationAlgorithm = match x509_signature_algorithm { + include_bytes!("data/alg-rsa-pkcs1-sha256.der") => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA256, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + include_bytes!("data/alg-rsa-pkcs1-sha384.der") => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA384, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + include_bytes!("data/alg-rsa-pkcs1-sha512.der") => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA512, _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }; + }, + include_bytes!("data/alg-ecdsa-sha256.der") => match x509_pkey_alg { + include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA256_ASN1, + include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA256_ASN1, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + include_bytes!("data/alg-ecdsa-sha384.der") => match x509_pkey_alg { + include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA384_ASN1, + include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA384_ASN1, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + include_bytes!("data/alg-ed25519.der") => match x509_pkey_alg { + include_bytes!("data/alg-ed25519.der") => &signature::ED25519, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + e if e.len() > 11 && &e[..11] == BUF => { + let alg = parse_rsa_pss(&e[11..])?; + match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => alg, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + } + } + _ => return Err(Error::UnsupportedSignatureAlgorithm), + }; Ok(signature::UnparsedPublicKey::new( algorithm, x509_pkey_bytes, From 35421b67c4ade8c7c462d44ba80a278a186ea56f Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 18 Mar 2020 20:31:49 -0400 Subject: [PATCH 149/202] Replace the old webpki-based verifier --- LICENSE | 2 +- README.md | 5 + protocols/tls/Cargo.toml | 4 +- protocols/tls/LICENSE-ISC | 17 ++ protocols/tls/rustfmt.toml | 13 ++ protocols/tls/src/certificate.rs | 48 +++--- protocols/tls/src/lib.rs | 6 +- protocols/tls/src/verifier.rs | 136 +++------------ protocols/tls/src/verifier/calendar.rs | 155 +++++++++++++++++ protocols/tls/src/verifier/der.rs | 105 ++++++++++++ protocols/tls/src/verifier/x509.rs | 219 ++++++++++++------------- transports/quic/src/endpoint.rs | 4 +- 12 files changed, 452 insertions(+), 262 deletions(-) create mode 100644 protocols/tls/LICENSE-ISC create mode 100644 protocols/tls/rustfmt.toml create mode 100644 protocols/tls/src/verifier/calendar.rs create mode 100644 protocols/tls/src/verifier/der.rs diff --git a/LICENSE b/LICENSE index d4bb412c336..da84696303f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017 Parity Technologies (UK) Ltd. +Copyright 2017-2020 Parity Technologies (UK) Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index e3bb85191a1..d076fb50dac 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,8 @@ Where to ask questions? - https://github.com/sigp/lighthouse - https://github.com/golemfactory/golem-libp2p - https://github.com/comit-network/comit-rs + +## License + +Most code in the repository is under the MIT license in LICENSE. The files under +transports/x509 are under the ISC-style license in LICENSE-ISC. \ No newline at end of file diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml index 6ecd769468d..4f7cea9dbf8 100644 --- a/protocols/tls/Cargo.toml +++ b/protocols/tls/Cargo.toml @@ -3,8 +3,8 @@ name = "libp2p-tls" version = "0.16.0" authors = ["Parity Technologies "] edition = "2018" -description = "TLS encryption for libp2p" -license = "MIT" +description = "X.509 verification for libp2p" +license = "ISC" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] diff --git a/protocols/tls/LICENSE-ISC b/protocols/tls/LICENSE-ISC new file mode 100644 index 00000000000..76c61d6e1b0 --- /dev/null +++ b/protocols/tls/LICENSE-ISC @@ -0,0 +1,17 @@ +Except as otherwise noted, this project is licensed under the following +(ISC-style) terms: + +Copyrignt 2020-present Parity Technologies Ltd +Copyright 2015 Brian Smith. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/protocols/tls/rustfmt.toml b/protocols/tls/rustfmt.toml new file mode 100644 index 00000000000..f315886cd3a --- /dev/null +++ b/protocols/tls/rustfmt.toml @@ -0,0 +1,13 @@ +edition = "2018" +fn_args_layout = "Compressed" +fn_single_line = true +match_arm_blocks = false +match_block_trailing_comma = true +max_width = 100 +merge_imports = true +newline_style = "Unix" +reorder_imports = true +trailing_comma = "Vertical" +use_field_init_shorthand = true +use_try_shorthand = true +wrap_comments = true diff --git a/protocols/tls/src/certificate.rs b/protocols/tls/src/certificate.rs index 7948dcca84e..0417e96ff4d 100644 --- a/protocols/tls/src/certificate.rs +++ b/protocols/tls/src/certificate.rs @@ -1,30 +1,25 @@ // Copyright 2017-2018 Parity Technologies (UK) Ltd. // -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. // -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. //! Certificate handling for libp2p //! //! This handles generation, signing, and verification. //! -//! This crate uses the `log` crate to emit log output. Events that will occur normally are output -//! at `trace` level, while “expected” error conditions (ones that can result during correct use of the -//! library) are logged at `debug` level. +//! This crate uses the `log` crate to emit log output. Events that will occur +//! normally are output at `trace` level, while “expected” error conditions +//! (ones that can result during correct use of the library) are logged at +//! `debug` level. use super::LIBP2P_SIGNING_PREFIX_LENGTH; use libp2p_core::identity; @@ -35,10 +30,11 @@ const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; // preferred, but not supported by rustls yet //const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 32; -//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ED25519; -// same but with P-384 +//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = +// &rcgen::PKCS_ED25519; same but with P-384 //const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 97; -//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P384_SHA384; +//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = +// &rcgen::PKCS_ECDSA_P384_SHA384; fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { let public_key = public_key.into_protobuf_encoding(); @@ -78,8 +74,8 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu ) } -/// Generates a self-signed TLS certificate that includes a libp2p-specific certificate extension -/// containing the public key of the given keypair. +/// Generates a self-signed TLS certificate that includes a libp2p-specific +/// certificate extension containing the public key of the given keypair. pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { let mut params = rcgen::CertificateParams::new(vec![]); let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); @@ -117,10 +113,10 @@ pub fn extract_peerid(certificate: &[u8]) -> Result webpki::Understood::No, }; webpki::EndEntityCert::from_with_extension_cb(certificate, cb)?; diff --git a/protocols/tls/src/lib.rs b/protocols/tls/src/lib.rs index 4cc694b77e7..83894c2aec5 100644 --- a/protocols/tls/src/lib.rs +++ b/protocols/tls/src/lib.rs @@ -79,8 +79,7 @@ const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; fn make_client_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, + certificate: rustls::Certificate, key: rustls::PrivateKey, verifier: Arc, ) -> rustls::ClientConfig { let mut crypto = rustls::ClientConfig::new(); @@ -95,8 +94,7 @@ fn make_client_config( } fn make_server_config( - certificate: rustls::Certificate, - key: rustls::PrivateKey, + certificate: rustls::Certificate, key: rustls::PrivateKey, verifier: Arc, ) -> rustls::ServerConfig { let mut crypto = rustls::ServerConfig::new(verifier); diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index 21f1ec272da..d0e2f5d8077 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -18,27 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod calendar; +mod der; mod x509; -static ALL_SUPPORTED_SIGNATURE_ALGORITHMS: &[&webpki::SignatureAlgorithm] = { - &[ - &webpki::ECDSA_P256_SHA256, - &webpki::ED25519, - &webpki::ECDSA_P384_SHA384, - &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, - &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, - // Deprecated and/or undesirable algorithms. - - // deprecated elliptic curve algorithms - &webpki::ECDSA_P384_SHA256, - &webpki::ECDSA_P256_SHA384, - // RSA PKCS 1.5 - &webpki::RSA_PKCS1_2048_8192_SHA256, - &webpki::RSA_PKCS1_2048_8192_SHA384, - &webpki::RSA_PKCS1_2048_8192_SHA512, - &webpki::RSA_PKCS1_3072_8192_SHA384, - ] -}; /// Libp2p client and server certificate verifier. pub(crate) struct Libp2pCertificateVerifier; @@ -47,8 +29,8 @@ pub(crate) struct Libp2pCertificateVerifier; /// /// * Exactly one certificate must be presented. /// * The certificate must be self-signed. -/// * The certificate must have a valid libp2p extension that includes -/// a signature of its public key. +/// * The certificate must have a valid libp2p extension that includes a +/// signature of its public key. /// /// The check that the [`PeerId`] matches the expected `PeerId` must be done by /// the caller. @@ -56,80 +38,11 @@ pub(crate) struct Libp2pCertificateVerifier; /// [`PeerId`]: libp2p_core::PeerId impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { fn verify_server_cert( - &self, - _roots: &rustls::RootCertStore, - presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, - _ocsp_response: &[u8], + &self, _roots: &rustls::RootCertStore, presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { - let (end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; - end_entity_cert - .verify_is_valid_tls_server_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSServerTrustAnchors(&[trust_anchor]), - &[], - get_time()?, - ) - .map(|()| rustls::ServerCertVerified::assertion()) - .map_err(rustls::TLSError::WebPKIError) - } -} - -fn verify_libp2p_extension( - extension: untrusted::Input<'_>, - subject_public_key_info: untrusted::Input<'_>, -) -> Result<(), ring::error::Unspecified> { - use libp2p_core::identity::PublicKey; - use ring::{error::Unspecified, io::der}; - let certificate_key = subject_public_key_info.read_all(Unspecified, |mut reader| { - der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - der::bit_string_with_no_unused_bits(&mut reader) - })?; - extension.read_all(Unspecified, |mut reader| { - let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - inner.read_all(Unspecified, |mut reader| { - let public_key = der::bit_string_with_no_unused_bits(&mut reader)?; - let signature = der::bit_string_with_no_unused_bits(&mut reader)?; - // We deliberately discard the error information because this is - // either a broken peer or an attack. - let public_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) - .map_err(|_| Unspecified)?; - let mut v = - Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + certificate_key.len()); - v.extend_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); - v.extend_from_slice(certificate_key.as_slice_less_safe()); - if public_key.verify(&v, signature.as_slice_less_safe()) { - Ok(()) - } else { - Err(Unspecified) - } - }) - }) -} - -pub(super) fn verify_single_cert( - raw_certificate: &[u8], -) -> Result<(webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>), rustls::TLSError> { - let mut num_libp2p_extensions = 0usize; - let cb = &mut |oid: untrusted::Input<'_>, value, _, spki| match oid.as_slice_less_safe() { - super::LIBP2P_OID_BYTES => { - num_libp2p_extensions += 1; - if verify_libp2p_extension(value, spki).is_err() { - num_libp2p_extensions = 2; // this will force an error - } - webpki::Understood::Yes - } - _ => webpki::Understood::No, - }; - let parsed_cert = webpki::EndEntityCert::from_with_extension_cb(raw_certificate, cb) - .map_err(rustls::TLSError::WebPKIError)?; - if num_libp2p_extensions != 1 { - // this also includes the case where an extension was not valid - return Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer)); + verify_presented_certs(presented_certs).map(|()| rustls::ServerCertVerified::assertion()) } - let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(raw_certificate) - .map_err(rustls::TLSError::WebPKIError)?; - Ok((parsed_cert, trust_anchor)) } fn get_time() -> Result { @@ -137,13 +50,16 @@ fn get_time() -> Result { .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime) } -fn verify_presented_certs( - presented_certs: &[rustls::Certificate], -) -> Result<(webpki::EndEntityCert<'_>, webpki::TrustAnchor<'_>), rustls::TLSError> { +fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), rustls::TLSError> { if presented_certs.len() != 1 { return Err(rustls::TLSError::NoCertificatesPresented); } - verify_single_cert(presented_certs[0].as_ref()) + x509::verify_certificate( + x509::parse_certificate(presented_certs[0].as_ref()) + .map_err(rustls::TLSError::WebPKIError)?, + get_time()?, + ) + .map_err(rustls::TLSError::WebPKIError) } /// libp2p requires the following of X.509 client certificate chains: @@ -151,8 +67,8 @@ fn verify_presented_certs( /// * Exactly one certificate must be presented. In particular, client /// authentication is mandatory in libp2p. /// * The certificate must be self-signed. -/// * The certificate must have a valid libp2p extension that includes -/// a signature of its public key. +/// * The certificate must have a valid libp2p extension that includes a +/// signature of its public key. /// /// The check that the [`PeerId`] matches the expected `PeerId` must be done by /// the caller. @@ -164,28 +80,14 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { } fn client_auth_root_subjects( - &self, - _dns_name: Option<&webpki::DNSName>, + &self, _dns_name: Option<&webpki::DNSName>, ) -> Option { Some(vec![]) } fn verify_client_cert( - &self, - presented_certs: &[rustls::Certificate], - _dns_name: Option<&webpki::DNSName>, + &self, presented_certs: &[rustls::Certificate], _dns_name: Option<&webpki::DNSName>, ) -> Result { - let (end_entity_cert, trust_anchor) = verify_presented_certs(presented_certs)?; - end_entity_cert - .verify_is_valid_tls_client_cert( - ALL_SUPPORTED_SIGNATURE_ALGORITHMS, - &webpki::TLSClientTrustAnchors(&[trust_anchor]), - &[], - get_time()?, - ) - .map_err(rustls::TLSError::WebPKIError)?; - x509::verify_certificate(x509::parse_certificate(presented_certs[0].as_ref()).unwrap()) - .unwrap(); - Ok(rustls::ClientCertVerified::assertion()) + verify_presented_certs(presented_certs).map(|()| rustls::ClientCertVerified::assertion()) } } diff --git a/protocols/tls/src/verifier/calendar.rs b/protocols/tls/src/verifier/calendar.rs new file mode 100644 index 00000000000..9381af7ed3a --- /dev/null +++ b/protocols/tls/src/verifier/calendar.rs @@ -0,0 +1,155 @@ +// Copyright 2015-2016 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use webpki::{Error, Time}; + +pub(super) fn time_from_ymdhms_utc( + year: u64, month: u64, day_of_month: u64, hours: u64, minutes: u64, seconds: u64, +) -> Result { + let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?; + + const JAN: u64 = 31; + let feb = days_in_feb(year); + const MAR: u64 = 31; + const APR: u64 = 30; + const MAY: u64 = 31; + const JUN: u64 = 30; + const JUL: u64 = 31; + const AUG: u64 = 31; + const SEP: u64 = 30; + const OCT: u64 = 31; + const NOV: u64 = 30; + let days_before_month_in_year = match month { + 1 => 0, + 2 => JAN, + 3 => JAN + feb, + 4 => JAN + feb + MAR, + 5 => JAN + feb + MAR + APR, + 6 => JAN + feb + MAR + APR + MAY, + 7 => JAN + feb + MAR + APR + MAY + JUN, + 8 => JAN + feb + MAR + APR + MAY + JUN + JUL, + 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG, + 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP, + 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT, + 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV, + _ => unreachable!(), // `read_two_digits` already bounds-checked it. + }; + + let days_before = + days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1; + + let seconds_since_unix_epoch = + (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds; + + Ok(Time::from_seconds_since_unix_epoch( + seconds_since_unix_epoch, + )) +} + +fn days_before_year_since_unix_epoch(year: u64) -> Result { + // We don't support dates before January 1, 1970 because that is the + // Unix epoch. It is likely that other software won't deal well with + // certificates that have dates before the epoch. + if year < 1970 { + return Err(Error::BadDERTime); + } + let days_before_year_ad = days_before_year_ad(year); + debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD); + Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD) +} + +fn days_before_year_ad(year: u64) -> u64 { + ((year - 1) * 365) + + ((year - 1) / 4) // leap years are every 4 years, + - ((year - 1) / 100) // except years divisible by 100, + + ((year - 1) / 400) // except years divisible by 400. +} + +pub(super) fn days_in_month(year: u64, month: u64) -> u64 { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 => days_in_feb(year), + _ => unreachable!(), // `read_two_digits` already bounds-checked it. + } +} + +fn days_in_feb(year: u64) -> u64 { + if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { + 29 + } else { + 28 + } +} + +const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 719162; + +#[cfg(test)] +mod tests { + #[test] + fn test_days_before_unix_epoch() { + use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD}; + assert_eq!(DAYS_BEFORE_UNIX_EPOCH_AD, days_before_year_ad(1970)); + } + + #[test] + fn test_days_in_month() { + use super::days_in_month; + assert_eq!(days_in_month(2017, 1), 31); + assert_eq!(days_in_month(2017, 2), 28); + assert_eq!(days_in_month(2017, 3), 31); + assert_eq!(days_in_month(2017, 4), 30); + assert_eq!(days_in_month(2017, 5), 31); + assert_eq!(days_in_month(2017, 6), 30); + assert_eq!(days_in_month(2017, 7), 31); + assert_eq!(days_in_month(2017, 8), 31); + assert_eq!(days_in_month(2017, 9), 30); + assert_eq!(days_in_month(2017, 10), 31); + assert_eq!(days_in_month(2017, 11), 30); + assert_eq!(days_in_month(2017, 12), 31); + + // leap cases + assert_eq!(days_in_month(2000, 2), 29); + assert_eq!(days_in_month(2004, 2), 29); + assert_eq!(days_in_month(2016, 2), 29); + assert_eq!(days_in_month(2100, 2), 28); + } + + #[test] + fn test_time_from_ymdhms_utc() { + use super::{time_from_ymdhms_utc, Time}; + + // year boundary + assert_eq!( + Time::from_seconds_since_unix_epoch(1483228799), + time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap() + ); + assert_eq!( + Time::from_seconds_since_unix_epoch(1483228800), + time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap() + ); + + // not a leap year + assert_eq!( + Time::from_seconds_since_unix_epoch(1492449162), + time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap() + ); + + // leap year, post-feb + assert_eq!( + Time::from_seconds_since_unix_epoch(1460913162), + time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap() + ); + } +} diff --git a/protocols/tls/src/verifier/der.rs b/protocols/tls/src/verifier/der.rs new file mode 100644 index 00000000000..a5af4b15869 --- /dev/null +++ b/protocols/tls/src/verifier/der.rs @@ -0,0 +1,105 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2015 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use super::calendar; +pub(super) use ring::io::{ + der::{nested, Tag}, + Positive, +}; +use webpki::{Error, Time}; + +#[inline(always)] +pub(super) fn expect_tag_and_get_value<'a>( + input: &mut untrusted::Reader<'a>, tag: Tag, +) -> Result, Error> { + ring::io::der::expect_tag_and_get_value(input, tag).map_err(|_| Error::BadDER) +} + +pub(super) fn expect_bytes(input: &mut untrusted::Reader<'_>, bytes: &[u8]) -> Result<(), Error> { + match input.read_bytes(bytes.len()) { + Ok(e) if e == untrusted::Input::from(bytes) => Ok(()), + _ => Err(Error::BadDER), + } +} + +#[inline(always)] +pub(super) fn bit_string_with_no_unused_bits<'a>( + input: &mut untrusted::Reader<'a>, +) -> Result, Error> { + ring::io::der::bit_string_with_no_unused_bits(input).map_err(|_| Error::BadDER) +} + +#[inline(always)] +pub(super) fn positive_integer<'a>( + input: &'a mut untrusted::Reader<'_>, +) -> Result, Error> { + ring::io::der::positive_integer(input).map_err(|_| Error::BadDER) +} + +pub(super) fn time_choice(input: &mut untrusted::Reader<'_>) -> Result { + let is_utc_time = input.peek(Tag::UTCTime as u8); + let expected_tag = if is_utc_time { + Tag::UTCTime + } else { + Tag::GeneralizedTime + }; + + fn read_digit(inner: &mut untrusted::Reader<'_>) -> Result { + let b = inner.read_byte().map_err(|_| Error::BadDERTime)?; + if b < b'0' || b > b'9' { + return Err(Error::BadDERTime); + } + Ok((b - b'0') as u64) + } + + fn read_two_digits( + inner: &mut untrusted::Reader<'_>, min: u64, max: u64, + ) -> Result { + let hi = read_digit(inner)?; + let lo = read_digit(inner)?; + let value = (hi * 10) + lo; + if value < min || value > max { + return Err(Error::BadDERTime); + } + Ok(value) + } + + nested(input, expected_tag, Error::BadDER, |value| { + let (year_hi, year_lo) = if is_utc_time { + let lo = read_two_digits(value, 0, 99)?; + let hi = if lo >= 50 { 19 } else { 20 }; + (hi, lo) + } else { + let hi = read_two_digits(value, 0, 99)?; + let lo = read_two_digits(value, 0, 99)?; + (hi, lo) + }; + + let year = (year_hi * 100) + year_lo; + let month = read_two_digits(value, 1, 12)?; + let days_in_month = calendar::days_in_month(year, month); + let day_of_month = read_two_digits(value, 1, days_in_month)?; + let hours = read_two_digits(value, 0, 23)?; + let minutes = read_two_digits(value, 0, 59)?; + let seconds = read_two_digits(value, 0, 59)?; + + let time_zone = value.read_byte().map_err(|_| Error::BadDERTime)?; + if time_zone != b'Z' { + return Err(Error::BadDERTime); + } + + calendar::time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds) + }) +} diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index a66dc276292..c6708b4e28b 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -1,15 +1,33 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. + +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use super::{ + super::{LIBP2P_OID_BYTES, LIBP2P_SIGNING_PREFIX, LIBP2P_SIGNING_PREFIX_LENGTH}, + der, +}; use libp2p_core::identity::PublicKey; -use ring::{error::Unspecified, io::der, signature}; -use webpki::Error; +use ring::signature; +use untrusted::{Input, Reader}; +use webpki::{Error, Time}; pub(super) struct X509Certificate<'a> { libp2p_extension: Libp2pExtension<'a>, - x509_tbs: untrusted::Input<'a>, - #[allow(dead_code)] - x509_validity: untrusted::Input<'a>, - x509_spki: untrusted::Input<'a>, - x509_signature_algorithm: untrusted::Input<'a>, - x509_signature: untrusted::Input<'a>, + x509_tbs: Input<'a>, + x509_validity: Input<'a>, + x509_spki: Input<'a>, + x509_signature_algorithm: Input<'a>, + x509_signature: Input<'a>, } struct Libp2pExtension<'a> { @@ -17,124 +35,101 @@ struct Libp2pExtension<'a> { signature: &'a [u8], } -fn expect_sequence<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::Sequence).map_err(|Unspecified| Error::BadDER) +#[inline(always)] +fn read_sequence<'a>(input: &mut Reader<'a>) -> Result, Error> { + der::expect_tag_and_get_value(input, der::Tag::Sequence) } -fn expect_oid<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::OID).map_err(|Unspecified| Error::BadDER) +#[inline(always)] +fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Error> { + der::bit_string_with_no_unused_bits(input).map_err(|_| e) } -fn expect_octet_string<'a>( - reader: &mut untrusted::Reader<'a>, -) -> Result, Error> { - der::expect_tag_and_get_value(reader, der::Tag::OctetString) - .map_err(|Unspecified| Error::BadDER) -} - -fn parse_libp2p_extension<'a>( - extension: untrusted::Input<'a>, -) -> Result, Error> { - untrusted::Input::read_all(&extension, Unspecified, |reader| { - der::nested(reader, der::Tag::Sequence, Unspecified, |reader| { - let public_key = der::bit_string_with_no_unused_bits(reader)?; - let signature = der::bit_string_with_no_unused_bits(reader)?; +fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result, Error> { + let e = Error::ExtensionValueInvalid; + Input::read_all(&extension, e, |input| { + der::nested(input, der::Tag::Sequence, e, |input| { + let public_key = read_bit_string(input, e)?.as_slice_less_safe(); + let signature = read_bit_string(input, e)?.as_slice_less_safe(); // We deliberately discard the error information because this is // either a broken peer or an attack. - let peer_key = PublicKey::from_protobuf_encoding(public_key.as_slice_less_safe()) - .map_err(|_| Unspecified)?; + let peer_key = PublicKey::from_protobuf_encoding(public_key).map_err(|_| e)?; Ok(Libp2pExtension { - signature: signature.as_slice_less_safe(), + signature, peer_key, }) }) }) - .map_err(|Unspecified| Error::ExtensionValueInvalid) } /// Parse X.509 extensions -fn parse_extensions<'a>(reader: &mut untrusted::Reader<'a>) -> Result, Error> { +fn parse_extensions<'a>(input: &mut Reader<'a>) -> Result, Error> { let mut libp2p_extension = None; - while !reader.at_end() { - const BOOLEAN: u8 = der::Tag::Boolean as _; - const OCTET_STRING: u8 = der::Tag::OctetString as _; - der::nested(reader, der::Tag::Sequence, Error::BadDER, |reader| { - let oid = expect_oid(reader)?; - let (tag, data) = der::read_tag_and_get_value(reader).map_err(|_| Error::BadDER)?; - let (critical, extension) = match tag { - BOOLEAN => match data.as_slice_less_safe() { - [0xFF] => (true, expect_octet_string(reader)?), - [0] => (false, expect_octet_string(reader)?), - _ => return Err(Error::BadDER), - }, - OCTET_STRING => (false, data), - _ => return Err(Error::BadDER), - }; + while !input.at_end() { + der::nested(input, der::Tag::Sequence, Error::BadDER, |input| { + let oid = der::expect_tag_and_get_value(input, der::Tag::OID)?; + let mut critical = false; + if input.peek(der::Tag::Boolean as _) { + critical = true; + der::expect_bytes(input, &[der::Tag::Boolean as _, 1, 0xFF])? + } + let extension = der::expect_tag_and_get_value(input, der::Tag::OctetString)?; match oid.as_slice_less_safe() { - super::super::LIBP2P_OID_BYTES if libp2p_extension.is_some() => Err(Error::BadDER), - super::super::LIBP2P_OID_BYTES => { - libp2p_extension = Some(parse_libp2p_extension(extension)?); - Ok(()) - } + LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), + LIBP2P_OID_BYTES => libp2p_extension = Some(parse_libp2p_extension(extension)?), _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => Ok(()), + _ => {}, } + Ok(()) })? } - libp2p_extension.ok_or(Error::BadDER) + libp2p_extension.ok_or(Error::UnknownIssuer) } /// Extracts the algorithm id and public key from a certificate pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = - untrusted::Input::from(certificate) - .read_all(Error::BadDER, expect_sequence)? + Input::from(certificate) + .read_all(Error::BadDER, read_sequence)? .read_all(Error::BadDER, |reader| { // tbsCertificate - let tbs = reader.read_partial(|reader| expect_sequence(reader))?; + let tbs = reader.read_partial(|reader| read_sequence(reader))?; // signatureAlgorithm - let signature_algorithm = expect_sequence(reader)?; + let signature_algorithm = read_sequence(reader)?; // signatureValue - let signature = - der::bit_string_with_no_unused_bits(reader).map_err(|_| Error::BadDER)?; + let signature = read_bit_string(reader, Error::BadDER)?; Ok((tbs, signature_algorithm, signature)) })?; - let (x509_validity, x509_spki, libp2p_extension) = - inner_tbs.read_all(Error::BadDER, |mut reader| { + let (x509_validity, x509_spki, extensions) = + inner_tbs.read_all(Error::BadDER, |mut input| { // We require extensions, which means we require version 3 - if reader - .read_bytes(5) - .map_err(|_| Error::BadDER)? - .as_slice_less_safe() - != [160, 3, 2, 1, 2] - { + if der::expect_bytes(input, &[160, 3, 2, 1, 2]).is_err() { return Err(Error::UnsupportedCertVersion); } // serialNumber - der::positive_integer(&mut reader).map_err(|_| Error::BadDER)?; + der::positive_integer(&mut input)?; // signature - if expect_sequence(reader)? != x509_signature_algorithm { + if read_sequence(input)? != x509_signature_algorithm { // signature algorithms don’t match return Err(Error::SignatureAlgorithmMismatch); } // issuer - expect_sequence(reader)?; + read_sequence(input)?; // validity - let x509_validity = expect_sequence(reader)?; + let x509_validity = read_sequence(input)?; // subject - expect_sequence(reader)?; + read_sequence(input)?; // subjectPublicKeyInfo - let spki = expect_sequence(reader)?; + let spki = read_sequence(input)?; // subjectUniqueId and issuerUniqueId are unsupported // parse the extensions - let libp2p_extension = der::nested( - reader, - der::Tag::ContextSpecificConstructed3, - Error::BadDER, - |reader| der::nested(reader, der::Tag::Sequence, Error::BadDER, parse_extensions), - )?; - Ok((x509_validity, spki, libp2p_extension)) + let extensions = + der::expect_tag_and_get_value(input, der::Tag::ContextSpecificConstructed3)?; + Ok((x509_validity, spki, extensions)) })?; + let extensions = extensions.read_all(Error::BadDER, read_sequence)?; + let libp2p_extension = extensions.read_all(Error::BadDER, parse_extensions)?; + Ok(X509Certificate { libp2p_extension, x509_tbs, @@ -146,12 +141,10 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result, - x509_pkey_bytes: &[u8], + libp2p_extension: Libp2pExtension<'_>, x509_pkey_bytes: &[u8], ) -> Result<(), Error> { - let mut v = - Vec::with_capacity(super::super::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); - v.extend_from_slice(&super::super::LIBP2P_SIGNING_PREFIX[..]); + let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); + v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); v.extend_from_slice(x509_pkey_bytes); if libp2p_extension .peer_key @@ -163,40 +156,47 @@ fn verify_libp2p_signature( } } -pub(super) fn verify_certificate(certificate: X509Certificate<'_>) -> Result<(), Error> { +pub(super) fn verify_certificate( + certificate: X509Certificate<'_>, time: Time, +) -> Result<(), Error> { let X509Certificate { x509_tbs, x509_spki, x509_signature, x509_signature_algorithm, libp2p_extension, - .. + x509_validity, } = certificate; - let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |reader| { - let x509_pkey_alg = expect_sequence(reader)?.as_slice_less_safe(); - let x509_pkey_bytes = der::bit_string_with_no_unused_bits(reader) - .map_err(|Unspecified| Error::BadDER)? - .as_slice_less_safe(); + let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |input| { + let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); + let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); Ok((x509_pkey_alg, x509_pkey_bytes)) })?; - let pkey = verify_self_signature( + verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; + get_public_key( x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm.as_slice_less_safe(), - )?; - verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; - pkey.verify( + )? + .verify( x509_tbs.as_slice_less_safe(), x509_signature.as_slice_less_safe(), ) - .map_err(|_| Error::InvalidSignatureForPublicKey) + .map_err(|_| Error::InvalidSignatureForPublicKey)?; + x509_validity.read_all(Error::BadDER, |input| { + let not_before = der::time_choice(input)?; + let not_after = der::time_choice(input)?; + if time < not_before || time > not_after { + Err(Error::ExtensionValueInvalid) + } else { + Ok(()) + } + }) } -fn verify_self_signature<'a>( - x509_pkey_alg: &[u8], - x509_pkey_bytes: &'a [u8], - x509_signature_algorithm: &[u8], +fn get_public_key<'a>( + x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], ) -> Result, Error> { const BUF: &[u8] = &[ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, @@ -237,7 +237,7 @@ fn verify_self_signature<'a>( include_bytes!("data/alg-rsa-encryption.der") => alg, _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), } - } + }, _ => return Err(Error::UnsupportedSignatureAlgorithm), }; Ok(signature::UnparsedPublicKey::new( @@ -254,21 +254,18 @@ fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters, Error include_bytes!("data/alg-rsa-pss-sha256-v0.der") | include_bytes!("data/alg-rsa-pss-sha256-v1.der") | include_bytes!("data/alg-rsa-pss-sha256-v2.der") - | include_bytes!("data/alg-rsa-pss-sha256-v3.der") => { - Ok(&signature::RSA_PSS_2048_8192_SHA256) - } + | include_bytes!("data/alg-rsa-pss-sha256-v3.der") => + Ok(&signature::RSA_PSS_2048_8192_SHA256), include_bytes!("data/alg-rsa-pss-sha384-v0.der") | include_bytes!("data/alg-rsa-pss-sha384-v1.der") | include_bytes!("data/alg-rsa-pss-sha384-v2.der") - | include_bytes!("data/alg-rsa-pss-sha384-v3.der") => { - Ok(&signature::RSA_PSS_2048_8192_SHA384) - } + | include_bytes!("data/alg-rsa-pss-sha384-v3.der") => + Ok(&signature::RSA_PSS_2048_8192_SHA384), include_bytes!("data/alg-rsa-pss-sha512-v0.der") | include_bytes!("data/alg-rsa-pss-sha512-v1.der") | include_bytes!("data/alg-rsa-pss-sha512-v2.der") - | include_bytes!("data/alg-rsa-pss-sha512-v3.der") => { - Ok(&signature::RSA_PSS_2048_8192_SHA512) - } + | include_bytes!("data/alg-rsa-pss-sha512-v3.der") => + Ok(&signature::RSA_PSS_2048_8192_SHA512), _ => Err(Error::UnsupportedSignatureAlgorithm), } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 54f9ad9baab..8f2b82766d3 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -254,11 +254,13 @@ impl Connection { if self.connection.is_closed() { return Poll::Ready(Err(Error::ConnectionLost)); } - if self.connection.side().is_server() { + let side = self.connection.side(); + if side.is_server() { ready!(self.channel.poll_ready(cx))?; self.channel .start_send(EndpointMessage::ConnectionAccepted)? } + let certificate = self .connection .crypto_session() From ac255524cccfbb39279d6170b2b6a16e543fb318 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 20 Mar 2020 11:41:11 -0400 Subject: [PATCH 150/202] Minor cleanups --- protocols/tls/src/verifier.rs | 4 +--- protocols/tls/src/verifier/der.rs | 6 ++++-- protocols/tls/src/verifier/x509.rs | 21 ++++++++++----------- transports/quic/src/endpoint.rs | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index d0e2f5d8077..0251ed6c506 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -75,9 +75,7 @@ fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), /// /// [`PeerId`]: libp2p_core::PeerId impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { - fn offer_client_auth(&self) -> bool { - true - } + fn offer_client_auth(&self) -> bool { true } fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, diff --git a/protocols/tls/src/verifier/der.rs b/protocols/tls/src/verifier/der.rs index a5af4b15869..3a744bb2500 100644 --- a/protocols/tls/src/verifier/der.rs +++ b/protocols/tls/src/verifier/der.rs @@ -27,10 +27,12 @@ pub(super) fn expect_tag_and_get_value<'a>( ring::io::der::expect_tag_and_get_value(input, tag).map_err(|_| Error::BadDER) } -pub(super) fn expect_bytes(input: &mut untrusted::Reader<'_>, bytes: &[u8]) -> Result<(), Error> { +pub(super) fn expect_bytes( + input: &mut untrusted::Reader<'_>, bytes: &[u8], error: Error, +) -> Result<(), Error> { match input.read_bytes(bytes.len()) { Ok(e) if e == untrusted::Input::from(bytes) => Ok(()), - _ => Err(Error::BadDER), + _ => Err(error), } } diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index c6708b4e28b..5f9e17f04a8 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -15,6 +15,7 @@ use super::{ super::{LIBP2P_OID_BYTES, LIBP2P_SIGNING_PREFIX, LIBP2P_SIGNING_PREFIX_LENGTH}, der, + der::Tag, }; use libp2p_core::identity::PublicKey; use ring::signature; @@ -37,7 +38,7 @@ struct Libp2pExtension<'a> { #[inline(always)] fn read_sequence<'a>(input: &mut Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(input, der::Tag::Sequence) + der::expect_tag_and_get_value(input, Tag::Sequence) } #[inline(always)] @@ -48,7 +49,7 @@ fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Er fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result, Error> { let e = Error::ExtensionValueInvalid; Input::read_all(&extension, e, |input| { - der::nested(input, der::Tag::Sequence, e, |input| { + der::nested(input, Tag::Sequence, e, |input| { let public_key = read_bit_string(input, e)?.as_slice_less_safe(); let signature = read_bit_string(input, e)?.as_slice_less_safe(); // We deliberately discard the error information because this is @@ -66,14 +67,14 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result(input: &mut Reader<'a>) -> Result, Error> { let mut libp2p_extension = None; while !input.at_end() { - der::nested(input, der::Tag::Sequence, Error::BadDER, |input| { - let oid = der::expect_tag_and_get_value(input, der::Tag::OID)?; + der::nested(input, Tag::Sequence, Error::BadDER, |input| { + let oid = der::expect_tag_and_get_value(input, Tag::OID)?; let mut critical = false; - if input.peek(der::Tag::Boolean as _) { + if input.peek(Tag::Boolean as _) { critical = true; - der::expect_bytes(input, &[der::Tag::Boolean as _, 1, 0xFF])? + der::expect_bytes(input, &[Tag::Boolean as _, 1, 0xFF], Error::BadDER)? } - let extension = der::expect_tag_and_get_value(input, der::Tag::OctetString)?; + let extension = der::expect_tag_and_get_value(input, Tag::OctetString)?; match oid.as_slice_less_safe() { LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), LIBP2P_OID_BYTES => libp2p_extension = Some(parse_libp2p_extension(extension)?), @@ -103,9 +104,7 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result Result Date: Fri, 20 Mar 2020 14:25:05 -0400 Subject: [PATCH 151/202] Remove use of webpki in certificate verification Also add tracing --- protocols/tls/src/certificate.rs | 60 +----------------------------- protocols/tls/src/lib.rs | 2 +- protocols/tls/src/verifier.rs | 14 +++++++ protocols/tls/src/verifier/x509.rs | 48 ++++++++++++------------ transports/quic/Cargo.toml | 3 +- transports/quic/src/connection.rs | 23 ++++++++++-- transports/quic/src/endpoint.rs | 20 +++++++--- transports/quic/src/stream_map.rs | 11 ++++-- transports/quic/tests/tests.rs | 12 +++--- 9 files changed, 89 insertions(+), 104 deletions(-) diff --git a/protocols/tls/src/certificate.rs b/protocols/tls/src/certificate.rs index 0417e96ff4d..7e5c4ea9102 100644 --- a/protocols/tls/src/certificate.rs +++ b/protocols/tls/src/certificate.rs @@ -23,7 +23,6 @@ use super::LIBP2P_SIGNING_PREFIX_LENGTH; use libp2p_core::identity; -use log::error; const LIBP2P_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 53594, 1, 1]; const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 65; @@ -31,10 +30,7 @@ static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECD // preferred, but not supported by rustls yet //const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 32; //static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = -// &rcgen::PKCS_ED25519; same but with P-384 -//const LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH: usize = 97; -//static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = -// &rcgen::PKCS_ECDSA_P384_SHA384; +// &rcgen::PKCS_ED25519 fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { let public_key = public_key.into_protobuf_encoding(); @@ -85,57 +81,3 @@ pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { rcgen::Certificate::from_params(params) .expect("certificate generation with valid params will succeed; qed") } - -/// Extracts the `PeerId` from a certificate’s libp2p extension. It is erroneous -/// to call this unless the certificate is known to be a well-formed X.509 -/// certificate with a valid libp2p extension. The certificate verifiers in this -/// crate validate check this. -/// -/// If you get `Err` from this function, there is a bug somewhere. Either you -/// called it without checking the preconditions, or there is a bug in this -/// library or one of its dependencies. -pub fn extract_peerid(certificate: &[u8]) -> Result { - let mut id = None; - let cb = &mut |oid: untrusted::Input<'_>, value, _, _| match oid.as_slice_less_safe() { - super::LIBP2P_OID_BYTES => { - if id.is_some() { - error!( - "multiple libp2p extensions should have been detected \ - earlier; something is wrong" - ); - id = Some(Err(webpki::Error::UnknownIssuer)) - } - id = Some(match extract_libp2p_peerid(value) { - Ok(value) => Ok(value), - Err(_) => { - error!( - "bogus libp2p extension should have been detected \ - earlier; something is wrong" - ); - Err(webpki::Error::UnknownIssuer) - }, - }); - webpki::Understood::Yes - }, - _ => webpki::Understood::No, - }; - webpki::EndEntityCert::from_with_extension_cb(certificate, cb)?; - id.unwrap_or(Err(webpki::Error::UnknownIssuer)) -} - -fn extract_libp2p_peerid( - extension: untrusted::Input<'_>, -) -> Result { - use ring::{error::Unspecified, io::der}; - extension - .read_all(Unspecified, |mut reader| { - let inner = der::expect_tag_and_get_value(&mut reader, der::Tag::Sequence)?; - inner.read_all(Unspecified, |mut reader| { - let public_key = - der::bit_string_with_no_unused_bits(&mut reader)?.as_slice_less_safe(); - der::bit_string_with_no_unused_bits(&mut reader)?; - identity::PublicKey::from_protobuf_encoding(public_key).map_err(|_| Unspecified) - }) - }) - .map(From::from) -} diff --git a/protocols/tls/src/lib.rs b/protocols/tls/src/lib.rs index 83894c2aec5..9ff0dd4abf0 100644 --- a/protocols/tls/src/lib.rs +++ b/protocols/tls/src/lib.rs @@ -71,8 +71,8 @@ mod certificate; mod verifier; -pub use certificate::extract_peerid; use std::sync::Arc; +pub use verifier::extract_peerid_or_panic; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); diff --git a/protocols/tls/src/verifier.rs b/protocols/tls/src/verifier.rs index 0251ed6c506..a162aa81fcb 100644 --- a/protocols/tls/src/verifier.rs +++ b/protocols/tls/src/verifier.rs @@ -89,3 +89,17 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { verify_presented_certs(presented_certs).map(|()| rustls::ClientCertVerified::assertion()) } } + +/// Extracts the `PeerId` from a certificate’s libp2p extension. It is erroneous +/// to call this unless the certificate is known to be a well-formed X.509 +/// certificate with a valid libp2p extension. The certificate verifiers in this +/// crate validate check this. +/// +/// # Panics +/// +/// Panics if called on an invalid certificate. +pub fn extract_peerid_or_panic(certificate: &[u8]) -> libp2p_core::PeerId { + let parsed = x509::parse_certificate(certificate) + .expect("our certificate verifiers already checked that the certificate is valid; qed"); + x509::get_peerid(parsed) +} diff --git a/protocols/tls/src/verifier/x509.rs b/protocols/tls/src/verifier/x509.rs index 5f9e17f04a8..0c410311776 100644 --- a/protocols/tls/src/verifier/x509.rs +++ b/protocols/tls/src/verifier/x509.rs @@ -27,7 +27,7 @@ pub(super) struct X509Certificate<'a> { x509_tbs: Input<'a>, x509_validity: Input<'a>, x509_spki: Input<'a>, - x509_signature_algorithm: Input<'a>, + x509_signature_algorithm: &'a [u8], x509_signature: Input<'a>, } @@ -90,17 +90,17 @@ fn parse_extensions<'a>(input: &mut Reader<'a>) -> Result, E /// Extracts the algorithm id and public key from a certificate pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = - Input::from(certificate) - .read_all(Error::BadDER, read_sequence)? - .read_all(Error::BadDER, |reader| { + Input::from(certificate).read_all(Error::BadDER, |input| { + der::nested(input, Tag::Sequence, Error::BadDER, |reader| { // tbsCertificate let tbs = reader.read_partial(|reader| read_sequence(reader))?; // signatureAlgorithm - let signature_algorithm = read_sequence(reader)?; + let signature_algorithm = read_sequence(reader)?.as_slice_less_safe(); // signatureValue let signature = read_bit_string(reader, Error::BadDER)?; Ok((tbs, signature_algorithm, signature)) - })?; + }) + })?; let (x509_validity, x509_spki, extensions) = inner_tbs.read_all(Error::BadDER, |mut input| { // We require extensions, which means we require version 3 @@ -108,7 +108,7 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result) -> libp2p_core::PeerId { + certificate.libp2p_extension.peer_key.into_peer_id() +} + pub(super) fn verify_certificate( certificate: X509Certificate<'_>, time: Time, ) -> Result<(), Error> { @@ -173,21 +177,19 @@ pub(super) fn verify_certificate( Ok((x509_pkey_alg, x509_pkey_bytes)) })?; verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; - get_public_key( - x509_pkey_alg, - x509_pkey_bytes, - x509_signature_algorithm.as_slice_less_safe(), - )? - .verify( - x509_tbs.as_slice_less_safe(), - x509_signature.as_slice_less_safe(), - ) - .map_err(|_| Error::InvalidSignatureForPublicKey)?; + get_public_key(x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm)? + .verify( + x509_tbs.as_slice_less_safe(), + x509_signature.as_slice_less_safe(), + ) + .map_err(|_| Error::InvalidSignatureForPublicKey)?; x509_validity.read_all(Error::BadDER, |input| { let not_before = der::time_choice(input)?; let not_after = der::time_choice(input)?; - if time < not_before || time > not_after { - Err(Error::ExtensionValueInvalid) + if time < not_before { + Err(Error::CertNotValidYet) + } else if time > not_after { + Err(Error::CertExpired) } else { Ok(()) } @@ -197,9 +199,7 @@ pub(super) fn verify_certificate( fn get_public_key<'a>( x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], ) -> Result, Error> { - const BUF: &[u8] = &[ - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, - ]; + const RSASSA_PSS_PREFIX: &[u8; 11] = include_bytes!("data/alg-rsa-pss.der"); use signature::{ RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, }; @@ -230,8 +230,8 @@ fn get_public_key<'a>( include_bytes!("data/alg-ed25519.der") => &signature::ED25519, _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), }, - e if e.len() > 11 && &e[..11] == BUF => { - let alg = parse_rsa_pss(&e[11..])?; + e if e.starts_with(&RSASSA_PSS_PREFIX[..]) => { + let alg = parse_rsa_pss(&e[RSASSA_PSS_PREFIX.len()..])?; match x509_pkey_alg { include_bytes!("data/alg-rsa-encryption.der") => alg, _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index ac471d4056e..884b9d1a2e3 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -29,7 +29,6 @@ webpki = "0.21.2" untrusted = "0.7.0" tls = { path = "../../protocols/tls", package = "libp2p-tls", version = "0.16.0" } either = "1.5.3" - -[dev-dependencies] tracing-subscriber = "0.2.1" tracing = "0.1.12" +tracing-core = "0.1.10" diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index cc089a05835..b9d56b8c741 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -23,13 +23,13 @@ use async_macros::ready; use either::Either; use futures::{channel::oneshot, prelude::*}; use libp2p_core::StreamMuxer; -use log::{error, trace}; use parking_lot::{Mutex, MutexGuard}; use std::{ mem::replace, sync::Arc, task::{Context, Poll}, }; +use tracing::{error, trace}; /// A QUIC substream #[derive(Debug)] @@ -99,6 +99,13 @@ enum OutboundInner { Done, } +macro_rules! span { + ($name:expr, $inner:expr, $id:expr) => { + let span = tracing::trace_span!($name, side = debug($inner.side()), id = debug(&$id.id)); + let _guard = span.enter(); + }; +} + /// An outbound QUIC substream. This will eventually resolve to either a /// [`Substream`] or an [`Error`]. #[derive(Debug)] @@ -137,7 +144,9 @@ impl StreamMuxer for QuicMuxer { } fn destroy_substream(&self, substream: Self::Substream) { - self.inner().destroy_stream(substream.id) + let mut inner = self.inner(); + span!("destroy", inner, substream); + inner.destroy_stream(substream.id) } fn is_remote_acknowledged(&self) -> bool { @@ -147,8 +156,10 @@ impl StreamMuxer for QuicMuxer { } fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { - trace!("being polled for inbound connections!"); let mut inner = self.inner(); + let span = tracing::trace_span!("inbound", side = debug(inner.side())); + let _guard = span.enter(); + trace!("being polled for inbound connections!"); inner.close_reason()?; inner.wake_driver(); let stream = ready!(inner.accept(cx)); @@ -163,6 +174,7 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::WriteError; let mut inner = self.inner(); + span!("write", inner, substream); inner.wake_driver(); match inner.write(cx, &substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), @@ -213,6 +225,7 @@ impl StreamMuxer for QuicMuxer { ) -> Poll> { use quinn_proto::ReadError; let mut inner = self.inner(); + span!("read", inner, substream); match inner.read(cx, &substream.id, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), Err(ReadError::Blocked) => { @@ -252,7 +265,9 @@ impl StreamMuxer for QuicMuxer { } SubstreamStatus::Unwritten | SubstreamStatus::Live => {} } - match self.inner().shutdown_stream(cx, &substream.id) { + let mut inner = self.inner(); + span!("shutdown", inner, substream); + match inner.shutdown_stream(cx, &substream.id) { Ok(receiver) => { substream.status = SubstreamStatus::Finishing(receiver); Poll::Pending diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index ddf3f8ae4e7..74010cebb32 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -27,7 +27,6 @@ use libp2p_core::{ transport::{ListenerEvent, TransportError}, Transport, }; -use log::{debug, info, trace}; use parking_lot::Mutex; use quinn_proto::ConnectionHandle; use std::{ @@ -37,6 +36,7 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; +use tracing::{debug, info, trace}; use channel_ref::Channel; @@ -261,6 +261,10 @@ impl Connection { .start_send(EndpointMessage::ConnectionAccepted)? } + assert!( + !self.connection.crypto_session().is_handshaking(), + "QUIC handshake cannot complete before TLS handshake" + ); let certificate = self .connection .crypto_session() @@ -268,9 +272,7 @@ impl Connection { .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, // and that it has a valid libp2p extension. - Poll::Ready(Ok(tls::extract_peerid(certificate[0].as_ref()).expect( - "our certificate verifiers guarantee that this will succeed; qed", - ))) + Poll::Ready(Ok(tls::extract_peerid_or_panic(certificate[0].as_ref()))) } /// Wake up the last task registered by @@ -602,7 +604,7 @@ fn accept_muxer( let streams = Arc::new(Mutex::new(Streams::new(connection))); inner.muxers.insert(handle, streams.clone()); - let upgrade = crate::Upgrade::spawn(streams, socket); + let upgrade = crate::Upgrade::spawn(streams, quinn_proto::Side::Server, socket); if endpoint .new_connections .unbounded_send(ListenerEvent::Upgrade { @@ -622,6 +624,8 @@ impl Future for EndpointRef { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let Self { reference, channel } = self.get_mut(); let mut inner = reference.inner.lock(); + let span = tracing::trace_span!("Endpoint", address = display(&reference.address)); + let _guard = span.enter(); trace!("driving events"); inner.drive_events(cx); trace!("driving incoming packets"); @@ -742,7 +746,11 @@ impl Transport for Endpoint { }; let streams = Arc::new(Mutex::new(Streams::new(connection))); inner.muxers.insert(handle, streams.clone()); - Ok(crate::Upgrade::spawn(streams.clone(), socket)) + Ok(crate::Upgrade::spawn( + streams.clone(), + quinn_proto::Side::Client, + socket, + )) } } diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index 60b0cde880f..a6d09743d82 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -26,7 +26,6 @@ use super::{socket, Error}; use async_macros::ready; use either::Either; use futures::{channel::oneshot, future::FutureExt}; -use log::{debug, error, info, trace}; use parking_lot::Mutex; use quinn_proto::Dir; use std::collections::HashMap; @@ -37,6 +36,7 @@ use std::{ task::{Context, Poll, Waker}, time::Instant, }; +use tracing::{debug, error, info, trace}; /// A stream ID. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -419,10 +419,12 @@ struct ConnectionDriver { impl Upgrade { pub(crate) fn spawn( connection: Arc>, + _side: quinn_proto::Side, socket: Arc, ) -> Upgrade { + let inner = connection.clone(); async_std::task::spawn(ConnectionDriver { - inner: connection.clone(), + inner, outgoing_packet: None, timer: None, last_timeout: None, @@ -438,8 +440,9 @@ impl Future for ConnectionDriver { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - debug!("being polled for timer!"); let mut inner = this.inner.lock(); + let span = tracing::trace_span!("Connection", side = debug(inner.side()),); + let _guard = span.enter(); inner.waker = Some(cx.waker().clone()); loop { let now = Instant::now(); @@ -490,6 +493,8 @@ impl Future for Upgrade { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); let mut inner = muxer.as_mut().expect("polled after yielding Ready").lock(); + let span = tracing::trace_span!("Upgrade", side = debug(inner.side()),); + let _guard = span.enter(); if let Some(close_reason) = &inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 135ef4c9b3b..dcaea8e25e5 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -26,6 +26,8 @@ use libp2p_core::{ StreamMuxer, Transport, }; use libp2p_quic::{Config, Endpoint, Muxer, Substream}; +use tracing::info_span; + use log::{debug, info, trace}; use std::{ io::Result, @@ -169,16 +171,13 @@ fn wildcard_expansion() { #[test] fn communicating_between_dialer_and_listener() { - use tracing::info_span; init(); for i in 0..1000u32 { - let span = info_span!("test", id = i); - let _guard = span.enter(); - do_test() + do_test(i) } } -fn do_test() { +fn do_test(_i: u32) { use std::error::Error as _; let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); @@ -190,6 +189,9 @@ fn do_test() { let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); trace!("running tests"); let handle = async_std::task::spawn(async move { + // let span = info_span!("test", id = i, side = "server"); + // let _guard = span.enter(); + let key = loop { trace!("awaiting connection"); match listener.next().await.unwrap().unwrap() { From 817851ccf77aa09a9f6f4cced3a32bcfbca97e88 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 22 Mar 2020 16:54:44 -0400 Subject: [PATCH 152/202] Use a working quinn-proto version --- transports/quic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 884b9d1a2e3..bbd83e40b16 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4.8" ipnet = "2.2.0" err-derive = "0.2.2" futures = "0.3.4" -quinn-proto = { git = "https://github.com/djc/quinn" } +quinn-proto = "0.6.0" async-std = "1.5.0" async-macros = "2.0.0" futures-timer = "3.0.2" From d2ef7d2a19a1ffa7ba38159f38a0ee3e2b6049ae Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 22 Mar 2020 17:43:50 -0400 Subject: [PATCH 153/202] Remove unneeded feature in yasna --- transports/quic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index bbd83e40b16..71c205d7446 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -23,7 +23,7 @@ env_logger = "0.7.1" parking_lot = "0.10.0" rcgen = "0.7.0" protobuf = "2.10.1" -yasna = { version = "0.3.1", features = ["num-bigint"] } +yasna = { version = "0.3.1" } ring = "0.16.11" webpki = "0.21.2" untrusted = "0.7.0" From 636299850c3a2a60a138fd82ed123c6a81d31b11 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 23 Mar 2020 10:33:54 -0400 Subject: [PATCH 154/202] Packets cannot send themselves --- transports/quic/src/endpoint.rs | 9 +++----- transports/quic/src/socket.rs | 38 ++++++++++++++++----------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 74010cebb32..b3105e43fb8 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -199,8 +199,8 @@ impl EndpointInner { cx: &mut Context<'_>, ) -> Poll> { let Self { inner, pending, .. } = self; - pending - .send_packet(cx, socket, &mut || inner.poll_transmit()) + socket + .send_packets(cx, pending, &mut || inner.poll_transmit()) .map_err(Error::IO) } } @@ -336,10 +336,7 @@ impl Connection { socket: &socket::Socket, ) -> Result<(), Error> { let connection = &mut self.connection; - match self - .pending - .send_packet(cx, socket, &mut || connection.poll_transmit(now)) - { + match socket.send_packets(cx, &mut self.pending, &mut || connection.poll_transmit(now)) { Poll::Ready(Ok(())) | Poll::Pending => Ok(()), Poll::Ready(Err(e)) => Err(Error::IO(e)), } diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 6196062540d..18dae7c930c 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -23,7 +23,7 @@ //! This provides a central location for socket I/O, and logs all incoming and outgoing packets. use async_std::net::UdpSocket; -use log::{trace, warn}; +use tracing::{trace, warn}; use quinn_proto::Transmit; use std::{future::Future, io::Result, task::Context, task::Poll}; @@ -42,9 +42,9 @@ pub(crate) struct Socket { impl Socket { /// Transmit a packet if possible, with appropriate logging. /// - /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition - /// and drop it. If it is not, the connection will eventually time out. This provides a very - /// high degree of robustness. Connections will transparently resume after a transient network + /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition and + /// drop it. If it is not, the connection will eventually time out. This provides a very high + /// degree of robustness. Connections will transparently resume after a transient network /// outage, and problems that are specific to one peer will not effect other peers. pub(crate) fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { match { @@ -94,36 +94,28 @@ impl Socket { } } } -} - -impl Socket { - pub(crate) fn new(socket: UdpSocket) -> Self { - Self { socket } - } -} -impl Pending { - pub(crate) fn send_packet( - &mut self, + pub(crate) fn send_packets( + &self, cx: &mut Context<'_>, - socket: &Socket, + pending: &mut Pending, source: &mut dyn FnMut() -> Option, ) -> Poll> { - if let Some(ref mut transmit) = self.pending { + if let Some(ref mut transmit) = pending.pending { trace!("trying to send packet!"); - match socket.poll_send_to(cx, &transmit) { + match self.poll_send_to(cx, &transmit) { Poll::Pending => return Poll::Pending, Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } } - self.pending = None; + pending.pending = None; while let Some(transmit) = source() { trace!("trying to send packet!"); - match socket.poll_send_to(cx, &transmit) { + match self.poll_send_to(cx, &transmit) { Poll::Ready(Ok(())) => {} Poll::Pending => { - self.pending = Some(transmit); + pending.pending = Some(transmit); return Poll::Pending; } Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), @@ -132,3 +124,9 @@ impl Pending { Poll::Ready(Ok(())) } } + +impl Socket { + pub(crate) fn new(socket: UdpSocket) -> Self { + Self { socket } + } +} From c775c9382c5ad498f2f532409e4edaf37fb14ba3 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 24 Mar 2020 10:47:09 -0400 Subject: [PATCH 155/202] Send an empty distinguished name --- protocols/tls/src/certificate.rs | 1 + transports/quic/src/socket.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/protocols/tls/src/certificate.rs b/protocols/tls/src/certificate.rs index 7e5c4ea9102..341c941b83c 100644 --- a/protocols/tls/src/certificate.rs +++ b/protocols/tls/src/certificate.rs @@ -74,6 +74,7 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu /// certificate extension containing the public key of the given keypair. pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { let mut params = rcgen::CertificateParams::new(vec![]); + params.distinguished_name = rcgen::DistinguishedName::new(); let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); params.custom_extensions.push(libp2p_extension); params.alg = &LIBP2P_SIGNATURE_ALGORITHM; diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 18dae7c930c..1c9435f3aff 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -23,9 +23,9 @@ //! This provides a central location for socket I/O, and logs all incoming and outgoing packets. use async_std::net::UdpSocket; -use tracing::{trace, warn}; use quinn_proto::Transmit; use std::{future::Future, io::Result, task::Context, task::Poll}; +use tracing::{trace, warn}; /// A pending packet for libp2p-quic #[derive(Debug, Default)] From 401750685d3ee0099285d2f8b80d7019eab2fe7b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 26 Mar 2020 11:13:20 -0400 Subject: [PATCH 156/202] Make tracing optional --- transports/quic/Cargo.toml | 13 ++++++++++--- transports/quic/src/connection.rs | 15 ++++++++++++--- transports/quic/src/endpoint.rs | 6 +++++- transports/quic/src/lib.rs | 5 +++++ transports/quic/src/socket.rs | 2 +- transports/quic/src/stream_map.rs | 6 +++++- transports/quic/tests/tests.rs | 7 +++---- 7 files changed, 41 insertions(+), 13 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 71c205d7446..c3c250734d8 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -29,6 +29,13 @@ webpki = "0.21.2" untrusted = "0.7.0" tls = { path = "../../protocols/tls", package = "libp2p-tls", version = "0.16.0" } either = "1.5.3" -tracing-subscriber = "0.2.1" -tracing = "0.1.12" -tracing-core = "0.1.10" +tracing-subscriber = { version = "0.2.1", optional = true } +tracing = { version = "0.1.12", optional = true } +tracing-core = { version = "0.1.10", optional = true } + +[features] +trace = [ + "tracing-subscriber", + "tracing", + "tracing-core", +] diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index b9d56b8c741..59733e47699 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use super::{error::Error, stream_map}; +use crate::{error, trace}; use async_macros::ready; use either::Either; use futures::{channel::oneshot, prelude::*}; @@ -29,7 +30,6 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tracing::{error, trace}; /// A QUIC substream #[derive(Debug)] @@ -99,12 +99,22 @@ enum OutboundInner { Done, } +#[cfg(feature = "tracing")] macro_rules! span { + ($name:expr, $($id:ident = $e:expr),*) => { + let span = tracing::trace_span!($name, $($id:ident = $e:expr),*); + let _guard = span.enter(); + } ($name:expr, $inner:expr, $id:expr) => { let span = tracing::trace_span!($name, side = debug($inner.side()), id = debug(&$id.id)); let _guard = span.enter(); }; } +#[cfg(not(feature = "tracing"))] +macro_rules! span { + ($name:expr, $($id:ident = $e:expr),*) => {}; + ($name:expr, $inner:expr, $id:expr) => {}; +} /// An outbound QUIC substream. This will eventually resolve to either a /// [`Substream`] or an [`Error`]. @@ -157,8 +167,7 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { let mut inner = self.inner(); - let span = tracing::trace_span!("inbound", side = debug(inner.side())); - let _guard = span.enter(); + span!("inbound", side = debug(inner.side())); trace!("being polled for inbound connections!"); inner.close_reason()?; inner.wake_driver(); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index b3105e43fb8..073fceac78e 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::{debug, info, trace}; use crate::{error::Error, socket, stream_map::Streams, Upgrade}; use async_macros::ready; use async_std::{net::SocketAddr, task::spawn}; @@ -36,7 +37,6 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; -use tracing::{debug, info, trace}; use channel_ref::Channel; @@ -621,7 +621,9 @@ impl Future for EndpointRef { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let Self { reference, channel } = self.get_mut(); let mut inner = reference.inner.lock(); + #[cfg(feature = "tracing")] let span = tracing::trace_span!("Endpoint", address = display(&reference.address)); + #[cfg(feature = "tracing")] let _guard = span.enter(); trace!("driving events"); inner.drive_events(cx); @@ -777,7 +779,9 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { #[test] fn multiaddr_to_udp_conversion() { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + #[cfg(feature = "tracing")] use tracing_subscriber::{fmt::Subscriber, EnvFilter}; + #[cfg(feature = "tracing")] let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) .try_init(); diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 35dbdf3d716..21f17d775ca 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -101,6 +101,11 @@ bare_trait_objects )] +#[cfg(not(feature = "tracing"))] +use log::{debug, error, info, trace, warn}; +#[cfg(feature = "tracing")] +use tracing::{debug, error, info, trace, warn}; + mod connection; mod endpoint; mod error; diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 1c9435f3aff..55bc420ccd3 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -22,10 +22,10 @@ //! //! This provides a central location for socket I/O, and logs all incoming and outgoing packets. +use crate::{trace, warn}; use async_std::net::UdpSocket; use quinn_proto::Transmit; use std::{future::Future, io::Result, task::Context, task::Poll}; -use tracing::{trace, warn}; /// A pending packet for libp2p-quic #[derive(Debug, Default)] diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index a6d09743d82..f0d8ea06669 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -23,6 +23,7 @@ use super::endpoint::Connection; use super::stream::StreamState; use super::{socket, Error}; +use crate::{debug, error, info, trace}; use async_macros::ready; use either::Either; use futures::{channel::oneshot, future::FutureExt}; @@ -36,7 +37,6 @@ use std::{ task::{Context, Poll, Waker}, time::Instant, }; -use tracing::{debug, error, info, trace}; /// A stream ID. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -441,7 +441,9 @@ impl Future for ConnectionDriver { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); let mut inner = this.inner.lock(); + #[cfg(feature = "tracing")] let span = tracing::trace_span!("Connection", side = debug(inner.side()),); + #[cfg(feature = "tracing")] let _guard = span.enter(); inner.waker = Some(cx.waker().clone()); loop { @@ -493,7 +495,9 @@ impl Future for Upgrade { let muxer = &mut self.get_mut().muxer; trace!("outbound polling!"); let mut inner = muxer.as_mut().expect("polled after yielding Ready").lock(); + #[cfg(feature = "tracing")] let span = tracing::trace_span!("Upgrade", side = debug(inner.side()),); + #[cfg(feature = "tracing")] let _guard = span.enter(); if let Some(close_reason) = &inner.close_reason { return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index dcaea8e25e5..2a2d0760269 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -26,7 +26,6 @@ use libp2p_core::{ StreamMuxer, Transport, }; use libp2p_quic::{Config, Endpoint, Muxer, Substream}; -use tracing::info_span; use log::{debug, info, trace}; use std::{ @@ -115,12 +114,15 @@ impl<'a> futures::Stream for Inbound<'a> { } } +#[cfg(feature = "tracing")] fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) .try_init(); } +#[cfg(not(feature = "tracing"))] +fn init() {} struct Closer(Muxer); @@ -189,9 +191,6 @@ fn do_test(_i: u32) { let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); trace!("running tests"); let handle = async_std::task::spawn(async move { - // let span = info_span!("test", id = i, side = "server"); - // let _guard = span.enter(); - let key = loop { trace!("awaiting connection"); match listener.next().await.unwrap().unwrap() { From c3c9393db8990dfde6eeaa21fe881282b3010e86 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 26 Mar 2020 11:19:36 -0400 Subject: [PATCH 157/202] Update description of libp2p-tls --- protocols/tls/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/tls/Cargo.toml b/protocols/tls/Cargo.toml index 4f7cea9dbf8..36ab37a0166 100644 --- a/protocols/tls/Cargo.toml +++ b/protocols/tls/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-tls" version = "0.16.0" authors = ["Parity Technologies "] edition = "2018" -description = "X.509 verification for libp2p" +description = "X.509 generation and verification for libp2p" license = "ISC" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] From d0ab22977ca4ef74b4ab340ea57af505bef7f504 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 26 Mar 2020 11:27:11 -0400 Subject: [PATCH 158/202] =?UTF-8?q?libp2p-tls=20=E2=86=92=20libp2p-x509?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- {protocols/tls => misc/x509}/Cargo.toml | 2 +- {protocols/tls => misc/x509}/LICENSE-ISC | 0 {protocols/tls => misc/x509}/rustfmt.toml | 0 {protocols/tls => misc/x509}/src/certificate.rs | 0 {protocols/tls => misc/x509}/src/lib.rs | 0 {protocols/tls => misc/x509}/src/verifier.rs | 0 .../tls => misc/x509}/src/verifier/calendar.rs | 0 .../tls => misc/x509}/src/verifier/data/LICENSE | 0 .../tls => misc/x509}/src/verifier/data/README.md | 0 .../x509}/src/verifier/data/alg-ecdsa-p256.der | 0 .../x509}/src/verifier/data/alg-ecdsa-p384.der | Bin .../x509}/src/verifier/data/alg-ecdsa-sha256.der | 0 .../x509}/src/verifier/data/alg-ecdsa-sha384.der | 0 .../x509}/src/verifier/data/alg-ed25519.der | 0 .../x509}/src/verifier/data/alg-rsa-encryption.der | Bin .../src/verifier/data/alg-rsa-pkcs1-sha256.der | Bin .../src/verifier/data/alg-rsa-pkcs1-sha384.der | Bin .../src/verifier/data/alg-rsa-pkcs1-sha512.der | Bin .../src/verifier/data/alg-rsa-pss-sha256-v0.der | 0 .../src/verifier/data/alg-rsa-pss-sha256-v1.der | Bin .../src/verifier/data/alg-rsa-pss-sha256-v2.der | Bin .../src/verifier/data/alg-rsa-pss-sha256-v3.der | Bin .../x509}/src/verifier/data/alg-rsa-pss-sha256.der | Bin .../src/verifier/data/alg-rsa-pss-sha384-v0.der | 0 .../src/verifier/data/alg-rsa-pss-sha384-v1.der | Bin .../src/verifier/data/alg-rsa-pss-sha384-v2.der | Bin .../src/verifier/data/alg-rsa-pss-sha384-v3.der | Bin .../x509}/src/verifier/data/alg-rsa-pss-sha384.der | Bin .../src/verifier/data/alg-rsa-pss-sha512-v0.der | 0 .../src/verifier/data/alg-rsa-pss-sha512-v1.der | Bin .../src/verifier/data/alg-rsa-pss-sha512-v2.der | Bin .../src/verifier/data/alg-rsa-pss-sha512-v3.der | Bin .../x509}/src/verifier/data/alg-rsa-pss-sha512.der | Bin .../x509}/src/verifier/data/alg-rsa-pss.der | 0 {protocols/tls => misc/x509}/src/verifier/der.rs | 0 {protocols/tls => misc/x509}/src/verifier/x509.rs | 0 transports/quic/Cargo.toml | 2 +- transports/quic/src/endpoint.rs | 4 ++-- 39 files changed, 5 insertions(+), 5 deletions(-) rename {protocols/tls => misc/x509}/Cargo.toml (96%) rename {protocols/tls => misc/x509}/LICENSE-ISC (100%) rename {protocols/tls => misc/x509}/rustfmt.toml (100%) rename {protocols/tls => misc/x509}/src/certificate.rs (100%) rename {protocols/tls => misc/x509}/src/lib.rs (100%) rename {protocols/tls => misc/x509}/src/verifier.rs (100%) rename {protocols/tls => misc/x509}/src/verifier/calendar.rs (100%) rename {protocols/tls => misc/x509}/src/verifier/data/LICENSE (100%) rename {protocols/tls => misc/x509}/src/verifier/data/README.md (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-ecdsa-p256.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-ecdsa-p384.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-ecdsa-sha256.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-ecdsa-sha384.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-ed25519.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-encryption.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pkcs1-sha256.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pkcs1-sha384.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pkcs1-sha512.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha256-v0.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha256-v1.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha256-v2.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha256-v3.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha256.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha384-v0.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha384-v1.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha384-v2.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha384-v3.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha384.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha512-v0.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha512-v1.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha512-v2.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha512-v3.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss-sha512.der (100%) rename {protocols/tls => misc/x509}/src/verifier/data/alg-rsa-pss.der (100%) rename {protocols/tls => misc/x509}/src/verifier/der.rs (100%) rename {protocols/tls => misc/x509}/src/verifier/x509.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 8b923981d86..60ae443d2b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ members = [ "misc/multiaddr", "misc/multistream-select", "misc/peer-id-generator", + "misc/x509", "muxers/mplex", "muxers/yamux", "protocols/floodsub", @@ -108,7 +109,6 @@ members = [ "protocols/ping", "protocols/plaintext", "protocols/secio", - "protocols/tls", "swarm", "transports/dns", "transports/quic", diff --git a/protocols/tls/Cargo.toml b/misc/x509/Cargo.toml similarity index 96% rename from protocols/tls/Cargo.toml rename to misc/x509/Cargo.toml index 36ab37a0166..47da187bbdc 100644 --- a/protocols/tls/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "libp2p-tls" +name = "libp2p-x509" version = "0.16.0" authors = ["Parity Technologies "] edition = "2018" diff --git a/protocols/tls/LICENSE-ISC b/misc/x509/LICENSE-ISC similarity index 100% rename from protocols/tls/LICENSE-ISC rename to misc/x509/LICENSE-ISC diff --git a/protocols/tls/rustfmt.toml b/misc/x509/rustfmt.toml similarity index 100% rename from protocols/tls/rustfmt.toml rename to misc/x509/rustfmt.toml diff --git a/protocols/tls/src/certificate.rs b/misc/x509/src/certificate.rs similarity index 100% rename from protocols/tls/src/certificate.rs rename to misc/x509/src/certificate.rs diff --git a/protocols/tls/src/lib.rs b/misc/x509/src/lib.rs similarity index 100% rename from protocols/tls/src/lib.rs rename to misc/x509/src/lib.rs diff --git a/protocols/tls/src/verifier.rs b/misc/x509/src/verifier.rs similarity index 100% rename from protocols/tls/src/verifier.rs rename to misc/x509/src/verifier.rs diff --git a/protocols/tls/src/verifier/calendar.rs b/misc/x509/src/verifier/calendar.rs similarity index 100% rename from protocols/tls/src/verifier/calendar.rs rename to misc/x509/src/verifier/calendar.rs diff --git a/protocols/tls/src/verifier/data/LICENSE b/misc/x509/src/verifier/data/LICENSE similarity index 100% rename from protocols/tls/src/verifier/data/LICENSE rename to misc/x509/src/verifier/data/LICENSE diff --git a/protocols/tls/src/verifier/data/README.md b/misc/x509/src/verifier/data/README.md similarity index 100% rename from protocols/tls/src/verifier/data/README.md rename to misc/x509/src/verifier/data/README.md diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-p256.der b/misc/x509/src/verifier/data/alg-ecdsa-p256.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-ecdsa-p256.der rename to misc/x509/src/verifier/data/alg-ecdsa-p256.der diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-p384.der b/misc/x509/src/verifier/data/alg-ecdsa-p384.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-ecdsa-p384.der rename to misc/x509/src/verifier/data/alg-ecdsa-p384.der diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-sha256.der b/misc/x509/src/verifier/data/alg-ecdsa-sha256.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-ecdsa-sha256.der rename to misc/x509/src/verifier/data/alg-ecdsa-sha256.der diff --git a/protocols/tls/src/verifier/data/alg-ecdsa-sha384.der b/misc/x509/src/verifier/data/alg-ecdsa-sha384.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-ecdsa-sha384.der rename to misc/x509/src/verifier/data/alg-ecdsa-sha384.der diff --git a/protocols/tls/src/verifier/data/alg-ed25519.der b/misc/x509/src/verifier/data/alg-ed25519.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-ed25519.der rename to misc/x509/src/verifier/data/alg-ed25519.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-encryption.der b/misc/x509/src/verifier/data/alg-rsa-encryption.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-encryption.der rename to misc/x509/src/verifier/data/alg-rsa-encryption.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha256.der b/misc/x509/src/verifier/data/alg-rsa-pkcs1-sha256.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha256.der rename to misc/x509/src/verifier/data/alg-rsa-pkcs1-sha256.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha384.der b/misc/x509/src/verifier/data/alg-rsa-pkcs1-sha384.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha384.der rename to misc/x509/src/verifier/data/alg-rsa-pkcs1-sha384.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha512.der b/misc/x509/src/verifier/data/alg-rsa-pkcs1-sha512.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pkcs1-sha512.der rename to misc/x509/src/verifier/data/alg-rsa-pkcs1-sha512.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v0.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v0.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v0.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha256-v0.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v1.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v1.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v1.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha256-v1.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v2.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v2.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha256-v2.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v3.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha256-v3.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha256-v3.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha256.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha256.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha256.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v0.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v1.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v1.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v1.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha384-v1.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v2.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v2.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha384-v2.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v3.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha384-v3.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha384-v3.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha384.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha384.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha384.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v0.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v1.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v1.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v1.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha512-v1.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v2.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v2.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha512-v2.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v3.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha512-v3.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha512-v3.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss-sha512.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss-sha512.der rename to misc/x509/src/verifier/data/alg-rsa-pss-sha512.der diff --git a/protocols/tls/src/verifier/data/alg-rsa-pss.der b/misc/x509/src/verifier/data/alg-rsa-pss.der similarity index 100% rename from protocols/tls/src/verifier/data/alg-rsa-pss.der rename to misc/x509/src/verifier/data/alg-rsa-pss.der diff --git a/protocols/tls/src/verifier/der.rs b/misc/x509/src/verifier/der.rs similarity index 100% rename from protocols/tls/src/verifier/der.rs rename to misc/x509/src/verifier/der.rs diff --git a/protocols/tls/src/verifier/x509.rs b/misc/x509/src/verifier/x509.rs similarity index 100% rename from protocols/tls/src/verifier/x509.rs rename to misc/x509/src/verifier/x509.rs diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index c3c250734d8..a415e6c4223 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -27,7 +27,7 @@ yasna = { version = "0.3.1" } ring = "0.16.11" webpki = "0.21.2" untrusted = "0.7.0" -tls = { path = "../../protocols/tls", package = "libp2p-tls", version = "0.16.0" } +x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.16.0" } either = "1.5.3" tracing-subscriber = { version = "0.2.1", optional = true } tracing = { version = "0.1.12", optional = true } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 073fceac78e..21b7402f329 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -61,7 +61,7 @@ impl Config { transport.datagram_receive_buffer_size(None); transport.keep_alive_interval(Some(Duration::from_millis(10))); let transport = Arc::new(transport); - let (client_tls_config, server_tls_config) = tls::make_tls_config(keypair); + let (client_tls_config, server_tls_config) = x509::make_tls_config(keypair); let mut server_config = quinn_proto::ServerConfig::default(); server_config.transport = transport.clone(); server_config.crypto = Arc::new(server_tls_config); @@ -272,7 +272,7 @@ impl Connection { .expect("we always require the peer to present a certificate; qed"); // we have already verified that there is (exactly) one peer certificate, // and that it has a valid libp2p extension. - Poll::Ready(Ok(tls::extract_peerid_or_panic(certificate[0].as_ref()))) + Poll::Ready(Ok(x509::extract_peerid_or_panic(certificate[0].as_ref()))) } /// Wake up the last task registered by From 530728039ac323194bbd379a322919b21de671fa Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 26 Mar 2020 12:05:25 -0400 Subject: [PATCH 159/202] Fix silly macro error --- transports/quic/src/connection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 59733e47699..db8ed0ce8e9 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -101,10 +101,10 @@ enum OutboundInner { #[cfg(feature = "tracing")] macro_rules! span { - ($name:expr, $($id:ident = $e:expr),*) => { - let span = tracing::trace_span!($name, $($id:ident = $e:expr),*); + ($name:expr, side = $e:expr) => { + let span = tracing::trace_span!($name, side = $e); let _guard = span.enter(); - } + }; ($name:expr, $inner:expr, $id:expr) => { let span = tracing::trace_span!($name, side = debug($inner.side()), id = debug(&$id.id)); let _guard = span.enter(); From eff0ec80152ee73870d2d0794d44ea750a1f58db Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 30 Mar 2020 18:30:47 -0400 Subject: [PATCH 160/202] Bump async-tls --- transports/websocket/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index 6a0dfe2b335..86c2aba4e43 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-tls = "0.6" +async-tls = "0.7.0" bytes = "0.5" either = "1.5.3" futures = "0.3.1" From def5bd646774e9c1bcf4e26a23ec675d1d50a3af Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Apr 2020 12:37:54 -0400 Subject: [PATCH 161/202] Fix silly typos in Cargo.toml --- Cargo.toml | 3 +-- misc/x509/Cargo.toml | 4 ++-- transports/quic/Cargo.toml | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 92eb5ba96f7..8f26369f0a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,5 +119,4 @@ members = [ ] [patch.crates-io] -webpki = { git = "https://github.com/paritytech/webpki", branch = "extension-handlers" } -async-tls = { git = "https://github.com/async-std/async-tls" } +rustls = { git = "https://github.com/DemiMarie-parity/rustls", branch = "manual-verify" } diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 47da187bbdc..654f6dd16c2 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-x509" -version = "0.16.0" +version = "0.17.1" authors = ["Parity Technologies "] edition = "2018" description = "X.509 generation and verification for libp2p" @@ -16,5 +16,5 @@ rcgen = "0.7.0" webpki = "0.21.2" untrusted = "0.7.0" log = "0.4.8" -libp2p-core = { path = "../../core", version = "0.16.0" } +libp2p-core = { path = "../../core", version = "0.17.1" } yasna = "0.3.1" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index a415e6c4223..05725eca1f9 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-quic" -version = "0.16.0" +version = "0.17.1" authors = ["Parity Technologies "] edition = "2018" license = "MIT" @@ -10,7 +10,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] rustls = { version = "0.17.0", features = ["dangerous_configuration"] } -libp2p-core = { path = "../../core", version = "0.16.0" } +libp2p-core = { path = "../../core", version = "0.17.1" } log = "0.4.8" ipnet = "2.2.0" err-derive = "0.2.2" @@ -27,7 +27,7 @@ yasna = { version = "0.3.1" } ring = "0.16.11" webpki = "0.21.2" untrusted = "0.7.0" -x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.16.0" } +x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.17.1" } either = "1.5.3" tracing-subscriber = { version = "0.2.1", optional = true } tracing = { version = "0.1.12", optional = true } From 9d83c2cf17e406c3c50ab94cbfef03834abdff94 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Apr 2020 16:53:49 -0400 Subject: [PATCH 162/202] Avoid depending on pem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s a default feature of rcgen, but libp2p doesn’t need it. --- misc/x509/Cargo.toml | 2 +- transports/quic/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 654f6dd16c2..ddb8365e9c5 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] rustls = { version = "0.17.0", features = ["dangerous_configuration"] } ring = "0.16.11" -rcgen = "0.7.0" +rcgen = { version = "0.7.0", default-features = false } webpki = "0.21.2" untrusted = "0.7.0" log = "0.4.8" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 05725eca1f9..b1bbb49fa86 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -21,7 +21,7 @@ async-macros = "2.0.0" futures-timer = "3.0.2" env_logger = "0.7.1" parking_lot = "0.10.0" -rcgen = "0.7.0" +rcgen = { version = "0.7.0", default_features = false } protobuf = "2.10.1" yasna = { version = "0.3.1" } ring = "0.16.11" From f71cbc131c19489cf4d6d29fb881c7d5da95ff52 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Fri, 3 Apr 2020 16:54:28 -0400 Subject: [PATCH 163/202] Implement certificate signature verification manually --- misc/x509/src/verifier.rs | 34 ++++++-- misc/x509/src/verifier/x509.rs | 138 ++++++++++++++++++++++-------- transports/quic/src/connection.rs | 2 +- 3 files changed, 130 insertions(+), 44 deletions(-) diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index a162aa81fcb..3a0c3fcdfc3 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -43,6 +43,18 @@ impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { ) -> Result { verify_presented_certs(presented_certs).map(|()| rustls::ServerCertVerified::assertion()) } + + fn verify_certificate_signature( + &self, scheme: rustls::SignatureScheme, version: rustls::ProtocolVersion, + certificate: &rustls::Certificate, msg: &[u8], signature: &[u8], + ) -> Result { + assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); + x509::parse_certificate(certificate.as_ref()) + .map_err(rustls::TLSError::WebPKIError)? + .verify_signature(scheme, msg, signature) + .map_err(rustls::TLSError::WebPKIError) + .map(|()| rustls::HandshakeSignatureValid::assertion()) + } } fn get_time() -> Result { @@ -54,12 +66,10 @@ fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), if presented_certs.len() != 1 { return Err(rustls::TLSError::NoCertificatesPresented); } - x509::verify_certificate( - x509::parse_certificate(presented_certs[0].as_ref()) - .map_err(rustls::TLSError::WebPKIError)?, - get_time()?, - ) - .map_err(rustls::TLSError::WebPKIError) + x509::parse_certificate(presented_certs[0].as_ref()) + .map_err(rustls::TLSError::WebPKIError)? + .verify_libp2p(get_time()?) + .map_err(rustls::TLSError::WebPKIError) } /// libp2p requires the following of X.509 client certificate chains: @@ -88,6 +98,18 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { ) -> Result { verify_presented_certs(presented_certs).map(|()| rustls::ClientCertVerified::assertion()) } + + fn verify_certificate_signature( + &self, scheme: rustls::SignatureScheme, version: rustls::ProtocolVersion, + certificate: &rustls::Certificate, msg: &[u8], signature: &[u8], + ) -> Result { + assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); + x509::parse_certificate(certificate.as_ref()) + .map_err(rustls::TLSError::WebPKIError)? + .verify_signature(scheme, msg, signature) + .map_err(rustls::TLSError::WebPKIError) + .map(|()| rustls::HandshakeSignatureValid::assertion()) + } } /// Extracts the `PeerId` from a certificate’s libp2p extension. It is erroneous diff --git a/misc/x509/src/verifier/x509.rs b/misc/x509/src/verifier/x509.rs index 0c410311776..5f490a5748f 100644 --- a/misc/x509/src/verifier/x509.rs +++ b/misc/x509/src/verifier/x509.rs @@ -31,6 +31,56 @@ pub(super) struct X509Certificate<'a> { x509_signature: Input<'a>, } +impl X509Certificate<'_> { + pub(super) fn verify_libp2p(&self, time: Time) -> Result<(), Error> { + let X509Certificate { + x509_tbs, + x509_spki, + x509_signature, + x509_signature_algorithm, + libp2p_extension, + x509_validity, + } = self; + + let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |input| { + let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); + let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); + Ok((x509_pkey_alg, x509_pkey_bytes)) + })?; + verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; + get_public_key_x509(x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm)? + .verify( + x509_tbs.as_slice_less_safe(), + x509_signature.as_slice_less_safe(), + ) + .map_err(|_| Error::InvalidSignatureForPublicKey)?; + x509_validity.read_all(Error::BadDER, |input| { + let not_before = der::time_choice(input)?; + let not_after = der::time_choice(input)?; + if time < not_before { + Err(Error::CertNotValidYet) + } else if time > not_after { + Err(Error::CertExpired) + } else { + Ok(()) + } + }) + } + + pub(super) fn verify_signature( + &self, scheme: rustls::SignatureScheme, message: &[u8], signature: &[u8], + ) -> Result<(), Error> { + let (pkey_alg, pkey_bytes) = self.x509_spki.read_all(Error::BadDER, |input| { + let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); + let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); + Ok((x509_pkey_alg, x509_pkey_bytes)) + })?; + get_public_key_tls(pkey_alg, pkey_bytes, scheme)? + .verify(message, signature) + .map_err(|_| Error::InvalidSignatureForPublicKey) + } +} + struct Libp2pExtension<'a> { peer_key: PublicKey, signature: &'a [u8], @@ -140,7 +190,7 @@ pub(super) fn parse_certificate(certificate: &[u8]) -> Result, x509_pkey_bytes: &[u8], + libp2p_extension: &Libp2pExtension<'_>, x509_pkey_bytes: &[u8], ) -> Result<(), Error> { let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); @@ -159,44 +209,58 @@ pub(super) fn get_peerid(certificate: X509Certificate<'_>) -> libp2p_core::PeerI certificate.libp2p_extension.peer_key.into_peer_id() } -pub(super) fn verify_certificate( - certificate: X509Certificate<'_>, time: Time, -) -> Result<(), Error> { - let X509Certificate { - x509_tbs, - x509_spki, - x509_signature, - x509_signature_algorithm, - libp2p_extension, - x509_validity, - } = certificate; - - let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |input| { - let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); - let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); - Ok((x509_pkey_alg, x509_pkey_bytes)) - })?; - verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; - get_public_key(x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm)? - .verify( - x509_tbs.as_slice_less_safe(), - x509_signature.as_slice_less_safe(), - ) - .map_err(|_| Error::InvalidSignatureForPublicKey)?; - x509_validity.read_all(Error::BadDER, |input| { - let not_before = der::time_choice(input)?; - let not_after = der::time_choice(input)?; - if time < not_before { - Err(Error::CertNotValidYet) - } else if time > not_after { - Err(Error::CertExpired) - } else { - Ok(()) - } - }) +fn get_public_key_tls<'a>( + x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], tls_signature_scheme: rustls::SignatureScheme, +) -> Result, Error> { + use signature::{ + RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, + }; + let algorithm: &'static dyn signature::VerificationAlgorithm = match tls_signature_scheme { + rustls::SignatureScheme::RSA_PKCS1_SHA256 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA256, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::RSA_PKCS1_SHA384 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA384, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::RSA_PKCS1_SHA512 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA512, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256 => match x509_pkey_alg { + include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA256_ASN1, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384 => match x509_pkey_alg { + include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA384_ASN1, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::ED25519 => match x509_pkey_alg { + include_bytes!("data/alg-ed25519.der") => &signature::ED25519, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::RSA_PSS_SHA256 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA256, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::RSA_PSS_SHA384 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA384, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + rustls::SignatureScheme::RSA_PSS_SHA512 => match x509_pkey_alg { + include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA512, + _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), + }, + _ => return Err(Error::UnsupportedSignatureAlgorithm), + }; + Ok(signature::UnparsedPublicKey::new( + algorithm, + x509_pkey_bytes, + )) } -fn get_public_key<'a>( +fn get_public_key_x509<'a>( x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], ) -> Result, Error> { const RSASSA_PSS_PREFIX: &[u8; 11] = include_bytes!("data/alg-rsa-pss.der"); diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index db8ed0ce8e9..8cb9157a9d3 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -104,7 +104,7 @@ macro_rules! span { ($name:expr, side = $e:expr) => { let span = tracing::trace_span!($name, side = $e); let _guard = span.enter(); - }; + }; ($name:expr, $inner:expr, $id:expr) => { let span = tracing::trace_span!($name, side = debug($inner.side()), id = debug(&$id.id)); let _guard = span.enter(); From 16d26e99cbbb24dc5cd6dee49400857691155b3e Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 6 Apr 2020 17:23:12 -0400 Subject: [PATCH 164/202] Switch to x509-signature crate --- misc/x509/Cargo.toml | 6 + misc/x509/src/verifier.rs | 104 +++++- misc/x509/src/verifier/calendar.rs | 155 -------- misc/x509/src/verifier/data/LICENSE | 22 -- misc/x509/src/verifier/data/README.md | 21 -- .../x509/src/verifier/data/alg-ecdsa-p256.der | 1 - .../x509/src/verifier/data/alg-ecdsa-p384.der | Bin 16 -> 0 bytes .../src/verifier/data/alg-ecdsa-sha256.der | 1 - .../src/verifier/data/alg-ecdsa-sha384.der | 1 - misc/x509/src/verifier/data/alg-ed25519.der | 1 - .../src/verifier/data/alg-rsa-encryption.der | Bin 13 -> 0 bytes .../verifier/data/alg-rsa-pkcs1-sha256.der | Bin 13 -> 0 bytes .../verifier/data/alg-rsa-pkcs1-sha384.der | Bin 13 -> 0 bytes .../verifier/data/alg-rsa-pkcs1-sha512.der | Bin 13 -> 0 bytes .../verifier/data/alg-rsa-pss-sha256-v0.der | 1 - .../verifier/data/alg-rsa-pss-sha256-v1.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha256-v2.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha256-v3.der | Bin 54 -> 0 bytes .../src/verifier/data/alg-rsa-pss-sha256.der | Bin 65 -> 0 bytes .../verifier/data/alg-rsa-pss-sha384-v0.der | 1 - .../verifier/data/alg-rsa-pss-sha384-v1.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha384-v2.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha384-v3.der | Bin 54 -> 0 bytes .../src/verifier/data/alg-rsa-pss-sha384.der | Bin 65 -> 0 bytes .../verifier/data/alg-rsa-pss-sha512-v0.der | 1 - .../verifier/data/alg-rsa-pss-sha512-v1.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha512-v2.der | Bin 52 -> 0 bytes .../verifier/data/alg-rsa-pss-sha512-v3.der | Bin 54 -> 0 bytes .../src/verifier/data/alg-rsa-pss-sha512.der | Bin 65 -> 0 bytes misc/x509/src/verifier/data/alg-rsa-pss.der | 1 - misc/x509/src/verifier/der.rs | 107 ------ misc/x509/src/verifier/x509.rs | 334 ------------------ 32 files changed, 95 insertions(+), 662 deletions(-) delete mode 100644 misc/x509/src/verifier/calendar.rs delete mode 100644 misc/x509/src/verifier/data/LICENSE delete mode 100644 misc/x509/src/verifier/data/README.md delete mode 100644 misc/x509/src/verifier/data/alg-ecdsa-p256.der delete mode 100644 misc/x509/src/verifier/data/alg-ecdsa-p384.der delete mode 100644 misc/x509/src/verifier/data/alg-ecdsa-sha256.der delete mode 100644 misc/x509/src/verifier/data/alg-ecdsa-sha384.der delete mode 100644 misc/x509/src/verifier/data/alg-ed25519.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-encryption.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pkcs1-sha256.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pkcs1-sha384.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pkcs1-sha512.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha256-v0.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha256-v1.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha256-v2.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha256-v3.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha256.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha384-v1.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha384-v2.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha384-v3.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha384.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha512-v1.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha512-v2.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha512-v3.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss-sha512.der delete mode 100644 misc/x509/src/verifier/data/alg-rsa-pss.der delete mode 100644 misc/x509/src/verifier/der.rs delete mode 100644 misc/x509/src/verifier/x509.rs diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index ddb8365e9c5..7a286ac2a7e 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -18,3 +18,9 @@ untrusted = "0.7.0" log = "0.4.8" libp2p-core = { path = "../../core", version = "0.17.1" } yasna = "0.3.1" + +[dependencies.x509] +package = "x509-signature" +git = "https://github.com/paritytech/x509-signature" +branch = "master" +features = ["webpki", "rustls"] diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 3a0c3fcdfc3..1bc0b1cb679 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -18,9 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod calendar; -mod der; -mod x509; +use libp2p_core::identity::PublicKey; +use ring::io::der; +use untrusted::{Input, Reader}; +use webpki::Error; /// Libp2p client and server certificate verifier. pub(crate) struct Libp2pCertificateVerifier; @@ -51,27 +52,98 @@ impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); x509::parse_certificate(certificate.as_ref()) .map_err(rustls::TLSError::WebPKIError)? - .verify_signature(scheme, msg, signature) + .verify_signature_against_scheme(get_time()?, scheme, msg, signature) .map_err(rustls::TLSError::WebPKIError) .map(|()| rustls::HandshakeSignatureValid::assertion()) } } -fn get_time() -> Result { - webpki::Time::try_from(std::time::SystemTime::now()) - .map_err(|ring::error::Unspecified| rustls::TLSError::FailedToGetCurrentTime) +fn verify_libp2p_signature( + libp2p_extension: &Libp2pExtension<'_>, x509_pkey_bytes: &[u8], +) -> Result<(), Error> { + let mut v = Vec::with_capacity(crate::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); + v.extend_from_slice(&crate::LIBP2P_SIGNING_PREFIX[..]); + v.extend_from_slice(x509_pkey_bytes); + if libp2p_extension + .peer_key + .verify(&v, libp2p_extension.signature) + { + Ok(()) + } else { + Err(Error::UnknownIssuer) + } +} + +fn get_time() -> Result { + use std::time::{SystemTime, SystemTimeError}; + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|_: SystemTimeError| rustls::TLSError::FailedToGetCurrentTime) + .map(|e| e.as_secs()) +} + +fn parse_certificate( + certificate: &[u8], +) -> Result<(x509::X509Certificate<'_>, Libp2pExtension<'_>), Error> { + let parsed = x509::parse_certificate(certificate)?; + let mut libp2p_extension = None; + + parsed + .extensions() + .iterate(&mut |oid, critical, extension| { + Ok(match oid { + crate::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), + crate::LIBP2P_OID_BYTES => { + libp2p_extension = Some(parse_libp2p_extension(extension)?) + } + _ if critical => return Err(Error::UnsupportedCriticalExtension), + _ => {} + }) + })?; + let libp2p_extension = libp2p_extension.ok_or(Error::UnknownIssuer)?; + Ok((parsed, libp2p_extension)) } fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), rustls::TLSError> { if presented_certs.len() != 1 { return Err(rustls::TLSError::NoCertificatesPresented); } - x509::parse_certificate(presented_certs[0].as_ref()) - .map_err(rustls::TLSError::WebPKIError)? - .verify_libp2p(get_time()?) + let (certificate, extension) = + parse_certificate(presented_certs[0].as_ref()).map_err(rustls::TLSError::WebPKIError)?; + let now = get_time()?; + certificate + .verify_data_algorithm_signature(now, &certificate.das()) + .map_err(rustls::TLSError::WebPKIError)?; + verify_libp2p_signature(&extension, certificate.subject_public_key_info().key()) .map_err(rustls::TLSError::WebPKIError) } +struct Libp2pExtension<'a> { + peer_key: PublicKey, + signature: &'a [u8], +} + +#[inline(always)] +fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Error> { + der::bit_string_with_no_unused_bits(input).map_err(|_| e) +} + +fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result, Error> { + let e = Error::ExtensionValueInvalid; + Input::read_all(&extension, e, |input| { + der::nested(input, der::Tag::Sequence, e, |input| { + let public_key = read_bit_string(input, e)?.as_slice_less_safe(); + let signature = read_bit_string(input, e)?.as_slice_less_safe(); + // We deliberately discard the error information because this is + // either a broken peer or an attack. + let peer_key = PublicKey::from_protobuf_encoding(public_key).map_err(|_| e)?; + Ok(Libp2pExtension { + signature, + peer_key, + }) + }) + }) +} /// libp2p requires the following of X.509 client certificate chains: /// /// * Exactly one certificate must be presented. In particular, client @@ -85,7 +157,9 @@ fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), /// /// [`PeerId`]: libp2p_core::PeerId impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { - fn offer_client_auth(&self) -> bool { true } + fn offer_client_auth(&self) -> bool { + true + } fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, @@ -106,7 +180,7 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); x509::parse_certificate(certificate.as_ref()) .map_err(rustls::TLSError::WebPKIError)? - .verify_signature(scheme, msg, signature) + .verify_signature_against_scheme(get_time()?, scheme, msg, signature) .map_err(rustls::TLSError::WebPKIError) .map(|()| rustls::HandshakeSignatureValid::assertion()) } @@ -121,7 +195,7 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { /// /// Panics if called on an invalid certificate. pub fn extract_peerid_or_panic(certificate: &[u8]) -> libp2p_core::PeerId { - let parsed = x509::parse_certificate(certificate) - .expect("our certificate verifiers already checked that the certificate is valid; qed"); - x509::get_peerid(parsed) + let r = parse_certificate(certificate) + .expect("we already checked that the certificate was valid during the handshake; qed"); + libp2p_core::PeerId::from_public_key(r.1.peer_key) } diff --git a/misc/x509/src/verifier/calendar.rs b/misc/x509/src/verifier/calendar.rs deleted file mode 100644 index 9381af7ed3a..00000000000 --- a/misc/x509/src/verifier/calendar.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2015-2016 Brian Smith. -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -use webpki::{Error, Time}; - -pub(super) fn time_from_ymdhms_utc( - year: u64, month: u64, day_of_month: u64, hours: u64, minutes: u64, seconds: u64, -) -> Result { - let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?; - - const JAN: u64 = 31; - let feb = days_in_feb(year); - const MAR: u64 = 31; - const APR: u64 = 30; - const MAY: u64 = 31; - const JUN: u64 = 30; - const JUL: u64 = 31; - const AUG: u64 = 31; - const SEP: u64 = 30; - const OCT: u64 = 31; - const NOV: u64 = 30; - let days_before_month_in_year = match month { - 1 => 0, - 2 => JAN, - 3 => JAN + feb, - 4 => JAN + feb + MAR, - 5 => JAN + feb + MAR + APR, - 6 => JAN + feb + MAR + APR + MAY, - 7 => JAN + feb + MAR + APR + MAY + JUN, - 8 => JAN + feb + MAR + APR + MAY + JUN + JUL, - 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG, - 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP, - 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT, - 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV, - _ => unreachable!(), // `read_two_digits` already bounds-checked it. - }; - - let days_before = - days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1; - - let seconds_since_unix_epoch = - (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds; - - Ok(Time::from_seconds_since_unix_epoch( - seconds_since_unix_epoch, - )) -} - -fn days_before_year_since_unix_epoch(year: u64) -> Result { - // We don't support dates before January 1, 1970 because that is the - // Unix epoch. It is likely that other software won't deal well with - // certificates that have dates before the epoch. - if year < 1970 { - return Err(Error::BadDERTime); - } - let days_before_year_ad = days_before_year_ad(year); - debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD); - Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD) -} - -fn days_before_year_ad(year: u64) -> u64 { - ((year - 1) * 365) - + ((year - 1) / 4) // leap years are every 4 years, - - ((year - 1) / 100) // except years divisible by 100, - + ((year - 1) / 400) // except years divisible by 400. -} - -pub(super) fn days_in_month(year: u64, month: u64) -> u64 { - match month { - 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, - 4 | 6 | 9 | 11 => 30, - 2 => days_in_feb(year), - _ => unreachable!(), // `read_two_digits` already bounds-checked it. - } -} - -fn days_in_feb(year: u64) -> u64 { - if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { - 29 - } else { - 28 - } -} - -const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 719162; - -#[cfg(test)] -mod tests { - #[test] - fn test_days_before_unix_epoch() { - use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD}; - assert_eq!(DAYS_BEFORE_UNIX_EPOCH_AD, days_before_year_ad(1970)); - } - - #[test] - fn test_days_in_month() { - use super::days_in_month; - assert_eq!(days_in_month(2017, 1), 31); - assert_eq!(days_in_month(2017, 2), 28); - assert_eq!(days_in_month(2017, 3), 31); - assert_eq!(days_in_month(2017, 4), 30); - assert_eq!(days_in_month(2017, 5), 31); - assert_eq!(days_in_month(2017, 6), 30); - assert_eq!(days_in_month(2017, 7), 31); - assert_eq!(days_in_month(2017, 8), 31); - assert_eq!(days_in_month(2017, 9), 30); - assert_eq!(days_in_month(2017, 10), 31); - assert_eq!(days_in_month(2017, 11), 30); - assert_eq!(days_in_month(2017, 12), 31); - - // leap cases - assert_eq!(days_in_month(2000, 2), 29); - assert_eq!(days_in_month(2004, 2), 29); - assert_eq!(days_in_month(2016, 2), 29); - assert_eq!(days_in_month(2100, 2), 28); - } - - #[test] - fn test_time_from_ymdhms_utc() { - use super::{time_from_ymdhms_utc, Time}; - - // year boundary - assert_eq!( - Time::from_seconds_since_unix_epoch(1483228799), - time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap() - ); - assert_eq!( - Time::from_seconds_since_unix_epoch(1483228800), - time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap() - ); - - // not a leap year - assert_eq!( - Time::from_seconds_since_unix_epoch(1492449162), - time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap() - ); - - // leap year, post-feb - assert_eq!( - Time::from_seconds_since_unix_epoch(1460913162), - time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap() - ); - } -} diff --git a/misc/x509/src/verifier/data/LICENSE b/misc/x509/src/verifier/data/LICENSE deleted file mode 100644 index 93eb6d87eff..00000000000 --- a/misc/x509/src/verifier/data/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The files in this directory are taken from webpki. The license of webpki -is below: - -Except as otherwise noted, this project is licensed under the following -(ISC-style) terms: - -Copyright 2015 Brian Smith. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -The files under third-party/chromium are licensed as described in -third-party/chromium/LICENSE. diff --git a/misc/x509/src/verifier/data/README.md b/misc/x509/src/verifier/data/README.md deleted file mode 100644 index 78fc7788d37..00000000000 --- a/misc/x509/src/verifier/data/README.md +++ /dev/null @@ -1,21 +0,0 @@ -These files contain the binary DER encoding of the *values* of some -ASN.1 [`AlgorithmIdentifier`]s, without the outer `SEQUENCE` tag or the outer -length component. - -These files were encoded with the help of [der-ascii]. They can be decoded -using: - -```sh -go get github.com/google/der-ascii/cmd/der2ascii -der2ascii -i -o .ascii -``` - -New or modified der-ascii files can be encoded using: - -```sh -go get github.com/google/der-ascii/cmd/ascii2der -ascii2der i .ascii -o -``` - -[`AlgorithmIdentifier`]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2] -[der-ascii]: https://github.com/google/der-ascii diff --git a/misc/x509/src/verifier/data/alg-ecdsa-p256.der b/misc/x509/src/verifier/data/alg-ecdsa-p256.der deleted file mode 100644 index d49c30da3df..00000000000 --- a/misc/x509/src/verifier/data/alg-ecdsa-p256.der +++ /dev/null @@ -1 +0,0 @@ -*H=*H= \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-ecdsa-p384.der b/misc/x509/src/verifier/data/alg-ecdsa-p384.der deleted file mode 100644 index 8b24916caf9bfaeaecb98407738772aa659fc65e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZQ$*J|@PXUoLM#;V=O!k`2I8~OtA diff --git a/misc/x509/src/verifier/data/alg-ecdsa-sha256.der b/misc/x509/src/verifier/data/alg-ecdsa-sha256.der deleted file mode 100644 index b2ee1289144..00000000000 --- a/misc/x509/src/verifier/data/alg-ecdsa-sha256.der +++ /dev/null @@ -1 +0,0 @@ -*H= \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-ecdsa-sha384.der b/misc/x509/src/verifier/data/alg-ecdsa-sha384.der deleted file mode 100644 index 7c61d3aabdb..00000000000 --- a/misc/x509/src/verifier/data/alg-ecdsa-sha384.der +++ /dev/null @@ -1 +0,0 @@ -*H= \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-ed25519.der b/misc/x509/src/verifier/data/alg-ed25519.der deleted file mode 100644 index 7ca46fd9522..00000000000 --- a/misc/x509/src/verifier/data/alg-ed25519.der +++ /dev/null @@ -1 +0,0 @@ -+ep \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-rsa-encryption.der b/misc/x509/src/verifier/data/alg-rsa-encryption.der deleted file mode 100644 index 77d159a1c6fcc68fac95281029ab0c6ce52bb58f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13 UcmZSM)N1o+`_9YA$jHh702QtRng9R* diff --git a/misc/x509/src/verifier/data/alg-rsa-pkcs1-sha256.der b/misc/x509/src/verifier/data/alg-rsa-pkcs1-sha256.der deleted file mode 100644 index ab52bcd80b62813edb30a9ab628a5530b2ada8eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13 UcmZSM)N1o+`_9YA$j!@5qwPB{BO`|aFOnQ9!y;xTMg;&5k_k}& diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v2.der deleted file mode 100644 index 787d2d53b99857e577897d7fb12d13990e66c6fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 ycmXpoTEK6>%f^||=E0cC%)-RT%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mg;%`C<#vh diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256-v3.der deleted file mode 100644 index cc80b1dcd41cc950f4f09343c9ea44f90e225dfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 wcmXpoS-@|=%f^||=E0cC%)-RT%CJzzK#Gl1tIebBJ1-+62b%06W+p}j02gfuTmS$7 diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha256.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha256.der deleted file mode 100644 index 87328f7c64bc252ea54283bb542fbe36ac98e9a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^7`sVWEtH6dPOx2b%06W+p}j0JRPa A`2YX_ diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der deleted file mode 100644 index 3aac20fc905..00000000000 --- a/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v0.der +++ /dev/null @@ -1 +0,0 @@ -00 0  `He0 *H 0  `He0 \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v1.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v1.der deleted file mode 100644 index 899aed10a89c5acdfff00f5a5c464a8f91fe7dff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 ycmXpoTEJ_-&BmF~=E0cC%)-R9P{u%tjZ>@5qwPB{BO`|aFOnQ9!y;xTMgss3&Iw}x diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v2.der deleted file mode 100644 index 499a319cd175cd7f8861a8f6fbbe4e423d331bba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 ycmXpoTEK6>%f^||=E0cC%)-RP%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mgsr^VhLdY diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384-v3.der deleted file mode 100644 index d27817aec985d905dc2fb0a9628ed5317b3744c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 wcmXpoS-@|=%f^||=E0cC%)-RP%CJzzK#Gl1tIebBJ1-+62b%06W+p}h02iwXZU6uP diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha384.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha384.der deleted file mode 100644 index 9c3b170f2bef5721f65f49f0e3beb46f89c7ed90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^7ukVWEtH6dPOx2b%06W+p}h0JTgE A3jhEB diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der deleted file mode 100644 index f6a490c9d01..00000000000 --- a/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v0.der +++ /dev/null @@ -1 +0,0 @@ -00 0  `He0 *H 0  `He@ \ No newline at end of file diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v1.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v1.der deleted file mode 100644 index bb4418cb0db939212f0b59fb683e3c515057c0a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 ycmXpoTEJ_-&BmF~=E0cC%)-RHP{u%tjZ>@5qwPB{BO`|aFOnQ9!y;xTMh5^83JG-p diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v2.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v2.der deleted file mode 100644 index 4612c5700519cec06536233a473d7c59ae9f71bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 ycmXpoTEK6>%f^||=E0cC%)-RX%CJz%K!S}^tIebBJ1-+6hXFT|>>_3+Mh5@|oC$LP diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v3.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512-v3.der deleted file mode 100644 index 7f067108e1cc36f354a13369eeee9d7cd10190c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 wcmXpoS-@|=%f^||=E0cC%)-RX%CJzzK#Gl1tIebBJ1-+62b%06W+p}l02k>AfB*mh diff --git a/misc/x509/src/verifier/data/alg-rsa-pss-sha512.der b/misc/x509/src/verifier/data/alg-rsa-pss-sha512.der deleted file mode 100644 index c0ad57d612a2bd66d003342a1d32ca975baed06d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65 zcmZSM)N1o+`_9YA$Yo%%fZu?ZjWeOmgE5tvg^8J!VWEtH6dPOx2b%06W+p}l0JVw? A9RL6T diff --git a/misc/x509/src/verifier/data/alg-rsa-pss.der b/misc/x509/src/verifier/data/alg-rsa-pss.der deleted file mode 100644 index f2267d4935a..00000000000 --- a/misc/x509/src/verifier/data/alg-rsa-pss.der +++ /dev/null @@ -1 +0,0 @@ - *H  diff --git a/misc/x509/src/verifier/der.rs b/misc/x509/src/verifier/der.rs deleted file mode 100644 index 3a744bb2500..00000000000 --- a/misc/x509/src/verifier/der.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// Copyright 2015 Brian Smith. -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -use super::calendar; -pub(super) use ring::io::{ - der::{nested, Tag}, - Positive, -}; -use webpki::{Error, Time}; - -#[inline(always)] -pub(super) fn expect_tag_and_get_value<'a>( - input: &mut untrusted::Reader<'a>, tag: Tag, -) -> Result, Error> { - ring::io::der::expect_tag_and_get_value(input, tag).map_err(|_| Error::BadDER) -} - -pub(super) fn expect_bytes( - input: &mut untrusted::Reader<'_>, bytes: &[u8], error: Error, -) -> Result<(), Error> { - match input.read_bytes(bytes.len()) { - Ok(e) if e == untrusted::Input::from(bytes) => Ok(()), - _ => Err(error), - } -} - -#[inline(always)] -pub(super) fn bit_string_with_no_unused_bits<'a>( - input: &mut untrusted::Reader<'a>, -) -> Result, Error> { - ring::io::der::bit_string_with_no_unused_bits(input).map_err(|_| Error::BadDER) -} - -#[inline(always)] -pub(super) fn positive_integer<'a>( - input: &'a mut untrusted::Reader<'_>, -) -> Result, Error> { - ring::io::der::positive_integer(input).map_err(|_| Error::BadDER) -} - -pub(super) fn time_choice(input: &mut untrusted::Reader<'_>) -> Result { - let is_utc_time = input.peek(Tag::UTCTime as u8); - let expected_tag = if is_utc_time { - Tag::UTCTime - } else { - Tag::GeneralizedTime - }; - - fn read_digit(inner: &mut untrusted::Reader<'_>) -> Result { - let b = inner.read_byte().map_err(|_| Error::BadDERTime)?; - if b < b'0' || b > b'9' { - return Err(Error::BadDERTime); - } - Ok((b - b'0') as u64) - } - - fn read_two_digits( - inner: &mut untrusted::Reader<'_>, min: u64, max: u64, - ) -> Result { - let hi = read_digit(inner)?; - let lo = read_digit(inner)?; - let value = (hi * 10) + lo; - if value < min || value > max { - return Err(Error::BadDERTime); - } - Ok(value) - } - - nested(input, expected_tag, Error::BadDER, |value| { - let (year_hi, year_lo) = if is_utc_time { - let lo = read_two_digits(value, 0, 99)?; - let hi = if lo >= 50 { 19 } else { 20 }; - (hi, lo) - } else { - let hi = read_two_digits(value, 0, 99)?; - let lo = read_two_digits(value, 0, 99)?; - (hi, lo) - }; - - let year = (year_hi * 100) + year_lo; - let month = read_two_digits(value, 1, 12)?; - let days_in_month = calendar::days_in_month(year, month); - let day_of_month = read_two_digits(value, 1, days_in_month)?; - let hours = read_two_digits(value, 0, 23)?; - let minutes = read_two_digits(value, 0, 59)?; - let seconds = read_two_digits(value, 0, 59)?; - - let time_zone = value.read_byte().map_err(|_| Error::BadDERTime)?; - if time_zone != b'Z' { - return Err(Error::BadDERTime); - } - - calendar::time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds) - }) -} diff --git a/misc/x509/src/verifier/x509.rs b/misc/x509/src/verifier/x509.rs deleted file mode 100644 index 5f490a5748f..00000000000 --- a/misc/x509/src/verifier/x509.rs +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. - -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -use super::{ - super::{LIBP2P_OID_BYTES, LIBP2P_SIGNING_PREFIX, LIBP2P_SIGNING_PREFIX_LENGTH}, - der, - der::Tag, -}; -use libp2p_core::identity::PublicKey; -use ring::signature; -use untrusted::{Input, Reader}; -use webpki::{Error, Time}; - -pub(super) struct X509Certificate<'a> { - libp2p_extension: Libp2pExtension<'a>, - x509_tbs: Input<'a>, - x509_validity: Input<'a>, - x509_spki: Input<'a>, - x509_signature_algorithm: &'a [u8], - x509_signature: Input<'a>, -} - -impl X509Certificate<'_> { - pub(super) fn verify_libp2p(&self, time: Time) -> Result<(), Error> { - let X509Certificate { - x509_tbs, - x509_spki, - x509_signature, - x509_signature_algorithm, - libp2p_extension, - x509_validity, - } = self; - - let (x509_pkey_alg, x509_pkey_bytes) = x509_spki.read_all(Error::BadDER, |input| { - let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); - let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); - Ok((x509_pkey_alg, x509_pkey_bytes)) - })?; - verify_libp2p_signature(libp2p_extension, x509_pkey_bytes)?; - get_public_key_x509(x509_pkey_alg, x509_pkey_bytes, x509_signature_algorithm)? - .verify( - x509_tbs.as_slice_less_safe(), - x509_signature.as_slice_less_safe(), - ) - .map_err(|_| Error::InvalidSignatureForPublicKey)?; - x509_validity.read_all(Error::BadDER, |input| { - let not_before = der::time_choice(input)?; - let not_after = der::time_choice(input)?; - if time < not_before { - Err(Error::CertNotValidYet) - } else if time > not_after { - Err(Error::CertExpired) - } else { - Ok(()) - } - }) - } - - pub(super) fn verify_signature( - &self, scheme: rustls::SignatureScheme, message: &[u8], signature: &[u8], - ) -> Result<(), Error> { - let (pkey_alg, pkey_bytes) = self.x509_spki.read_all(Error::BadDER, |input| { - let x509_pkey_alg = read_sequence(input)?.as_slice_less_safe(); - let x509_pkey_bytes = read_bit_string(input, Error::BadDER)?.as_slice_less_safe(); - Ok((x509_pkey_alg, x509_pkey_bytes)) - })?; - get_public_key_tls(pkey_alg, pkey_bytes, scheme)? - .verify(message, signature) - .map_err(|_| Error::InvalidSignatureForPublicKey) - } -} - -struct Libp2pExtension<'a> { - peer_key: PublicKey, - signature: &'a [u8], -} - -#[inline(always)] -fn read_sequence<'a>(input: &mut Reader<'a>) -> Result, Error> { - der::expect_tag_and_get_value(input, Tag::Sequence) -} - -#[inline(always)] -fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Error> { - der::bit_string_with_no_unused_bits(input).map_err(|_| e) -} - -fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result, Error> { - let e = Error::ExtensionValueInvalid; - Input::read_all(&extension, e, |input| { - der::nested(input, Tag::Sequence, e, |input| { - let public_key = read_bit_string(input, e)?.as_slice_less_safe(); - let signature = read_bit_string(input, e)?.as_slice_less_safe(); - // We deliberately discard the error information because this is - // either a broken peer or an attack. - let peer_key = PublicKey::from_protobuf_encoding(public_key).map_err(|_| e)?; - Ok(Libp2pExtension { - signature, - peer_key, - }) - }) - }) -} - -/// Parse X.509 extensions -fn parse_extensions<'a>(input: &mut Reader<'a>) -> Result, Error> { - let mut libp2p_extension = None; - while !input.at_end() { - der::nested(input, Tag::Sequence, Error::BadDER, |input| { - let oid = der::expect_tag_and_get_value(input, Tag::OID)?; - let mut critical = false; - if input.peek(Tag::Boolean as _) { - critical = true; - der::expect_bytes(input, &[Tag::Boolean as _, 1, 0xFF], Error::BadDER)? - } - let extension = der::expect_tag_and_get_value(input, Tag::OctetString)?; - match oid.as_slice_less_safe() { - LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), - LIBP2P_OID_BYTES => libp2p_extension = Some(parse_libp2p_extension(extension)?), - _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => {}, - } - Ok(()) - })? - } - libp2p_extension.ok_or(Error::UnknownIssuer) -} - -/// Extracts the algorithm id and public key from a certificate -pub(super) fn parse_certificate(certificate: &[u8]) -> Result, Error> { - let ((x509_tbs, inner_tbs), x509_signature_algorithm, x509_signature) = - Input::from(certificate).read_all(Error::BadDER, |input| { - der::nested(input, Tag::Sequence, Error::BadDER, |reader| { - // tbsCertificate - let tbs = reader.read_partial(|reader| read_sequence(reader))?; - // signatureAlgorithm - let signature_algorithm = read_sequence(reader)?.as_slice_less_safe(); - // signatureValue - let signature = read_bit_string(reader, Error::BadDER)?; - Ok((tbs, signature_algorithm, signature)) - }) - })?; - let (x509_validity, x509_spki, extensions) = - inner_tbs.read_all(Error::BadDER, |mut input| { - // We require extensions, which means we require version 3 - der::expect_bytes(input, &[160, 3, 2, 1, 2], Error::UnsupportedCertVersion)?; - // serialNumber - der::positive_integer(&mut input)?; - // signature - if read_sequence(input)?.as_slice_less_safe() != x509_signature_algorithm { - // signature algorithms don’t match - return Err(Error::SignatureAlgorithmMismatch); - } - // issuer - read_sequence(input)?; - // validity - let x509_validity = read_sequence(input)?; - // subject - read_sequence(input)?; - // subjectPublicKeyInfo - let spki = read_sequence(input)?; - // subjectUniqueId and issuerUniqueId are unsupported - // parse the extensions - let extensions = - der::expect_tag_and_get_value(input, Tag::ContextSpecificConstructed3)?; - Ok((x509_validity, spki, extensions)) - })?; - let extensions = extensions.read_all(Error::BadDER, read_sequence)?; - let libp2p_extension = extensions.read_all(Error::BadDER, parse_extensions)?; - - Ok(X509Certificate { - libp2p_extension, - x509_tbs, - x509_validity, - x509_spki, - x509_signature, - x509_signature_algorithm, - }) -} - -fn verify_libp2p_signature( - libp2p_extension: &Libp2pExtension<'_>, x509_pkey_bytes: &[u8], -) -> Result<(), Error> { - let mut v = Vec::with_capacity(LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); - v.extend_from_slice(&LIBP2P_SIGNING_PREFIX[..]); - v.extend_from_slice(x509_pkey_bytes); - if libp2p_extension - .peer_key - .verify(&v, libp2p_extension.signature) - { - Ok(()) - } else { - Err(Error::UnknownIssuer) - } -} - -pub(super) fn get_peerid(certificate: X509Certificate<'_>) -> libp2p_core::PeerId { - certificate.libp2p_extension.peer_key.into_peer_id() -} - -fn get_public_key_tls<'a>( - x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], tls_signature_scheme: rustls::SignatureScheme, -) -> Result, Error> { - use signature::{ - RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, - }; - let algorithm: &'static dyn signature::VerificationAlgorithm = match tls_signature_scheme { - rustls::SignatureScheme::RSA_PKCS1_SHA256 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA256, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::RSA_PKCS1_SHA384 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA384, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::RSA_PKCS1_SHA512 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA512, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::ECDSA_NISTP256_SHA256 => match x509_pkey_alg { - include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA256_ASN1, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::ECDSA_NISTP384_SHA384 => match x509_pkey_alg { - include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA384_ASN1, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::ED25519 => match x509_pkey_alg { - include_bytes!("data/alg-ed25519.der") => &signature::ED25519, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::RSA_PSS_SHA256 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA256, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::RSA_PSS_SHA384 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA384, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - rustls::SignatureScheme::RSA_PSS_SHA512 => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &signature::RSA_PSS_2048_8192_SHA512, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - _ => return Err(Error::UnsupportedSignatureAlgorithm), - }; - Ok(signature::UnparsedPublicKey::new( - algorithm, - x509_pkey_bytes, - )) -} - -fn get_public_key_x509<'a>( - x509_pkey_alg: &[u8], x509_pkey_bytes: &'a [u8], x509_signature_algorithm: &[u8], -) -> Result, Error> { - const RSASSA_PSS_PREFIX: &[u8; 11] = include_bytes!("data/alg-rsa-pss.der"); - use signature::{ - RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, - }; - let algorithm: &'static dyn signature::VerificationAlgorithm = match x509_signature_algorithm { - include_bytes!("data/alg-rsa-pkcs1-sha256.der") => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA256, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - include_bytes!("data/alg-rsa-pkcs1-sha384.der") => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA384, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - include_bytes!("data/alg-rsa-pkcs1-sha512.der") => match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => &RSA_PKCS1_2048_8192_SHA512, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - include_bytes!("data/alg-ecdsa-sha256.der") => match x509_pkey_alg { - include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA256_ASN1, - include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA256_ASN1, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - include_bytes!("data/alg-ecdsa-sha384.der") => match x509_pkey_alg { - include_bytes!("data/alg-ecdsa-p256.der") => &signature::ECDSA_P256_SHA384_ASN1, - include_bytes!("data/alg-ecdsa-p384.der") => &signature::ECDSA_P384_SHA384_ASN1, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - include_bytes!("data/alg-ed25519.der") => match x509_pkey_alg { - include_bytes!("data/alg-ed25519.der") => &signature::ED25519, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - }, - e if e.starts_with(&RSASSA_PSS_PREFIX[..]) => { - let alg = parse_rsa_pss(&e[RSASSA_PSS_PREFIX.len()..])?; - match x509_pkey_alg { - include_bytes!("data/alg-rsa-encryption.der") => alg, - _ => return Err(Error::UnsupportedSignatureAlgorithmForPublicKey), - } - }, - _ => return Err(Error::UnsupportedSignatureAlgorithm), - }; - Ok(signature::UnparsedPublicKey::new( - algorithm, - x509_pkey_bytes, - )) -} - -// While the RSA-PSS parameters are a ASN.1 SEQUENCE, it is simpler to match -// against the 12 different possibilities. The binary files are *generated* by a -// Go program. -fn parse_rsa_pss(data: &[u8]) -> Result<&'static signature::RsaParameters, Error> { - match data { - include_bytes!("data/alg-rsa-pss-sha256-v0.der") - | include_bytes!("data/alg-rsa-pss-sha256-v1.der") - | include_bytes!("data/alg-rsa-pss-sha256-v2.der") - | include_bytes!("data/alg-rsa-pss-sha256-v3.der") => - Ok(&signature::RSA_PSS_2048_8192_SHA256), - include_bytes!("data/alg-rsa-pss-sha384-v0.der") - | include_bytes!("data/alg-rsa-pss-sha384-v1.der") - | include_bytes!("data/alg-rsa-pss-sha384-v2.der") - | include_bytes!("data/alg-rsa-pss-sha384-v3.der") => - Ok(&signature::RSA_PSS_2048_8192_SHA384), - include_bytes!("data/alg-rsa-pss-sha512-v0.der") - | include_bytes!("data/alg-rsa-pss-sha512-v1.der") - | include_bytes!("data/alg-rsa-pss-sha512-v2.der") - | include_bytes!("data/alg-rsa-pss-sha512-v3.der") => - Ok(&signature::RSA_PSS_2048_8192_SHA512), - _ => Err(Error::UnsupportedSignatureAlgorithm), - } -} From 53fd8742934eeb15d5dfe78305f8b1557577acfd Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 7 Apr 2020 18:37:52 -0400 Subject: [PATCH 165/202] Better error handling --- misc/x509/Cargo.toml | 1 + misc/x509/src/lib.rs | 70 +++++++++++++-------------------- transports/quic/src/endpoint.rs | 11 ++++-- transports/quic/tests/tests.rs | 13 +++--- 4 files changed, 42 insertions(+), 53 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index ddb8365e9c5..b4c07edaea7 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -18,3 +18,4 @@ untrusted = "0.7.0" log = "0.4.8" libp2p-core = { path = "../../core", version = "0.17.1" } yasna = "0.3.1" +err-derive = "0.2.2" diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 9ff0dd4abf0..d47f29ce8f0 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -20,40 +20,17 @@ //! TLS configuration for `libp2p-quic`. #![deny( - exceeding_bitshifts, - invalid_type_param_default, - missing_fragment_specifier, - mutable_transmutes, - no_mangle_const_items, - overflowing_literals, - patterns_in_fns_without_body, - pub_use_of_private_extern_crate, - unknown_crate_types, const_err, - order_dependent_trait_objects, - illegal_floating_point_literal_pattern, + deprecated, improper_ctypes, - late_bound_lifetime_arguments, - non_camel_case_types, non_shorthand_field_patterns, - non_snake_case, - non_upper_case_globals, + nonstandard_style, no_mangle_generic_items, - path_statements, - private_in_public, - safe_packed_borrows, - stable_features, + renamed_and_removed_lints, + unknown_lints, type_alias_bounds, - tyvar_behind_raw_pointer, unconditional_recursion, - unused, - unused_allocation, - unused_comparisons, - unused_mut, - unreachable_pub, while_true, - anonymous_parameters, - bare_trait_objects, elided_lifetimes_in_paths, missing_copy_implementations, missing_debug_implementations, @@ -61,9 +38,9 @@ single_use_lifetimes, trivial_casts, trivial_numeric_casts, - unused_extern_crates, - unused_import_braces, - unused_qualifications, + rust_2018_idioms, + unused, + future_incompatible, clippy::all )] #![forbid(unsafe_code)] @@ -72,12 +49,24 @@ mod certificate; mod verifier; use std::sync::Arc; +use err_derive::Error; pub use verifier::extract_peerid_or_panic; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; +/// Error creating a configuration +#[derive(Debug, Error)] +pub enum ConfigError { + /// TLS private key or certificate rejected + #[error(display = "TLS private or certificate key rejected: {}", _0)] + TLSError(#[error(source)] rustls::TLSError), + /// Certificate generation error + #[error(display = "Certificate generation error: {}", _0)] + RcgenError(#[error(source)] rcgen::RcgenError) +} + fn make_client_config( certificate: rustls::Certificate, key: rustls::PrivateKey, verifier: Arc, @@ -96,30 +85,25 @@ fn make_client_config( fn make_server_config( certificate: rustls::Certificate, key: rustls::PrivateKey, verifier: Arc, -) -> rustls::ServerConfig { +) -> Result { let mut crypto = rustls::ServerConfig::new(verifier); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.alpn_protocols = vec![b"libp2p".to_vec()]; - crypto - .set_single_cert(vec![certificate], key) - .expect("we have a valid certificate; qed"); - crypto + crypto.set_single_cert(vec![certificate], key)?; + Ok(crypto) } /// Create TLS client and server configurations for libp2p. pub fn make_tls_config( keypair: &libp2p_core::identity::Keypair, -) -> (rustls::ClientConfig, rustls::ServerConfig) { +) -> Result<(rustls::ClientConfig, rustls::ServerConfig), ConfigError> { let cert = certificate::make_cert(&keypair); let private_key = cert.serialize_private_key_der(); let verifier = Arc::new(verifier::Libp2pCertificateVerifier); - let cert = rustls::Certificate( - cert.serialize_der() - .expect("serialization of a valid cert will succeed; qed"), - ); + let cert = rustls::Certificate(cert.serialize_der()?); let key = rustls::PrivateKey(private_key); - ( + Ok(( make_client_config(cert.clone(), key.clone(), verifier.clone()), - make_server_config(cert, key, verifier), - ) + make_server_config(cert, key, verifier)?, + )) } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 21b7402f329..961de710d57 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -55,25 +55,28 @@ pub struct Config { impl Config { /// Creates a new configuration object for QUIC. - pub fn new(keypair: &libp2p_core::identity::Keypair, multiaddr: Multiaddr) -> Self { + pub fn new( + keypair: &libp2p_core::identity::Keypair, + multiaddr: Multiaddr, + ) -> Result { let mut transport = quinn_proto::TransportConfig::default(); transport.stream_window_uni(0); transport.datagram_receive_buffer_size(None); transport.keep_alive_interval(Some(Duration::from_millis(10))); let transport = Arc::new(transport); - let (client_tls_config, server_tls_config) = x509::make_tls_config(keypair); + let (client_tls_config, server_tls_config) = x509::make_tls_config(keypair)?; let mut server_config = quinn_proto::ServerConfig::default(); server_config.transport = transport.clone(); server_config.crypto = Arc::new(server_tls_config); let mut client_config = quinn_proto::ClientConfig::default(); client_config.transport = transport; client_config.crypto = Arc::new(client_tls_config); - Self { + Ok(Self { client_config, server_config: Arc::new(server_config), endpoint_config: Default::default(), multiaddr: multiaddr, - } + }) } } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 2a2d0760269..72fe705e8f2 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -138,7 +138,7 @@ fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let (listener, join) = Endpoint::new(Config::new(&keypair, addr.clone())).unwrap(); + let (listener, join) = Endpoint::new(Config::new(&keypair, addr.clone()).unwrap()).unwrap(); let mut incoming = listener.listen_on(addr).unwrap(); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. @@ -186,7 +186,7 @@ fn do_test(_i: u32) { let keypair = libp2p_core::identity::Keypair::generate_ed25519(); let keypair2 = keypair.clone(); let addr: Multiaddr = "/ip4/127.0.0.1/udp/0/quic".parse().expect("bad address?"); - let quic_config = Config::new(&keypair2, addr.clone()); + let quic_config = Config::new(&keypair2, addr.clone()).unwrap(); let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); trace!("running tests"); @@ -240,7 +240,8 @@ fn do_test(_i: u32) { let second_handle = async_std::task::spawn(async move { let addr = ready_rx.await.unwrap(); - let quic_config = Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()); + let quic_config = + Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()).unwrap(); let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); // Obtain a future socket through dialing let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); @@ -295,7 +296,7 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { let addr = "/ip4/127.0.0.1/udp/0/quic".parse::().unwrap(); assert!(addr.to_string().ends_with("udp/0/quic")); - let config = Config::new(&keypair, addr.clone()); + let config = Config::new(&keypair, addr.clone()).unwrap(); let (quic, join) = Endpoint::new(config).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) @@ -318,7 +319,7 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); - let config = Config::new(&keypair, addr.clone()); + let config = Config::new(&keypair, addr.clone()).unwrap(); let (quic, join) = Endpoint::new(config).expect("no error"); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) @@ -339,6 +340,6 @@ fn larger_addr_denied() { let addr = "/ip4/127.0.0.1/tcp/12345/tcp/12345" .parse::() .unwrap(); - let config = Config::new(&keypair, addr); + let config = Config::new(&keypair, addr).unwrap(); assert!(Endpoint::new(config).is_err()) } From b367b9d743bdac2025796251766113be4b050665 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 7 Apr 2020 21:07:38 -0400 Subject: [PATCH 166/202] Switch to a released version of x509-signature --- README.md | 5 ----- misc/x509/Cargo.toml | 5 ++--- misc/x509/LICENSE-ISC | 17 ----------------- misc/x509/src/verifier.rs | 11 ++++------- 4 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 misc/x509/LICENSE-ISC diff --git a/README.md b/README.md index d076fb50dac..e3bb85191a1 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,3 @@ Where to ask questions? - https://github.com/sigp/lighthouse - https://github.com/golemfactory/golem-libp2p - https://github.com/comit-network/comit-rs - -## License - -Most code in the repository is under the MIT license in LICENSE. The files under -transports/x509 are under the ISC-style license in LICENSE-ISC. \ No newline at end of file diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 7a286ac2a7e..60da3e75383 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -21,6 +21,5 @@ yasna = "0.3.1" [dependencies.x509] package = "x509-signature" -git = "https://github.com/paritytech/x509-signature" -branch = "master" -features = ["webpki", "rustls"] +version = "0.1.0" +features = ["webpki", "rustls", "std"] diff --git a/misc/x509/LICENSE-ISC b/misc/x509/LICENSE-ISC deleted file mode 100644 index 76c61d6e1b0..00000000000 --- a/misc/x509/LICENSE-ISC +++ /dev/null @@ -1,17 +0,0 @@ -Except as otherwise noted, this project is licensed under the following -(ISC-style) terms: - -Copyrignt 2020-present Parity Technologies Ltd -Copyright 2015 Brian Smith. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 1bc0b1cb679..73b5fb0a9e3 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -93,11 +93,10 @@ fn parse_certificate( .iterate(&mut |oid, critical, extension| { Ok(match oid { crate::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), - crate::LIBP2P_OID_BYTES => { - libp2p_extension = Some(parse_libp2p_extension(extension)?) - } + crate::LIBP2P_OID_BYTES => + libp2p_extension = Some(parse_libp2p_extension(extension)?), _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => {} + _ => {}, }) })?; let libp2p_extension = libp2p_extension.ok_or(Error::UnknownIssuer)?; @@ -157,9 +156,7 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result bool { - true - } + fn offer_client_auth(&self) -> bool { true } fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, From 072e2c7c810c926aae2aaab021e67e7986f93967 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 7 Apr 2020 21:15:01 -0400 Subject: [PATCH 167/202] Fix bogus unwrap --- misc/x509/src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index d47f29ce8f0..9b0717202ae 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -48,8 +48,8 @@ mod certificate; mod verifier; -use std::sync::Arc; use err_derive::Error; +use std::sync::Arc; pub use verifier::extract_peerid_or_panic; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -64,22 +64,20 @@ pub enum ConfigError { TLSError(#[error(source)] rustls::TLSError), /// Certificate generation error #[error(display = "Certificate generation error: {}", _0)] - RcgenError(#[error(source)] rcgen::RcgenError) + RcgenError(#[error(source)] rcgen::RcgenError), } fn make_client_config( certificate: rustls::Certificate, key: rustls::PrivateKey, verifier: Arc, -) -> rustls::ClientConfig { +) -> Result { let mut crypto = rustls::ClientConfig::new(); crypto.versions = vec![rustls::ProtocolVersion::TLSv1_3]; crypto.alpn_protocols = vec![b"libp2p".to_vec()]; crypto.enable_early_data = false; - crypto - .set_single_client_cert(vec![certificate], key) - .expect("we have a valid certificate; qed"); + crypto.set_single_client_cert(vec![certificate], key)?; crypto.dangerous().set_certificate_verifier(verifier); - crypto + Ok(crypto) } fn make_server_config( @@ -103,7 +101,7 @@ pub fn make_tls_config( let cert = rustls::Certificate(cert.serialize_der()?); let key = rustls::PrivateKey(private_key); Ok(( - make_client_config(cert.clone(), key.clone(), verifier.clone()), + make_client_config(cert.clone(), key.clone(), verifier.clone())?, make_server_config(cert, key, verifier)?, )) } From 8ab0a3b309b2f7d2c2cb783f0ada04bb8d40f999 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 7 Apr 2020 21:38:44 -0400 Subject: [PATCH 168/202] Fix doc test --- transports/quic/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 21f17d775ca..f3ead63311f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -30,7 +30,7 @@ //! //! let keypair = libp2p_core::identity::Keypair::generate_ed25519(); //! let addr = "/ip4/127.0.0.1/udp/12345/quic".parse().expect("bad address?"); -//! let quic_config = Config::new(&keypair, addr); +//! let quic_config = Config::new(&keypair, addr).expect("could not make config"); //! let quic_endpoint = Endpoint::new(quic_config).expect("I/O error"); //! ``` //! From 63afb4cf79a05056abaa6d00370a7a3262fe29c6 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 12:10:59 -0400 Subject: [PATCH 169/202] Clean up copyright headers --- misc/x509/Cargo.toml | 2 +- misc/x509/src/certificate.rs | 28 +++++++++++++++++----------- misc/x509/src/verifier.rs | 2 +- transports/quic/src/connection.rs | 2 +- transports/quic/src/lib.rs | 2 +- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 4be4296074b..9cf38272f0a 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -4,7 +4,7 @@ version = "0.17.1" authors = ["Parity Technologies "] edition = "2018" description = "X.509 generation and verification for libp2p" -license = "ISC" +license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] diff --git a/misc/x509/src/certificate.rs b/misc/x509/src/certificate.rs index 341c941b83c..8e066447da9 100644 --- a/misc/x509/src/certificate.rs +++ b/misc/x509/src/certificate.rs @@ -1,16 +1,22 @@ -// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: // -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. //! Certificate handling for libp2p //! diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 73b5fb0a9e3..73ad83d2005 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 8cb9157a9d3..db70afa0c5c 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index f3ead63311f..9a45c5081be 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2019-2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), From 941fa6de2415b865b07564149bc1e94fe4334e60 Mon Sep 17 00:00:00 2001 From: Demi Obenour Date: Wed, 8 Apr 2020 16:12:05 +0000 Subject: [PATCH 170/202] Apply suggestions from code review Co-Authored-By: Toralf Wittner Co-Authored-By: Max Inden --- transports/quic/src/connection.rs | 2 +- transports/quic/src/lib.rs | 4 ++-- transports/quic/src/socket.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 8cb9157a9d3..db70afa0c5c 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index f3ead63311f..382c24db8eb 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -34,7 +34,7 @@ //! let quic_endpoint = Endpoint::new(quic_config).expect("I/O error"); //! ``` //! -//! The `Config` structs implements the `Transport` trait of the `swarm` library. See the +//! The `Endpoint` struct implements the `Transport` trait of the `swarm` library. See the //! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. //! //! Note that QUIC provides transport, security, and multiplexing in a single protocol. Therefore, diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index 55bc420ccd3..e9ac939b23c 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -101,7 +101,7 @@ impl Socket { pending: &mut Pending, source: &mut dyn FnMut() -> Option, ) -> Poll> { - if let Some(ref mut transmit) = pending.pending { + if let Some(transmit) = &pending.pending { trace!("trying to send packet!"); match self.poll_send_to(cx, &transmit) { Poll::Pending => return Poll::Pending, From b3469a79aa990297ec28667c2fec5643dd966ef0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 12:45:26 -0400 Subject: [PATCH 171/202] Remove unwraps from certificate generation --- misc/x509/src/certificate.rs | 18 ++++++++---------- misc/x509/src/lib.rs | 5 ++++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/misc/x509/src/certificate.rs b/misc/x509/src/certificate.rs index 8e066447da9..7d7e980ed42 100644 --- a/misc/x509/src/certificate.rs +++ b/misc/x509/src/certificate.rs @@ -55,9 +55,8 @@ fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen ext } -fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::CustomExtension) { - let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM) - .expect("we pass valid parameters, and assume we have enough memory and randomness; qed"); +fn gen_signed_keypair(keypair: &identity::Keypair) -> Result<(rcgen::KeyPair, rcgen::CustomExtension), crate::ConfigError> { + let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM)?; let mut signing_buf = [0u8; LIBP2P_SIGNING_PREFIX_LENGTH + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; let public = temp_keypair.public_key_raw(); @@ -69,22 +68,21 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> (rcgen::KeyPair, rcgen::Cu ); signing_buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); signing_buf[LIBP2P_SIGNING_PREFIX_LENGTH..].copy_from_slice(public); - let signature = keypair.sign(&signing_buf).expect("signing failed"); - ( + let signature = keypair.sign(&signing_buf)?; + Ok(( temp_keypair, encode_signed_key(keypair.public(), &signature), - ) + )) } /// Generates a self-signed TLS certificate that includes a libp2p-specific /// certificate extension containing the public key of the given keypair. -pub(crate) fn make_cert(keypair: &identity::Keypair) -> rcgen::Certificate { +pub(crate) fn make_cert(keypair: &identity::Keypair) -> Result { let mut params = rcgen::CertificateParams::new(vec![]); params.distinguished_name = rcgen::DistinguishedName::new(); - let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair); + let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair)?; params.custom_extensions.push(libp2p_extension); params.alg = &LIBP2P_SIGNATURE_ALGORITHM; params.key_pair = Some(cert_keypair); - rcgen::Certificate::from_params(params) - .expect("certificate generation with valid params will succeed; qed") + rcgen::Certificate::from_params(params).map_err(From::from) } diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 9b0717202ae..98cff2fcf44 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -62,6 +62,9 @@ pub enum ConfigError { /// TLS private key or certificate rejected #[error(display = "TLS private or certificate key rejected: {}", _0)] TLSError(#[error(source)] rustls::TLSError), + /// Signing failed + #[error(display = "Signing failed: {}", _0)] + SigningError(#[error(source)] libp2p_core::identity::error::SigningError), /// Certificate generation error #[error(display = "Certificate generation error: {}", _0)] RcgenError(#[error(source)] rcgen::RcgenError), @@ -95,7 +98,7 @@ fn make_server_config( pub fn make_tls_config( keypair: &libp2p_core::identity::Keypair, ) -> Result<(rustls::ClientConfig, rustls::ServerConfig), ConfigError> { - let cert = certificate::make_cert(&keypair); + let cert = certificate::make_cert(&keypair)?; let private_key = cert.serialize_private_key_der(); let verifier = Arc::new(verifier::Libp2pCertificateVerifier); let cert = rustls::Certificate(cert.serialize_der()?); From fbcb517fe9e594547bdde1eea4944d35f5232fc9 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 12:46:42 -0400 Subject: [PATCH 172/202] Clean up documentation and lints --- transports/quic/src/lib.rs | 52 ++++++++++---------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 382c24db8eb..e0a30d6c846 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -34,11 +34,11 @@ //! let quic_endpoint = Endpoint::new(quic_config).expect("I/O error"); //! ``` //! -//! The `Endpoint` struct implements the `Transport` trait of the `swarm` library. See the -//! documentation of `swarm` and of libp2p in general to learn how to use the `Transport` trait. +//! The `Endpoint` struct implements the `Transport` trait of the `core` library. See the +//! documentation of `core` and of libp2p in general to learn how to use the `Transport` trait. //! //! Note that QUIC provides transport, security, and multiplexing in a single protocol. Therefore, -//! QUIC connections do not need to be upgraded. You will get a compile-time error if you try. +//! QUIC connections do not need to be upgraded. You will get a compile-time error if you try. //! Instead, you must pass all needed configuration into the constructor. //! //! # Design Notes @@ -50,56 +50,30 @@ //! `QuicConnection` also manages a background task, which handles socket output and timer polling. #![deny( - exceeding_bitshifts, - invalid_type_param_default, - missing_fragment_specifier, - mutable_transmutes, - no_mangle_const_items, - overflowing_literals, - patterns_in_fns_without_body, - pub_use_of_private_extern_crate, - unknown_crate_types, const_err, - order_dependent_trait_objects, - illegal_floating_point_literal_pattern, + deprecated, improper_ctypes, - late_bound_lifetime_arguments, - non_camel_case_types, non_shorthand_field_patterns, - non_snake_case, - non_upper_case_globals, + nonstandard_style, no_mangle_generic_items, - path_statements, - private_in_public, - stable_features, + renamed_and_removed_lints, + unknown_lints, type_alias_bounds, - tyvar_behind_raw_pointer, unconditional_recursion, - unused, - unused_allocation, - unused_comparisons, - unused_mut, - unreachable_pub, - anonymous_parameters, + while_true, + elided_lifetimes_in_paths, missing_copy_implementations, missing_debug_implementations, missing_docs, single_use_lifetimes, trivial_casts, trivial_numeric_casts, - unused_extern_crates, - unused_import_braces, - unused_qualifications, + rust_2018_idioms, + unused, + future_incompatible, clippy::all )] -#![forbid( - unsafe_code, - intra_doc_link_resolution_failure, - safe_packed_borrows, - while_true, - elided_lifetimes_in_paths, - bare_trait_objects -)] +#![forbid(unsafe_code, intra_doc_link_resolution_failure)] #[cfg(not(feature = "tracing"))] use log::{debug, error, info, trace, warn}; From 046ba18a6d15a9c0860202d2cfb6d0037653ee5b Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 12:48:34 -0400 Subject: [PATCH 173/202] Sort dependencies --- misc/x509/Cargo.toml | 12 ++++++------ transports/quic/Cargo.toml | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 9cf38272f0a..a9380906541 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -10,15 +10,15 @@ keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] [dependencies] -rustls = { version = "0.17.0", features = ["dangerous_configuration"] } -ring = "0.16.11" +err-derive = "0.2.2" +libp2p-core = { path = "../../core", version = "0.17.1" } +log = "0.4.8" rcgen = { version = "0.7.0", default-features = false } -webpki = "0.21.2" +ring = "0.16.11" +rustls = { version = "0.17.0", features = ["dangerous_configuration"] } untrusted = "0.7.0" -log = "0.4.8" -libp2p-core = { path = "../../core", version = "0.17.1" } +webpki = "0.21.2" yasna = "0.3.1" -err-derive = "0.2.2" [dependencies.x509] package = "x509-signature" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index b1bbb49fa86..380fc2ff5b0 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -9,29 +9,29 @@ keywords = ["peer-to-peer", "libp2p", "quic", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -rustls = { version = "0.17.0", features = ["dangerous_configuration"] } -libp2p-core = { path = "../../core", version = "0.17.1" } -log = "0.4.8" -ipnet = "2.2.0" +async-macros = "2.0.0" +async-std = "1.5.0" +either = "1.5.3" +env_logger = "0.7.1" err-derive = "0.2.2" futures = "0.3.4" -quinn-proto = "0.6.0" -async-std = "1.5.0" -async-macros = "2.0.0" futures-timer = "3.0.2" -env_logger = "0.7.1" +ipnet = "2.2.0" +libp2p-core = { path = "../../core", version = "0.17.1" } +log = "0.4.8" parking_lot = "0.10.0" -rcgen = { version = "0.7.0", default_features = false } protobuf = "2.10.1" -yasna = { version = "0.3.1" } +quinn-proto = "0.6.0" +rcgen = { version = "0.7.0", default_features = false } ring = "0.16.11" -webpki = "0.21.2" -untrusted = "0.7.0" -x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.17.1" } -either = "1.5.3" -tracing-subscriber = { version = "0.2.1", optional = true } +rustls = { version = "0.17.0", features = ["dangerous_configuration"] } tracing = { version = "0.1.12", optional = true } tracing-core = { version = "0.1.10", optional = true } +tracing-subscriber = { version = "0.2.1", optional = true } +untrusted = "0.7.0" +webpki = "0.21.2" +x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.17.1" } +yasna = { version = "0.3.1" } [features] trace = [ From e31722ef22a9f75557182dbff3d6ec42cb3a38b0 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 13:43:07 -0400 Subject: [PATCH 174/202] Remove a potential panic and dead code --- transports/quic/src/endpoint.rs | 21 +++++++-------------- transports/quic/src/error.rs | 9 ++++++--- transports/quic/src/stream_map.rs | 1 - 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 961de710d57..55de5fcb6d2 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -604,7 +604,7 @@ fn accept_muxer( let streams = Arc::new(Mutex::new(Streams::new(connection))); inner.muxers.insert(handle, streams.clone()); - let upgrade = crate::Upgrade::spawn(streams, quinn_proto::Side::Server, socket); + let upgrade = crate::Upgrade::spawn(streams, socket); if endpoint .new_connections .unbounded_send(ListenerEvent::Upgrade { @@ -730,14 +730,11 @@ impl Transport for Endpoint { }; let Endpoint(endpoint) = self; let mut inner = endpoint.reference.inner.lock(); - let (handle, connection) = inner - .inner - .connect( - endpoint.reference.config.client_config.clone(), - socket_addr, - "l", - ) - .expect("this function does no I/O, and we pass valid parameters, so it will succeed"); + let (handle, connection) = inner.inner.connect( + endpoint.reference.config.client_config.clone(), + socket_addr, + "l", + ).map_err(|e| TransportError::Other(Error::ConnectError(e)))?; let socket = endpoint.reference.socket.clone(); let connection = Connection { pending: socket::Pending::default(), @@ -748,11 +745,7 @@ impl Transport for Endpoint { }; let streams = Arc::new(Mutex::new(Streams::new(connection))); inner.muxers.insert(handle, streams.clone()); - Ok(crate::Upgrade::spawn( - streams.clone(), - quinn_proto::Side::Client, - socket, - )) + Ok(crate::Upgrade::spawn(streams.clone(), socket)) } } diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 7abd8605261..5951932ee55 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -38,6 +38,9 @@ pub enum Error { /// Connection was prematurely closed #[error(display = "Connection was prematurely closed")] ConnectionLost, + /// Error making the connection + #[error(display = "Connection failure: {}", _0)] + ConnectError(#[error(source)] quinn_proto::ConnectError), /// Cannot listen on the same endpoint more than once #[error(display = "Cannot listen on the same endpoint more than once")] AlreadyListening, @@ -71,9 +74,9 @@ impl From for io::Error { match e { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), Error::ConnectionError(e) => e.into(), - e @ Error::NetworkFailure | e @ Error::ConnectionClosing => { - io::Error::new(ErrorKind::Other, e) - } + e @ Error::NetworkFailure + | e @ Error::ConnectionClosing + | e @ Error::ConnectError(_) => io::Error::new(ErrorKind::Other, e), e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index f0d8ea06669..b091c1a5ac8 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -419,7 +419,6 @@ struct ConnectionDriver { impl Upgrade { pub(crate) fn spawn( connection: Arc>, - _side: quinn_proto::Side, socket: Arc, ) -> Upgrade { let inner = connection.clone(); From 016de39a03ca2be58510a109e44a87351fb2e708 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 13:44:15 -0400 Subject: [PATCH 175/202] Explain that QUIC resends lost packets --- transports/quic/src/socket.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index e9ac939b23c..e1f3815a77e 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -45,8 +45,9 @@ impl Socket { /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition and /// drop it. If it is not, the connection will eventually time out. This provides a very high /// degree of robustness. Connections will transparently resume after a transient network - /// outage, and problems that are specific to one peer will not effect other peers. - pub(crate) fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { + /// outage, and problems that are specific to one peer will not effect other peers. QUIC + /// automatically resends lost packets. + fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); futures::pin_mut!(fut); From ec3787c3e29c2ac16bf082144e15db3580bd561a Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 13:45:49 -0400 Subject: [PATCH 176/202] We already ignore transmit I/O errors so there is no need to handle them. --- transports/quic/src/socket.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs index e1f3815a77e..c2baf42fbb5 100644 --- a/transports/quic/src/socket.rs +++ b/transports/quic/src/socket.rs @@ -47,7 +47,7 @@ impl Socket { /// degree of robustness. Connections will transparently resume after a transient network /// outage, and problems that are specific to one peer will not effect other peers. QUIC /// automatically resends lost packets. - fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll> { + fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll<()> { match { let fut = self.socket.send_to(&packet.contents, &packet.destination); futures::pin_mut!(fut); @@ -56,11 +56,11 @@ impl Socket { Poll::Pending => Poll::Pending, Poll::Ready(Ok(e)) => { trace!("sent packet of length {} to {}", e, packet.destination); - Poll::Ready(Ok(())) + Poll::Ready(()) } Poll::Ready(Err(e)) => { warn!("Ignoring I/O error on transmit: {:?}", e); - Poll::Ready(Ok(())) + Poll::Ready(()) } } } @@ -106,20 +106,18 @@ impl Socket { trace!("trying to send packet!"); match self.poll_send_to(cx, &transmit) { Poll::Pending => return Poll::Pending, - Poll::Ready(Ok(())) => {} - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Ready(()) => {} } } pending.pending = None; while let Some(transmit) = source() { trace!("trying to send packet!"); match self.poll_send_to(cx, &transmit) { - Poll::Ready(Ok(())) => {} + Poll::Ready(()) => {} Poll::Pending => { pending.pending = Some(transmit); return Poll::Pending; } - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), } } Poll::Ready(Ok(())) From 45fdfd6c30e5e2bf5c06d4c24ff41b3ff96bca69 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 13:47:52 -0400 Subject: [PATCH 177/202] Remove unused field --- transports/quic/src/stream_map.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index b091c1a5ac8..2549141b557 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -406,8 +406,6 @@ impl Streams { #[derive(Debug)] struct ConnectionDriver { inner: Arc>, - /// The packet awaiting transmission, if any. - outgoing_packet: Option, /// The timer being used by this connection timer: Option, /// The last timeout returned by `quinn_proto::poll_timeout`. @@ -424,7 +422,6 @@ impl Upgrade { let inner = connection.clone(); async_std::task::spawn(ConnectionDriver { inner, - outgoing_packet: None, timer: None, last_timeout: None, socket, From f8558937be55a5a7cff52c7253f27234c63fd88c Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 8 Apr 2020 18:00:44 -0400 Subject: [PATCH 178/202] Update to x509-signature 0.2.0 --- misc/x509/Cargo.toml | 2 +- misc/x509/src/verifier.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index a9380906541..cce68ede125 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -22,5 +22,5 @@ yasna = "0.3.1" [dependencies.x509] package = "x509-signature" -version = "0.1.0" +version = "0.2.0" features = ["webpki", "rustls", "std"] diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 73ad83d2005..898d48da43d 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -111,7 +111,7 @@ fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), parse_certificate(presented_certs[0].as_ref()).map_err(rustls::TLSError::WebPKIError)?; let now = get_time()?; certificate - .verify_data_algorithm_signature(now, &certificate.das()) + .verify_signature_of_certificate(now, &certificate) .map_err(rustls::TLSError::WebPKIError)?; verify_libp2p_signature(&extension, certificate.subject_public_key_info().key()) .map_err(rustls::TLSError::WebPKIError) From a532c1185e0c85e8a45556d265816946b92278ba Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 12 Apr 2020 20:10:22 -0400 Subject: [PATCH 179/202] Switch to the TLS patch that will be merged --- Cargo.toml | 3 +- misc/x509/Cargo.toml | 2 +- misc/x509/src/verifier.rs | 89 ++++++++++++++++++++++++--------------- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f26369f0a5..55ac52c3e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ wasm-ext = ["libp2p-wasm-ext"] websocket = ["libp2p-websocket"] yamux = ["libp2p-yamux"] secp256k1 = ["libp2p-core/secp256k1", "libp2p-secio/secp256k1"] +tracing = ["libp2p-quic/tracing"] [dependencies] bytes = "0.5" @@ -119,4 +120,4 @@ members = [ ] [patch.crates-io] -rustls = { git = "https://github.com/DemiMarie-parity/rustls", branch = "manual-verify" } +rustls = { git = "https://github.com/ctz/rustls", branch = "jbp-custom-sigverify" } \ No newline at end of file diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index cce68ede125..abdfbc6c649 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -22,5 +22,5 @@ yasna = "0.3.1" [dependencies.x509] package = "x509-signature" -version = "0.2.0" +version = "0.3.0" features = ["webpki", "rustls", "std"] diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 898d48da43d..46caf456b4e 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -20,6 +20,10 @@ use libp2p_core::identity::PublicKey; use ring::io::der; +use rustls::{ + internal::msgs::handshake::DigitallySignedStruct, Certificate, ClientCertVerified, + HandshakeSignatureValid, ServerCertVerified, TLSError, +}; use untrusted::{Input, Reader}; use webpki::Error; @@ -42,20 +46,30 @@ impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { &self, _roots: &rustls::RootCertStore, presented_certs: &[rustls::Certificate], _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { - verify_presented_certs(presented_certs).map(|()| rustls::ServerCertVerified::assertion()) + verify_presented_certs(presented_certs).map(|()| ServerCertVerified::assertion()) } - fn verify_certificate_signature( - &self, scheme: rustls::SignatureScheme, version: rustls::ProtocolVersion, - certificate: &rustls::Certificate, msg: &[u8], signature: &[u8], - ) -> Result { - assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); - x509::parse_certificate(certificate.as_ref()) - .map_err(rustls::TLSError::WebPKIError)? - .verify_signature_against_scheme(get_time()?, scheme, msg, signature) - .map_err(rustls::TLSError::WebPKIError) - .map(|()| rustls::HandshakeSignatureValid::assertion()) + fn verify_tls12_signature( + &self, _message: &[u8], _cert: &Certificate, _dss: &DigitallySignedStruct, + ) -> Result { + panic!("got asked to verify a TLS1.2 signature, but TLS1.2 was disabled") } + + fn verify_tls13_signature( + &self, message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature(message, cert, dss) + } +} + +fn verify_tls13_signature( + message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, +) -> Result { + x509::parse_certificate(cert.as_ref()) + .map_err(rustls::TLSError::WebPKIError)? + .check_tls13_signature(dss.scheme, message, dss.sig.0.as_ref()) + .map_err(rustls::TLSError::WebPKIError) + .map(|()| rustls::HandshakeSignatureValid::assertion()) } fn verify_libp2p_signature( @@ -74,11 +88,11 @@ fn verify_libp2p_signature( } } -fn get_time() -> Result { +fn get_time() -> Result { use std::time::{SystemTime, SystemTimeError}; SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_: SystemTimeError| rustls::TLSError::FailedToGetCurrentTime) + .map_err(|_: SystemTimeError| TLSError::FailedToGetCurrentTime) .map(|e| e.as_secs()) } @@ -93,28 +107,31 @@ fn parse_certificate( .iterate(&mut |oid, critical, extension| { Ok(match oid { crate::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), - crate::LIBP2P_OID_BYTES => - libp2p_extension = Some(parse_libp2p_extension(extension)?), + crate::LIBP2P_OID_BYTES => { + libp2p_extension = Some(parse_libp2p_extension(extension)?) + } _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => {}, + _ => {} }) })?; let libp2p_extension = libp2p_extension.ok_or(Error::UnknownIssuer)?; Ok((parsed, libp2p_extension)) } -fn verify_presented_certs(presented_certs: &[rustls::Certificate]) -> Result<(), rustls::TLSError> { +fn verify_presented_certs(presented_certs: &[Certificate]) -> Result<(), TLSError> { if presented_certs.len() != 1 { - return Err(rustls::TLSError::NoCertificatesPresented); + return Err(TLSError::NoCertificatesPresented); } let (certificate, extension) = - parse_certificate(presented_certs[0].as_ref()).map_err(rustls::TLSError::WebPKIError)?; + parse_certificate(presented_certs[0].as_ref()).map_err(TLSError::WebPKIError)?; let now = get_time()?; + certificate.valid(now) + .map_err(TLSError::WebPKIError)?; certificate - .verify_signature_of_certificate(now, &certificate) - .map_err(rustls::TLSError::WebPKIError)?; + .check_self_signature() + .map_err(TLSError::WebPKIError)?; verify_libp2p_signature(&extension, certificate.subject_public_key_info().key()) - .map_err(rustls::TLSError::WebPKIError) + .map_err(TLSError::WebPKIError) } struct Libp2pExtension<'a> { @@ -156,7 +173,9 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result bool { true } + fn offer_client_auth(&self) -> bool { + true + } fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, @@ -165,19 +184,23 @@ impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { } fn verify_client_cert( - &self, presented_certs: &[rustls::Certificate], _dns_name: Option<&webpki::DNSName>, - ) -> Result { - verify_presented_certs(presented_certs).map(|()| rustls::ClientCertVerified::assertion()) + &self, presented_certs: &[Certificate], _dns_name: Option<&webpki::DNSName>, + ) -> Result { + verify_presented_certs(presented_certs).map(|()| ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, _message: &[u8], _cert: &Certificate, _dss: &DigitallySignedStruct, + ) -> Result { + panic!("got asked to verify a TLS1.2 signature, but TLS1.2 was disabled") } - fn verify_certificate_signature( - &self, scheme: rustls::SignatureScheme, version: rustls::ProtocolVersion, - certificate: &rustls::Certificate, msg: &[u8], signature: &[u8], - ) -> Result { - assert_eq!(version, rustls::ProtocolVersion::TLSv1_3); - x509::parse_certificate(certificate.as_ref()) + fn verify_tls13_signature( + &self, message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, + ) -> Result { + x509::parse_certificate(cert.as_ref()) .map_err(rustls::TLSError::WebPKIError)? - .verify_signature_against_scheme(get_time()?, scheme, msg, signature) + .check_tls13_signature(dss.scheme, message, dss.sig.0.as_ref()) .map_err(rustls::TLSError::WebPKIError) .map(|()| rustls::HandshakeSignatureValid::assertion()) } From 416f99e9694bf3356feaaa2ec5d728cc1131592f Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 15 Apr 2020 20:26:29 -0400 Subject: [PATCH 180/202] =?UTF-8?q?err-derive=20=E2=87=92=20thiserror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for consistency with the rest of libp2p --- transports/quic/Cargo.toml | 2 +- transports/quic/src/error.rs | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 0584e6616a9..279eadbd270 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -13,7 +13,6 @@ async-macros = "2.0.0" async-std = "1.5.0" either = "1.5.3" env_logger = "0.7.1" -err-derive = "0.2.2" futures = "0.3.4" futures-timer = "3.0.2" ipnet = "2.2.0" @@ -32,6 +31,7 @@ untrusted = "0.7.0" webpki = "0.21.2" x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.18.0" } yasna = { version = "0.3.1" } +thiserror = "1.0.15" [features] trace = [ diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 5951932ee55..74e80acdbfd 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use err_derive::Error; +use thiserror::Error; use futures::channel::mpsc::SendError; use io::ErrorKind; use std::io; @@ -27,39 +27,39 @@ use std::io; #[derive(Error, Debug)] pub enum Error { /// Fatal I/O error - #[error(display = "Fatal I/O error {}", _0)] - IO(#[error(source)] std::io::Error), + #[error("Fatal I/O error {0}")] + IO(#[error(transparent)] #[from] std::io::Error), /// QUIC protocol error - #[error(display = "QUIC protocol error: {}", _0)] - ConnectionError(#[error(source)] quinn_proto::ConnectionError), + #[error("QUIC protocol error: {0}")] + ConnectionError(#[from] quinn_proto::ConnectionError), /// Peer stopped receiving data - #[error(display = "Peer stopped receiving data: code {}", _0)] + #[error("Peer stopped receiving data: code {0}")] Stopped(quinn_proto::VarInt), /// Connection was prematurely closed - #[error(display = "Connection was prematurely closed")] + #[error("Connection was prematurely closed")] ConnectionLost, - /// Error making the connection - #[error(display = "Connection failure: {}", _0)] - ConnectError(#[error(source)] quinn_proto::ConnectError), + /// Error making the connection. + #[error("Connection failure: {0}")] + ConnectError(#[from] quinn_proto::ConnectError), /// Cannot listen on the same endpoint more than once - #[error(display = "Cannot listen on the same endpoint more than once")] + #[error("Cannot listen on the same endpoint more than once")] AlreadyListening, /// The stream was reset by the peer. - #[error(display = "Peer reset stream: code {}", _0)] + #[error("Peer reset stream: code {0}")] Reset(quinn_proto::VarInt), /// Either an attempt was made to write to a stream that was already shut down, /// or a previous operation on this stream failed. - #[error(display = "Use of a stream that has is no longer valid. This is a \ + #[error("Use of a stream that has is no longer valid. This is a \ bug in the application.")] ExpiredStream, /// Reading from a stream that has not been written to. - #[error(display = "Reading from a stream that has not been written to.")] + #[error("Reading from a stream that has not been written to.")] CannotReadFromUnwrittenStream, /// Fatal internal error or network failure - #[error(display = "Fatal internal error or network failure")] + #[error("Fatal internal error or network failure")] NetworkFailure, /// Connection already being closed - #[error(display = "Connection already being closed")] + #[error("Connection already being closed")] ConnectionClosing, } From 90da0f78558b9cb524c4c3b2e90b7e05500ec599 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 16 Apr 2020 10:50:09 -0400 Subject: [PATCH 181/202] =?UTF-8?q?=E2=80=98err-derive=E2=80=99=20?= =?UTF-8?q?=E2=87=92=20=E2=80=98thiserror=E2=80=99=20in=20libp2p-x509?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is also done for consistency. --- misc/x509/Cargo.toml | 2 +- misc/x509/src/lib.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 514c663b40b..317efa681f2 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -10,12 +10,12 @@ keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] categories = ["network-programming", "asynchronous"] [dependencies] -err-derive = "0.2.2" libp2p-core = { path = "../../core", version = "0.18.0" } log = "0.4.8" rcgen = { version = "0.7.0", default-features = false } ring = "0.16.11" rustls = { version = "0.17.0", features = ["dangerous_configuration"] } +thiserror = "1.0.15" untrusted = "0.7.0" webpki = "0.21.2" yasna = "0.3.1" diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 98cff2fcf44..76345d85be6 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -48,7 +48,7 @@ mod certificate; mod verifier; -use err_derive::Error; +use thiserror::Error; use std::sync::Arc; pub use verifier::extract_peerid_or_panic; @@ -60,14 +60,14 @@ const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; #[derive(Debug, Error)] pub enum ConfigError { /// TLS private key or certificate rejected - #[error(display = "TLS private or certificate key rejected: {}", _0)] - TLSError(#[error(source)] rustls::TLSError), + #[error("TLS private or certificate key rejected: {0}")] + TLSError(#[error(transparent)] #[from] rustls::TLSError), /// Signing failed - #[error(display = "Signing failed: {}", _0)] - SigningError(#[error(source)] libp2p_core::identity::error::SigningError), + #[error("Signing failed: {0}")] + SigningError(#[from] libp2p_core::identity::error::SigningError), /// Certificate generation error - #[error(display = "Certificate generation error: {}", _0)] - RcgenError(#[error(source)] rcgen::RcgenError), + #[error("Certificate generation error: {0}")] + RcgenError(#[from] rcgen::RcgenError), } fn make_client_config( From 5f8a3762f18735f1499522d81437d27509bea9ea Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Thu, 16 Apr 2020 11:11:02 -0400 Subject: [PATCH 182/202] Switch to rustls master It now supports user-defined signature verification. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 35e7e7a7ec5..7894950af84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,4 +120,4 @@ members = [ ] [patch.crates-io] -rustls = { git = "https://github.com/ctz/rustls", branch = "jbp-custom-sigverify" } +rustls = { git = "https://github.com/ctz/rustls" } From ebc238242dfbe0cccc2fc903bc9361a0bf538368 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 20 Apr 2020 16:51:27 -0400 Subject: [PATCH 183/202] Bump rcgen --- misc/x509/Cargo.toml | 2 +- transports/quic/Cargo.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index 317efa681f2..edbaa8cfdca 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] libp2p-core = { path = "../../core", version = "0.18.0" } log = "0.4.8" -rcgen = { version = "0.7.0", default-features = false } +rcgen = { version = "0.8.1", default-features = false } ring = "0.16.11" rustls = { version = "0.17.0", features = ["dangerous_configuration"] } thiserror = "1.0.15" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 279eadbd270..fb973b283f5 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -21,7 +21,6 @@ log = "0.4.8" parking_lot = "0.10.0" protobuf = "2.10.1" quinn-proto = "0.6.0" -rcgen = { version = "0.7.0", default_features = false } ring = "0.16.11" rustls = { version = "0.17.0", features = ["dangerous_configuration"] } tracing = { version = "0.1.12", optional = true } From ec5c33132c54532bf143f57b3c782d32a1d91bbc Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Tue, 21 Apr 2020 13:52:10 -0400 Subject: [PATCH 184/202] Bump x509-signature dependency This fixes a critical bug in the x509-signature crate, which fortunately does not affect libp2p. It also makes deprecation warnings no longer be errors, as they can appear in minor releases of upstream dependencies. --- misc/x509/Cargo.toml | 2 +- misc/x509/src/lib.rs | 1 - misc/x509/src/verifier.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index edbaa8cfdca..fd77c913ee0 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -22,5 +22,5 @@ yasna = "0.3.1" [dependencies.x509] package = "x509-signature" -version = "0.3.0" +version = "0.3.3" features = ["webpki", "rustls", "std"] diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 76345d85be6..47eead33db3 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -21,7 +21,6 @@ //! TLS configuration for `libp2p-quic`. #![deny( const_err, - deprecated, improper_ctypes, non_shorthand_field_patterns, nonstandard_style, diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 46caf456b4e..6e36b59108b 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -128,7 +128,7 @@ fn verify_presented_certs(presented_certs: &[Certificate]) -> Result<(), TLSErro certificate.valid(now) .map_err(TLSError::WebPKIError)?; certificate - .check_self_signature() + .check_self_issued() .map_err(TLSError::WebPKIError)?; verify_libp2p_signature(&extension, certificate.subject_public_key_info().key()) .map_err(TLSError::WebPKIError) From b9e0b48a1fcb473b8bb25c632de742d115b80447 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 6 May 2020 14:35:57 -0400 Subject: [PATCH 185/202] Upgrade to the newest x509-signature crate --- misc/x509/Cargo.toml | 2 +- misc/x509/src/certificate.rs | 8 ++++++-- misc/x509/src/lib.rs | 8 ++++++-- misc/x509/src/verifier.rs | 23 +++++------------------ transports/quic/src/endpoint.rs | 13 ++++++++----- transports/quic/src/error.rs | 14 ++++++++++---- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml index fd77c913ee0..2fd3fa534c9 100644 --- a/misc/x509/Cargo.toml +++ b/misc/x509/Cargo.toml @@ -22,5 +22,5 @@ yasna = "0.3.1" [dependencies.x509] package = "x509-signature" -version = "0.3.3" +version = "0.4.0" features = ["webpki", "rustls", "std"] diff --git a/misc/x509/src/certificate.rs b/misc/x509/src/certificate.rs index 7d7e980ed42..fd3cbeb2d4c 100644 --- a/misc/x509/src/certificate.rs +++ b/misc/x509/src/certificate.rs @@ -55,7 +55,9 @@ fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen ext } -fn gen_signed_keypair(keypair: &identity::Keypair) -> Result<(rcgen::KeyPair, rcgen::CustomExtension), crate::ConfigError> { +fn gen_signed_keypair( + keypair: &identity::Keypair, +) -> Result<(rcgen::KeyPair, rcgen::CustomExtension), crate::ConfigError> { let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM)?; let mut signing_buf = [0u8; LIBP2P_SIGNING_PREFIX_LENGTH + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; @@ -77,7 +79,9 @@ fn gen_signed_keypair(keypair: &identity::Keypair) -> Result<(rcgen::KeyPair, rc /// Generates a self-signed TLS certificate that includes a libp2p-specific /// certificate extension containing the public key of the given keypair. -pub(crate) fn make_cert(keypair: &identity::Keypair) -> Result { +pub(crate) fn make_cert( + keypair: &identity::Keypair, +) -> Result { let mut params = rcgen::CertificateParams::new(vec![]); params.distinguished_name = rcgen::DistinguishedName::new(); let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair)?; diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 47eead33db3..6afe2b43cd6 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -47,8 +47,8 @@ mod certificate; mod verifier; -use thiserror::Error; use std::sync::Arc; +use thiserror::Error; pub use verifier::extract_peerid_or_panic; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -60,7 +60,11 @@ const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; pub enum ConfigError { /// TLS private key or certificate rejected #[error("TLS private or certificate key rejected: {0}")] - TLSError(#[error(transparent)] #[from] rustls::TLSError), + TLSError( + #[error(transparent)] + #[from] + rustls::TLSError, + ), /// Signing failed #[error("Signing failed: {0}")] SigningError(#[from] libp2p_core::identity::error::SigningError), diff --git a/misc/x509/src/verifier.rs b/misc/x509/src/verifier.rs index 6e36b59108b..98a87fb2d25 100644 --- a/misc/x509/src/verifier.rs +++ b/misc/x509/src/verifier.rs @@ -88,14 +88,6 @@ fn verify_libp2p_signature( } } -fn get_time() -> Result { - use std::time::{SystemTime, SystemTimeError}; - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_: SystemTimeError| TLSError::FailedToGetCurrentTime) - .map(|e| e.as_secs()) -} - fn parse_certificate( certificate: &[u8], ) -> Result<(x509::X509Certificate<'_>, Libp2pExtension<'_>), Error> { @@ -107,11 +99,10 @@ fn parse_certificate( .iterate(&mut |oid, critical, extension| { Ok(match oid { crate::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), - crate::LIBP2P_OID_BYTES => { - libp2p_extension = Some(parse_libp2p_extension(extension)?) - } + crate::LIBP2P_OID_BYTES => + libp2p_extension = Some(parse_libp2p_extension(extension)?), _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => {} + _ => {}, }) })?; let libp2p_extension = libp2p_extension.ok_or(Error::UnknownIssuer)?; @@ -124,9 +115,7 @@ fn verify_presented_certs(presented_certs: &[Certificate]) -> Result<(), TLSErro } let (certificate, extension) = parse_certificate(presented_certs[0].as_ref()).map_err(TLSError::WebPKIError)?; - let now = get_time()?; - certificate.valid(now) - .map_err(TLSError::WebPKIError)?; + certificate.valid().map_err(TLSError::WebPKIError)?; certificate .check_self_issued() .map_err(TLSError::WebPKIError)?; @@ -173,9 +162,7 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result bool { - true - } + fn offer_client_auth(&self) -> bool { true } fn client_auth_root_subjects( &self, _dns_name: Option<&webpki::DNSName>, diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 55de5fcb6d2..31e62d9940d 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -730,11 +730,14 @@ impl Transport for Endpoint { }; let Endpoint(endpoint) = self; let mut inner = endpoint.reference.inner.lock(); - let (handle, connection) = inner.inner.connect( - endpoint.reference.config.client_config.clone(), - socket_addr, - "l", - ).map_err(|e| TransportError::Other(Error::ConnectError(e)))?; + let (handle, connection) = inner + .inner + .connect( + endpoint.reference.config.client_config.clone(), + socket_addr, + "l", + ) + .map_err(|e| TransportError::Other(Error::ConnectError(e)))?; let socket = endpoint.reference.socket.clone(); let connection = Connection { pending: socket::Pending::default(), diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 74e80acdbfd..dde5e6cd35a 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -18,17 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use thiserror::Error; use futures::channel::mpsc::SendError; use io::ErrorKind; use std::io; +use thiserror::Error; /// An error that can be returned by libp2p-quic. #[derive(Error, Debug)] pub enum Error { /// Fatal I/O error #[error("Fatal I/O error {0}")] - IO(#[error(transparent)] #[from] std::io::Error), + IO( + #[error(transparent)] + #[from] + std::io::Error, + ), /// QUIC protocol error #[error("QUIC protocol error: {0}")] ConnectionError(#[from] quinn_proto::ConnectionError), @@ -49,8 +53,10 @@ pub enum Error { Reset(quinn_proto::VarInt), /// Either an attempt was made to write to a stream that was already shut down, /// or a previous operation on this stream failed. - #[error("Use of a stream that has is no longer valid. This is a \ - bug in the application.")] + #[error( + "Use of a stream that has is no longer valid. This is a \ + bug in the application." + )] ExpiredStream, /// Reading from a stream that has not been written to. #[error("Reading from a stream that has not been written to.")] From a3ba313ec879d99ff6f7431c9f202b671bbc5258 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Wed, 13 May 2020 21:21:42 -0400 Subject: [PATCH 186/202] Update to quinn-proto and rustls master --- Cargo.toml | 1 + misc/x509/src/lib.rs | 6 +---- transports/quic/src/error.rs | 6 +---- transports/quic/src/stream_map.rs | 41 ++++++++++++++++--------------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 721458bb736..3f172d046ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,3 +121,4 @@ members = [ [patch.crates-io] rustls = { git = "https://github.com/ctz/rustls" } +quinn-proto = { git = "https://github.com/djc/quinn" } diff --git a/misc/x509/src/lib.rs b/misc/x509/src/lib.rs index 6afe2b43cd6..2b6e0fdbb16 100644 --- a/misc/x509/src/lib.rs +++ b/misc/x509/src/lib.rs @@ -60,11 +60,7 @@ const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; pub enum ConfigError { /// TLS private key or certificate rejected #[error("TLS private or certificate key rejected: {0}")] - TLSError( - #[error(transparent)] - #[from] - rustls::TLSError, - ), + TLSError(#[from] rustls::TLSError), /// Signing failed #[error("Signing failed: {0}")] SigningError(#[from] libp2p_core::identity::error::SigningError), diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index dde5e6cd35a..9671950d4e3 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -28,11 +28,7 @@ use thiserror::Error; pub enum Error { /// Fatal I/O error #[error("Fatal I/O error {0}")] - IO( - #[error(transparent)] - #[from] - std::io::Error, - ), + IO(#[from] std::io::Error), /// QUIC protocol error #[error("QUIC protocol error: {0}")] ConnectionError(#[from] quinn_proto::ConnectionError), diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs index 2549141b557..6979c50ce0a 100644 --- a/transports/quic/src/stream_map.rs +++ b/transports/quic/src/stream_map.rs @@ -158,16 +158,20 @@ impl Streams { } pub(super) fn process_app_events(&mut self) { - use quinn_proto::Event; + use quinn_proto::{Event as E, StreamEvent as S}; 'a: while let Some(event) = self.connection.poll() { match event { - Event::StreamOpened { dir: Dir::Uni } | Event::DatagramReceived => { + E::Stream(S::Opened { dir: Dir::Uni }) | E::DatagramReceived => { // This should never happen, but if it does, it is better to // log a nasty message and recover than to tear down the // process. - error!("we disabled incoming unidirectional streams and datagrams") + error!( + "We don’t use unidirectional streams or datagrams, but got one \ + anyway. This is a libp2p-quic bug; please report it \ + at ." + ) } - Event::StreamAvailable { dir: Dir::Uni } => { + E::Stream(S::Available { dir: Dir::Uni }) => { // Ditto error!( "We don’t use unidirectional streams, but got one \ @@ -175,30 +179,30 @@ impl Streams { at ." ) } - Event::StreamReadable { stream } => { + E::Stream(S::Readable { id }) => { trace!( "Stream {:?} readable for side {:?}", - stream, + id, self.connection.side() ); // Wake up the task waiting on us (if any) - if let Some(stream) = self.map.get_mut(&stream) { + if let Some(stream) = self.map.get_mut(&id) { stream.wake_reader() } } - Event::StreamWritable { stream } => { + E::Stream(S::Writable { id }) => { trace!( "Stream {:?} writable for side {:?}", - stream, + id, self.connection.side() ); // Wake up the task waiting on us (if any). // This will panic if quinn-proto has already emitted a // `StreamFinished` event, but it will never emit // `StreamWritable` after `StreamFinished`. - self.wake_writer(stream) + self.wake_writer(id) } - Event::StreamAvailable { dir: Dir::Bi } => { + E::Stream(S::Available { dir: Dir::Bi }) => { trace!( "Bidirectional stream available for side {:?}", self.connection.side() @@ -225,7 +229,7 @@ impl Streams { } self.pending_stream = Some(stream) } - Event::ConnectionLost { reason } => { + E::ConnectionLost { reason } => { debug!( "lost connection due to {:?} for side {:?}", reason, @@ -235,30 +239,27 @@ impl Streams { self.wake_closer(); self.shutdown(0); } - Event::StreamFinished { - stream, - stop_reason, - } => { + E::Stream(S::Finished { id, stop_reason }) => { trace!( "Stream {:?} finished for side {:?} because of {:?}", - stream, + id, self.connection.side(), stop_reason ); // If someone is waiting for this stream to become writable, // wake them up, so that they can find out the stream is // dead. - if let Some(status) = self.map.get_mut(&stream) { + if let Some(status) = self.map.get_mut(&id) { status.finish() } // Connection close could be blocked on this. self.wake_closer() } - Event::Connected => { + E::Connected => { debug!("connected for side {:?}!", self.connection.side()); self.connection.wake(); } - Event::StreamOpened { dir: Dir::Bi } => { + E::Stream(S::Opened { dir: Dir::Bi }) => { debug!("stream opened for side {:?}", self.connection.side()); self.connection.wake() } From a8de82b38e018b617b17fda190ff1d98cd435ec3 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 9 Jun 2020 15:43:26 +0200 Subject: [PATCH 187/202] QUICk fix --- Cargo.toml | 4 +++- transports/quic/Cargo.toml | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce67ed68b0d..2c3ccebe271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ default = [ "ping", "plaintext", "pnet", + "quic", "secio", "secp256k1", "tcp-async-std", @@ -43,6 +44,7 @@ noise = ["libp2p-noise"] ping = ["libp2p-ping"] plaintext = ["libp2p-plaintext"] pnet = ["libp2p-pnet"] +quic = ["libp2p-quic"] secio = ["libp2p-secio"] tcp-async-std = ["libp2p-tcp", "libp2p-tcp/async-std"] tcp-tokio = ["libp2p-tcp", "libp2p-tcp/tokio"] @@ -122,4 +124,4 @@ members = [ [patch.crates-io] rustls = { git = "https://github.com/ctz/rustls" } -quinn-proto = { git = "https://github.com/djc/quinn" } +quinn-proto = { git = "https://github.com/djc/quinn", rev = "6e9f7e8063926f8bdab56bbc1615277ff0bdece5" } diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 5038fa9c1e6..3e06228e5f9 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -10,7 +10,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-macros = "2.0.0" -async-std = "1.5.0" +async-std = "^1.5.0" either = "1.5.3" env_logger = "0.7.1" futures = "0.3.4" @@ -19,8 +19,7 @@ ipnet = "2.2.0" libp2p-core = { path = "../../core", version = "0.19.0" } log = "0.4.8" parking_lot = "0.10.0" -protobuf = "2.10.1" -quinn-proto = "0.6.0" +quinn-proto = "0.6.1" ring = "0.16.11" rustls = { version = "0.17.0", features = ["dangerous_configuration"] } tracing = { version = "0.1.12", optional = true } From 341a39d14f4507bfbca8b6d44eb11fd15028c19c Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 12 Jun 2020 18:44:10 +0200 Subject: [PATCH 188/202] Working prototype --- Cargo.toml | 7 +- core/src/muxing.rs | 4 + misc/x509/Cargo.toml | 26 - misc/x509/rustfmt.toml | 13 - transports/quic/Cargo.toml | 22 +- transports/quic/src/connection.rs | 535 ++++---- transports/quic/src/endpoint.rs | 1111 ++++++----------- transports/quic/src/lib.rs | 49 +- transports/quic/src/muxer.rs | 324 +++++ transports/quic/src/socket.rs | 131 -- transports/quic/src/stream.rs | 126 -- transports/quic/src/stream_map.rs | 519 -------- transports/quic/src/transport.rs | 192 +++ transports/quic/src/upgrade.rs | 90 ++ .../src/lib.rs => transports/quic/src/x509.rs | 32 +- .../quic/src/x509}/certificate.rs | 100 +- .../quic/src/x509}/verifier.rs | 152 ++- 17 files changed, 1476 insertions(+), 1957 deletions(-) delete mode 100644 misc/x509/Cargo.toml delete mode 100644 misc/x509/rustfmt.toml create mode 100644 transports/quic/src/muxer.rs delete mode 100644 transports/quic/src/socket.rs delete mode 100644 transports/quic/src/stream.rs delete mode 100644 transports/quic/src/stream_map.rs create mode 100644 transports/quic/src/transport.rs create mode 100644 transports/quic/src/upgrade.rs rename misc/x509/src/lib.rs => transports/quic/src/x509.rs (83%) rename {misc/x509/src => transports/quic/src/x509}/certificate.rs (50%) rename {misc/x509/src => transports/quic/src/x509}/verifier.rs (80%) diff --git a/Cargo.toml b/Cargo.toml index 2c3ccebe271..7541ba08378 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ wasm-ext = ["libp2p-wasm-ext"] websocket = ["libp2p-websocket"] yamux = ["libp2p-yamux"] secp256k1 = ["libp2p-core/secp256k1", "libp2p-secio/secp256k1"] -tracing = ["libp2p-quic/tracing"] [dependencies] bytes = "0.5" @@ -101,7 +100,6 @@ members = [ "misc/multiaddr", "misc/multistream-select", "misc/peer-id-generator", - "misc/x509", "muxers/mplex", "muxers/yamux", "protocols/floodsub", @@ -123,5 +121,8 @@ members = [ ] [patch.crates-io] -rustls = { git = "https://github.com/ctz/rustls" } +# TODO: waiting for a release +rustls = { git = "https://github.com/ctz/rustls", rev = "cac66a8c184f3ef8510bb62b390f241c2760f51d" } +# TODO: overwrite necessary because quinn-proto 0.6.1 is incompatible with the rustls overwrite above +# TODO: UUUUUUUGHHHHHH quinn-proto = { git = "https://github.com/djc/quinn", rev = "6e9f7e8063926f8bdab56bbc1615277ff0bdece5" } diff --git a/core/src/muxing.rs b/core/src/muxing.rs index 64a93051baa..e9618f48f57 100644 --- a/core/src/muxing.rs +++ b/core/src/muxing.rs @@ -91,6 +91,10 @@ pub trait StreamMuxer { /// Only the latest task that was used to call this method may be notified. /// /// An error can be generated if the connection has been closed. + /// + /// Polling for an inbound substream is guaranteed to be performed continuously for as long + /// as the [`StreamMuxer`] as a whole is alive, and can therefore be used to drive some + /// connection-wide background processing such as sending connection-wide PINGs/PONGs. fn poll_inbound(&self, cx: &mut Context) -> Poll>; /// Opens a new outgoing substream, and produces the equivalent to a future that will be diff --git a/misc/x509/Cargo.toml b/misc/x509/Cargo.toml deleted file mode 100644 index 643ab309d9a..00000000000 --- a/misc/x509/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "libp2p-x509" -version = "0.19.0" -authors = ["Parity Technologies "] -edition = "2018" -description = "X.509 generation and verification for libp2p" -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking", "tls", "x509"] -categories = ["network-programming", "asynchronous"] - -[dependencies] -libp2p-core = { path = "../../core", version = "0.19.0" } -log = "0.4.8" -rcgen = { version = "0.8.1", default-features = false } -ring = "0.16.11" -rustls = { version = "0.17.0", features = ["dangerous_configuration"] } -thiserror = "1.0.15" -untrusted = "0.7.0" -webpki = "0.21.2" -yasna = "0.3.1" - -[dependencies.x509] -package = "x509-signature" -version = "0.4.0" -features = ["webpki", "rustls", "std"] diff --git a/misc/x509/rustfmt.toml b/misc/x509/rustfmt.toml deleted file mode 100644 index f315886cd3a..00000000000 --- a/misc/x509/rustfmt.toml +++ /dev/null @@ -1,13 +0,0 @@ -edition = "2018" -fn_args_layout = "Compressed" -fn_single_line = true -match_arm_blocks = false -match_block_trailing_comma = true -max_width = 100 -merge_imports = true -newline_style = "Unix" -reorder_imports = true -trailing_comma = "Vertical" -use_field_init_shorthand = true -use_try_shorthand = true -wrap_comments = true diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 3e06228e5f9..960192c3a35 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -9,7 +9,6 @@ keywords = ["peer-to-peer", "libp2p", "quic", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-macros = "2.0.0" async-std = "^1.5.0" either = "1.5.3" env_logger = "0.7.1" @@ -17,23 +16,18 @@ futures = "0.3.4" futures-timer = "3.0.2" ipnet = "2.2.0" libp2p-core = { path = "../../core", version = "0.19.0" } -log = "0.4.8" +log = "0.4.0" parking_lot = "0.10.0" quinn-proto = "0.6.1" +rcgen = { version = "0.8.1", default-features = false } ring = "0.16.11" rustls = { version = "0.17.0", features = ["dangerous_configuration"] } -tracing = { version = "0.1.12", optional = true } -tracing-core = { version = "0.1.10", optional = true } -tracing-subscriber = { version = "0.2.1", optional = true } +thiserror = "1.0.15" untrusted = "0.7.0" webpki = "0.21.2" -x509 = { path = "../../misc/x509", package = "libp2p-x509", version = "0.19.0" } -yasna = { version = "0.3.1" } -thiserror = "1.0.15" +yasna = "0.3.1" -[features] -trace = [ - "tracing-subscriber", - "tracing", - "tracing-core", -] +# TODO: RGMLRMLG fix that crate name +[dependencies.x509-signature] +version = "0.4.0" +features = ["webpki", "rustls", "std"] diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index db70afa0c5c..5acf1ce2805 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -18,293 +18,332 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::{error::Error, stream_map}; -use crate::{error, trace}; -use async_macros::ready; -use either::Either; -use futures::{channel::oneshot, prelude::*}; +//! A single QUIC connection. +//! +//! +// TODO: docs + +use crate::endpoint::Endpoint; + +use futures::{channel::mpsc, prelude::*}; use libp2p_core::StreamMuxer; -use parking_lot::{Mutex, MutexGuard}; use std::{ - mem::replace, + fmt, + net::SocketAddr, + pin::Pin, sync::Arc, task::{Context, Poll}, + time::Instant, }; -/// A QUIC substream -#[derive(Debug)] -pub struct Substream { - id: stream_map::StreamId, - status: SubstreamStatus, -} +/// Underlying structure for both [`crate::QuicMuxer`] and [`crate::Upgrade`]. +/// +/// Contains everything needed to process a connection with a remote. +/// Tied to a specific [`crate::Endpoint`]. +pub(crate) struct Connection { + /// Endpoint this connection belongs to. + endpoint: Arc, + /// Future whose job is to send a message to the endpoint. Only one at a time. + pending_to_endpoint: Option + Send + Sync>>>, + /// Events that the endpoint will send in destination to our local [`quinn_proto::Connection`]. + /// Passed at initialization. + from_endpoint: mpsc::Receiver, -/// The status of a QUIC substream -#[derive(Debug)] -enum SubstreamStatus { - /// The stream has not been written to yet. Reading from such a stream will - /// return an error. - Unwritten, - /// The stream is live, and can be read from or written to. - Live, - /// The stream is being shut down. It can be read from, but not written to. - /// The `oneshot::Receiver` is used to wait for the peer to acknowledge the - /// shutdown. - Finishing(oneshot::Receiver<()>), - /// The stream has been shut down. Reads are still permissible. Writes - /// will return an error. - Finished, + /// The QUIC state machine for this specific connection. + connection: quinn_proto::Connection, + /// Identifier for this connection according to the endpoint. Used when sending messages to + /// the endpoint. + connection_id: quinn_proto::ConnectionHandle, + /// `Future` that triggers at the `Instant` that `self.connection.poll_timeout()` indicates. + next_timeout: Option, + + /// In other to avoid race conditions where a "connected" event happens if we were not + /// handshaking, we cache whether the connection is handshaking and only set this to true + /// after a "connected" event has been received. + /// + /// In other words, this flag indicates whether a "connected" hasn't been received yet. + is_handshaking: bool, } -impl Substream { - /// Construct an unwritten stream. Such a stream must be written to before - /// it can be read from. - fn unwritten(id: stream_map::StreamId) -> Self { - let status = SubstreamStatus::Unwritten; - Self { id, status } - } +impl Connection { + /// Crate-internal function that builds a [`Connection`] from raw components. + /// + /// This function assumes that there exists a background task that will process the messages + /// sent to `to_endpoint` and send us messages on `from_endpoint`. + /// + /// The `from_endpoint` can be purposefully closed by the endpoint if the connection is too + /// slow to process. + // TODO: is this necessary ^? figure out if quinn_proto doesn't forbid that situation in the first place + /// + /// `connection_id` is used to identify the local connection in the messages sent to + /// `to_endpoint`. + /// + /// This function assumes that the [`quinn_proto::Connection`] is completely fresh and none of + /// its methods has ever been called. Failure to comply might lead to logic errors and panics. + // TODO: maybe abstract `to_endpoint` more and make it generic? dunno + pub(crate) fn from_quinn_connection( + endpoint: Arc, + connection: quinn_proto::Connection, + connection_id: quinn_proto::ConnectionHandle, + from_endpoint: mpsc::Receiver, + ) -> Self { + // As the documentation mention, one is not supposed to call any of the methods on the + // `quinn_proto::Connection` before entering this function, and consequently, even if the + // connection has already been closed, there is no way for it to know that it has been + // closed. + assert!(!connection.is_closed()); - /// Construct a live stream, which can be read from or written to. - fn live(id: stream_map::StreamId) -> Self { - let status = SubstreamStatus::Live; - Self { id, status } - } -} + let is_handshaking = connection.is_handshaking(); -/// A QUIC connection, either client or server. -/// -/// QUIC opens streams lazily, so the peer is not notified that a stream has -/// been opened until data is written (either on this stream, or a -/// higher-numbered one). Therefore, reading on a stream that has not been -/// written to will deadlock, unless another stream is opened and written to -/// before the first read returns. Because this is not needed in practice, and -/// to ease debugging, [`::read_substream`] returns an -/// error in this case. -#[derive(Debug, Clone)] -pub struct QuicMuxer(pub(crate) Arc>); - -impl QuicMuxer { - /// Returns the underlying data structure, including all state. - fn inner(&self) -> MutexGuard<'_, stream_map::Streams> { - self.0.lock() + Connection { + endpoint, + pending_to_endpoint: None, + connection, + is_handshaking, + next_timeout: None, + from_endpoint, + connection_id, + } } -} -#[derive(Debug)] -enum OutboundInner { - /// The substream is fully set up - Complete(Result), - /// We are waiting on a stream to become available - Pending(oneshot::Receiver), - /// We have already returned our substream - Done, -} - -#[cfg(feature = "tracing")] -macro_rules! span { - ($name:expr, side = $e:expr) => { - let span = tracing::trace_span!($name, side = $e); - let _guard = span.enter(); - }; - ($name:expr, $inner:expr, $id:expr) => { - let span = tracing::trace_span!($name, side = debug($inner.side()), id = debug(&$id.id)); - let _guard = span.enter(); - }; -} -#[cfg(not(feature = "tracing"))] -macro_rules! span { - ($name:expr, $($id:ident = $e:expr),*) => {}; - ($name:expr, $inner:expr, $id:expr) => {}; -} + /// Returns the certificates send by the remote through the underlying TLS session. + /// Returns `None` if the connection is still handshaking. + pub(crate) fn peer_certificates( + &self, + ) -> Option> { + self.connection + .crypto_session() + .get_peer_certificates() + .map(|l| l.into_iter().map(|l| l.into())) + } -/// An outbound QUIC substream. This will eventually resolve to either a -/// [`Substream`] or an [`Error`]. -#[derive(Debug)] -pub struct Outbound(OutboundInner); - -impl StreamMuxer for QuicMuxer { - type OutboundSubstream = Outbound; - type Substream = Substream; - type Error = Error; - fn open_outbound(&self) -> Self::OutboundSubstream { - let mut inner = self.inner(); - Outbound(if let Err(e) = inner.close_reason() { - OutboundInner::Complete(Err(e)) - } else { - match inner.get_pending_stream() { - Either::Left(id) => OutboundInner::Complete(Ok(id)), - Either::Right(receiver) => OutboundInner::Pending(receiver), - } - }) + /// Returns the address of the node we're connected to. + pub(crate) fn remote_addr(&self) -> SocketAddr { + self.connection.remote_address() } - fn destroy_outbound(&self, outbound: Outbound) { - let mut inner = self.inner(); - let id = match outbound.0 { - OutboundInner::Complete(Err(_)) => return, - OutboundInner::Complete(Ok(id)) => id, - // try_recv is race-free, because the lock on `self` prevents - // other tasks from sending on the other end of the channel. - OutboundInner::Pending(mut channel) => match channel.try_recv() { - Ok(Some(id)) => id, - Err(oneshot::Canceled) | Ok(None) => return, - }, - OutboundInner::Done => return, - }; - inner.destroy_stream(id) + /// Returns `true` if this connection is still pending and not actually connected to the + /// remote. + pub(crate) fn is_handshaking(&self) -> bool { + self.is_handshaking } - fn destroy_substream(&self, substream: Self::Substream) { - let mut inner = self.inner(); - span!("destroy", inner, substream); - inner.destroy_stream(substream.id) + /// Start closing the connection. A [`ConnectionEvent::ConnectionLost`] event will be + /// produced in the future. + pub(crate) fn close(&mut self) { + // We send a dummy `0` error code with no message, as the API of StreamMuxer doesn't + // support this. + self.connection + .close(Instant::now(), From::from(0u32), Default::default()); } - fn is_remote_acknowledged(&self) -> bool { - // we do not allow 0RTT traffic, for security reasons, so this is - // always true. - true + /// Pops a new substream opened by the remote. + /// + /// If `None` is returned, then a [`ConnectionEvent::StreamAvailable`] event will later be + /// produced when a substream is available. + pub(crate) fn pop_incoming_substream(&mut self) -> Option { + self.connection.accept(quinn_proto::Dir::Bi) } - fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.inner(); - span!("inbound", side = debug(inner.side())); - trace!("being polled for inbound connections!"); - inner.close_reason()?; - inner.wake_driver(); - let stream = ready!(inner.accept(cx)); - Poll::Ready(Ok(Substream::live(stream))) + /// Pops a new substream opened locally. + /// + /// The API can be thought as if outgoing substreams were automatically opened by the local + /// QUIC connection and were added to a queue for availability. + /// + /// If `None` is returned, then a [`ConnectionEvent::StreamOpened`] event will later be + /// produced when a substream is available. + pub(crate) fn pop_outgoing_substream(&mut self) -> Option { + self.connection.open(quinn_proto::Dir::Bi) } - fn write_substream( - &self, - cx: &mut Context<'_>, - substream: &mut Self::Substream, - buf: &[u8], - ) -> Poll> { - use quinn_proto::WriteError; - let mut inner = self.inner(); - span!("write", inner, substream); - inner.wake_driver(); - match inner.write(cx, &substream.id, buf) { - Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(WriteError::Blocked) => Poll::Pending, - Err(WriteError::UnknownStream) => { - error!( - "The application used a connection that is already being \ - closed. This is a bug in the application or in libp2p." - ); - inner.close_reason()?; - Poll::Ready(Err(Error::ExpiredStream)) - } - Err(WriteError::Stopped(e)) => { - substream.status = SubstreamStatus::Finished; - Poll::Ready(Err(Error::Stopped(e))) - } + // TODO: + /*pub(crate) fn read_substream(&mut self, id: quinn_proto::StreamId, buf: &mut [u8]) -> Poll<()> { + match self.connection.read(id, buf) { + quinn_proto::ReadError::Blocked => Poll::Pending, } - } + }*/ - fn poll_outbound( - &self, - cx: &mut Context<'_>, - substream: &mut Self::OutboundSubstream, - ) -> Poll> { - let stream = match substream.0 { - OutboundInner::Complete(_) => match replace(&mut substream.0, OutboundInner::Done) { - OutboundInner::Complete(e) => e?, - _ => unreachable!(), - }, - OutboundInner::Pending(ref mut receiver) => { - let result = ready!(receiver.poll_unpin(cx)) - .map_err(|oneshot::Canceled| Error::ConnectionLost)?; - substream.0 = OutboundInner::Done; - result + /*pub(crate) fn write_substream(&mut self, id: quinn_proto::StreamId, buf: &mut [u8]) -> Poll<()> { + match self.connection.read(id, buf) { + quinn_proto::ReadError::Blocked => Poll::Pending, + } + }*/ + + /*pub(crate) fn shutdown_substream(&mut self, id: quinn_proto::StreamId) { + match self.connection.read(id, buf) { + quinn_proto::ReadError::Blocked => Poll::Pending, + } + }*/ + + /// Polls the connection for an event that happend on it. + pub(crate) fn poll_event(&mut self, cx: &mut Context<'_>) -> Poll { + // Process events that the endpoint has sent to us. + loop { + match Pin::new(&mut self.from_endpoint).poll_next(cx) { + Poll::Ready(Some(event)) => self.connection.handle_event(event), + Poll::Ready(None) => break, // TODO: error + Poll::Pending => break, } - OutboundInner::Done => panic!("polled after yielding Ready"), - }; - Poll::Ready(Ok(Substream::unwritten(stream))) - } + } - /// Try to read from a substream. This will return an error if the substream has - /// not yet been written to. - fn read_substream( - &self, - cx: &mut Context<'_>, - substream: &mut Self::Substream, - buf: &mut [u8], - ) -> Poll> { - use quinn_proto::ReadError; - let mut inner = self.inner(); - span!("read", inner, substream); - match inner.read(cx, &substream.id, buf) { - Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(ReadError::Blocked) => { - inner.close_reason()?; - if let SubstreamStatus::Unwritten = substream.status { - Poll::Ready(Err(Error::CannotReadFromUnwrittenStream)) - } else { - trace!( - "Blocked on reading stream {:?} with side {:?}", - substream.id, - inner.side() - ); - Poll::Pending + 'send_pending: loop { + // Sending the pending event to the endpoint. If the endpoint is too busy, we just + // stop the processing here. + // There is a bit of a question in play here: should be continue to accept events + // through `from_endpoint` if `to_endpoint` is busy? + // We need to be careful to avoid a potential deadlock if both `from_endpoint` and + // `to_endpoint` are full. As such, we continue to transfer data from `from_endpoint` + // to the `quinn_proto::Connection` (see above). + // However we don't deliver substream-related events to the user as long as + // `to_endpoint` is full. This should propagate the back-pressure of `to_endpoint` + // being full to the user. + if let Some(pending_to_endpoint) = &mut self.pending_to_endpoint { + match Future::poll(Pin::new(pending_to_endpoint), cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => self.pending_to_endpoint = None, } } - Err(ReadError::UnknownStream) => { - error!( - "The application used a stream that has already been closed. This is a bug." - ); - Poll::Ready(Err(Error::ExpiredStream)) + + let now = Instant::now(); + + // Poll the connection for packets to send on the UDP socket and try to send them on + // `to_endpoint`. + while let Some(transmit) = self.connection.poll_transmit(now) { + let endpoint = self.endpoint.clone(); + debug_assert!(self.pending_to_endpoint.is_none()); + self.pending_to_endpoint = Some(Box::pin(async move { + // TODO: ECN bits not handled + endpoint + .send_udp_packet(transmit.destination, transmit.contents) + .await; + })); + continue 'send_pending; } - Err(ReadError::Reset(e)) => Poll::Ready(Err(Error::Reset(e))), - } - } - fn shutdown_substream( - &self, - cx: &mut Context<'_>, - substream: &mut Self::Substream, - ) -> Poll> { - match substream.status { - SubstreamStatus::Finished => return Poll::Ready(Ok(())), - SubstreamStatus::Finishing(ref mut channel) => { - self.inner().wake_driver(); - ready!(channel.poll_unpin(cx)).map_err(|_| Error::NetworkFailure)?; - return Poll::Ready(Ok(())); + // The connection also needs to be able to send control messages to the endpoint. This is + // handled here, and we try to send them on `to_endpoint` as well. + while let Some(endpoint_event) = self.connection.poll_endpoint_events() { + let endpoint = self.endpoint.clone(); + let connection_id = self.connection_id; + debug_assert!(self.pending_to_endpoint.is_none()); + self.pending_to_endpoint = Some(Box::pin(async move { + endpoint + .report_quinn_event(connection_id, endpoint_event) + .await; + })); + continue 'send_pending; } - SubstreamStatus::Unwritten | SubstreamStatus::Live => {} - } - let mut inner = self.inner(); - span!("shutdown", inner, substream); - match inner.shutdown_stream(cx, &substream.id) { - Ok(receiver) => { - substream.status = SubstreamStatus::Finishing(receiver); - Poll::Pending + + // Timeout system. + // We break out of the following loop until if `poll_timeout()` returns `None` or if + // polling `self.next_timeout` returns `Poll::Pending`. + loop { + if let Some(next_timeout) = &mut self.next_timeout { + match Future::poll(Pin::new(next_timeout), cx) { + Poll::Ready(()) => { + self.connection.handle_timeout(now); + self.next_timeout = None; + } + Poll::Pending => break, + } + } else if let Some(when) = self.connection.poll_timeout() { + if when <= now { + self.connection.handle_timeout(now); + } else { + let delay = when - now; + self.next_timeout = Some(futures_timer::Delay::new(delay)); + } + } else { + break; + } } - Err(quinn_proto::FinishError::Stopped(e)) => Poll::Ready(Err(Error::Stopped(e))), - Err(quinn_proto::FinishError::UnknownStream) => Poll::Ready(Err(Error::ExpiredStream)), + + // The final step consists in handling the events related to the various substreams. + while let Some(event) = self.connection.poll() { + match event { + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Opened { + dir: quinn_proto::Dir::Uni, + }) + | quinn_proto::Event::Stream(quinn_proto::StreamEvent::Available { + dir: quinn_proto::Dir::Uni, + }) + | quinn_proto::Event::DatagramReceived => { + // We don't use datagrams or unidirectional streams. If these events + // happen, it is by some code not compatible with libp2p-quic. + // TODO: kill the connection + } + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Readable { id }) => { + return Poll::Ready(ConnectionEvent::StreamReadable(id)); + } + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Writable { id }) => { + return Poll::Ready(ConnectionEvent::StreamWritable(id)); + } + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Available { + dir: quinn_proto::Dir::Bi, + }) => { + return Poll::Ready(ConnectionEvent::StreamAvailable); + } + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Opened { + dir: quinn_proto::Dir::Bi, + }) => { + return Poll::Ready(ConnectionEvent::StreamOpened); + } + quinn_proto::Event::ConnectionLost { reason } => { + return Poll::Ready(ConnectionEvent::ConnectionLost(reason)); + } + quinn_proto::Event::Stream(quinn_proto::StreamEvent::Finished { + id, + stop_reason, + }) => { + // TODO: transmit `stop_reason` + return Poll::Ready(ConnectionEvent::StreamFinished(id)); + } + quinn_proto::Event::Connected => { + debug_assert!(self.is_handshaking); + debug_assert!(!self.connection.is_handshaking()); + self.is_handshaking = false; + return Poll::Ready(ConnectionEvent::Connected); + } + } + } + + break; } - } - /// Flush pending data on this stream. libp2p-quic sends out data as soon as - /// possible, so this method does nothing. - fn flush_substream( - &self, - _cx: &mut Context<'_>, - _substream: &mut Self::Substream, - ) -> Poll> { - Poll::Ready(Ok(())) + Poll::Pending } +} - /// Flush all pending data to the peer. libp2p-quic sends out data as soon - /// as possible, so this method does nothing. - fn flush_all(&self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) +impl fmt::Debug for Connection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Connection").finish() } +} - /// Close the connection. Once this function is called, it is a logic error - /// to call other methods on this object. - fn close(&self, cx: &mut Context<'_>) -> Poll> { - self.inner().close(cx) +impl Drop for Connection { + fn drop(&mut self) { + // TODO: send a quinn_proto::EndpointEvent::drained() } } + +/// Event generated by the [`Connection`]. +#[derive(Debug)] +pub(crate) enum ConnectionEvent { + /// Now connected to the remote. Can only happen if [`Connection::is_handshaking`] was + /// returning `true`. + Connected, + + /// Connection has been closed and can no longer be used. + ConnectionLost(quinn_proto::ConnectionError), + + /// Generated after [`Connection::pop_incoming_substream`] has been called and has returned + /// `None`. After this event has been generated, this method is guaranteed to return `Some`. + StreamAvailable, + /// Generated after [`Connection::pop_outgoing_substream`] has been called and has returned + /// `None`. After this event has been generated, this method is guaranteed to return `Some`. + StreamOpened, + + StreamReadable(quinn_proto::StreamId), + StreamWritable(quinn_proto::StreamId), + StreamFinished(quinn_proto::StreamId), +} diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 31e62d9940d..be7b98a2303 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -18,43 +18,51 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{debug, info, trace}; -use crate::{error::Error, socket, stream_map::Streams, Upgrade}; -use async_macros::ready; -use async_std::{net::SocketAddr, task::spawn}; -use futures::{channel::mpsc, prelude::*}; +//! Background task dedicated to manage the QUIC state machine. +//! +//! Considering that all QUIC communications happen over a single UDP socket, one needs to +//! maintain a unique synchronization point that holds the state of all the active connections. +//! +//! The [`Endpoint`] object represents this synchronization point. It maintains a background task +//! whose role is to interface with the UDP socket. Communication between the background task and +//! the rest of the code only happens through channels. See the documentation of the +//! [`background_task`] for a thorough description. + +use crate::{connection::Connection, error::Error, x509}; + +use async_std::net::SocketAddr; +use futures::{ + channel::{mpsc, oneshot}, + lock::Mutex, + prelude::*, +}; use libp2p_core::{ - multiaddr::{host_addresses, Multiaddr, Protocol}, - transport::{ListenerEvent, TransportError}, + multiaddr::{Multiaddr, Protocol}, Transport, }; -use parking_lot::Mutex; -use quinn_proto::ConnectionHandle; use std::{ - collections::{hash_map::Entry, HashMap}, - pin::Pin, - sync::Arc, + collections::HashMap, + fmt, io, + sync::{Arc, Weak}, task::{Context, Poll}, time::{Duration, Instant}, }; -use channel_ref::Channel; - -/// Represents the configuration for a QUIC transport capability for libp2p. +/// Represents the configuration for the [`Endpoint`]. #[derive(Debug, Clone)] pub struct Config { - /// The client configuration + /// The client configuration to pass to `quinn_proto`. client_config: quinn_proto::ClientConfig, - /// The server configuration + /// The server configuration to pass to `quinn_proto`. server_config: Arc, - /// The endpoint configuration + /// The endpoint configuration to pass to `quinn_proto`. endpoint_config: Arc, - /// The [`Multiaddr`] + /// The [`Multiaddr`] to use to spawn the UDP socket. multiaddr: Multiaddr, } impl Config { - /// Creates a new configuration object for QUIC. + /// Creates a new configuration object with default values. pub fn new( keypair: &libp2p_core::identity::Keypair, multiaddr: Multiaddr, @@ -80,447 +88,71 @@ impl Config { } } -#[derive(Debug)] -enum EndpointMessage { - Dummy, - ConnectionAccepted, - EndpointEvent { - handle: ConnectionHandle, - event: quinn_proto::EndpointEvent, - }, -} - -#[derive(Debug)] -pub(super) struct EndpointInner { - inner: quinn_proto::Endpoint, - muxers: HashMap>>, - pending: socket::Pending, - /// Used to receive events from connections - event_receiver: mpsc::Receiver, - buffer: Vec, -} - -impl EndpointInner { - fn handle_event( - &mut self, - handle: ConnectionHandle, - event: quinn_proto::EndpointEvent, - ) -> Option { - if event.is_drained() { - let res = self.inner.handle_event(handle, event); - self.muxers.remove(&handle); - res - } else { - self.inner.handle_event(handle, event) - } - } - - fn drive_events(&mut self, cx: &mut Context<'_>) { - while let Poll::Ready(e) = self.event_receiver.poll_next_unpin(cx).map(|e| e.unwrap()) { - match e { - EndpointMessage::ConnectionAccepted => { - debug!("accepting connection!"); - self.inner.accept(); - } - EndpointMessage::EndpointEvent { handle, event } => { - debug!("we have event {:?} from connection {:?}", event, handle); - if let Some(event) = self.handle_event(handle, event) { - self.send_connection_event(handle, event) - } - } - EndpointMessage::Dummy => {} - } - } - } - - /// Send the given `ConnectionEvent` to the appropriate connection, - /// and process any events it sends back. - fn send_connection_event( - &mut self, - handle: ConnectionHandle, - mut event: quinn_proto::ConnectionEvent, - ) { - let Self { inner, muxers, .. } = self; - let entry = match muxers.entry(handle) { - Entry::Vacant(_) => return, - Entry::Occupied(occupied) => occupied, - }; - let mut connection = entry.get().lock(); - let mut is_drained = false; - loop { - let endpoint_event = match connection.handle_event(event) { - None => break, - Some(endpoint_event) => endpoint_event, - }; - is_drained |= endpoint_event.is_drained(); - event = match inner.handle_event(handle, endpoint_event) { - Some(event) => event, - None => break, - } - } - connection.process_app_events(); - connection.wake_driver(); - if is_drained { - drop(connection); - entry.remove(); - } - } - - fn drive_receive( - &mut self, - socket: &socket::Socket, - cx: &mut Context<'_>, - ) -> Poll> { - use quinn_proto::DatagramEvent; - loop { - let (bytes, peer) = ready!(socket.recv_from(cx, &mut self.buffer[..])?); - let (handle, event) = - match self - .inner - .handle(Instant::now(), peer, None, self.buffer[..bytes].into()) - { - Some(e) => e, - None => continue, - }; - trace!("have an event!"); - match event { - DatagramEvent::ConnectionEvent(event) => { - self.send_connection_event(handle, event); - continue; - } - DatagramEvent::NewConnection(connection) => { - debug!("new connection detected!"); - break Poll::Ready(Ok((handle, connection))); - } - } - } - } - - fn poll_transmit_pending( - &mut self, - socket: &socket::Socket, - cx: &mut Context<'_>, - ) -> Poll> { - let Self { inner, pending, .. } = self; - socket - .send_packets(cx, pending, &mut || inner.poll_transmit()) - .map_err(Error::IO) - } -} - -type ListenerResult = ListenerEvent; - -#[derive(Debug)] -pub(super) struct EndpointData { - /// The single UDP socket used for I/O - socket: Arc, - /// A `Mutex` protecting the QUIC state machine. - inner: Mutex, - /// The channel on which new connections are sent. This is bounded in practice by the accept - /// backlog. - new_connections: mpsc::UnboundedSender, - /// The channel used to receive new connections. - receive_connections: Mutex>>, - /// The `Multiaddr` - address: Multiaddr, - /// The configuration +/// Object containing all the QUIC resources shared between all connections. +// TODO: expand docs +// TODO: Debug trait +// TODO: remove useless fields +pub struct Endpoint { + /// Channel to the background of the endpoint. + to_endpoint: mpsc::Sender, + /// Channel where new connections are being sent. + /// This is protected by a futures-friendly `Mutex`, meaning that receiving a connection is + /// done in two steps: locking this mutex, and grabbing the next element on the `Receiver`. + /// The only consequence of this `Mutex` is that multiple simultaneous calls to + /// [`Endpoint::next_incoming`] are serialized. + new_connections: Mutex>, + /// Configuration passed at initialization. + // TODO: remove? config: Config, + /// Multiaddr of the local UDP socket passed in the configuration at initialization after it + /// has potentially been modified to handle port number `0`. + local_multiaddr: Multiaddr, } -type Sender = mpsc::Sender; - -#[derive(Debug)] -pub(crate) struct Connection { - pending: socket::Pending, - connection: quinn_proto::Connection, - handle: ConnectionHandle, - channel: Channel, - waker: Option, -} - -impl Connection { - /// Notify the endpoint that the handshake has completed, - /// and get the certificates from it. - /// - /// If this returns [`Poll::Pending`], the current task can be awoken by - /// a call to [`Connection::wake`]. - /// - /// Calling this after it has returned `Ready` is erroneous. - pub(crate) fn notify_handshake_complete( - &mut self, - cx: &mut Context<'_>, - ) -> Poll> { - if self.connection.is_handshaking() { - self.set_waker(cx); - return Poll::Pending; - } - if self.connection.is_closed() { - return Poll::Ready(Err(Error::ConnectionLost)); - } - let side = self.connection.side(); - if side.is_server() { - ready!(self.channel.poll_ready(cx))?; - self.channel - .start_send(EndpointMessage::ConnectionAccepted)? - } - - assert!( - !self.connection.crypto_session().is_handshaking(), - "QUIC handshake cannot complete before TLS handshake" - ); - let certificate = self - .connection - .crypto_session() - .get_peer_certificates() - .expect("we always require the peer to present a certificate; qed"); - // we have already verified that there is (exactly) one peer certificate, - // and that it has a valid libp2p extension. - Poll::Ready(Ok(x509::extract_peerid_or_panic(certificate[0].as_ref()))) - } - - /// Wake up the last task registered by - /// [`Connection::notify_handshake_complete`] or - /// [`Connection::set_waker`]. - pub(crate) fn wake(&mut self) { - if let Some(waker) = self.waker.take() { - waker.wake() - } - } - - fn set_waker(&mut self, cx: &mut Context<'_>) { - self.waker = Some(cx.waker().clone()) - } - - /// Send as many endpoint events as possible. If this returns `Err`, the connection is dead. - pub(crate) fn send_endpoint_events(&mut self, cx: &mut Context<'_>) -> Result<(), Error> { - loop { - match self.channel.poll_ready(cx) { - Poll::Pending => break Ok(()), - Poll::Ready(token) => token?, - } - if let Some(event) = self.connection.poll_endpoint_events() { - self.channel.start_send(EndpointMessage::EndpointEvent { - handle: self.handle, - event, - })? - } else { - break Ok(()); - } - } - } - - /// Poll for the timeout - pub(crate) fn poll_timeout(&mut self) -> Option { - self.connection.poll_timeout() - } - - /// Handle a timeout - pub(crate) fn handle_timeout(&mut self, now: Instant) { - self.connection.handle_timeout(now) - } - - pub(crate) fn send_streams(&self) -> usize { - self.connection.send_streams() - } - - /// Destroy a substream - pub(crate) fn destroy_stream(&mut self, id: quinn_proto::StreamId) { - // if either of these returns an error, there is nothing we can do, so - // just ignore it. That said, an error here should never happen unless - // the connection is closed. - let _ = self.connection.finish(id); - let _ = self.connection.stop_sending(id, Default::default()); - } - - pub(crate) fn poll_transmit_pending( - &mut self, - now: Instant, - cx: &mut Context<'_>, - socket: &socket::Socket, - ) -> Result<(), Error> { - let connection = &mut self.connection; - match socket.send_packets(cx, &mut self.pending, &mut || connection.poll_transmit(now)) { - Poll::Ready(Ok(())) | Poll::Pending => Ok(()), - Poll::Ready(Err(e)) => Err(Error::IO(e)), - } - } - - pub(crate) fn handle_event( - &mut self, - event: quinn_proto::ConnectionEvent, - ) -> Option { - self.connection.handle_event(event); - self.connection.poll_endpoint_events() - } - - /// Check if this connection is drained. - pub(crate) fn is_drained(&self) -> bool { - self.connection.is_drained() - } - - /// Accept an incoming stream, if possible. - pub(crate) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { - match self.connection.accept(quinn_proto::Dir::Bi) { - None => { - self.set_waker(cx); - Poll::Pending - } - Some(e) => Poll::Ready(e), - } - } - - /// Open an outgoing stream if it is possible to do so. - pub(crate) fn open(&mut self) -> Option { - self.connection.open(quinn_proto::Dir::Bi) - } - - /// Write to a stream. - pub(crate) fn write( - &mut self, - id: quinn_proto::StreamId, - buffer: &[u8], - ) -> Result { - self.connection.write(id, buffer) - } - - /// Shut down the writing side of a stream. - pub(crate) fn finish( - &mut self, - id: quinn_proto::StreamId, - ) -> Result<(), quinn_proto::FinishError> { - self.connection.finish(id) - } - - /// Get application-facing events - pub(crate) fn poll(&mut self) -> Option { - self.connection.poll() - } - - /// Get the side of the stream - pub(crate) fn side(&self) -> quinn_proto::Side { - self.connection.side() - } - - /// Read from a stream - pub(crate) fn read( - &mut self, - id: quinn_proto::StreamId, - buf: &mut [u8], - ) -> Result { - self.connection.read(id, buf).map(|size| size.unwrap_or(0)) - } - - /// Close the stream, if it is not closed already - pub(crate) fn close(&mut self, status: u32) { - if self.connection.is_closed() { - return; - } - self.connection.close( - std::time::Instant::now(), - quinn_proto::VarInt::from_u32(status), - Default::default(), - ) - } -} - -mod channel_ref { - use super::{Arc, EndpointData, EndpointMessage::Dummy, Sender}; - #[derive(Debug, Clone)] - pub(super) struct Channel(Sender); - - impl Channel { - pub(super) fn new(s: Sender) -> Self { - Self(s) - } - } - - impl std::ops::Deref for Channel { - type Target = Sender; - fn deref(&self) -> &Sender { - &self.0 - } - } - - impl std::ops::DerefMut for Channel { - fn deref_mut(&mut self) -> &mut Sender { - &mut self.0 - } - } - - impl Drop for Channel { - fn drop(&mut self) { - // Wake up the endpoint task, to avoid deadlocks. - let _ = self.0.start_send(Dummy); - } - } - - #[derive(Debug, Clone)] - pub(crate) struct EndpointRef { - pub(super) reference: Arc, - // MUST COME LATER so that the endpoint driver gets notified *after* its reference count is - // dropped. - pub(super) channel: Channel, - } -} - -pub(crate) use channel_ref::EndpointRef; - -/// A QUIC endpoint. Each endpoint has its own configuration and listening socket. -/// -/// You generally need at most one of these per thread. Endpoints are [`Send`] and [`Sync`], so you -/// can share them among as many threads as you like. However, performance may be better if you -/// have one per CPU core, as this reduces lock contention. Most applications will not need to -/// worry about this. [`Endpoint`] tries to use fine-grained locking to reduce the overhead. -/// -/// [`Endpoint`] wraps the underlying data structure in an [`Arc`], so cloning it just bumps the -/// reference count. All state is shared between the clones. For example, you can pass different -/// clones to [`::listen_on`]. Each incoming connection will be received by -/// exactly one of them. -/// -/// The **only** valid [`Multiaddr`] to pass to [`::listen_on`] is the one -/// used to create the [`Endpoint`]. If you pass a different one, you will get -/// [`TransportError::MultiaddrNotSupported`]. -#[derive(Debug, Clone)] -pub struct Endpoint(EndpointRef); - -/// A handle that can be used to wait for the endpoint driver to finish. -pub type JoinHandle = async_std::task::JoinHandle>; - -#[derive(Debug)] -pub(crate) struct SendToken(()); - impl Endpoint { - /// Construct a [`Endpoint`] with the given `Config`. - pub fn new( - config: Config, - ) -> Result<(Self, JoinHandle), TransportError<::Error>> { - let Config { - mut multiaddr, - client_config, - server_config, - endpoint_config, - } = config; - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&multiaddr) { - sa - } else { - return Err(TransportError::MultiaddrNotSupported(multiaddr)); + /// Builds a new `Endpoint`. + pub fn new(config: Config) -> Result, io::Error> { + let local_socket_addr = match crate::transport::multiaddr_to_socketaddr(&config.multiaddr) { + Ok(a) => a, + Err(()) => panic!(), // TODO: Err(TransportError::MultiaddrNotSupported(multiaddr)), }; + // NOT blocking, as per man:bind(2), as we pass an IP address. - let socket = std::net::UdpSocket::bind(&socket_addr) - .map_err(|e| TransportError::Other(Error::IO(e)))?; - let port_is_zero = socket_addr.port() == 0; - let socket_addr = socket.local_addr().expect("this socket is bound; qed"); - info!("bound socket to {:?}", socket_addr); + let socket = std::net::UdpSocket::bind(&local_socket_addr)?; + // TODO: + /*let port_is_zero = local_socket_addr.port() == 0; + let local_socket_addr = socket.local_addr()?; if port_is_zero { - assert_ne!(socket_addr.port(), 0); + assert_ne!(local_socket_addr.port(), 0); assert_eq!(multiaddr.pop(), Some(Protocol::Quic)); assert_eq!(multiaddr.pop(), Some(Protocol::Udp(0))); - multiaddr.push(Protocol::Udp(socket_addr.port())); + multiaddr.push(Protocol::Udp(local_socket_addr.port())); multiaddr.push(Protocol::Quic); - } - let (new_connections, receive_connections) = mpsc::unbounded(); - let (event_sender, event_receiver) = mpsc::channel(0); - if socket_addr.ip().is_unspecified() { + }*/ + + let (to_endpoint_tx, to_endpoint_rx) = mpsc::channel(32); + let (new_connections_tx, new_connections_rx) = mpsc::channel(8); + + let endpoint = Arc::new(Endpoint { + to_endpoint: to_endpoint_tx, + new_connections: Mutex::new(new_connections_rx), + config: config.clone(), + local_multiaddr: config.multiaddr.clone(), // TODO: no + }); + + // TODO: just for testing, do proper task spawning + async_std::task::spawn(background_task( + config.clone(), + Arc::downgrade(&endpoint), + async_std::net::UdpSocket::from(socket), + new_connections_tx, + to_endpoint_rx.fuse(), + )); + + Ok(endpoint) + + // TODO: IP address stuff + /*if socket_addr.ip().is_unspecified() { info!("returning all local IPs for unspecified address"); let suffixes = [Protocol::Udp(socket_addr.port()), Protocol::Quic]; let local_addresses = @@ -537,29 +169,7 @@ impl Endpoint { .unbounded_send(ListenerEvent::NewAddress(multiaddr.clone())) .expect("we have a reference to the peer, so this will not fail; qed"); } - let reference = Arc::new(EndpointData { - socket: Arc::new(socket::Socket::new(socket.into())), - inner: Mutex::new(EndpointInner { - inner: quinn_proto::Endpoint::new( - endpoint_config.clone(), - Some(server_config.clone()), - ), - muxers: HashMap::new(), - event_receiver, - pending: Default::default(), - buffer: vec![0; 0xFFFF], - }), - address: multiaddr.clone(), - receive_connections: Mutex::new(Some(receive_connections)), - new_connections, - config: Config { - endpoint_config, - client_config, - server_config, - multiaddr: multiaddr.clone(), - }, - }); - let channel = Channel::new(event_sender); + if socket_addr.ip().is_unspecified() { debug!("returning all local IPs for unspecified address"); let local_addresses = @@ -579,255 +189,350 @@ impl Endpoint { .unbounded_send(ListenerEvent::NewAddress(multiaddr)) .expect("we have a reference to the peer, so this will not fail; qed"); } + let endpoint = EndpointRef { reference, channel }; let join_handle = spawn(endpoint.clone()); - Ok((Self(endpoint), join_handle)) + Ok((Self(endpoint), join_handle))*/ } -} -fn accept_muxer( - endpoint: &Arc, - connection: quinn_proto::Connection, - handle: ConnectionHandle, - inner: &mut EndpointInner, - channel: Channel, -) { - let connection = Connection { - pending: socket::Pending::default(), - connection, - handle, - channel, - waker: None, - }; - - let socket = endpoint.socket.clone(); - - let streams = Arc::new(Mutex::new(Streams::new(connection))); - inner.muxers.insert(handle, streams.clone()); - let upgrade = crate::Upgrade::spawn(streams, socket); - if endpoint - .new_connections - .unbounded_send(ListenerEvent::Upgrade { - upgrade, - local_addr: endpoint.address.clone(), - remote_addr: endpoint.address.clone(), - }) - .is_err() - { - inner.inner.accept(); - inner.inner.reject_new_connections(); + /// Asks the endpoint to start dialing the given address. + /// + /// Note that this method only *starts* the dialing. `Ok` is returned as soon as possible, even + /// when the remote might end up being unreachable. + pub(crate) async fn dial( + &self, + addr: SocketAddr, + ) -> Result { + // The two `expect`s below can panic if the background task has stopped. The background + // task can stop only if it itself panics. In other words, we panic here iff a panic + // has already happened somewhere else, which is a reasonable thing to do. + let (tx, rx) = oneshot::channel(); + self.to_endpoint + .clone() + .send(ToEndpoint::Dial { addr, result: tx }) + .await + .expect("background task has crashed"); + rx.await.expect("background task has crashed") + } + + /// Tries to pop a new incoming connection from the queue. + pub(crate) async fn next_incoming(&self) -> Connection { + let mut new_connections = self.new_connections.lock().await; + // TODO: write docs about panic possibility + new_connections + .next() + .await + .expect("background task has crashed") + } + + /// Asks the endpoint to send a UDP packet. + /// + /// Note that this method only queues the packet and returns as soon as the packet is in queue. + /// There is no guarantee that the packet will actually be sent, but considering that this is + /// a UDP packet, you cannot rely on the packet being delivered anyway. + pub(crate) async fn send_udp_packet( + &self, + destination: SocketAddr, + data: impl Into>, + ) { + let _ = self + .to_endpoint + .clone() + .send(ToEndpoint::SendUdpPacket { + destination, + data: data.into(), + }) + .await; } -} -impl Future for EndpointRef { - type Output = Result<(), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self { reference, channel } = self.get_mut(); - let mut inner = reference.inner.lock(); - #[cfg(feature = "tracing")] - let span = tracing::trace_span!("Endpoint", address = display(&reference.address)); - #[cfg(feature = "tracing")] - let _guard = span.enter(); - trace!("driving events"); - inner.drive_events(cx); - trace!("driving incoming packets"); - match inner.drive_receive(&reference.socket, cx) { - Poll::Pending => {} - Poll::Ready(Ok((handle, connection))) => { - trace!("have a new connection"); - accept_muxer(reference, connection, handle, &mut *inner, channel.clone()); - trace!("connection accepted"); - } - Poll::Ready(Err(e)) => { - if reference - .new_connections - .unbounded_send(ListenerEvent::Error(e)) - .is_err() - { - inner.inner.reject_new_connections() - } - } - } - match inner.poll_transmit_pending(&reference.socket, cx)? { - Poll::Pending | Poll::Ready(()) => {} - } - - if !inner.muxers.is_empty() { - Poll::Pending - } else if Arc::strong_count(&reference) == 1 { - debug!("All connections have closed. Closing QUIC endpoint driver."); - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + /// Report to the endpoint an event on a [`quinn_proto::Connection`]. + /// + /// This is typically called by a [`Connection`]. + // TODO: bad API? + // TODO: talk about lifetime of connections + pub(crate) async fn report_quinn_event( + &self, + connection_id: quinn_proto::ConnectionHandle, + event: quinn_proto::EndpointEvent, + ) { + self.to_endpoint + .clone() + .send(ToEndpoint::ProcessConnectionEvent { + connection_id, + event, + }) + .await + .expect("background task has crashed"); } } -/// A QUIC listener +/// Message sent to the endpoint background task. #[derive(Debug)] -pub struct Listener { - reference: Arc, - /// MUST COME AFTER `reference`! - drop_notifier: Channel, - channel: mpsc::UnboundedReceiver, +enum ToEndpoint { + /// Instruct the endpoint to start connecting to the given address. + Dial { + /// UDP address to connect to. + addr: SocketAddr, + /// Channel to return the result of the dialing to. + result: oneshot::Sender>, + }, + /// Sent by a `quinn_proto` connection when the endpoint needs to process an event generated + /// by a connection. The event itself is opaque to us. Only `quinn_proto` knows what is in + /// there. + ProcessConnectionEvent { + connection_id: quinn_proto::ConnectionHandle, + event: quinn_proto::EndpointEvent, + }, + /// Instruct the endpoint to send a packet of data on its UDP socket. + SendUdpPacket { + /// Destination of the UDP packet. + destination: SocketAddr, + /// Packet of data to send. + data: Box<[u8]>, + }, } -impl Stream for Listener { - type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.get_mut() - .channel - .poll_next_unpin(cx) - .map(|e| e.map(Ok)) - } -} +/// Task that runs in the background for as long as the endpont is alive. Responsible for +/// processing messages and the UDP socket. +/// +/// The `receiver` parameter must be the receiving side of the `Endpoint::to_endpoint` sender. +/// +/// # Behaviour +/// +/// This background task is responsible for the following: +/// +/// - Sending packets on the UDP socket. +/// - Receiving packets from the UDP socket and feed them to the [`quinn_proto::Endpoint`] state +/// machine. +/// - Transmitting events generated by the [`quinn_proto::Endpoint`] to the corresponding +/// [`Connection`]. +/// - Receiving messages from the `receiver` and processing the requested actions. This includes +/// UDP packets to send and events emitted by the [`Connection`] objects. +/// - Sending new connections on `new_connections`. +/// +/// When it comes to channels, there exists three main multi-producer-single-consumer channels +/// in play: +/// +/// - One channel, represented by `Endpoint::to_endpoint` and `receiver`, that communicates +/// messages from [`Endpoint`] to the background task and from the [`Connection`] to the +/// background task. +/// - One channel per each existing connection that communicates messages from the background +/// task to that [`Connection`]. +/// - One channel for the background task to send newly-opened connections to. The receiving +/// side is normally processed by a "listener" as defined by the [`libp2p_core::Transport`] +/// trait. +/// +/// In order to avoid an unbounded buffering of events, we prioritize sending data on the UDP +/// socket over everything else. If the network interface is too busy to process our packets, +/// everything comes to a freeze (including receiving UDP packets) until it is ready to accept +/// more. +/// +/// Apart from freezing when the network interface is too busy, the background task should sleep +/// as little as possible. It is in particular important for the `receiver` to be drained as +/// quickly as possible in order to avoid unnecessary back-pressure on the [`Connection`] objects. +/// +/// ## Back-pressure on `new_connections` +/// +/// The [`quinn_proto::Endpoint`] object contains an accept buffer, in other words a buffer of the +/// incoming connections waiting to be accepted. When a new connection is signalled, we send this +/// new connection on the `new_connections` channel in an asynchronous way, and we only free a slot +/// in the accept buffer once the element has actually been enqueued on `new_connections`. There +/// are therefore in total three buffers in play: the `new_connections` channel itself, the queue +/// of elements being sent on `new_connections`, and the accept buffer of the +/// [`quinn_proto::Endpoint`]. +/// +/// Unfortunately, this design has the consequence that, on the network layer, we will accept a +/// certain number of incoming connections even if [`Endpoint::next_incoming`] is never even +/// called. The `quinn-proto` library doesn't provide any way to not accept incoming connections +/// apart from filling the accept buffer. +/// +/// ## Back-pressure on connections +/// +/// Because connections are processed by the user at a rate of their choice, we cannot properly +/// handle the situation where the channel from the background task to individual connections is +/// full. Sleeping the task while waiting for the connection to be processed by the user could +/// even lead to a deadlock if this processing is also sleeping waiting for some other action that +/// itself depends on the background task (e.g. if processing the connection is waiting for a +/// message arriving on a different connection). +/// +/// In an ideal world, we would handle a background-task-to-connection channel being full by +/// dropping UDP packets destined to this connection, as a way to back-pressure the remote. +/// Unfortunately, the `quinn-proto` library doesn't provide any way for us to know which +/// connection a UDP packet is destined for before it has been turned into a [`ConnectionEvent`], +/// and because these [`ConnectionEvent`]s are sometimes used to synchronize the states of the +/// endpoint and connection, it would be a logic error to silently drop them. +/// +/// We handle this tricky situation by simply killing connections as soon as their associated +/// channel is full. +/// +// TODO: actually implement the listener thing +// TODO: actually implement the killing of connections if channel is full, at the moment we just +// wait +/// # Shutdown +/// +/// The background task shuts down if `endpoint_weak`, `receiver` or `new_connections` become +/// disconnected/invalid. This corresponds to the lifetime of the associated [`Endpoint`]. +/// +/// Keep in mind that we pass an `Arc` whenever we create a new connection, which +/// guarantees that the [`Endpoint`], and therefore the background task, is properly kept alive +/// for as long as any QUIC connection is open. +/// +async fn background_task( + config: Config, + endpoint_weak: Weak, + udp_socket: async_std::net::UdpSocket, + mut new_connections: mpsc::Sender, + mut receiver: stream::Fuse>, +) { + let mut endpoint = quinn_proto::Endpoint::new( + config.endpoint_config.clone(), + Some(config.server_config.clone()), + ); -impl Transport for Endpoint { - type Output = (libp2p_core::PeerId, super::Muxer); - type Error = Error; - type Listener = Listener; - type ListenerUpgrade = Upgrade; - type Dial = Upgrade; - - fn listen_on(self, addr: Multiaddr) -> Result> { - let Endpoint(EndpointRef { - reference, - channel: drop_notifier, - }) = self; - let socket_addr = if let Ok(sa) = multiaddr_to_socketaddr(&addr) { - sa - } else { - return Err(TransportError::MultiaddrNotSupported(addr)); - }; - let own_socket_addr = - multiaddr_to_socketaddr(&reference.address).expect("our own Multiaddr is valid; qed"); - if socket_addr.ip() != own_socket_addr.ip() - || (socket_addr.port() != 0 && socket_addr.port() != own_socket_addr.port()) - { - return Err(TransportError::MultiaddrNotSupported(addr)); - } - let channel = reference - .receive_connections - .lock() - .take() - .ok_or_else(|| TransportError::Other(Error::AlreadyListening))?; - Ok(Listener { - reference, - drop_notifier, - channel, - }) - } + // List of all active connections, with a sender to notify them of events. + let mut alive_connections = HashMap::>::new(); + + // Buffer where we write packets received from the UDP socket. + let mut socket_recv_buffer = vec![0; 65536]; + + // Channel where to send new incoming connections. + let mut active_listener: Option> = None; + + // Next packet waiting to be transmitted on the UDP socket, if any. + // Note that this variable isn't strictly necessary, but it reduces code duplication in the + // code below. + let mut next_packet_out: Option<(SocketAddr, Box<[u8]>)> = None; + + // Main loop of the task. + loop { + // Start by flushing `next_packet_out`. + if let Some((destination, data)) = next_packet_out.take() { + // We block the current task until the packet is sent. This way, if the + // network interface is too busy, we back-pressure all of our internal + // channels. + // TODO: set ECN bits; there is no support for them in the ecosystem right now + match udp_socket.send_to(&data, destination).await { + Ok(n) if n == data.len() => {} + Ok(_) => log::error!( + "QUIC UDP socket violated expectation that packets are always fully \ + transferred" + ), - fn dial(self, addr: Multiaddr) -> Result> { - let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - debug!("Instantly refusing dialing {}, as it is invalid", addr); - return Err(TransportError::MultiaddrNotSupported(addr)); + // Errors on the socket are expected to never happen, and we handle them by simply + // printing a log message. The packet gets discarded in case of error, but we are + // robust to packet losses and it is consequently not a logic error to process with + // normal operations. + Err(err) => log::error!("Error while sending on QUIC UDP socket: {:?}", err), } - socket_addr - } else { - return Err(TransportError::MultiaddrNotSupported(addr)); - }; - let Endpoint(endpoint) = self; - let mut inner = endpoint.reference.inner.lock(); - let (handle, connection) = inner - .inner - .connect( - endpoint.reference.config.client_config.clone(), - socket_addr, - "l", - ) - .map_err(|e| TransportError::Other(Error::ConnectError(e)))?; - let socket = endpoint.reference.socket.clone(); - let connection = Connection { - pending: socket::Pending::default(), - connection, - handle, - channel: endpoint.channel, - waker: None, - }; - let streams = Arc::new(Mutex::new(Streams::new(connection))); - inner.muxers.insert(handle, streams.clone()); - Ok(crate::Upgrade::spawn(streams.clone(), socket)) - } -} + } + + // The endpoint might request packets to be sent out. This is handled in priority to avoid + // buffering up packets. + if let Some(packet) = endpoint.poll_transmit() { + debug_assert!(next_packet_out.is_none()); + next_packet_out = Some((packet.destination, packet.contents)); + continue; + } -// This type of logic should probably be moved into the multiaddr package -fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { - let mut iter = addr.iter(); - let proto1 = iter.next().ok_or(())?; - let proto2 = iter.next().ok_or(())?; - let proto3 = iter.next().ok_or(())?; + futures::select! { + message = receiver.next() => { + // Received a message from a different part of the code requesting us to + // do something. + match message { + // Shut down if the endpoint has shut down. + None => return, + + Some(ToEndpoint::Dial { addr, result }) => { + // TODO: "l"?! + let outcome = endpoint.connect(config.client_config.clone(),addr, "l"); + let outcome = match outcome { + Ok((connection_id, connection)) => { + debug_assert_eq!(connection.side(), quinn_proto::Side::Client); + let (tx, rx) = mpsc::channel(16); + alive_connections.insert(connection_id, tx); + let endpoint_arc = match endpoint_weak.upgrade() { + Some(ep) => ep, + None => return, // Shut down the task if the endpoint is dead. + }; + Ok(Connection::from_quinn_connection(endpoint_arc, connection, connection_id, rx)) + }, + Err(err) => Err(err), + }; + let _ = result.send(outcome); + } - if iter.next().is_some() { - return Err(()); - } + // A connection wants to notify the endpoint of something. + Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { + debug_assert!(alive_connections.contains_key(&connection_id)); + if event.is_drained() { + alive_connections.remove(&connection_id); + } + if let Some(event_back) = endpoint.handle_event(connection_id, event) { + // TODO: don't await here /!\ + alive_connections.get_mut(&connection_id).unwrap().send(event_back).await; + } + } - match (proto1, proto2, proto3) { - (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::Quic) => { - Ok(SocketAddr::new(ip.into(), port)) - } - (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::Quic) => { - Ok(SocketAddr::new(ip.into(), port)) + // Data needs to be sent on the UDP socket. + Some(ToEndpoint::SendUdpPacket { destination, data }) => { + debug_assert!(next_packet_out.is_none()); + next_packet_out = Some((destination, data)); + continue; + } + } + } + + result = udp_socket.recv_from(&mut socket_recv_buffer).fuse() => { + let (packet_len, packet_src) = match result { + Ok(v) => v, + // Errors on the socket are expected to never happen, and we handle them by + // simply printing a log message. + Err(err) => { + log::error!("Error while receive on QUIC UDP socket: {:?}", err); + continue; + }, + }; + + // Received a UDP packet from the socket. + debug_assert!(packet_len <= socket_recv_buffer.len()); + let packet = From::from(&socket_recv_buffer[..packet_len]); + // TODO: ECN bits aren't handled + match endpoint.handle(Instant::now(), packet_src, None, packet) { + None => {}, + Some((connec_id, quinn_proto::DatagramEvent::ConnectionEvent(event))) => { + // Event to send to an existing connection. + if let Some(sender) = alive_connections.get_mut(&connec_id) { + let _ = sender.send(event).await; // TODO: don't await here /!\ + } else { + log::error!("State mismatch: event for closed connection"); + } + }, + Some((connec_id, quinn_proto::DatagramEvent::NewConnection(connec))) => { + // A new connection has been received. `connec_id` is a newly-allocated + // identifier. + debug_assert_eq!(connec.side(), quinn_proto::Side::Server); + let (tx, rx) = mpsc::channel(16); + alive_connections.insert(connec_id, tx); + let endpoint_arc = match endpoint_weak.upgrade() { + Some(ep) => ep, + None => return, // Shut down the task if the endpoint is dead. + }; + let connection = Connection::from_quinn_connection(endpoint_arc, connec, connec_id, rx); + // TODO: don't await here /!\ implement the thing described in the docs + if new_connections.send(connection).await.is_err() { + // Shut down the task if the endpoint is dead. + return; + } + endpoint.accept(); + }, + } + } } - _ => Err(()), } } -#[cfg(test)] -#[test] -fn multiaddr_to_udp_conversion() { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - #[cfg(feature = "tracing")] - use tracing_subscriber::{fmt::Subscriber, EnvFilter}; - #[cfg(feature = "tracing")] - let _ = Subscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() - ); - - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), - 8080, - )) - ); - assert_eq!( - multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - )) - ); - assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" - .parse::() - .unwrap() - ), - Ok(SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, - )), - 8080, - )) - ); +impl fmt::Debug for Endpoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Endpoint").finish() + } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index e0a30d6c846..bb9861b8464 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#![recursion_limit = "512"] + //! Implementation of the libp2p `Transport` and `StreamMuxer` traits for QUIC. //! //! # Usage @@ -49,44 +51,19 @@ //! `Endpoint` manages a background task that processes all incoming packets. Each //! `QuicConnection` also manages a background task, which handles socket output and timer polling. -#![deny( - const_err, - deprecated, - improper_ctypes, - non_shorthand_field_patterns, - nonstandard_style, - no_mangle_generic_items, - renamed_and_removed_lints, - unknown_lints, - type_alias_bounds, - unconditional_recursion, - while_true, - elided_lifetimes_in_paths, - missing_copy_implementations, - missing_debug_implementations, - missing_docs, - single_use_lifetimes, - trivial_casts, - trivial_numeric_casts, - rust_2018_idioms, - unused, - future_incompatible, - clippy::all -)] -#![forbid(unsafe_code, intra_doc_link_resolution_failure)] - -#[cfg(not(feature = "tracing"))] -use log::{debug, error, info, trace, warn}; -#[cfg(feature = "tracing")] -use tracing::{debug, error, info, trace, warn}; +#![deny(unsafe_code)] mod connection; mod endpoint; mod error; -mod socket; -mod stream; -mod stream_map; -pub use connection::{Outbound, QuicMuxer as Muxer, Substream}; -pub use endpoint::{Config, Endpoint, JoinHandle, Listener}; +mod muxer; +mod upgrade; +mod x509; + +pub mod transport; + +pub use endpoint::{Config, Endpoint}; pub use error::Error; -pub use stream_map::Upgrade; +pub use muxer::QuicMuxer; +pub use transport::QuicTransport; +pub use upgrade::Upgrade; diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs new file mode 100644 index 00000000000..60b64672912 --- /dev/null +++ b/transports/quic/src/muxer.rs @@ -0,0 +1,324 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use crate::connection::{Connection, ConnectionEvent}; +use crate::error::Error; + +use futures::prelude::*; +use libp2p_core::StreamMuxer; +use parking_lot::{Mutex, MutexGuard}; +use std::{ + collections::HashMap, + fmt, + mem::replace, + pin::Pin, + sync::Arc, + task::{Context, Poll, Waker}, +}; + +/// State for a single opened QUIC connection. +// TODO: the inner `Mutex` should theoretically be a `futures::lock::Mutex` (or something similar), +// in order to sleep the current task if concurrent access to the connection is required +pub struct QuicMuxer { + inner: Mutex, +} + +/// Mutex-protected fields of [`QuicMuxer`]. +struct QuicMuxerInner { + /// Inner connection object that yields events. + connection: Connection, + /// State of all the substreams that the muxer reports as open. + substreams: HashMap, + /// Waker to wake if a new outgoing substream is opened. + poll_substream_opened_waker: Option, + /// Waker to wake if the connection is closed. + poll_close_waker: Option, +} + +/// State of a single substream. +#[derive(Default)] +struct SubstreamState { + /// Waker to wake if the substream becomes readable. + read_waker: Option, + /// Waker to wake if the substream becomes writable. + write_waker: Option, + /// Waker to wake if the substream becomes closed. + finished_waker: Option, +} + +impl QuicMuxer { + /// Crate-internal function that builds a [`QuicMuxer`] from a raw connection. + /// + /// # Panic + /// + /// Panics if `connection.is_handshaking()` returns `true`. + pub(crate) fn from_connection(connection: Connection) -> Self { + assert!(!connection.is_handshaking()); + + QuicMuxer { + inner: Mutex::new(QuicMuxerInner { + connection, + substreams: Default::default(), + poll_substream_opened_waker: None, + poll_close_waker: None, + }), + } + } +} + +impl StreamMuxer for QuicMuxer { + type OutboundSubstream = (); + type Substream = quinn_proto::StreamId; + type Error = Error; + + fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { + // We use `poll_inbound` to perform the background processing of the entire connection. + let mut inner = self.inner.lock(); + + while let Poll::Ready(event) = inner.connection.poll_event(cx) { + match event { + ConnectionEvent::Connected => { + log::error!("Unexpected Connected event on established QUIC connection"); + } + ConnectionEvent::ConnectionLost(_) => { + if let Some(waker) = inner.poll_close_waker.take() { + waker.wake(); + } + } + + ConnectionEvent::StreamOpened => { + if let Some(waker) = inner.poll_substream_opened_waker.take() { + waker.wake(); + } + } + ConnectionEvent::StreamReadable(substream) => { + if let Some(substream) = inner.substreams.get_mut(&substream) { + if let Some(waker) = substream.read_waker.take() { + waker.wake(); + } + } + } + ConnectionEvent::StreamWritable(substream) => { + if let Some(substream) = inner.substreams.get_mut(&substream) { + if let Some(waker) = substream.write_waker.take() { + waker.wake(); + } + } + } + ConnectionEvent::StreamFinished(substream) => { + if let Some(substream) = inner.substreams.get_mut(&substream) { + if let Some(waker) = substream.read_waker.take() { + waker.wake(); + } + if let Some(waker) = substream.write_waker.take() { + waker.wake(); + } + if let Some(waker) = substream.finished_waker.take() { + waker.wake(); + } + } + } + + // Do nothing as this is handled below. + ConnectionEvent::StreamAvailable => {} + } + } + + if let Some(substream) = inner.connection.pop_incoming_substream() { + inner.substreams.insert(substream, Default::default()); + Poll::Ready(Ok(substream)) + } else { + Poll::Pending + } + } + + fn open_outbound(&self) -> Self::OutboundSubstream { + () + } + + fn poll_outbound( + &self, + cx: &mut Context<'_>, + _: &mut Self::OutboundSubstream, + ) -> Poll> { + // Note that this implementation makes it possible to poll the same outbound substream + // over and over again and get new substreams. Using the API this way is invalid and would + // normally result in a panic, but we decide to just ignore this question. + + let mut inner = self.inner.lock(); + if let Some(substream) = inner.connection.pop_outgoing_substream() { + inner.substreams.insert(substream, Default::default()); + return Poll::Ready(Ok(substream)); + } + + // Register `cx.waker()` as having to be woken up once a substream is available. + if !inner + .poll_substream_opened_waker + .as_ref() + .map_or(false, |w| w.will_wake(cx.waker())) + { + inner.poll_substream_opened_waker = Some(cx.waker().clone()); + } + Poll::Pending + } + + fn destroy_outbound(&self, _: Self::OutboundSubstream) {} + + fn is_remote_acknowledged(&self) -> bool { + // TODO: stub + true + } + + fn write_substream( + &self, + cx: &mut Context<'_>, + substream: &mut Self::Substream, + buf: &[u8], + ) -> Poll> { + unimplemented!() + /*use quinn_proto::WriteError; + let mut inner = self.0.lock(); + inner.wake_driver(); + match inner.write(cx, &substream.id, buf) { + Ok(bytes) => Poll::Ready(Ok(bytes)), + Err(WriteError::Blocked) => Poll::Pending, + Err(WriteError::UnknownStream) => { + error!( + "The application used a connection that is already being \ + closed. This is a bug in the application or in libp2p." + ); + inner.close_reason()?; + Poll::Ready(Err(Error::ExpiredStream)) + } + Err(WriteError::Stopped(e)) => { + substream.status = SubstreamStatus::Finished; + Poll::Ready(Err(Error::Stopped(e))) + } + }*/ + } + + /// Try to read from a substream. This will return an error if the substream has + /// not yet been written to. + fn read_substream( + &self, + cx: &mut Context<'_>, + substream: &mut Self::Substream, + buf: &mut [u8], + ) -> Poll> { + unimplemented!() + /*use quinn_proto::ReadError; + let mut inner = self.0.lock(); + match inner.read(cx, &substream.id, buf) { + Ok(bytes) => Poll::Ready(Ok(bytes)), + Err(ReadError::Blocked) => { + inner.close_reason()?; + if let SubstreamStatus::Unwritten = substream.status { + Poll::Ready(Err(Error::CannotReadFromUnwrittenStream)) + } else { + trace!( + "Blocked on reading stream {:?} with side {:?}", + substream.id, + inner.side() + ); + Poll::Pending + } + } + Err(ReadError::UnknownStream) => { + error!( + "The application used a stream that has already been closed. This is a bug." + ); + Poll::Ready(Err(Error::ExpiredStream)) + } + Err(ReadError::Reset(e)) => Poll::Ready(Err(Error::Reset(e))), + }*/ + } + + fn shutdown_substream( + &self, + cx: &mut Context<'_>, + substream: &mut Self::Substream, + ) -> Poll> { + unimplemented!() + /*match substream.status { + SubstreamStatus::Finished => return Poll::Ready(Ok(())), + SubstreamStatus::Finishing(ref mut channel) => { + self.0.lock().wake_driver(); + ready!(channel.poll_unpin(cx)).map_err(|_| Error::NetworkFailure)?; + return Poll::Ready(Ok(())); + } + SubstreamStatus::Unwritten | SubstreamStatus::Live => {} + } + let mut inner = self.0.lock(); + match inner.shutdown_stream(cx, &substream.id) { + Ok(receiver) => { + substream.status = SubstreamStatus::Finishing(receiver); + Poll::Pending + } + Err(quinn_proto::FinishError::Stopped(e)) => Poll::Ready(Err(Error::Stopped(e))), + Err(quinn_proto::FinishError::UnknownStream) => Poll::Ready(Err(Error::ExpiredStream)), + }*/ + } + + fn destroy_substream(&self, substream: Self::Substream) { + let mut inner = self.inner.lock(); + inner.substreams.remove(&substream); + } + + fn flush_substream( + &self, + _cx: &mut Context<'_>, + _substream: &mut Self::Substream, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn flush_all(&self, _cx: &mut Context<'_>) -> Poll> { + // TODO: call poll_transmit() and stuff + Poll::Ready(Ok(())) + } + + fn close(&self, cx: &mut Context<'_>) -> Poll> { + // StreamMuxer's `close` documentation mentions that it automatically implies `flush_all`. + if let Poll::Pending = self.flush_all(cx)? { + return Poll::Pending; + } + + // TODO: poll if closed or something + + let mut inner = self.inner.lock(); + //self.connection.close(); + + // Register `cx.waker()` as being woken up if the connection closes. + if !inner + .poll_close_waker + .as_ref() + .map_or(false, |w| w.will_wake(cx.waker())) + { + inner.poll_close_waker = Some(cx.waker().clone()); + } + Poll::Pending + } +} + +impl fmt::Debug for QuicMuxer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("QuicMuxer").finish() + } +} diff --git a/transports/quic/src/socket.rs b/transports/quic/src/socket.rs deleted file mode 100644 index c2baf42fbb5..00000000000 --- a/transports/quic/src/socket.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! I/O for libp2p-quic. -//! -//! This provides a central location for socket I/O, and logs all incoming and outgoing packets. - -use crate::{trace, warn}; -use async_std::net::UdpSocket; -use quinn_proto::Transmit; -use std::{future::Future, io::Result, task::Context, task::Poll}; - -/// A pending packet for libp2p-quic -#[derive(Debug, Default)] -pub(crate) struct Pending { - pending: Option, -} - -/// A socket wrapper for libp2p-quic -#[derive(Debug)] -pub(crate) struct Socket { - socket: UdpSocket, -} - -impl Socket { - /// Transmit a packet if possible, with appropriate logging. - /// - /// We ignore I/O errors. If a packet cannot be sent, we assume it is a transient condition and - /// drop it. If it is not, the connection will eventually time out. This provides a very high - /// degree of robustness. Connections will transparently resume after a transient network - /// outage, and problems that are specific to one peer will not effect other peers. QUIC - /// automatically resends lost packets. - fn poll_send_to(&self, cx: &mut Context<'_>, packet: &Transmit) -> Poll<()> { - match { - let fut = self.socket.send_to(&packet.contents, &packet.destination); - futures::pin_mut!(fut); - fut.poll(cx) - } { - Poll::Pending => Poll::Pending, - Poll::Ready(Ok(e)) => { - trace!("sent packet of length {} to {}", e, packet.destination); - Poll::Ready(()) - } - Poll::Ready(Err(e)) => { - warn!("Ignoring I/O error on transmit: {:?}", e); - Poll::Ready(()) - } - } - } - - /// A wrapper around `recv_from` that handles ECONNRESET and logging - pub(crate) fn recv_from( - &self, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - loop { - match { - let fut = self.socket.recv_from(buf); - futures::pin_mut!(fut); - fut.poll(cx) - } { - Poll::Pending => { - break Poll::Pending; - } - Poll::Ready(Ok(e)) => { - trace!("received packet of length {} from {}", e.0, e.1); - break Poll::Ready(Ok(e)); - } - // May be injected by a malicious ICMP packet - Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::ConnectionReset => { - warn!("Got ECONNRESET from recv_from() - possible MITM attack") - } - Poll::Ready(Err(e)) => { - warn!("Fatal I/O error: {:?}", e); - break Poll::Ready(Err(e)); - } - } - } - } - - pub(crate) fn send_packets( - &self, - cx: &mut Context<'_>, - pending: &mut Pending, - source: &mut dyn FnMut() -> Option, - ) -> Poll> { - if let Some(transmit) = &pending.pending { - trace!("trying to send packet!"); - match self.poll_send_to(cx, &transmit) { - Poll::Pending => return Poll::Pending, - Poll::Ready(()) => {} - } - } - pending.pending = None; - while let Some(transmit) = source() { - trace!("trying to send packet!"); - match self.poll_send_to(cx, &transmit) { - Poll::Ready(()) => {} - Poll::Pending => { - pending.pending = Some(transmit); - return Poll::Pending; - } - } - } - Poll::Ready(Ok(())) - } -} - -impl Socket { - pub(crate) fn new(socket: UdpSocket) -> Self { - Self { socket } - } -} diff --git a/transports/quic/src/stream.rs b/transports/quic/src/stream.rs deleted file mode 100644 index e3e64750ba4..00000000000 --- a/transports/quic/src/stream.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! The per-stream state in a QUIC connection -use futures::channel::oneshot; -use std::{mem, task}; - -/// The state of a writer. -#[derive(Debug)] -enum WriterStatus { - /// Nobody is waiting to write to or finish this stream. - Unblocked, - /// Writing to the stream is blocked. `waker` is the waker used to wake up - /// the writer task. - Blocked { waker: task::Waker }, - /// The stream is being shut down. `finisher` is the channel used to notify - /// the finishing task. - Finishing { finisher: oneshot::Sender<()> }, - /// The stream has been finished. Further operations will panic. - Finished, -} - -impl WriterStatus { - fn take(self) { - match self { - Self::Unblocked => {} - Self::Blocked { waker } => waker.wake(), - Self::Finishing { finisher } => { - let _ = finisher.send(()); - } - Self::Finished => {} - } - } -} - -/// The state of a stream. Dropping this will wake up any tasks blocked on it. -#[derive(Debug)] -pub(crate) struct StreamState { - /// If a task is blocked on reading this stream, this holds a waker that - /// will wake it up. Otherwise, it is `None`. - reader: Option, - /// The state of the writing portion of this stream. - writer: WriterStatus, -} - -impl Default for StreamState { - fn default() -> Self { - Self { - reader: None, - writer: WriterStatus::Unblocked, - } - } -} - -impl Drop for StreamState { - fn drop(&mut self) { - self.wake_all() - } -} - -impl StreamState { - /// Indicate that the stream is open for reading. Calling this when nobody - /// is waiting for this stream to be readable is a harmless no-op. - pub(crate) fn wake_reader(&mut self) { - if let Some(waker) = self.reader.take() { - waker.wake() - } - } - - /// If a task is waiting for this stream to be finished or written to, wake - /// it up. Otherwise, do nothing. - pub(crate) fn wake_writer(&mut self) { - mem::replace(&mut self.writer, WriterStatus::Unblocked).take(); - } - - /// Set a waker that will be notified when the state becomes readable. - /// Wake up any waker that has already been registered. - pub(crate) fn set_reader(&mut self, waker: task::Waker) { - if let Some(waker) = mem::replace(&mut self.reader, Some(waker)) { - waker.wake() - } - } - - /// Set a waker that will be notified when the task becomes writable or is - /// finished, waking up any waker or channel that has already been - /// registered. - pub(crate) fn set_writer(&mut self, waker: task::Waker) { - mem::replace(&mut self.writer, WriterStatus::Blocked { waker }).take(); - } - - /// Set a channel that will be notified when the task becomes writable or is - /// finished, waking up any existing registered waker or channel - pub(crate) fn set_finisher(&mut self, finisher: oneshot::Sender<()>) { - mem::replace(&mut self.writer, WriterStatus::Finishing { finisher }).take(); - } - - /// Wake up both readers and writers. This is just a shorthand for calling - /// [`StreamState::wake_writer`] and [`StreamState::wake_reader`]. - pub(crate) fn wake_all(&mut self) { - self.wake_writer(); - self.wake_reader(); - } - - /// Mark this stream done for writing. Return `true` if this stream was not - /// already finished. - pub(crate) fn finish(&mut self) { - mem::replace(&mut self.writer, WriterStatus::Finished).take() - } -} diff --git a/transports/quic/src/stream_map.rs b/transports/quic/src/stream_map.rs deleted file mode 100644 index 6979c50ce0a..00000000000 --- a/transports/quic/src/stream_map.rs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! The state of all active streams in a QUIC connection - -use super::endpoint::Connection; -use super::stream::StreamState; -use super::{socket, Error}; -use crate::{debug, error, info, trace}; -use async_macros::ready; -use either::Either; -use futures::{channel::oneshot, future::FutureExt}; -use parking_lot::Mutex; -use quinn_proto::Dir; -use std::collections::HashMap; -use std::sync::Arc; -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll, Waker}, - time::Instant, -}; - -/// A stream ID. -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] -pub(super) struct StreamId(quinn_proto::StreamId); - -impl std::ops::Deref for StreamId { - type Target = quinn_proto::StreamId; - fn deref(&self) -> &quinn_proto::StreamId { - &self.0 - } -} - -/// A QUIC connection that is being upgraded. -#[derive(Debug)] -pub struct Upgrade { - muxer: Option>>, -} - -#[cfg(test)] -impl Drop for Upgrade { - fn drop(&mut self) { - debug!("dropping upgrade!"); - assert!( - self.muxer.is_none(), - "dropped before being polled to completion" - ); - } -} - -type StreamSenderQueue = std::collections::VecDeque>; - -/// A set of streams. -#[derive(Debug)] -pub(super) struct Streams { - /// If this is `Some`, it is a stream that has been opened by the peer, - /// but not yet accepted by the application. Otherwise, this is `None`. - pending_stream: Option, - /// The stream statuses - map: HashMap, - /// The QUIC state machine and endpoint messaging. - connection: Connection, - /// Tasks waiting to make a connection. - connectors: StreamSenderQueue, - /// The close reason, if this connection has been lost - close_reason: Option, - /// Waker to wake up the driver. This is not a `MaybeWaker` because only the - /// driver task ever puts a waker here. - waker: Option, - /// Close waker - close_waker: Option, -} - -impl Streams { - pub(crate) fn wake_driver(&mut self) { - if let Some(waker) = self.waker.take() { - waker.wake() - } - } - - fn wake_closer(&mut self) { - if let Some(close_waker) = self.close_waker.take() { - close_waker.wake() - } - } - - fn add_stream(&mut self, id: quinn_proto::StreamId) -> StreamId { - if self.map.insert(id, Default::default()).is_some() { - panic!( - "Internal state corrupted. \ - You probably used a Substream with the wrong StreamMuxer", - ) - } - StreamId(id) - } - - pub(crate) fn new(connection: Connection) -> Self { - Self { - pending_stream: None, - map: Default::default(), - connection, - connectors: Default::default(), - close_reason: None, - waker: None, - close_waker: None, - } - } - - /// Get the stream state for the stream. - /// - /// # Panics - /// - /// Panics if there is no stream. This indicates misuse of the API. - fn get(&mut self, id: &StreamId) -> &mut StreamState { - self.map.get_mut(id).expect( - "Internal state corrupted. \ - You probably used a Substream with the wrong StreamMuxer", - ) - } - - /// If a task is waiting for this stream to be finished or written to, wake - /// it up. Otherwise, do nothing. - fn wake_writer(&mut self, id: quinn_proto::StreamId) { - if let Some(stream) = self.map.get_mut(&id) { - stream.wake_writer() - } - } - - /// Poll for incoming streams - pub(super) fn accept(&mut self, cx: &mut Context<'_>) -> Poll { - self.wake_driver(); - self.connection.accept(cx).map(|e| self.add_stream(e)) - } - - /// Wake up everything - fn wake_all(&mut self) { - for value in self.map.values_mut() { - value.wake_all() - } - } - - pub(super) fn process_app_events(&mut self) { - use quinn_proto::{Event as E, StreamEvent as S}; - 'a: while let Some(event) = self.connection.poll() { - match event { - E::Stream(S::Opened { dir: Dir::Uni }) | E::DatagramReceived => { - // This should never happen, but if it does, it is better to - // log a nasty message and recover than to tear down the - // process. - error!( - "We don’t use unidirectional streams or datagrams, but got one \ - anyway. This is a libp2p-quic bug; please report it \ - at ." - ) - } - E::Stream(S::Available { dir: Dir::Uni }) => { - // Ditto - error!( - "We don’t use unidirectional streams, but got one \ - anyway. This is a libp2p-quic bug; please report it \ - at ." - ) - } - E::Stream(S::Readable { id }) => { - trace!( - "Stream {:?} readable for side {:?}", - id, - self.connection.side() - ); - // Wake up the task waiting on us (if any) - if let Some(stream) = self.map.get_mut(&id) { - stream.wake_reader() - } - } - E::Stream(S::Writable { id }) => { - trace!( - "Stream {:?} writable for side {:?}", - id, - self.connection.side() - ); - // Wake up the task waiting on us (if any). - // This will panic if quinn-proto has already emitted a - // `StreamFinished` event, but it will never emit - // `StreamWritable` after `StreamFinished`. - self.wake_writer(id) - } - E::Stream(S::Available { dir: Dir::Bi }) => { - trace!( - "Bidirectional stream available for side {:?}", - self.connection.side() - ); - if self.connectors.is_empty() { - // Nobody is waiting on the stream. Allow quinn-proto to - // queue it, so that it can apply backpressure. - continue; - } - assert!( - self.pending_stream.is_none(), - "we cannot have both pending tasks and a pending stream; qed" - ); - let mut stream = self.open_stream().expect( - "we just were told that there is a stream available; there is \ - a mutex that prevents other threads from calling open() in the \ - meantime; qed", - ); - while let Some(oneshot) = self.connectors.pop_front() { - stream = match oneshot.send(stream) { - Ok(()) => continue 'a, - Err(e) => e, - } - } - self.pending_stream = Some(stream) - } - E::ConnectionLost { reason } => { - debug!( - "lost connection due to {:?} for side {:?}", - reason, - self.connection.side() - ); - self.close_reason = Some(reason); - self.wake_closer(); - self.shutdown(0); - } - E::Stream(S::Finished { id, stop_reason }) => { - trace!( - "Stream {:?} finished for side {:?} because of {:?}", - id, - self.connection.side(), - stop_reason - ); - // If someone is waiting for this stream to become writable, - // wake them up, so that they can find out the stream is - // dead. - if let Some(status) = self.map.get_mut(&id) { - status.finish() - } - // Connection close could be blocked on this. - self.wake_closer() - } - E::Connected => { - debug!("connected for side {:?}!", self.connection.side()); - self.connection.wake(); - } - E::Stream(S::Opened { dir: Dir::Bi }) => { - debug!("stream opened for side {:?}", self.connection.side()); - self.connection.wake() - } - } - } - } - - fn shutdown(&mut self, error_code: u32) { - debug!("shutting connection down!"); - self.connection.wake(); - self.wake_all(); - self.connectors.clear(); - self.connection.close(error_code); - self.wake_driver(); - } - - fn open_stream(&mut self) -> Option { - self.connection.open().map(|e| self.add_stream(e)) - } - - fn finish(&mut self, id: &StreamId) -> Result<(), quinn_proto::FinishError> { - self.connection.finish(id.0).map_err(|e| { - self.get(&id).finish(); - e - }) - } - - pub(crate) fn write( - &mut self, - cx: &mut Context<'_>, - substream: &StreamId, - buf: &[u8], - ) -> Result { - use quinn_proto::WriteError; - self.wake_driver(); - self.connection.write(substream.0, buf).map_err(|e| { - let stream = self.get(substream); - match e { - WriteError::Blocked => stream.set_writer(cx.waker().clone()), - WriteError::Stopped(_) | WriteError::UnknownStream => stream.finish(), - } - e - }) - } - - pub(crate) fn side(&self) -> quinn_proto::Side { - self.connection.side() - } - - pub(crate) fn read( - &mut self, - cx: &mut Context<'_>, - substream: &StreamId, - buf: &mut [u8], - ) -> Result { - use quinn_proto::ReadError; - self.wake_driver(); - match self.connection.read(substream.0, buf) { - e @ Err(ReadError::Blocked) => { - self.get(substream).set_reader(cx.waker().clone()); - e - } - e => e, - } - } - - pub(crate) fn get_pending_stream(&mut self) -> Either> { - self.wake_driver(); - let pending = std::mem::replace(&mut self.pending_stream, None); - match pending.or_else(|| self.open_stream()) { - Some(stream) => Either::Left(stream), - None => { - let (sender, receiver) = oneshot::channel(); - self.connectors.push_front(sender); - self.wake_driver(); - Either::Right(receiver) - } - } - } - - pub(crate) fn close_reason(&self) -> Result<(), Error> { - match self - .close_reason - .as_ref() - .map(|e| Error::ConnectionError(e.clone())) - { - Some(e) => Err(e), - None => Ok(()), - } - } - - pub(crate) fn destroy_stream(&mut self, stream: StreamId) { - trace!("Removing substream {:?} from map", *stream); - self.connection.destroy_stream(*stream); - self.map.remove(&stream.0).expect( - "Internal state corrupted. \ - You probably used a Substream with the wrong StreamMuxer", - ); - } - - pub(crate) fn close(&mut self, cx: &mut Context<'_>) -> Poll> { - trace!( - "close() called with {} outbound streams for side {:?}", - self.connection.send_streams(), - self.connection.side() - ); - self.wake_closer(); - self.close_waker = Some(cx.waker().clone()); - if self.close_reason.is_some() { - return Poll::Ready(Ok(())); - } else if self.connection.send_streams() == 0 { - self.shutdown(0); - self.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - return Poll::Ready(Ok(())); - } - for (&stream, value) in &mut self.map { - value.wake_all(); - let _ = self.connection.finish(stream); - } - Poll::Pending - } - - pub(crate) fn handle_event( - &mut self, - event: quinn_proto::ConnectionEvent, - ) -> Option { - self.connection.handle_event(event) - } - - pub(crate) fn shutdown_stream( - &mut self, - cx: &mut Context<'_>, - substream: &StreamId, - ) -> Result, quinn_proto::FinishError> { - self.wake_driver(); - self.finish(substream)?; - let (sender, mut receiver) = oneshot::channel(); - let _ = receiver.poll_unpin(cx); - self.get(substream).set_finisher(sender); - Ok(receiver) - } -} - -/// The connection driver -#[derive(Debug)] -struct ConnectionDriver { - inner: Arc>, - /// The timer being used by this connection - timer: Option, - /// The last timeout returned by `quinn_proto::poll_timeout`. - last_timeout: Option, - /// Transmit socket - socket: Arc, -} - -impl Upgrade { - pub(crate) fn spawn( - connection: Arc>, - socket: Arc, - ) -> Upgrade { - let inner = connection.clone(); - async_std::task::spawn(ConnectionDriver { - inner, - timer: None, - last_timeout: None, - socket, - }); - Upgrade { - muxer: Some(connection), - } - } -} - -impl Future for ConnectionDriver { - type Output = Result<(), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - let mut inner = this.inner.lock(); - #[cfg(feature = "tracing")] - let span = tracing::trace_span!("Connection", side = debug(inner.side()),); - #[cfg(feature = "tracing")] - let _guard = span.enter(); - inner.waker = Some(cx.waker().clone()); - loop { - let now = Instant::now(); - inner - .connection - .poll_transmit_pending(now, cx, &this.socket)?; - inner.process_app_events(); - inner.connection.send_endpoint_events(cx)?; - match inner.connection.poll_timeout() { - None => { - this.timer = None; - this.last_timeout = None - } - Some(t) if t <= now => { - inner.connection.handle_timeout(now); - continue; - } - t if t == this.last_timeout => {} - Some(t) => this.timer = Some(futures_timer::Delay::new(t - now)), - } - if let Some(ref mut timer) = this.timer { - if timer.poll_unpin(cx).is_ready() { - inner.connection.handle_timeout(now); - continue; - } - } - if !inner.connection.is_drained() { - break Poll::Pending; - } - info!("exiting driver"); - let close_reason = inner - .close_reason - .clone() - .expect("we never have a closed connection with no reason; qed"); - break Poll::Ready(match close_reason { - quinn_proto::ConnectionError::LocallyClosed => Ok(()), - e => Err(e.into()), - }); - } - } -} - -const RESET: u32 = 1; - -impl Future for Upgrade { - type Output = Result<(libp2p_core::PeerId, crate::Muxer), Error>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let muxer = &mut self.get_mut().muxer; - trace!("outbound polling!"); - let mut inner = muxer.as_mut().expect("polled after yielding Ready").lock(); - #[cfg(feature = "tracing")] - let span = tracing::trace_span!("Upgrade", side = debug(inner.side()),); - #[cfg(feature = "tracing")] - let _guard = span.enter(); - if let Some(close_reason) = &inner.close_reason { - return Poll::Ready(Err(Error::ConnectionError(close_reason.clone()))); - } - match ready!(inner.connection.notify_handshake_complete(cx)) { - Ok(res) => { - drop(inner); - Poll::Ready(Ok(( - res, - crate::Muxer(muxer.take().expect("polled after yielding Ready")), - ))) - } - Err(e) => { - inner.shutdown(RESET); - inner.close_reason = Some(quinn_proto::ConnectionError::LocallyClosed); - drop(inner); - *muxer = None; - Poll::Ready(Err(e)) - } - } - } -} diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs new file mode 100644 index 00000000000..7303c70b367 --- /dev/null +++ b/transports/quic/src/transport.rs @@ -0,0 +1,192 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Implementation of the [`Transport`] trait for QUIC. +//! +//! Combines all the objects in the other modules to implement the trait. + +use crate::{endpoint::Endpoint, muxer::QuicMuxer, upgrade::Upgrade}; + +use futures::prelude::*; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerEvent, TransportError}, + PeerId, Transport, +}; +use std::{net::SocketAddr, pin::Pin, sync::Arc}; + +// We reexport the errors that are exposed in the API. +// All of these types use one another. +pub use quinn_proto::{ + ApplicationClose, ConfigError, ConnectError, ConnectionClose, ConnectionError, + TransportError as QuinnTransportError, TransportErrorCode, +}; + +/// Wraps around an `Arc` and implements the [`Transport`] trait. +/// +/// > **Note**: This type is necessary because Rust unfortunately forbids implementing the +/// > `Transport` trait directly on `Arc`. +#[derive(Debug, Clone)] +pub struct QuicTransport(pub Arc); + +/// Error that can happen on the transport. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Error while trying to reach a remote. + #[error("{0}")] + Reach(ConnectError), + /// Error after the remote has been reached. + #[error("{0}")] + Established(ConnectionError), +} + +impl Transport for QuicTransport { + type Output = (PeerId, QuicMuxer); + type Error = Error; + type Listener = Pin< + Box, Self::Error>> + Send>, + >; + type ListenerUpgrade = Upgrade; + type Dial = Pin> + Send>>; + + fn listen_on(self, addr: Multiaddr) -> Result> { + // TODO: check address correctness + + // TODO: report the locally opened addresses + + Ok(stream::unfold((), move |()| { + let endpoint = self.0.clone(); + let addr = addr.clone(); + async move { + let connec = endpoint.next_incoming().await; + + let remote_addr = { + let socket_addr = connec.remote_addr(); + Multiaddr::empty() + .with(socket_addr.ip().into()) + .with(Protocol::Udp(socket_addr.port())) + .with(Protocol::Quic) + }; + + let event = Ok(ListenerEvent::Upgrade { + upgrade: Upgrade::from_connection(connec), + local_addr: addr.clone(), // TODO: hack + remote_addr, + }); + Some((event, ())) + } + }) + .boxed()) + } + + fn dial(self, addr: Multiaddr) -> Result> { + let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(&addr) { + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + socket_addr + } else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + + Ok(async move { + let connection = self.0.dial(socket_addr).await.map_err(Error::Reach)?; + let final_connec = Upgrade::from_connection(connection).await?; + Ok(final_connec) + } + .boxed()) + } +} + +/// Tries to turn a QUIC multiaddress into a UDP [`SocketAddr`]. Returns an error if the format +/// of the multiaddr is wrong. +pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result { + let mut iter = addr.iter(); + let proto1 = iter.next().ok_or(())?; + let proto2 = iter.next().ok_or(())?; + let proto3 = iter.next().ok_or(())?; + + if iter.next().is_some() { + return Err(()); + } + + match (proto1, proto2, proto3) { + (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::Quic) => { + Ok(SocketAddr::new(ip.into(), port)) + } + _ => Err(()), + } +} + +#[cfg(test)] +#[test] +fn multiaddr_to_udp_conversion() { + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()).is_err() + ); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr(&"/ip6/::1/udp/12345/quic".parse::().unwrap()), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + 12345, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic" + .parse::() + .unwrap() + ), + Ok(SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + )) + ); +} diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs new file mode 100644 index 00000000000..1dabc1329a4 --- /dev/null +++ b/transports/quic/src/upgrade.rs @@ -0,0 +1,90 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Future that drives a QUIC connection until is has performed its TLS handshake. + +use crate::{ + connection::{Connection, ConnectionEvent}, + muxer::QuicMuxer, + transport, x509, +}; + +use futures::prelude::*; +use libp2p_core::PeerId; +use std::{ + fmt, + pin::Pin, + task::{Context, Poll}, +}; + +/// A QUIC connection currently being negotiated. +pub struct Upgrade { + connection: Option, +} + +impl Upgrade { + /// Builds an [`Upgrade`] that wraps around a [`Connection`]. + pub(crate) fn from_connection(connection: Connection) -> Self { + Upgrade { + connection: Some(connection), + } + } +} + +impl Future for Upgrade { + type Output = Result<(PeerId, QuicMuxer), transport::Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut connection = match self.connection.as_mut() { + Some(c) => c, + None => panic!("Future polled after it has ended"), + }; + + loop { + if !connection.is_handshaking() { + let mut certificates = connection + .peer_certificates() + .expect("peer_certificates always returns Some if !is_handshaking; qed"); + let peer_id = x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API + let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); + return Poll::Ready(Ok((peer_id, muxer))); + } + + match Connection::poll_event(connection, cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(ConnectionEvent::Connected) => { + // `is_handshaking()` will return `false` at the next loop iteration. + continue; + } + Poll::Ready(ConnectionEvent::ConnectionLost(err)) => { + return Poll::Ready(Err(transport::Error::Established(err))); + } + // TODO: enumerate the items and explain how they can't happen + _ => unreachable!(), + } + } + } +} + +impl fmt::Debug for Upgrade { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.connection, f) + } +} diff --git a/misc/x509/src/lib.rs b/transports/quic/src/x509.rs similarity index 83% rename from misc/x509/src/lib.rs rename to transports/quic/src/x509.rs index 2b6e0fdbb16..208da40706e 100644 --- a/misc/x509/src/lib.rs +++ b/transports/quic/src/x509.rs @@ -19,36 +19,13 @@ // DEALINGS IN THE SOFTWARE. //! TLS configuration for `libp2p-quic`. -#![deny( - const_err, - improper_ctypes, - non_shorthand_field_patterns, - nonstandard_style, - no_mangle_generic_items, - renamed_and_removed_lints, - unknown_lints, - type_alias_bounds, - unconditional_recursion, - while_true, - elided_lifetimes_in_paths, - missing_copy_implementations, - missing_debug_implementations, - missing_docs, - single_use_lifetimes, - trivial_casts, - trivial_numeric_casts, - rust_2018_idioms, - unused, - future_incompatible, - clippy::all -)] -#![forbid(unsafe_code)] mod certificate; mod verifier; use std::sync::Arc; use thiserror::Error; + pub use verifier::extract_peerid_or_panic; const LIBP2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; @@ -56,6 +33,7 @@ const LIBP2P_SIGNING_PREFIX_LENGTH: usize = LIBP2P_SIGNING_PREFIX.len(); const LIBP2P_OID_BYTES: &[u8] = &[43, 6, 1, 4, 1, 131, 162, 90, 1, 1]; /// Error creating a configuration +// TODO: remove this; what is the user supposed to do with this error? #[derive(Debug, Error)] pub enum ConfigError { /// TLS private key or certificate rejected @@ -70,7 +48,8 @@ pub enum ConfigError { } fn make_client_config( - certificate: rustls::Certificate, key: rustls::PrivateKey, + certificate: rustls::Certificate, + key: rustls::PrivateKey, verifier: Arc, ) -> Result { let mut crypto = rustls::ClientConfig::new(); @@ -83,7 +62,8 @@ fn make_client_config( } fn make_server_config( - certificate: rustls::Certificate, key: rustls::PrivateKey, + certificate: rustls::Certificate, + key: rustls::PrivateKey, verifier: Arc, ) -> Result { let mut crypto = rustls::ServerConfig::new(verifier); diff --git a/misc/x509/src/certificate.rs b/transports/quic/src/x509/certificate.rs similarity index 50% rename from misc/x509/src/certificate.rs rename to transports/quic/src/x509/certificate.rs index fd3cbeb2d4c..ba26291832d 100644 --- a/misc/x509/src/certificate.rs +++ b/transports/quic/src/x509/certificate.rs @@ -38,55 +38,61 @@ static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECD //static LIBP2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = // &rcgen::PKCS_ED25519 -fn encode_signed_key(public_key: identity::PublicKey, signature: &[u8]) -> rcgen::CustomExtension { - let public_key = public_key.into_protobuf_encoding(); - let contents = yasna::construct_der(|writer| { - writer.write_sequence(|writer| { - writer - .next() - .write_bitvec_bytes(&public_key, public_key.len() * 8); - writer - .next() - .write_bitvec_bytes(signature, signature.len() * 8); - }) - }); - let mut ext = rcgen::CustomExtension::from_oid_content(LIBP2P_OID, contents); - ext.set_criticality(true); - ext -} - -fn gen_signed_keypair( - keypair: &identity::Keypair, -) -> Result<(rcgen::KeyPair, rcgen::CustomExtension), crate::ConfigError> { - let temp_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM)?; - let mut signing_buf = - [0u8; LIBP2P_SIGNING_PREFIX_LENGTH + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; - let public = temp_keypair.public_key_raw(); - assert_eq!( - public.len(), - LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, - "ed25519 public keys are {} bytes", - LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH - ); - signing_buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); - signing_buf[LIBP2P_SIGNING_PREFIX_LENGTH..].copy_from_slice(public); - let signature = keypair.sign(&signing_buf)?; - Ok(( - temp_keypair, - encode_signed_key(keypair.public(), &signature), - )) -} - /// Generates a self-signed TLS certificate that includes a libp2p-specific /// certificate extension containing the public key of the given keypair. pub(crate) fn make_cert( keypair: &identity::Keypair, -) -> Result { - let mut params = rcgen::CertificateParams::new(vec![]); - params.distinguished_name = rcgen::DistinguishedName::new(); - let (cert_keypair, libp2p_extension) = gen_signed_keypair(keypair)?; - params.custom_extensions.push(libp2p_extension); - params.alg = &LIBP2P_SIGNATURE_ALGORITHM; - params.key_pair = Some(cert_keypair); - rcgen::Certificate::from_params(params).map_err(From::from) +) -> Result { + // Keypair used to sign the certificate. + let certif_keypair = rcgen::KeyPair::generate(&LIBP2P_SIGNATURE_ALGORITHM)?; + + // The libp2p-specific extension to the certificate contains a signature of the public key + // of the certificate using the libp2p private key. + let libp2p_ext_signature = { + let certif_pubkey = certif_keypair.public_key_raw(); + assert_eq!( + certif_pubkey.len(), + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH, + "ed25519 public keys are {} bytes", + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH + ); + + let mut buf = + [0u8; LIBP2P_SIGNING_PREFIX_LENGTH + LIBP2P_SIGNATURE_ALGORITHM_PUBLIC_KEY_LENGTH]; + buf[..LIBP2P_SIGNING_PREFIX_LENGTH].copy_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); + buf[LIBP2P_SIGNING_PREFIX_LENGTH..].copy_from_slice(certif_pubkey); + keypair.sign(&buf)? + }; + + // Generate the libp2p-specific extension. + let libp2p_extension: rcgen::CustomExtension = { + let extension_content = { + let serialized_pubkey = keypair.public().into_protobuf_encoding(); + yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer + .next() + .write_bitvec_bytes(&serialized_pubkey, serialized_pubkey.len() * 8); + writer + .next() + .write_bitvec_bytes(&libp2p_ext_signature, libp2p_ext_signature.len() * 8); + }) + }) + }; + + let mut ext = rcgen::CustomExtension::from_oid_content(LIBP2P_OID, extension_content); + ext.set_criticality(true); + ext + }; + + let certificate = { + let mut params = rcgen::CertificateParams::new(vec![]); + params.distinguished_name = rcgen::DistinguishedName::new(); + params.custom_extensions.push(libp2p_extension); + params.alg = &LIBP2P_SIGNATURE_ALGORITHM; + params.key_pair = Some(certif_keypair); + rcgen::Certificate::from_params(params)? + }; + + Ok(certificate) } diff --git a/misc/x509/src/verifier.rs b/transports/quic/src/x509/verifier.rs similarity index 80% rename from misc/x509/src/verifier.rs rename to transports/quic/src/x509/verifier.rs index 98a87fb2d25..6cc07b2d63b 100644 --- a/misc/x509/src/verifier.rs +++ b/transports/quic/src/x509/verifier.rs @@ -39,33 +39,97 @@ pub(crate) struct Libp2pCertificateVerifier; /// /// The check that the [`PeerId`] matches the expected `PeerId` must be done by /// the caller. -/// -/// [`PeerId`]: libp2p_core::PeerId impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { fn verify_server_cert( - &self, _roots: &rustls::RootCertStore, presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, _ocsp_response: &[u8], + &self, + _roots: &rustls::RootCertStore, + presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp_response: &[u8], ) -> Result { verify_presented_certs(presented_certs).map(|()| ServerCertVerified::assertion()) } fn verify_tls12_signature( - &self, _message: &[u8], _cert: &Certificate, _dss: &DigitallySignedStruct, + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, ) -> Result { panic!("got asked to verify a TLS1.2 signature, but TLS1.2 was disabled") } fn verify_tls13_signature( - &self, message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, ) -> Result { verify_tls13_signature(message, cert, dss) } } +/// libp2p requires the following of X.509 client certificate chains: +/// +/// * Exactly one certificate must be presented. In particular, client +/// authentication is mandatory in libp2p. +/// * The certificate must be self-signed. +/// * The certificate must have a valid libp2p extension that includes a +/// signature of its public key. +/// +/// The check that the [`PeerId`] matches the expected `PeerId` must be done by +/// the caller. +/// +/// [`PeerId`]: libp2p_core::PeerId +impl rustls::ClientCertVerifier for Libp2pCertificateVerifier { + fn offer_client_auth(&self) -> bool { + true + } + + fn client_auth_root_subjects( + &self, + _dns_name: Option<&webpki::DNSName>, + ) -> Option { + Some(vec![]) + } + + fn verify_client_cert( + &self, + presented_certs: &[Certificate], + _dns_name: Option<&webpki::DNSName>, + ) -> Result { + verify_presented_certs(presented_certs).map(|()| ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + panic!("got asked to verify a TLS1.2 signature, but TLS1.2 was disabled") + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + x509_signature::parse_certificate(cert.as_ref()) + .map_err(rustls::TLSError::WebPKIError)? + .check_tls13_signature(dss.scheme, message, dss.sig.0.as_ref()) + .map_err(rustls::TLSError::WebPKIError) + .map(|()| rustls::HandshakeSignatureValid::assertion()) + } +} + fn verify_tls13_signature( - message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, ) -> Result { - x509::parse_certificate(cert.as_ref()) + x509_signature::parse_certificate(cert.as_ref()) .map_err(rustls::TLSError::WebPKIError)? .check_tls13_signature(dss.scheme, message, dss.sig.0.as_ref()) .map_err(rustls::TLSError::WebPKIError) @@ -73,10 +137,11 @@ fn verify_tls13_signature( } fn verify_libp2p_signature( - libp2p_extension: &Libp2pExtension<'_>, x509_pkey_bytes: &[u8], + libp2p_extension: &Libp2pExtension<'_>, + x509_pkey_bytes: &[u8], ) -> Result<(), Error> { - let mut v = Vec::with_capacity(crate::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); - v.extend_from_slice(&crate::LIBP2P_SIGNING_PREFIX[..]); + let mut v = Vec::with_capacity(super::LIBP2P_SIGNING_PREFIX_LENGTH + x509_pkey_bytes.len()); + v.extend_from_slice(&super::LIBP2P_SIGNING_PREFIX[..]); v.extend_from_slice(x509_pkey_bytes); if libp2p_extension .peer_key @@ -90,19 +155,20 @@ fn verify_libp2p_signature( fn parse_certificate( certificate: &[u8], -) -> Result<(x509::X509Certificate<'_>, Libp2pExtension<'_>), Error> { - let parsed = x509::parse_certificate(certificate)?; +) -> Result<(x509_signature::X509Certificate<'_>, Libp2pExtension<'_>), Error> { + let parsed = x509_signature::parse_certificate(certificate)?; let mut libp2p_extension = None; parsed .extensions() .iterate(&mut |oid, critical, extension| { Ok(match oid { - crate::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), - crate::LIBP2P_OID_BYTES => - libp2p_extension = Some(parse_libp2p_extension(extension)?), + super::LIBP2P_OID_BYTES if libp2p_extension.is_some() => return Err(Error::BadDER), + super::LIBP2P_OID_BYTES => { + libp2p_extension = Some(parse_libp2p_extension(extension)?) + } _ if critical => return Err(Error::UnsupportedCriticalExtension), - _ => {}, + _ => {} }) })?; let libp2p_extension = libp2p_extension.ok_or(Error::UnknownIssuer)?; @@ -128,12 +194,11 @@ struct Libp2pExtension<'a> { signature: &'a [u8], } -#[inline(always)] -fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Error> { - der::bit_string_with_no_unused_bits(input).map_err(|_| e) -} - fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result, Error> { + fn read_bit_string<'a>(input: &mut Reader<'a>, e: Error) -> Result, Error> { + der::bit_string_with_no_unused_bits(input).map_err(|_| e) + } + let e = Error::ExtensionValueInvalid; Input::read_all(&extension, e, |input| { der::nested(input, der::Tag::Sequence, e, |input| { @@ -149,49 +214,6 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result bool { true } - - fn client_auth_root_subjects( - &self, _dns_name: Option<&webpki::DNSName>, - ) -> Option { - Some(vec![]) - } - - fn verify_client_cert( - &self, presented_certs: &[Certificate], _dns_name: Option<&webpki::DNSName>, - ) -> Result { - verify_presented_certs(presented_certs).map(|()| ClientCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, _message: &[u8], _cert: &Certificate, _dss: &DigitallySignedStruct, - ) -> Result { - panic!("got asked to verify a TLS1.2 signature, but TLS1.2 was disabled") - } - - fn verify_tls13_signature( - &self, message: &[u8], cert: &Certificate, dss: &DigitallySignedStruct, - ) -> Result { - x509::parse_certificate(cert.as_ref()) - .map_err(rustls::TLSError::WebPKIError)? - .check_tls13_signature(dss.scheme, message, dss.sig.0.as_ref()) - .map_err(rustls::TLSError::WebPKIError) - .map(|()| rustls::HandshakeSignatureValid::assertion()) - } -} /// Extracts the `PeerId` from a certificate’s libp2p extension. It is erroneous /// to call this unless the certificate is known to be a well-formed X.509 From 9eb64789c7aeb2da3bc31ed68fb08135c8e14533 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 16 Jun 2020 13:57:37 +0200 Subject: [PATCH 189/202] Working well enough --- transports/quic/src/connection.rs | 81 ++++++++++++++++------ transports/quic/src/endpoint.rs | 108 +++++++++++++++++++++--------- transports/quic/src/lib.rs | 2 +- transports/quic/src/muxer.rs | 100 +++++++++++++-------------- transports/quic/src/transport.rs | 21 +++--- transports/quic/src/upgrade.rs | 7 +- 6 files changed, 194 insertions(+), 125 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 5acf1ce2805..5effd2eeb41 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -20,7 +20,10 @@ //! A single QUIC connection. //! -//! +//! The [`Connection`] struct of this module contains, amongst other things, a +//! [`quinn_proto::Connection`] state machine and an `Arc`. This struct is responsible +//! for communication between quinn_proto's connection and its associated endpoint. +//! All interactions with a QUIC connection should be done through this struct. // TODO: docs use crate::endpoint::Endpoint; @@ -63,6 +66,21 @@ pub(crate) struct Connection { /// /// In other words, this flag indicates whether a "connected" hasn't been received yet. is_handshaking: bool, + /// Contains a `Some` if the connection is closed, with the reason of the closure. + /// Contains `None` if it is still open. + /// Contains `Some` if and only if a `ConnectionLost` event has been emitted. + closed: Option, +} + +/// Error on the connection as a whole. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// Endpoint has force-killed this connection because it was too busy. + #[error("Endpoint has force-killed our connection")] + ClosedChannel, + /// Error in the inner state machine. + #[error("{0}")] + Quinn(#[from] quinn_proto::ConnectionError), } impl Connection { @@ -99,15 +117,17 @@ impl Connection { endpoint, pending_to_endpoint: None, connection, - is_handshaking, next_timeout: None, from_endpoint, connection_id, + is_handshaking, + closed: None, } } /// Returns the certificates send by the remote through the underlying TLS session. /// Returns `None` if the connection is still handshaking. + // TODO: it seems to happen that is_handshaking is false but this returns None pub(crate) fn peer_certificates( &self, ) -> Option> { @@ -156,32 +176,48 @@ impl Connection { self.connection.open(quinn_proto::Dir::Bi) } - // TODO: - /*pub(crate) fn read_substream(&mut self, id: quinn_proto::StreamId, buf: &mut [u8]) -> Poll<()> { - match self.connection.read(id, buf) { - quinn_proto::ReadError::Blocked => Poll::Pending, - } - }*/ + // TODO: better API + pub(crate) fn read_substream( + &mut self, + id: quinn_proto::StreamId, + buf: &mut [u8], + ) -> Result { + self.connection.read(id, buf).map(|n| n.unwrap_or(0)) + } - /*pub(crate) fn write_substream(&mut self, id: quinn_proto::StreamId, buf: &mut [u8]) -> Poll<()> { - match self.connection.read(id, buf) { - quinn_proto::ReadError::Blocked => Poll::Pending, - } - }*/ + pub(crate) fn write_substream( + &mut self, + id: quinn_proto::StreamId, + buf: &[u8], + ) -> Result { + self.connection.write(id, buf) + } - /*pub(crate) fn shutdown_substream(&mut self, id: quinn_proto::StreamId) { - match self.connection.read(id, buf) { - quinn_proto::ReadError::Blocked => Poll::Pending, - } - }*/ + pub(crate) fn shutdown_substream( + &mut self, + id: quinn_proto::StreamId, + ) -> Result<(), quinn_proto::FinishError> { + self.connection.finish(id) + } /// Polls the connection for an event that happend on it. pub(crate) fn poll_event(&mut self, cx: &mut Context<'_>) -> Poll { + // Nothing more can be done if the connection is closed. + // Return `Pending` without registering the waker, essentially freezing the task forever. + if self.closed.is_some() { + return Poll::Pending; + } + // Process events that the endpoint has sent to us. loop { match Pin::new(&mut self.from_endpoint).poll_next(cx) { Poll::Ready(Some(event)) => self.connection.handle_event(event), - Poll::Ready(None) => break, // TODO: error + Poll::Ready(None) => { + debug_assert!(self.closed.is_none()); + let err = Error::ClosedChannel; + self.closed = Some(err.clone()); + return Poll::Ready(ConnectionEvent::ConnectionLost(err)); + } Poll::Pending => break, } } @@ -289,7 +325,10 @@ impl Connection { return Poll::Ready(ConnectionEvent::StreamOpened); } quinn_proto::Event::ConnectionLost { reason } => { - return Poll::Ready(ConnectionEvent::ConnectionLost(reason)); + debug_assert!(self.closed.is_none()); + let err = Error::Quinn(reason); + self.closed = Some(err.clone()); + return Poll::Ready(ConnectionEvent::ConnectionLost(err)); } quinn_proto::Event::Stream(quinn_proto::StreamEvent::Finished { id, @@ -334,7 +373,7 @@ pub(crate) enum ConnectionEvent { Connected, /// Connection has been closed and can no longer be used. - ConnectionLost(quinn_proto::ConnectionError), + ConnectionLost(Error), /// Generated after [`Connection::pop_incoming_substream`] has been called and has returned /// `None`. After this event has been generated, this method is guaranteed to return `Some`. diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index be7b98a2303..4d4051b4aaa 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -41,7 +41,7 @@ use libp2p_core::{ Transport, }; use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, fmt, io, sync::{Arc, Weak}, task::{Context, Poll}, @@ -131,7 +131,7 @@ impl Endpoint { }*/ let (to_endpoint_tx, to_endpoint_rx) = mpsc::channel(32); - let (new_connections_tx, new_connections_rx) = mpsc::channel(8); + let (new_connections_tx, new_connections_rx) = mpsc::channel(1); let endpoint = Arc::new(Endpoint { to_endpoint: to_endpoint_tx, @@ -204,11 +204,12 @@ impl Endpoint { addr: SocketAddr, ) -> Result { // The two `expect`s below can panic if the background task has stopped. The background - // task can stop only if it itself panics. In other words, we panic here iff a panic - // has already happened somewhere else, which is a reasonable thing to do. + // task can stop only if the `Endpoint` is destroyed or if the task itself panics. In other + // words, we panic here iff a panic has already happened somewhere else, which is a + // reasonable thing to do. let (tx, rx) = oneshot::channel(); self.to_endpoint - .clone() + .clone() // TODO: no .send(ToEndpoint::Dial { addr, result: tx }) .await .expect("background task has crashed"); @@ -217,8 +218,11 @@ impl Endpoint { /// Tries to pop a new incoming connection from the queue. pub(crate) async fn next_incoming(&self) -> Connection { + // The `expect` below can panic if the background task has stopped. The background task + // can stop only if the `Endpoint` is destroyed or if the task itself panics. In other + // words, we panic here iff a panic has already happened somewhere else, which is a + // reasonable thing to do. let mut new_connections = self.new_connections.lock().await; - // TODO: write docs about panic possibility new_connections .next() .await @@ -237,7 +241,7 @@ impl Endpoint { ) { let _ = self .to_endpoint - .clone() + .clone() // TODO: no .send(ToEndpoint::SendUdpPacket { destination, data: data.into(), @@ -256,7 +260,7 @@ impl Endpoint { event: quinn_proto::EndpointEvent, ) { self.to_endpoint - .clone() + .clone() // TODO: no .send(ToEndpoint::ProcessConnectionEvent { connection_id, event, @@ -365,7 +369,6 @@ enum ToEndpoint { /// We handle this tricky situation by simply killing connections as soon as their associated /// channel is full. /// -// TODO: actually implement the listener thing // TODO: actually implement the killing of connections if channel is full, at the moment we just // wait /// # Shutdown @@ -384,6 +387,7 @@ async fn background_task( mut new_connections: mpsc::Sender, mut receiver: stream::Fuse>, ) { + // The actual QUIC state machine. let mut endpoint = quinn_proto::Endpoint::new( config.endpoint_config.clone(), Some(config.server_config.clone()), @@ -395,8 +399,12 @@ async fn background_task( // Buffer where we write packets received from the UDP socket. let mut socket_recv_buffer = vec![0; 65536]; - // Channel where to send new incoming connections. - let mut active_listener: Option> = None; + // The quinn_proto endpoint can give us new connections for as long as its accept buffer + // isn't full. This buffer is used to push these new connections while we are waiting to + // send them on the `new_connections` channel. We only call `endpoint.accept()` when we remove + // an element from this list, which guarantees that it doesn't grow unbounded. + // TODO: with_capacity? + let mut queued_new_connections = VecDeque::new(); // Next packet waiting to be transmitted on the UDP socket, if any. // Note that this variable isn't strictly necessary, but it reduces code duplication in the @@ -443,28 +451,34 @@ async fn background_task( None => return, Some(ToEndpoint::Dial { addr, result }) => { - // TODO: "l"?! - let outcome = endpoint.connect(config.client_config.clone(),addr, "l"); - let outcome = match outcome { - Ok((connection_id, connection)) => { - debug_assert_eq!(connection.side(), quinn_proto::Side::Client); - let (tx, rx) = mpsc::channel(16); - alive_connections.insert(connection_id, tx); - let endpoint_arc = match endpoint_weak.upgrade() { - Some(ep) => ep, - None => return, // Shut down the task if the endpoint is dead. - }; - Ok(Connection::from_quinn_connection(endpoint_arc, connection, connection_id, rx)) - }, - Err(err) => Err(err), + // This `"l"` seems necessary because an empty string is an invalid domain + // name. While we don't use domain names, the underlying rustls library + // is based upon the assumption that we do. + let (connection_id, connection) = + match endpoint.connect(config.client_config.clone(), addr, "l") { + Ok(c) => c, + Err(err) => { + let _ = result.send(Err(err)); + continue; + } + }; + + let endpoint_arc = match endpoint_weak.upgrade() { + Some(ep) => ep, + None => return, // Shut down the task if the endpoint is dead. }; - let _ = result.send(outcome); + + debug_assert_eq!(connection.side(), quinn_proto::Side::Client); + let (tx, rx) = mpsc::channel(16); + let connection = Connection::from_quinn_connection(endpoint_arc, connection, connection_id, rx); + alive_connections.insert(connection_id, tx); + let _ = result.send(Ok(connection)); } // A connection wants to notify the endpoint of something. Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { debug_assert!(alive_connections.contains_key(&connection_id)); - if event.is_drained() { + if event.is_drained() { // TODO: is this correct? alive_connections.remove(&connection_id); } if let Some(event_back) = endpoint.handle_event(connection_id, event) { @@ -482,6 +496,34 @@ async fn background_task( } } + // The future we create here wakes up if two conditions are fulfilled: + // + // - The `new_connections` channel is ready to accept a new element. + // - `queued_new_connections` is not empty. + // + // When this happens, we pop an element from `queued_new_connections`, put it on the + // channel, and call `endpoint.accept()`, thereby allowing the QUIC state machine to + // feed a new incoming connection to us. + readiness = { + let active = !queued_new_connections.is_empty(); + let new_connections = &mut new_connections; + future::poll_fn(move |cx| { + if active { new_connections.poll_ready(cx) } else { Poll::Pending } + }).fuse() + } => { + if readiness.is_err() { + // new_connections channel has been dropped, meaning that the endpoint has + // been destroyed. + return; + } + + let elem = queued_new_connections.pop_front() + .expect("if queue is empty, the future above is always Pending; qed"); + new_connections.start_send(elem) + .expect("future is waken up only if poll_ready returned Ready; qed"); + endpoint.accept(); + } + result = udp_socket.recv_from(&mut socket_recv_buffer).fuse() => { let (packet_len, packet_src) = match result { Ok(v) => v, @@ -518,12 +560,12 @@ async fn background_task( None => return, // Shut down the task if the endpoint is dead. }; let connection = Connection::from_quinn_connection(endpoint_arc, connec, connec_id, rx); - // TODO: don't await here /!\ implement the thing described in the docs - if new_connections.send(connection).await.is_err() { - // Shut down the task if the endpoint is dead. - return; - } - endpoint.accept(); + + // As explained in the documentation, we put this new connection in an + // intermediary buffer. At the next loop iteration we will try to move it + // to the `new_connections` channel. We call `endpoint.accept()` only once + // the element has successfully been sent on `new_connections`. + queued_new_connections.push_back(connection); }, } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index bb9861b8464..97dc27ef996 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#![recursion_limit = "512"] +#![recursion_limit = "1024"] //! Implementation of the libp2p `Transport` and `StreamMuxer` traits for QUIC. //! diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs index 60b64672912..d5b7caca3ba 100644 --- a/transports/quic/src/muxer.rs +++ b/transports/quic/src/muxer.rs @@ -192,26 +192,31 @@ impl StreamMuxer for QuicMuxer { substream: &mut Self::Substream, buf: &[u8], ) -> Poll> { - unimplemented!() - /*use quinn_proto::WriteError; - let mut inner = self.0.lock(); - inner.wake_driver(); - match inner.write(cx, &substream.id, buf) { + let mut inner = self.inner.lock(); + + match inner.connection.write_substream(*substream, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(WriteError::Blocked) => Poll::Pending, - Err(WriteError::UnknownStream) => { - error!( + Err(quinn_proto::WriteError::Stopped(_)) => Poll::Ready(Ok(0)), // EOF + Err(quinn_proto::WriteError::Blocked) => { + if let Some(substream) = inner.substreams.get_mut(substream) { + if !substream + .write_waker + .as_ref() + .map_or(false, |w| w.will_wake(cx.waker())) + { + substream.write_waker = Some(cx.waker().clone()); + } + } + Poll::Pending + } + Err(quinn_proto::WriteError::UnknownStream) => { + log::error!( "The application used a connection that is already being \ closed. This is a bug in the application or in libp2p." ); - inner.close_reason()?; - Poll::Ready(Err(Error::ExpiredStream)) - } - Err(WriteError::Stopped(e)) => { - substream.status = SubstreamStatus::Finished; - Poll::Ready(Err(Error::Stopped(e))) + Poll::Pending } - }*/ + } } /// Try to read from a substream. This will return an error if the substream has @@ -222,32 +227,31 @@ impl StreamMuxer for QuicMuxer { substream: &mut Self::Substream, buf: &mut [u8], ) -> Poll> { - unimplemented!() - /*use quinn_proto::ReadError; - let mut inner = self.0.lock(); - match inner.read(cx, &substream.id, buf) { + let mut inner = self.inner.lock(); + + match inner.connection.read_substream(*substream, buf) { Ok(bytes) => Poll::Ready(Ok(bytes)), - Err(ReadError::Blocked) => { - inner.close_reason()?; - if let SubstreamStatus::Unwritten = substream.status { - Poll::Ready(Err(Error::CannotReadFromUnwrittenStream)) - } else { - trace!( - "Blocked on reading stream {:?} with side {:?}", - substream.id, - inner.side() - ); - Poll::Pending + Err(quinn_proto::ReadError::Reset(_)) => Poll::Ready(Ok(0)), // EOF + Err(quinn_proto::ReadError::Blocked) => { + if let Some(substream) = inner.substreams.get_mut(substream) { + if !substream + .read_waker + .as_ref() + .map_or(false, |w| w.will_wake(cx.waker())) + { + substream.read_waker = Some(cx.waker().clone()); + } } + Poll::Pending } - Err(ReadError::UnknownStream) => { - error!( - "The application used a stream that has already been closed. This is a bug." + Err(quinn_proto::ReadError::UnknownStream) => { + log::error!( + "The application used a connection that is already being \ + closed. This is a bug in the application or in libp2p." ); - Poll::Ready(Err(Error::ExpiredStream)) + Poll::Pending } - Err(ReadError::Reset(e)) => Poll::Ready(Err(Error::Reset(e))), - }*/ + } } fn shutdown_substream( @@ -255,25 +259,11 @@ impl StreamMuxer for QuicMuxer { cx: &mut Context<'_>, substream: &mut Self::Substream, ) -> Poll> { - unimplemented!() - /*match substream.status { - SubstreamStatus::Finished => return Poll::Ready(Ok(())), - SubstreamStatus::Finishing(ref mut channel) => { - self.0.lock().wake_driver(); - ready!(channel.poll_unpin(cx)).map_err(|_| Error::NetworkFailure)?; - return Poll::Ready(Ok(())); - } - SubstreamStatus::Unwritten | SubstreamStatus::Live => {} - } - let mut inner = self.0.lock(); - match inner.shutdown_stream(cx, &substream.id) { - Ok(receiver) => { - substream.status = SubstreamStatus::Finishing(receiver); - Poll::Pending - } - Err(quinn_proto::FinishError::Stopped(e)) => Poll::Ready(Err(Error::Stopped(e))), - Err(quinn_proto::FinishError::UnknownStream) => Poll::Ready(Err(Error::ExpiredStream)), - }*/ + let mut inner = self.inner.lock(); + + // TODO: needs more work + inner.connection.shutdown_substream(*substream); + Poll::Ready(Ok(())) } fn destroy_substream(&self, substream: Self::Substream) { diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 7303c70b367..fb5761a3225 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -34,6 +34,7 @@ use std::{net::SocketAddr, pin::Pin, sync::Arc}; // We reexport the errors that are exposed in the API. // All of these types use one another. +pub use crate::connection::Error as Libp2pQuicConnectionError; pub use quinn_proto::{ ApplicationClose, ConfigError, ConnectError, ConnectionClose, ConnectionError, TransportError as QuinnTransportError, TransportErrorCode, @@ -54,7 +55,7 @@ pub enum Error { Reach(ConnectError), /// Error after the remote has been reached. #[error("{0}")] - Established(ConnectionError), + Established(Libp2pQuicConnectionError), } impl Transport for QuicTransport { @@ -76,15 +77,7 @@ impl Transport for QuicTransport { let addr = addr.clone(); async move { let connec = endpoint.next_incoming().await; - - let remote_addr = { - let socket_addr = connec.remote_addr(); - Multiaddr::empty() - .with(socket_addr.ip().into()) - .with(Protocol::Udp(socket_addr.port())) - .with(Protocol::Quic) - }; - + let remote_addr = socketaddr_to_multiaddr(&connec.remote_addr()); let event = Ok(ListenerEvent::Upgrade { upgrade: Upgrade::from_connection(connec), local_addr: addr.clone(), // TODO: hack @@ -138,6 +131,14 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Result Multiaddr { + Multiaddr::empty() + .with(socket_addr.ip().into()) + .with(Protocol::Udp(socket_addr.port())) + .with(Protocol::Quic) +} + #[cfg(test)] #[test] fn multiaddr_to_udp_conversion() { diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs index 1dabc1329a4..71f2d00b285 100644 --- a/transports/quic/src/upgrade.rs +++ b/transports/quic/src/upgrade.rs @@ -52,16 +52,13 @@ impl Future for Upgrade { type Output = Result<(PeerId, QuicMuxer), transport::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut connection = match self.connection.as_mut() { + let connection = match self.connection.as_mut() { Some(c) => c, None => panic!("Future polled after it has ended"), }; loop { - if !connection.is_handshaking() { - let mut certificates = connection - .peer_certificates() - .expect("peer_certificates always returns Some if !is_handshaking; qed"); + if let Some(mut certificates) = connection.peer_certificates() { let peer_id = x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); return Poll::Ready(Ok((peer_id, muxer))); From 5b54d80db672ded1a654dda432d1df9c080adf9b Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 17 Jun 2020 12:00:02 +0200 Subject: [PATCH 190/202] Work --- transports/quic/src/connection.rs | 24 ++++++++++++--- transports/quic/src/endpoint.rs | 51 ++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 5effd2eeb41..c7e8d46ef15 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -125,7 +125,7 @@ impl Connection { } } - /// Returns the certificates send by the remote through the underlying TLS session. + /// Returns the certificates sent by the remote through the underlying TLS session. /// Returns `None` if the connection is still handshaking. // TODO: it seems to happen that is_handshaking is false but this returns None pub(crate) fn peer_certificates( @@ -138,19 +138,29 @@ impl Connection { } /// Returns the address of the node we're connected to. + // TODO: can change /!\ pub(crate) fn remote_addr(&self) -> SocketAddr { self.connection.remote_address() } - /// Returns `true` if this connection is still pending and not actually connected to the - /// remote. + /// Returns `true` if this connection is still pending. Returns `false` if we are connected to + /// the remote or if the connection is closed. pub(crate) fn is_handshaking(&self) -> bool { self.is_handshaking } + /// If the connection is closed, returns why. If the connection is open, returns `None`. + /// + /// > **Note**: This method is also the main way to determine whether a connection is closed. + pub(crate) fn close_reason(&self) -> Option<&Error> { + debug_assert!(!self.is_handshaking); + self.closed.as_ref() + } + /// Start closing the connection. A [`ConnectionEvent::ConnectionLost`] event will be /// produced in the future. pub(crate) fn close(&mut self) { + // TODO: what if the user calls this multiple times? // We send a dummy `0` error code with no message, as the API of StreamMuxer doesn't // support this. self.connection @@ -306,7 +316,8 @@ impl Connection { | quinn_proto::Event::DatagramReceived => { // We don't use datagrams or unidirectional streams. If these events // happen, it is by some code not compatible with libp2p-quic. - // TODO: kill the connection + self.connection + .close(Instant::now(), From::from(0u32), Default::default()); } quinn_proto::Event::Stream(quinn_proto::StreamEvent::Readable { id }) => { return Poll::Ready(ConnectionEvent::StreamReadable(id)); @@ -326,6 +337,7 @@ impl Connection { } quinn_proto::Event::ConnectionLost { reason } => { debug_assert!(self.closed.is_none()); + self.is_handshaking = false; let err = Error::Quinn(reason); self.closed = Some(err.clone()); return Poll::Ready(ConnectionEvent::ConnectionLost(err)); @@ -361,7 +373,9 @@ impl fmt::Debug for Connection { impl Drop for Connection { fn drop(&mut self) { - // TODO: send a quinn_proto::EndpointEvent::drained() + // TODO: don't do that if already drained + // We send a message to the endpoint. + self.endpoint.report_quinn_event_non_block(self.connection_id, quinn_proto::EndpointEvent::drained()); } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 4d4051b4aaa..b13e02a97f0 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -94,13 +94,20 @@ impl Config { // TODO: remove useless fields pub struct Endpoint { /// Channel to the background of the endpoint. - to_endpoint: mpsc::Sender, + /// See [`Endpoint::new_connections`] (just below) for a commentary about the mutex. + to_endpoint: Mutex>, + /// Channel where new connections are being sent. /// This is protected by a futures-friendly `Mutex`, meaning that receiving a connection is /// done in two steps: locking this mutex, and grabbing the next element on the `Receiver`. /// The only consequence of this `Mutex` is that multiple simultaneous calls to /// [`Endpoint::next_incoming`] are serialized. new_connections: Mutex>, + + /// Copy of [`to_endpoint`], except not behind a `Mutex`. Used if we want to be guaranteed a + /// slot in the messages buffer. + to_endpoint2: mpsc::Sender, + /// Configuration passed at initialization. // TODO: remove? config: Config, @@ -131,10 +138,12 @@ impl Endpoint { }*/ let (to_endpoint_tx, to_endpoint_rx) = mpsc::channel(32); + let to_endpoint2 = to_endpoint_tx.clone(); let (new_connections_tx, new_connections_rx) = mpsc::channel(1); let endpoint = Arc::new(Endpoint { - to_endpoint: to_endpoint_tx, + to_endpoint: Mutex::new(to_endpoint_tx), + to_endpoint2, new_connections: Mutex::new(new_connections_rx), config: config.clone(), local_multiaddr: config.multiaddr.clone(), // TODO: no @@ -209,7 +218,7 @@ impl Endpoint { // reasonable thing to do. let (tx, rx) = oneshot::channel(); self.to_endpoint - .clone() // TODO: no + .lock().await .send(ToEndpoint::Dial { addr, result: tx }) .await .expect("background task has crashed"); @@ -241,7 +250,7 @@ impl Endpoint { ) { let _ = self .to_endpoint - .clone() // TODO: no + .lock().await .send(ToEndpoint::SendUdpPacket { destination, data: data.into(), @@ -252,15 +261,16 @@ impl Endpoint { /// Report to the endpoint an event on a [`quinn_proto::Connection`]. /// /// This is typically called by a [`Connection`]. - // TODO: bad API? - // TODO: talk about lifetime of connections + /// + /// If `event.is_drained()` is true, the event indicates that the connection no longer exists. + /// This must therefore be the last event sent using this [`quinn_proto::ConnectionHandle`]. pub(crate) async fn report_quinn_event( &self, connection_id: quinn_proto::ConnectionHandle, event: quinn_proto::EndpointEvent, ) { self.to_endpoint - .clone() // TODO: no + .lock().await .send(ToEndpoint::ProcessConnectionEvent { connection_id, event, @@ -268,6 +278,27 @@ impl Endpoint { .await .expect("background task has crashed"); } + + /// Similar to [`Endpoint::report_quinn_event`], except that the message sending is guaranteed + /// to be instantaneous and to succeed. + /// + /// This method bypasses back-pressure mechanisms and is meant to be called only from + /// destructors, where waiting is not advisable. + pub(crate) fn report_quinn_event_non_block( + &self, + connection_id: quinn_proto::ConnectionHandle, + event: quinn_proto::EndpointEvent, + ) { + // We implement this by cloning the `mpsc::Sender`. Since each sender is guaranteed a slot + // in the buffer, cloning the sender reserves the slot and sending thus always succeeds. + let result = self.to_endpoint2 + .clone() + .try_send(ToEndpoint::ProcessConnectionEvent { + connection_id, + event, + }); + assert!(result.is_ok()); + } } /// Message sent to the endpoint background task. @@ -478,10 +509,14 @@ async fn background_task( // A connection wants to notify the endpoint of something. Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { debug_assert!(alive_connections.contains_key(&connection_id)); - if event.is_drained() { // TODO: is this correct? + // We "drained" event indicates that the connection no longer exists and + // its ID can be reclaimed. + let is_drained_event = event.is_drained(); + if is_drained_event { alive_connections.remove(&connection_id); } if let Some(event_back) = endpoint.handle_event(connection_id, event) { + debug_assert!(!is_drained_event); // TODO: don't await here /!\ alive_connections.get_mut(&connection_id).unwrap().send(event_back).await; } From b8fcd4cde903a0d75f35dd1bd366edfb3123ff21 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 27 Jun 2020 18:10:27 -0400 Subject: [PATCH 191/202] Trying to get the tests working --- transports/quic/Cargo.toml | 7 +- transports/quic/src/connection.rs | 7 +- transports/quic/src/endpoint.rs | 127 +++++++++++++++--------------- transports/quic/src/muxer.rs | 9 +-- transports/quic/tests/tests.rs | 105 ++++++++++++------------ 5 files changed, 131 insertions(+), 124 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 960192c3a35..a9a24c24e78 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-quic" -version = "0.19.0" +version = "0.19.2" authors = ["Parity Technologies "] edition = "2018" license = "MIT" @@ -31,3 +31,8 @@ yasna = "0.3.1" [dependencies.x509-signature] version = "0.4.0" features = ["webpki", "rustls", "std"] + +[dev-dependencies] +tracing = "0.1.15" +tracing-core = "0.1.10" +tracing-subscriber = "0.2.6" diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index c7e8d46ef15..9950b2011da 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -317,7 +317,7 @@ impl Connection { // We don't use datagrams or unidirectional streams. If these events // happen, it is by some code not compatible with libp2p-quic. self.connection - .close(Instant::now(), From::from(0u32), Default::default()); + .close(Instant::now(), From::from(0u32), Default::default()); } quinn_proto::Event::Stream(quinn_proto::StreamEvent::Readable { id }) => { return Poll::Ready(ConnectionEvent::StreamReadable(id)); @@ -375,7 +375,10 @@ impl Drop for Connection { fn drop(&mut self) { // TODO: don't do that if already drained // We send a message to the endpoint. - self.endpoint.report_quinn_event_non_block(self.connection_id, quinn_proto::EndpointEvent::drained()); + self.endpoint.report_quinn_event_non_block( + self.connection_id, + quinn_proto::EndpointEvent::drained(), + ); } } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index b13e02a97f0..4baa05c99a9 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -28,16 +28,19 @@ //! the rest of the code only happens through channels. See the documentation of the //! [`background_task`] for a thorough description. -use crate::{connection::Connection, error::Error, x509}; +use crate::{connection::Connection, x509}; +use crate::error::Error; use async_std::net::SocketAddr; +use either::Either; use futures::{ channel::{mpsc, oneshot}, lock::Mutex, prelude::*, }; use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, + multiaddr::{host_addresses, Multiaddr, Protocol}, + transport::{ListenerEvent, TransportError}, Transport, }; use std::{ @@ -102,7 +105,7 @@ pub struct Endpoint { /// done in two steps: locking this mutex, and grabbing the next element on the `Receiver`. /// The only consequence of this `Mutex` is that multiple simultaneous calls to /// [`Endpoint::next_incoming`] are serialized. - new_connections: Mutex>, + new_connections: Mutex>>, /// Copy of [`to_endpoint`], except not behind a `Mutex`. Used if we want to be guaranteed a /// slot in the messages buffer. @@ -118,28 +121,30 @@ pub struct Endpoint { impl Endpoint { /// Builds a new `Endpoint`. - pub fn new(config: Config) -> Result, io::Error> { + pub fn new(config: Config) -> Result, TransportError> { + let mut multiaddr = config.multiaddr.clone(); let local_socket_addr = match crate::transport::multiaddr_to_socketaddr(&config.multiaddr) { Ok(a) => a, - Err(()) => panic!(), // TODO: Err(TransportError::MultiaddrNotSupported(multiaddr)), + Err(()) => return Err(TransportError::MultiaddrNotSupported(multiaddr)), }; // NOT blocking, as per man:bind(2), as we pass an IP address. - let socket = std::net::UdpSocket::bind(&local_socket_addr)?; + let socket = + std::net::UdpSocket::bind(&local_socket_addr).map_err(TransportError::Other)?; // TODO: - /*let port_is_zero = local_socket_addr.port() == 0; - let local_socket_addr = socket.local_addr()?; + let port_is_zero = local_socket_addr.port() == 0; + let local_socket_addr = socket.local_addr().map_err(TransportError::Other)?; if port_is_zero { assert_ne!(local_socket_addr.port(), 0); assert_eq!(multiaddr.pop(), Some(Protocol::Quic)); assert_eq!(multiaddr.pop(), Some(Protocol::Udp(0))); multiaddr.push(Protocol::Udp(local_socket_addr.port())); multiaddr.push(Protocol::Quic); - }*/ + } let (to_endpoint_tx, to_endpoint_rx) = mpsc::channel(32); let to_endpoint2 = to_endpoint_tx.clone(); - let (new_connections_tx, new_connections_rx) = mpsc::channel(1); + let (new_connections_tx, new_connections_rx) = mpsc::channel(500); let endpoint = Arc::new(Endpoint { to_endpoint: Mutex::new(to_endpoint_tx), @@ -149,6 +154,27 @@ impl Endpoint { local_multiaddr: config.multiaddr.clone(), // TODO: no }); + let send_addr = |e| { + new_connections_tx + .clone() + .try_send(Either::Right(e)) + .expect("we just cloned this, so we have capacity; qed") + }; + + // TODO: IP address stuff + if local_socket_addr.ip().is_unspecified() { + log::info!("returning all local IPs for unspecified address"); + let suffixes = [Protocol::Udp(local_socket_addr.port()), Protocol::Quic]; + let local_addresses = host_addresses(&suffixes).map_err(TransportError::Other)?; + for (_, _, address) in local_addresses { + log::info!("sending address {:?}", address); + send_addr(address) + } + } else { + log::info!("sending address {:?}", multiaddr); + send_addr(multiaddr) + } + // TODO: just for testing, do proper task spawning async_std::task::spawn(background_task( config.clone(), @@ -158,50 +184,11 @@ impl Endpoint { to_endpoint_rx.fuse(), )); - Ok(endpoint) + // let endpoint = EndpointRef { reference, channel }; + // let join_handle = spawn(endpoint.clone()); + // Ok((Self(endpoint), join_handle)) - // TODO: IP address stuff - /*if socket_addr.ip().is_unspecified() { - info!("returning all local IPs for unspecified address"); - let suffixes = [Protocol::Udp(socket_addr.port()), Protocol::Quic]; - let local_addresses = - host_addresses(&suffixes).map_err(|e| TransportError::Other(Error::IO(e)))?; - for (_, _, address) in local_addresses { - info!("sending address {:?}", address); - new_connections - .unbounded_send(ListenerEvent::NewAddress(address)) - .expect("we have a reference to the peer, so this will not fail; qed") - } - } else { - info!("sending address {:?}", multiaddr); - new_connections - .unbounded_send(ListenerEvent::NewAddress(multiaddr.clone())) - .expect("we have a reference to the peer, so this will not fail; qed"); - } - - if socket_addr.ip().is_unspecified() { - debug!("returning all local IPs for unspecified address"); - let local_addresses = - host_addresses(&[Protocol::Udp(socket_addr.port()), Protocol::Quic]) - .map_err(|e| TransportError::Other(Error::IO(e)))?; - for i in local_addresses { - info!("sending address {:?}", i.2); - reference - .new_connections - .unbounded_send(ListenerEvent::NewAddress(i.2)) - .expect("we have a reference to the peer, so this will not fail; qed") - } - } else { - info!("sending address {:?}", multiaddr); - reference - .new_connections - .unbounded_send(ListenerEvent::NewAddress(multiaddr)) - .expect("we have a reference to the peer, so this will not fail; qed"); - } - - let endpoint = EndpointRef { reference, channel }; - let join_handle = spawn(endpoint.clone()); - Ok((Self(endpoint), join_handle))*/ + Ok(endpoint) } /// Asks the endpoint to start dialing the given address. @@ -218,7 +205,8 @@ impl Endpoint { // reasonable thing to do. let (tx, rx) = oneshot::channel(); self.to_endpoint - .lock().await + .lock() + .await .send(ToEndpoint::Dial { addr, result: tx }) .await .expect("background task has crashed"); @@ -232,10 +220,16 @@ impl Endpoint { // words, we panic here iff a panic has already happened somewhere else, which is a // reasonable thing to do. let mut new_connections = self.new_connections.lock().await; - new_connections - .next() - .await - .expect("background task has crashed") + loop { + match new_connections + .next() + .await + .expect("background task has crashed") + { + Either::Right(_addr) => continue, + Either::Left(connection) => break connection, + } + } } /// Asks the endpoint to send a UDP packet. @@ -250,7 +244,8 @@ impl Endpoint { ) { let _ = self .to_endpoint - .lock().await + .lock() + .await .send(ToEndpoint::SendUdpPacket { destination, data: data.into(), @@ -270,7 +265,8 @@ impl Endpoint { event: quinn_proto::EndpointEvent, ) { self.to_endpoint - .lock().await + .lock() + .await .send(ToEndpoint::ProcessConnectionEvent { connection_id, event, @@ -291,7 +287,8 @@ impl Endpoint { ) { // We implement this by cloning the `mpsc::Sender`. Since each sender is guaranteed a slot // in the buffer, cloning the sender reserves the slot and sending thus always succeeds. - let result = self.to_endpoint2 + let result = self + .to_endpoint2 .clone() .try_send(ToEndpoint::ProcessConnectionEvent { connection_id, @@ -410,12 +407,14 @@ enum ToEndpoint { /// Keep in mind that we pass an `Arc` whenever we create a new connection, which /// guarantees that the [`Endpoint`], and therefore the background task, is properly kept alive /// for as long as any QUIC connection is open. -/// + +#[derive(Copy, Clone, Debug)] +enum Void {} async fn background_task( config: Config, endpoint_weak: Weak, udp_socket: async_std::net::UdpSocket, - mut new_connections: mpsc::Sender, + mut new_connections: mpsc::Sender>, mut receiver: stream::Fuse>, ) { // The actual QUIC state machine. @@ -600,7 +599,7 @@ async fn background_task( // intermediary buffer. At the next loop iteration we will try to move it // to the `new_connections` channel. We call `endpoint.accept()` only once // the element has successfully been sent on `new_connections`. - queued_new_connections.push_back(connection); + queued_new_connections.push_back(Either::Left(connection)); }, } } diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs index d5b7caca3ba..88e3d49b8e2 100644 --- a/transports/quic/src/muxer.rs +++ b/transports/quic/src/muxer.rs @@ -23,13 +23,10 @@ use crate::error::Error; use futures::prelude::*; use libp2p_core::StreamMuxer; -use parking_lot::{Mutex, MutexGuard}; +use parking_lot::Mutex; use std::{ collections::HashMap, fmt, - mem::replace, - pin::Pin, - sync::Arc, task::{Context, Poll, Waker}, }; @@ -149,9 +146,7 @@ impl StreamMuxer for QuicMuxer { } } - fn open_outbound(&self) -> Self::OutboundSubstream { - () - } + fn open_outbound(&self) -> Self::OutboundSubstream {} fn poll_outbound( &self, diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 72fe705e8f2..3c7250d933f 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -18,30 +18,31 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use async_macros::ready; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, + muxing::StreamMuxer, transport::ListenerEvent, - StreamMuxer, Transport, + transport::Transport, }; -use libp2p_quic::{Config, Endpoint, Muxer, Substream}; +use libp2p_quic::{Config, Endpoint, QuicMuxer, QuicTransport}; use log::{debug, info, trace}; use std::{ io::Result, pin::Pin, + sync::{Arc, Mutex}, task::{Context, Poll}, }; #[derive(Debug)] -struct QuicStream { - id: Option, - muxer: Muxer, +struct QuicStream<'a> { + id: Option, + muxer: &'a QuicMuxer, shutdown: bool, } -impl AsyncWrite for QuicStream { +impl<'a> AsyncWrite for QuicStream<'a> { fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { assert!(!self.shutdown, "written after close"); let Self { muxer, id, .. } = self.get_mut(); @@ -54,7 +55,10 @@ impl AsyncWrite for QuicStream { self.shutdown = true; let Self { muxer, id, .. } = self.get_mut(); debug!("trying to close {:?}", id); - ready!(muxer.shutdown_substream(cx, id.as_mut().unwrap()))?; + match muxer.shutdown_substream(cx, id.as_mut().unwrap()) { + Poll::Pending => return Poll::Pending, + Poll::Ready(e) => e?, + }; debug!("closed {:?}", id); Poll::Ready(Ok(())) } @@ -64,7 +68,7 @@ impl AsyncWrite for QuicStream { } } -impl AsyncRead for QuicStream { +impl<'a> AsyncRead for QuicStream<'a> { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -77,7 +81,7 @@ impl AsyncRead for QuicStream { } } -impl Drop for QuicStream { +impl<'a> Drop for QuicStream<'a> { fn drop(&mut self) { match self.id.take() { None => {} @@ -86,45 +90,46 @@ impl Drop for QuicStream { } } -struct Outbound<'a>(&'a mut Muxer, libp2p_quic::Outbound); +struct Outbound<'a>(&'a QuicMuxer); impl<'a> futures::Future for Outbound<'a> { - type Output = Result; + type Output = Result>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Outbound(conn, id) = &mut *self; - let id: Option = Some(ready!(conn.poll_outbound(cx, id))?); - Poll::Ready(Ok(QuicStream { - id, - muxer: self.get_mut().0.clone(), - shutdown: false, - })) + let Outbound(conn) = &mut *self; + conn.poll_outbound(cx, &mut ()) + .map_ok(|id| QuicStream { + id: Some(id), + muxer: self.get_mut().0.clone(), + shutdown: false, + }) + .map_err(From::from) } } #[derive(Debug)] -struct Inbound<'a>(&'a mut Muxer); +struct Inbound<'a>(&'a QuicMuxer); impl<'a> futures::Stream for Inbound<'a> { - type Item = QuicStream; + type Item = QuicStream<'a>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Some(QuicStream { - id: Some(ready!(self.0.poll_inbound(cx)).expect("bug")), - muxer: self.get_mut().0.clone(), - shutdown: false, - })) + self.0.poll_inbound(cx).map(|id| { + Some(QuicStream { + id: Some(id.expect("bug")), + muxer: self.get_mut().0.clone(), + shutdown: false, + }) + }) } } -#[cfg(feature = "tracing")] fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + .try_init() + .unwrap(); } -#[cfg(not(feature = "tracing"))] -fn init() {} -struct Closer(Muxer); +struct Closer(QuicMuxer); impl Future for Closer { type Output = Result<()>; @@ -133,12 +138,14 @@ impl Future for Closer { } } +#[cfg(any())] #[test] fn wildcard_expansion() { init(); let addr: Multiaddr = "/ip4/0.0.0.0/udp/1234/quic".parse().unwrap(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); - let (listener, join) = Endpoint::new(Config::new(&keypair, addr.clone()).unwrap()).unwrap(); + let listener = + QuicTransport(Endpoint::new(Config::new(&keypair, addr.clone()).unwrap()).unwrap()); let mut incoming = listener.listen_on(addr).unwrap(); // Process all initial `NewAddress` events and make sure they // do not contain wildcard address or port. @@ -167,7 +174,6 @@ fn wildcard_expansion() { break; } drop(incoming); - join.await.unwrap() }); } @@ -175,6 +181,7 @@ fn wildcard_expansion() { fn communicating_between_dialer_and_listener() { init(); for i in 0..1000u32 { + trace!("running a test"); do_test(i) } } @@ -187,8 +194,10 @@ fn do_test(_i: u32) { let keypair2 = keypair.clone(); let addr: Multiaddr = "/ip4/127.0.0.1/udp/0/quic".parse().expect("bad address?"); let quic_config = Config::new(&keypair2, addr.clone()).unwrap(); - let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); - let mut listener = quic_endpoint.clone().listen_on(addr).unwrap(); + let quic_endpoint = Endpoint::new(quic_config).unwrap(); + let mut listener = QuicTransport(quic_endpoint.clone()) + .listen_on(addr) + .unwrap(); trace!("running tests"); let handle = async_std::task::spawn(async move { let key = loop { @@ -201,12 +210,10 @@ fn do_test(_i: u32) { } ListenerEvent::Upgrade { upgrade, .. } => { debug!("got a connection upgrade!"); - let (id, mut muxer): (_, Muxer) = upgrade.await.expect("upgrade failed"); + let (id, mut muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); debug!("got a new muxer!"); - let mut socket: QuicStream = Inbound(&mut muxer) - .next() - .await - .expect("no incoming stream"); + let mut socket: QuicStream = + Inbound(&muxer).next().await.expect("no incoming stream"); let mut buf = [0u8; 3]; debug!("reading data from accepted stream!"); @@ -234,7 +241,6 @@ fn do_test(_i: u32) { }; drop(listener); drop(quic_endpoint); - join.await.unwrap(); key }); @@ -242,12 +248,13 @@ fn do_test(_i: u32) { let addr = ready_rx.await.unwrap(); let quic_config = Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()).unwrap(); - let (quic_endpoint, join) = Endpoint::new(quic_config).unwrap(); + let quic_endpoint = QuicTransport(Endpoint::new(quic_config).unwrap()); // Obtain a future socket through dialing + log::error!("Dialing a Connection: {:?}", addr); let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); trace!("Received a Connection: {:?}", connection); - let id = connection.1.open_outbound(); - let mut stream = Outbound(&mut connection.1, id).await.expect("failed"); + let () = connection.1.open_outbound(); + let mut stream = Outbound(&mut connection.1).await.expect("failed"); debug!("opened a stream: id {:?}", stream.id); let result = stream.read(&mut [][..]).await; @@ -278,8 +285,6 @@ fn do_test(_i: u32) { debug!("have EOF"); Closer(connection.1).await.expect("closed successfully"); debug!("awaiting handle"); - join.await.unwrap(); - info!("endpoint is finished"); connection.0 }); assert_eq!( @@ -289,6 +294,7 @@ fn do_test(_i: u32) { } #[test] +#[cfg(any)] fn replace_port_0_in_returned_multiaddr_ipv4() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); @@ -297,7 +303,7 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { assert!(addr.to_string().ends_with("udp/0/quic")); let config = Config::new(&keypair, addr.clone()).unwrap(); - let (quic, join) = Endpoint::new(config).expect("no error"); + let quic = QuicTransport(Endpoint::new(config).expect("no error")); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -309,10 +315,10 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { if new_addr.to_string().contains("udp/0") { panic!("failed to expand address ― got {}", new_addr); } - futures::executor::block_on(join).unwrap() } #[test] +#[cfg(any)] fn replace_port_0_in_returned_multiaddr_ipv6() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); @@ -320,7 +326,7 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { let addr: Multiaddr = "/ip6/::1/udp/0/quic".parse().unwrap(); assert!(addr.to_string().contains("udp/0/quic")); let config = Config::new(&keypair, addr.clone()).unwrap(); - let (quic, join) = Endpoint::new(config).expect("no error"); + let quic = QuicTransport(Endpoint::new(config).expect("no error")); let new_addr = futures::executor::block_on_stream(quic.listen_on(addr).unwrap()) .next() @@ -330,7 +336,6 @@ fn replace_port_0_in_returned_multiaddr_ipv6() { .expect("listen address"); assert!(!new_addr.to_string().contains("udp/0")); - futures::executor::block_on(join).unwrap() } #[test] From 63ea64613ed04e582aebc72b974ea267bb53643d Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sat, 27 Jun 2020 18:19:12 -0400 Subject: [PATCH 192/202] Do not discard NewAddress messages --- transports/quic/src/endpoint.rs | 16 +++++----------- transports/quic/src/transport.rs | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 4baa05c99a9..7fa5f12597a 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -214,22 +214,16 @@ impl Endpoint { } /// Tries to pop a new incoming connection from the queue. - pub(crate) async fn next_incoming(&self) -> Connection { + pub(crate) async fn next_incoming(&self) -> Either { // The `expect` below can panic if the background task has stopped. The background task // can stop only if the `Endpoint` is destroyed or if the task itself panics. In other // words, we panic here iff a panic has already happened somewhere else, which is a // reasonable thing to do. let mut new_connections = self.new_connections.lock().await; - loop { - match new_connections - .next() - .await - .expect("background task has crashed") - { - Either::Right(_addr) => continue, - Either::Left(connection) => break connection, - } - } + new_connections + .next() + .await + .expect("background task has crashed") } /// Asks the endpoint to send a UDP packet. diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index fb5761a3225..de0976b7b41 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -24,6 +24,7 @@ use crate::{endpoint::Endpoint, muxer::QuicMuxer, upgrade::Upgrade}; +use either::{Left, Right}; use futures::prelude::*; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, @@ -76,13 +77,17 @@ impl Transport for QuicTransport { let endpoint = self.0.clone(); let addr = addr.clone(); async move { - let connec = endpoint.next_incoming().await; - let remote_addr = socketaddr_to_multiaddr(&connec.remote_addr()); - let event = Ok(ListenerEvent::Upgrade { - upgrade: Upgrade::from_connection(connec), - local_addr: addr.clone(), // TODO: hack - remote_addr, - }); + let event = match endpoint.next_incoming().await { + Left(connec) => { + let remote_addr = socketaddr_to_multiaddr(&connec.remote_addr()); + Ok(ListenerEvent::Upgrade { + upgrade: Upgrade::from_connection(connec), + local_addr: addr.clone(), // TODO: hack + remote_addr, + }) + } + Right(multiaddr) => Ok(ListenerEvent::NewAddress(multiaddr)), + }; Some((event, ())) } }) From 6a5bbe94c13690dc487d1de06bba40e4b1af90cb Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 28 Jun 2020 19:24:27 -0400 Subject: [PATCH 193/202] Avoid hangs when running tests --- transports/quic/Cargo.toml | 1 + transports/quic/src/connection.rs | 19 +++++---- transports/quic/src/endpoint.rs | 57 +++++++++++++++++-------- transports/quic/src/lib.rs | 7 ++++ transports/quic/src/muxer.rs | 3 +- transports/quic/src/upgrade.rs | 3 +- transports/quic/tests/tests.rs | 69 +++++++++++++++++++------------ 7 files changed, 106 insertions(+), 53 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index a9a24c24e78..05629e4c09d 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -26,6 +26,7 @@ thiserror = "1.0.15" untrusted = "0.7.0" webpki = "0.21.2" yasna = "0.3.1" +tracing = "0.1.15" # TODO: RGMLRMLG fix that crate name [dependencies.x509-signature] diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 9950b2011da..61895bf5c6d 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -125,6 +125,11 @@ impl Connection { } } + /// Returns the connection’s side (client or server) + pub(crate) fn side(&self) -> quinn_proto::Side { + self.connection.side() + } + /// Returns the certificates sent by the remote through the underlying TLS session. /// Returns `None` if the connection is still handshaking. // TODO: it seems to happen that is_handshaking is false but this returns None @@ -153,7 +158,7 @@ impl Connection { /// /// > **Note**: This method is also the main way to determine whether a connection is closed. pub(crate) fn close_reason(&self) -> Option<&Error> { - debug_assert!(!self.is_handshaking); + assert!(!self.is_handshaking); self.closed.as_ref() } @@ -223,7 +228,7 @@ impl Connection { match Pin::new(&mut self.from_endpoint).poll_next(cx) { Poll::Ready(Some(event)) => self.connection.handle_event(event), Poll::Ready(None) => { - debug_assert!(self.closed.is_none()); + assert!(self.closed.is_none()); let err = Error::ClosedChannel; self.closed = Some(err.clone()); return Poll::Ready(ConnectionEvent::ConnectionLost(err)); @@ -256,7 +261,7 @@ impl Connection { // `to_endpoint`. while let Some(transmit) = self.connection.poll_transmit(now) { let endpoint = self.endpoint.clone(); - debug_assert!(self.pending_to_endpoint.is_none()); + assert!(self.pending_to_endpoint.is_none()); self.pending_to_endpoint = Some(Box::pin(async move { // TODO: ECN bits not handled endpoint @@ -271,7 +276,7 @@ impl Connection { while let Some(endpoint_event) = self.connection.poll_endpoint_events() { let endpoint = self.endpoint.clone(); let connection_id = self.connection_id; - debug_assert!(self.pending_to_endpoint.is_none()); + assert!(self.pending_to_endpoint.is_none()); self.pending_to_endpoint = Some(Box::pin(async move { endpoint .report_quinn_event(connection_id, endpoint_event) @@ -336,7 +341,7 @@ impl Connection { return Poll::Ready(ConnectionEvent::StreamOpened); } quinn_proto::Event::ConnectionLost { reason } => { - debug_assert!(self.closed.is_none()); + assert!(self.closed.is_none()); self.is_handshaking = false; let err = Error::Quinn(reason); self.closed = Some(err.clone()); @@ -350,8 +355,8 @@ impl Connection { return Poll::Ready(ConnectionEvent::StreamFinished(id)); } quinn_proto::Event::Connected => { - debug_assert!(self.is_handshaking); - debug_assert!(!self.connection.is_handshaking()); + assert!(self.is_handshaking); + assert!(!self.connection.is_handshaking()); self.is_handshaking = false; return Poll::Ready(ConnectionEvent::Connected); } diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 7fa5f12597a..1bf4bf94f0f 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -50,6 +50,7 @@ use std::{ task::{Context, Poll}, time::{Duration, Instant}, }; +use tracing::{debug, error, info, trace, warn}; /// Represents the configuration for the [`Endpoint`]. #[derive(Debug, Clone)] @@ -163,15 +164,15 @@ impl Endpoint { // TODO: IP address stuff if local_socket_addr.ip().is_unspecified() { - log::info!("returning all local IPs for unspecified address"); + tracing::info!("returning all local IPs for unspecified address"); let suffixes = [Protocol::Udp(local_socket_addr.port()), Protocol::Quic]; let local_addresses = host_addresses(&suffixes).map_err(TransportError::Other)?; for (_, _, address) in local_addresses { - log::info!("sending address {:?}", address); + tracing::info!("sending address {:?}", address); send_addr(address) } } else { - log::info!("sending address {:?}", multiaddr); + tracing::info!("sending address {:?}", multiaddr); send_addr(multiaddr) } @@ -210,6 +211,7 @@ impl Endpoint { .send(ToEndpoint::Dial { addr, result: tx }) .await .expect("background task has crashed"); + info!("Sent dial message, awaiting response"); rx.await.expect("background task has crashed") } @@ -439,13 +441,15 @@ async fn background_task( loop { // Start by flushing `next_packet_out`. if let Some((destination, data)) = next_packet_out.take() { + tracing::trace!("sending {} bytes to {}", data.len(), destination); // We block the current task until the packet is sent. This way, if the // network interface is too busy, we back-pressure all of our internal // channels. // TODO: set ECN bits; there is no support for them in the ecosystem right now + // TODO: use a circular buffer instead. match udp_socket.send_to(&data, destination).await { Ok(n) if n == data.len() => {} - Ok(_) => log::error!( + Ok(_) => tracing::error!( "QUIC UDP socket violated expectation that packets are always fully \ transferred" ), @@ -454,14 +458,15 @@ async fn background_task( // printing a log message. The packet gets discarded in case of error, but we are // robust to packet losses and it is consequently not a logic error to process with // normal operations. - Err(err) => log::error!("Error while sending on QUIC UDP socket: {:?}", err), + Err(err) => tracing::error!("Error while sending on QUIC UDP socket: {:?}", err), } } // The endpoint might request packets to be sent out. This is handled in priority to avoid // buffering up packets. if let Some(packet) = endpoint.poll_transmit() { - debug_assert!(next_packet_out.is_none()); + tracing::trace!("Got a new packet to send"); + assert!(next_packet_out.is_none()); next_packet_out = Some((packet.destination, packet.contents)); continue; } @@ -470,11 +475,14 @@ async fn background_task( message = receiver.next() => { // Received a message from a different part of the code requesting us to // do something. + span!("message received"); match message { // Shut down if the endpoint has shut down. None => return, Some(ToEndpoint::Dial { addr, result }) => { + span!("dialing", addr = display(addr), side = debug(quinn_proto::Side::Client)); + info!("received dialout request"); // This `"l"` seems necessary because an empty string is an invalid domain // name. While we don't use domain names, the underlying rustls library // is based upon the assumption that we do. @@ -483,25 +491,32 @@ async fn background_task( Ok(c) => c, Err(err) => { let _ = result.send(Err(err)); + warn!("QUIC connection failure"); continue; } }; + info!("received connection ID: {:?}", connection_id); let endpoint_arc = match endpoint_weak.upgrade() { Some(ep) => ep, None => return, // Shut down the task if the endpoint is dead. }; - debug_assert_eq!(connection.side(), quinn_proto::Side::Client); + info!("endpoint is alive"); + + assert_eq!(connection.side(), quinn_proto::Side::Client); let (tx, rx) = mpsc::channel(16); let connection = Connection::from_quinn_connection(endpoint_arc, connection, connection_id, rx); alive_connections.insert(connection_id, tx); let _ = result.send(Ok(connection)); + info!("sent reply to dialer"); } // A connection wants to notify the endpoint of something. Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { - debug_assert!(alive_connections.contains_key(&connection_id)); + if !alive_connections.contains_key(&connection_id) { + continue + } // We "drained" event indicates that the connection no longer exists and // its ID can be reclaimed. let is_drained_event = event.is_drained(); @@ -509,15 +524,15 @@ async fn background_task( alive_connections.remove(&connection_id); } if let Some(event_back) = endpoint.handle_event(connection_id, event) { - debug_assert!(!is_drained_event); + assert!(!is_drained_event); // TODO: don't await here /!\ - alive_connections.get_mut(&connection_id).unwrap().send(event_back).await; + let _ = alive_connections.get_mut(&connection_id).unwrap().clone().try_send(event_back); } } // Data needs to be sent on the UDP socket. Some(ToEndpoint::SendUdpPacket { destination, data }) => { - debug_assert!(next_packet_out.is_none()); + assert!(next_packet_out.is_none()); next_packet_out = Some((destination, data)); continue; } @@ -539,6 +554,7 @@ async fn background_task( if active { new_connections.poll_ready(cx) } else { Poll::Pending } }).fuse() } => { + span!("time to accept connection"); if readiness.is_err() { // new_connections channel has been dropped, meaning that the endpoint has // been destroyed. @@ -558,13 +574,15 @@ async fn background_task( // Errors on the socket are expected to never happen, and we handle them by // simply printing a log message. Err(err) => { - log::error!("Error while receive on QUIC UDP socket: {:?}", err); + tracing::error!("Error while receive on QUIC UDP socket: {:?}", err); continue; }, }; + span!("received packet", len = display(packet_len), src = display(packet_src)); + tracing::trace!("processing"); // Received a UDP packet from the socket. - debug_assert!(packet_len <= socket_recv_buffer.len()); + assert!(packet_len <= socket_recv_buffer.len()); let packet = From::from(&socket_recv_buffer[..packet_len]); // TODO: ECN bits aren't handled match endpoint.handle(Instant::now(), packet_src, None, packet) { @@ -572,20 +590,23 @@ async fn background_task( Some((connec_id, quinn_proto::DatagramEvent::ConnectionEvent(event))) => { // Event to send to an existing connection. if let Some(sender) = alive_connections.get_mut(&connec_id) { - let _ = sender.send(event).await; // TODO: don't await here /!\ + let _ = sender.clone().try_send(event); } else { - log::error!("State mismatch: event for closed connection"); + tracing::error!("State mismatch: event for closed connection"); } }, Some((connec_id, quinn_proto::DatagramEvent::NewConnection(connec))) => { // A new connection has been received. `connec_id` is a newly-allocated // identifier. - debug_assert_eq!(connec.side(), quinn_proto::Side::Server); + assert_eq!(connec.side(), quinn_proto::Side::Server); let (tx, rx) = mpsc::channel(16); alive_connections.insert(connec_id, tx); let endpoint_arc = match endpoint_weak.upgrade() { Some(ep) => ep, - None => return, // Shut down the task if the endpoint is dead. + None => { + tracing::trace!("endpoint is dead, exiting"); + return // Shut down the task if the endpoint is dead. + } }; let connection = Connection::from_quinn_connection(endpoint_arc, connec, connec_id, rx); @@ -594,8 +615,10 @@ async fn background_task( // to the `new_connections` channel. We call `endpoint.accept()` only once // the element has successfully been sent on `new_connections`. queued_new_connections.push_back(Either::Left(connection)); + tracing::trace!("connection queued"); }, } + tracing::trace!("receive processing complete"); } } } diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 97dc27ef996..f9c9c7bc2c7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -53,6 +53,13 @@ #![deny(unsafe_code)] +macro_rules! span { + ($name: expr $(,$($i: tt)+)?) => { + let span = ::tracing::trace_span!($name $(,$($i)+)?); + let _guard = span.enter(); + } +} + mod connection; mod endpoint; mod error; diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs index 88e3d49b8e2..8f94d209a58 100644 --- a/transports/quic/src/muxer.rs +++ b/transports/quic/src/muxer.rs @@ -67,7 +67,7 @@ impl QuicMuxer { /// /// Panics if `connection.is_handshaking()` returns `true`. pub(crate) fn from_connection(connection: Connection) -> Self { - assert!(!connection.is_handshaking()); + // assert!(!connection.is_handshaking()); QuicMuxer { inner: Mutex::new(QuicMuxerInner { @@ -88,6 +88,7 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { // We use `poll_inbound` to perform the background processing of the entire connection. let mut inner = self.inner.lock(); + tracing::trace!("poll_inbound called for side {:?}", inner.connection.side()); while let Poll::Ready(event) = inner.connection.poll_event(cx) { match event { diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs index 71f2d00b285..b56748c9127 100644 --- a/transports/quic/src/upgrade.rs +++ b/transports/quic/src/upgrade.rs @@ -73,8 +73,9 @@ impl Future for Upgrade { Poll::Ready(ConnectionEvent::ConnectionLost(err)) => { return Poll::Ready(Err(transport::Error::Established(err))); } + Poll::Ready(ConnectionEvent::StreamOpened) => continue, // TODO: enumerate the items and explain how they can't happen - _ => unreachable!(), + Poll::Ready(e) => unreachable!("{:?}", e), } } } diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 3c7250d933f..ce9235ae64d 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -27,13 +27,13 @@ use libp2p_core::{ }; use libp2p_quic::{Config, Endpoint, QuicMuxer, QuicTransport}; -use log::{debug, info, trace}; use std::{ io::Result, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, }; +use tracing::{debug, error, info, trace}; #[derive(Debug)] struct QuicStream<'a> { @@ -46,6 +46,7 @@ impl<'a> AsyncWrite for QuicStream<'a> { fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { assert!(!self.shutdown, "written after close"); let Self { muxer, id, .. } = self.get_mut(); + let _ = muxer.poll_inbound(cx); muxer .write_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) @@ -54,6 +55,7 @@ impl<'a> AsyncWrite for QuicStream<'a> { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.shutdown = true; let Self { muxer, id, .. } = self.get_mut(); + let _ = muxer.poll_inbound(cx); debug!("trying to close {:?}", id); match muxer.shutdown_substream(cx, id.as_mut().unwrap()) { Poll::Pending => return Poll::Pending, @@ -75,6 +77,7 @@ impl<'a> AsyncRead for QuicStream<'a> { buf: &mut [u8], ) -> Poll> { let Self { id, muxer, .. } = self.get_mut(); + let _ = muxer.poll_inbound(cx); muxer .read_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) @@ -95,6 +98,7 @@ struct Outbound<'a>(&'a QuicMuxer); impl<'a> futures::Future for Outbound<'a> { type Output = Result>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let _ = self.0.poll_inbound(cx); let Outbound(conn) = &mut *self; conn.poll_outbound(cx, &mut ()) .map_ok(|id| QuicStream { @@ -111,6 +115,7 @@ struct Inbound<'a>(&'a QuicMuxer); impl<'a> futures::Stream for Inbound<'a> { type Item = QuicStream<'a>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + debug!("polling for inbound connections"); self.0.poll_inbound(cx).map(|id| { Some(QuicStream { id: Some(id.expect("bug")), @@ -125,16 +130,17 @@ fn init() { use tracing_subscriber::{fmt::Subscriber, EnvFilter}; let _ = Subscriber::builder() .with_env_filter(EnvFilter::from_default_env()) - .try_init() - .unwrap(); + .try_init(); } -struct Closer(QuicMuxer); +struct Closer(Arc); impl Future for Closer { type Output = Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.get_mut().0.close(cx).map_err(From::from) + let i = &mut self.get_mut().0; + let _ = i.poll_inbound(cx); + i.close(cx).map_err(From::from) } } @@ -198,7 +204,7 @@ fn do_test(_i: u32) { let mut listener = QuicTransport(quic_endpoint.clone()) .listen_on(addr) .unwrap(); - trace!("running tests"); + error!("running tests"); let handle = async_std::task::spawn(async move { let key = loop { trace!("awaiting connection"); @@ -209,12 +215,16 @@ fn do_test(_i: u32) { } } ListenerEvent::Upgrade { upgrade, .. } => { - debug!("got a connection upgrade!"); + info!("got a connection upgrade!"); let (id, mut muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); - debug!("got a new muxer!"); + info!("got a new muxer!"); + let muxer = Arc::new(muxer); let mut socket: QuicStream = - Inbound(&muxer).next().await.expect("no incoming stream"); - + Inbound(&*muxer).next().await.expect("no incoming stream"); + { + let cloned = muxer.clone(); + async_std::task::spawn(future::poll_fn(move |cx| cloned.poll_inbound(cx))); + } let mut buf = [0u8; 3]; debug!("reading data from accepted stream!"); { @@ -227,12 +237,12 @@ fn do_test(_i: u32) { debug!("writing data!"); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); debug!("data written!"); - // socket.close().await.unwrap(); + socket.close().await.unwrap(); // debug!("socket closed!"); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); debug!("end of stream"); drop(socket); - Closer(muxer).await.unwrap(); + // Closer(muxer).await.unwrap(); debug!("finished!"); break id; } @@ -250,22 +260,27 @@ fn do_test(_i: u32) { Config::new(&keypair, "/ip4/127.0.0.1/udp/0/quic".parse().unwrap()).unwrap(); let quic_endpoint = QuicTransport(Endpoint::new(quic_config).unwrap()); // Obtain a future socket through dialing - log::error!("Dialing a Connection: {:?}", addr); - let mut connection = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + error!("Dialing a Connection: {:?}", addr); + let (peer_id, mut connection) = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + let connection = Arc::new(connection); + { + let cloned = connection.clone(); + async_std::task::spawn(future::poll_fn(move |cx| cloned.poll_inbound(cx))); + } trace!("Received a Connection: {:?}", connection); - let () = connection.1.open_outbound(); - let mut stream = Outbound(&mut connection.1).await.expect("failed"); + let () = connection.open_outbound(); + let mut stream = Outbound(&*connection).await.expect("failed"); debug!("opened a stream: id {:?}", stream.id); - let result = stream.read(&mut [][..]).await; - let result = result.expect_err("reading from an unwritten stream cannot succeed"); - assert_eq!(result.kind(), std::io::ErrorKind::NotConnected); - assert!(result.source().is_none()); - let wrapped = result.get_ref().unwrap().downcast_ref().unwrap(); - match wrapped { - libp2p_quic::Error::CannotReadFromUnwrittenStream => {} - e => panic!("Wrong error from reading unwritten stream: {}", e), - } + // let result = stream.read(&mut [][..]).await; + // let result = result.expect_err("reading from an unwritten stream cannot succeed"); + // assert_eq!(result.kind(), std::io::ErrorKind::NotConnected); + // assert!(result.source().is_none()); + // let wrapped = result.get_ref().unwrap().downcast_ref().unwrap(); + // match wrapped { + // libp2p_quic::Error::CannotReadFromUnwrittenStream => {} + // e => panic!("Wrong error from reading unwritten stream: {}", e), + // } stream.write_all(&[4u8, 5, 6]).await.unwrap(); stream.close().await.unwrap(); let mut buf = [0u8; 3]; @@ -283,9 +298,9 @@ fn do_test(_i: u32) { assert_eq!(stream.read(&mut buf).await.unwrap(), 0); drop(stream); debug!("have EOF"); - Closer(connection.1).await.expect("closed successfully"); + // Closer(connection).await.expect("closed successfully"); debug!("awaiting handle"); - connection.0 + peer_id }); assert_eq!( async_std::task::block_on(handle), From ce977a9cc78bd35d26b6f9b114e04b444e4259fc Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 28 Jun 2020 20:54:35 -0400 Subject: [PATCH 194/202] Wait until the handshake is complete even if certificates are available sooner. Also fix some warnings and rustdoc links. --- transports/quic/src/connection.rs | 3 +-- transports/quic/src/endpoint.rs | 21 ++++++++++----------- transports/quic/src/muxer.rs | 5 ++--- transports/quic/src/upgrade.rs | 5 +++-- transports/quic/src/x509/verifier.rs | 10 +++++++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 61895bf5c6d..3cf2a1d7131 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -29,7 +29,6 @@ use crate::endpoint::Endpoint; use futures::{channel::mpsc, prelude::*}; -use libp2p_core::StreamMuxer; use std::{ fmt, net::SocketAddr, @@ -349,7 +348,7 @@ impl Connection { } quinn_proto::Event::Stream(quinn_proto::StreamEvent::Finished { id, - stop_reason, + stop_reason: _, }) => { // TODO: transmit `stop_reason` return Poll::Ready(ConnectionEvent::StreamFinished(id)); diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 1bf4bf94f0f..25a32419c1f 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -30,7 +30,6 @@ use crate::{connection::Connection, x509}; -use crate::error::Error; use async_std::net::SocketAddr; use either::Either; use futures::{ @@ -40,17 +39,16 @@ use futures::{ }; use libp2p_core::{ multiaddr::{host_addresses, Multiaddr, Protocol}, - transport::{ListenerEvent, TransportError}, - Transport, + transport::TransportError, }; use std::{ collections::{HashMap, VecDeque}, fmt, io, sync::{Arc, Weak}, - task::{Context, Poll}, + task::{Poll}, time::{Duration, Instant}, }; -use tracing::{debug, error, info, trace, warn}; +use tracing::{info, warn}; /// Represents the configuration for the [`Endpoint`]. #[derive(Debug, Clone)] @@ -108,7 +106,7 @@ pub struct Endpoint { /// [`Endpoint::next_incoming`] are serialized. new_connections: Mutex>>, - /// Copy of [`to_endpoint`], except not behind a `Mutex`. Used if we want to be guaranteed a + /// Copy of [`Endpoint::to_endpoint`], except not behind a `Mutex`. Used if we want to be guaranteed a /// slot in the messages buffer. to_endpoint2: mpsc::Sender, @@ -386,9 +384,10 @@ enum ToEndpoint { /// In an ideal world, we would handle a background-task-to-connection channel being full by /// dropping UDP packets destined to this connection, as a way to back-pressure the remote. /// Unfortunately, the `quinn-proto` library doesn't provide any way for us to know which -/// connection a UDP packet is destined for before it has been turned into a [`ConnectionEvent`], -/// and because these [`ConnectionEvent`]s are sometimes used to synchronize the states of the -/// endpoint and connection, it would be a logic error to silently drop them. +/// connection a UDP packet is destined for before it has been turned into a +/// [`ConnectionEvent`](quinn_proto::ConnectionEvent) and because these +/// [`ConnectionEvent`](quinn_proto::ConnectionEvent)s are sometimes used to synchronize the states +/// of the endpoint and connection, it would be a logic error to silently drop them. /// /// We handle this tricky situation by simply killing connections as soon as their associated /// channel is full. @@ -515,8 +514,8 @@ async fn background_task( // A connection wants to notify the endpoint of something. Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { if !alive_connections.contains_key(&connection_id) { - continue - } + continue + } // We "drained" event indicates that the connection no longer exists and // its ID can be reclaimed. let is_drained_event = event.is_drained(); diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs index 8f94d209a58..67b83755800 100644 --- a/transports/quic/src/muxer.rs +++ b/transports/quic/src/muxer.rs @@ -21,7 +21,6 @@ use crate::connection::{Connection, ConnectionEvent}; use crate::error::Error; -use futures::prelude::*; use libp2p_core::StreamMuxer; use parking_lot::Mutex; use std::{ @@ -67,7 +66,7 @@ impl QuicMuxer { /// /// Panics if `connection.is_handshaking()` returns `true`. pub(crate) fn from_connection(connection: Connection) -> Self { - // assert!(!connection.is_handshaking()); + assert!(!connection.is_handshaking()); QuicMuxer { inner: Mutex::new(QuicMuxerInner { @@ -252,7 +251,7 @@ impl StreamMuxer for QuicMuxer { fn shutdown_substream( &self, - cx: &mut Context<'_>, + _cx: &mut Context<'_>, substream: &mut Self::Substream, ) -> Poll> { let mut inner = self.inner.lock(); diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs index b56748c9127..c99a2b90c72 100644 --- a/transports/quic/src/upgrade.rs +++ b/transports/quic/src/upgrade.rs @@ -58,7 +58,8 @@ impl Future for Upgrade { }; loop { - if let Some(mut certificates) = connection.peer_certificates() { + if !connection.is_handshaking() { + let mut certificates = connection.peer_certificates().expect("we require a certificate"); let peer_id = x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); return Poll::Ready(Ok((peer_id, muxer))); @@ -73,7 +74,7 @@ impl Future for Upgrade { Poll::Ready(ConnectionEvent::ConnectionLost(err)) => { return Poll::Ready(Err(transport::Error::Established(err))); } - Poll::Ready(ConnectionEvent::StreamOpened) => continue, + Poll::Ready(ConnectionEvent::StreamOpened) | Poll::Ready(ConnectionEvent::StreamReadable(_)) => continue, // TODO: enumerate the items and explain how they can't happen Poll::Ready(e) => unreachable!("{:?}", e), } diff --git a/transports/quic/src/x509/verifier.rs b/transports/quic/src/x509/verifier.rs index 6cc07b2d63b..0dd911f9b62 100644 --- a/transports/quic/src/x509/verifier.rs +++ b/transports/quic/src/x509/verifier.rs @@ -37,8 +37,10 @@ pub(crate) struct Libp2pCertificateVerifier; /// * The certificate must have a valid libp2p extension that includes a /// signature of its public key. /// -/// The check that the [`PeerId`] matches the expected `PeerId` must be done by +/// The check that the [`PeerId`] matches the expected [`PeerId`] must be done by /// the caller. +/// +/// [`PeerId`]: libp2p_core::PeerId impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { fn verify_server_cert( &self, @@ -77,7 +79,7 @@ impl rustls::ServerCertVerifier for Libp2pCertificateVerifier { /// * The certificate must have a valid libp2p extension that includes a /// signature of its public key. /// -/// The check that the [`PeerId`] matches the expected `PeerId` must be done by +/// The check that the [`PeerId`] matches the expected [`PeerId`] must be done by /// the caller. /// /// [`PeerId`]: libp2p_core::PeerId @@ -215,7 +217,7 @@ fn parse_libp2p_extension<'a>(extension: Input<'a>) -> Result(extension: Input<'a>) -> Result libp2p_core::PeerId { let r = parse_certificate(certificate) .expect("we already checked that the certificate was valid during the handshake; qed"); From d7ac0ccff7a6dbdda7bb947d1f87c0daacc77096 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Sun, 28 Jun 2020 21:28:19 -0400 Subject: [PATCH 195/202] A handshake with no certificate is unfinished --- transports/quic/src/endpoint.rs | 6 +++--- transports/quic/src/upgrade.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 25a32419c1f..251224fdb96 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -45,7 +45,7 @@ use std::{ collections::{HashMap, VecDeque}, fmt, io, sync::{Arc, Weak}, - task::{Poll}, + task::Poll, time::{Duration, Instant}, }; use tracing::{info, warn}; @@ -514,8 +514,8 @@ async fn background_task( // A connection wants to notify the endpoint of something. Some(ToEndpoint::ProcessConnectionEvent { connection_id, event }) => { if !alive_connections.contains_key(&connection_id) { - continue - } + continue + } // We "drained" event indicates that the connection no longer exists and // its ID can be reclaimed. let is_drained_event = event.is_drained(); diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs index c99a2b90c72..0123923a317 100644 --- a/transports/quic/src/upgrade.rs +++ b/transports/quic/src/upgrade.rs @@ -59,7 +59,10 @@ impl Future for Upgrade { loop { if !connection.is_handshaking() { - let mut certificates = connection.peer_certificates().expect("we require a certificate"); + let mut certificates = match connection.peer_certificates() { + Some(certificates) => certificates, + None => continue, + }; let peer_id = x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); return Poll::Ready(Ok((peer_id, muxer))); @@ -74,7 +77,8 @@ impl Future for Upgrade { Poll::Ready(ConnectionEvent::ConnectionLost(err)) => { return Poll::Ready(Err(transport::Error::Established(err))); } - Poll::Ready(ConnectionEvent::StreamOpened) | Poll::Ready(ConnectionEvent::StreamReadable(_)) => continue, + Poll::Ready(ConnectionEvent::StreamOpened) + | Poll::Ready(ConnectionEvent::StreamReadable(_)) => continue, // TODO: enumerate the items and explain how they can't happen Poll::Ready(e) => unreachable!("{:?}", e), } From e7e6d84a12275df7abcb33ff1c7fe0be066371fd Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 14:28:04 -0400 Subject: [PATCH 196/202] Re-enable several tests --- transports/quic/tests/tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index ce9235ae64d..9f3b034bf43 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -144,7 +144,6 @@ impl Future for Closer { } } -#[cfg(any())] #[test] fn wildcard_expansion() { init(); @@ -309,7 +308,6 @@ fn do_test(_i: u32) { } #[test] -#[cfg(any)] fn replace_port_0_in_returned_multiaddr_ipv4() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); @@ -333,7 +331,6 @@ fn replace_port_0_in_returned_multiaddr_ipv4() { } #[test] -#[cfg(any)] fn replace_port_0_in_returned_multiaddr_ipv6() { init(); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); From 7f5925f1e5355942ce66b570b9615bd7eaff1894 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 14:44:10 -0400 Subject: [PATCH 197/202] Fix some warnings in the tests --- transports/quic/tests/tests.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 9f3b034bf43..8f1fb39ac15 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -192,7 +192,6 @@ fn communicating_between_dialer_and_listener() { } fn do_test(_i: u32) { - use std::error::Error as _; let (ready_tx, ready_rx) = futures::channel::oneshot::channel(); let mut ready_tx = Some(ready_tx); let keypair = libp2p_core::identity::Keypair::generate_ed25519(); @@ -215,7 +214,7 @@ fn do_test(_i: u32) { } ListenerEvent::Upgrade { upgrade, .. } => { info!("got a connection upgrade!"); - let (id, mut muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); + let (id, muxer): (_, QuicMuxer) = upgrade.await.expect("upgrade failed"); info!("got a new muxer!"); let muxer = Arc::new(muxer); let mut socket: QuicStream = @@ -260,7 +259,7 @@ fn do_test(_i: u32) { let quic_endpoint = QuicTransport(Endpoint::new(quic_config).unwrap()); // Obtain a future socket through dialing error!("Dialing a Connection: {:?}", addr); - let (peer_id, mut connection) = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); + let (peer_id, connection) = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); let connection = Arc::new(connection); { let cloned = connection.clone(); From 54035a040b90359bcb923dda26eacda4f6c70f04 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 14:45:31 -0400 Subject: [PATCH 198/202] Remove some commented-out test code --- transports/quic/tests/tests.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 8f1fb39ac15..1c597bae06c 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -270,15 +270,6 @@ fn do_test(_i: u32) { let mut stream = Outbound(&*connection).await.expect("failed"); debug!("opened a stream: id {:?}", stream.id); - // let result = stream.read(&mut [][..]).await; - // let result = result.expect_err("reading from an unwritten stream cannot succeed"); - // assert_eq!(result.kind(), std::io::ErrorKind::NotConnected); - // assert!(result.source().is_none()); - // let wrapped = result.get_ref().unwrap().downcast_ref().unwrap(); - // match wrapped { - // libp2p_quic::Error::CannotReadFromUnwrittenStream => {} - // e => panic!("Wrong error from reading unwritten stream: {}", e), - // } stream.write_all(&[4u8, 5, 6]).await.unwrap(); stream.close().await.unwrap(); let mut buf = [0u8; 3]; From 02058a68e060850dd8d7b89fb76ed34f98b1b1ac Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 14:57:20 -0400 Subject: [PATCH 199/202] Remove unused fields from the endpoint --- transports/quic/src/endpoint.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 251224fdb96..36dbd4ceede 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -109,13 +109,6 @@ pub struct Endpoint { /// Copy of [`Endpoint::to_endpoint`], except not behind a `Mutex`. Used if we want to be guaranteed a /// slot in the messages buffer. to_endpoint2: mpsc::Sender, - - /// Configuration passed at initialization. - // TODO: remove? - config: Config, - /// Multiaddr of the local UDP socket passed in the configuration at initialization after it - /// has potentially been modified to handle port number `0`. - local_multiaddr: Multiaddr, } impl Endpoint { @@ -149,8 +142,6 @@ impl Endpoint { to_endpoint: Mutex::new(to_endpoint_tx), to_endpoint2, new_connections: Mutex::new(new_connections_rx), - config: config.clone(), - local_multiaddr: config.multiaddr.clone(), // TODO: no }); let send_addr = |e| { From 49d6f84cc2edc6ae73628b422ff0050fa1bfc804 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 14:58:31 -0400 Subject: [PATCH 200/202] Avoid looping forever if no certificate is sent --- transports/quic/src/upgrade.rs | 19 +++++++------------ transports/quic/tests/tests.rs | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/transports/quic/src/upgrade.rs b/transports/quic/src/upgrade.rs index 0123923a317..f40cf41ab83 100644 --- a/transports/quic/src/upgrade.rs +++ b/transports/quic/src/upgrade.rs @@ -58,23 +58,18 @@ impl Future for Upgrade { }; loop { - if !connection.is_handshaking() { - let mut certificates = match connection.peer_certificates() { - Some(certificates) => certificates, - None => continue, - }; - let peer_id = x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API - let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); - return Poll::Ready(Ok((peer_id, muxer))); - } - match Connection::poll_event(connection, cx) { Poll::Pending => return Poll::Pending, Poll::Ready(ConnectionEvent::Connected) => { - // `is_handshaking()` will return `false` at the next loop iteration. - continue; + let mut certificates = connection.peer_certificates().unwrap(); + let peer_id = + x509::extract_peerid_or_panic(certificates.next().unwrap().as_der()); // TODO: bad API + let muxer = QuicMuxer::from_connection(self.connection.take().unwrap()); + self.connection = None; + return Poll::Ready(Ok((peer_id, muxer))); } Poll::Ready(ConnectionEvent::ConnectionLost(err)) => { + self.connection = None; return Poll::Ready(Err(transport::Error::Established(err))); } Poll::Ready(ConnectionEvent::StreamOpened) diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 1c597bae06c..95f0ebab14b 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -30,7 +30,7 @@ use libp2p_quic::{Config, Endpoint, QuicMuxer, QuicTransport}; use std::{ io::Result, pin::Pin, - sync::{Arc, Mutex}, + sync::Arc, task::{Context, Poll}, }; use tracing::{debug, error, info, trace}; From ac583adc804316acd058ce187369d55fc1538e36 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 20:58:11 -0400 Subject: [PATCH 201/202] Working libp2p-quic (again) This fixes the hang when running the test suite. --- transports/quic/src/connection.rs | 9 +++- transports/quic/src/error.rs | 26 ++++------ transports/quic/src/muxer.rs | 80 ++++++++++++++++++++++++++++--- transports/quic/tests/tests.rs | 18 ++----- 4 files changed, 92 insertions(+), 41 deletions(-) diff --git a/transports/quic/src/connection.rs b/transports/quic/src/connection.rs index 3cf2a1d7131..ed3d4911526 100644 --- a/transports/quic/src/connection.rs +++ b/transports/quic/src/connection.rs @@ -207,6 +207,10 @@ impl Connection { self.connection.write(id, buf) } + pub(crate) fn is_drained(&self) -> bool { + self.connection.is_drained() + } + pub(crate) fn shutdown_substream( &mut self, id: quinn_proto::StreamId, @@ -216,9 +220,10 @@ impl Connection { /// Polls the connection for an event that happend on it. pub(crate) fn poll_event(&mut self, cx: &mut Context<'_>) -> Poll { - // Nothing more can be done if the connection is closed. + // Nothing more can be done if the connection is drained. // Return `Pending` without registering the waker, essentially freezing the task forever. - if self.closed.is_some() { + if self.connection.is_drained() { + tracing::error!("poll_event called on a drained connection"); return Poll::Pending; } diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index 9671950d4e3..e9bc078ffea 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -47,24 +47,15 @@ pub enum Error { /// The stream was reset by the peer. #[error("Peer reset stream: code {0}")] Reset(quinn_proto::VarInt), - /// Either an attempt was made to write to a stream that was already shut down, - /// or a previous operation on this stream failed. - #[error( - "Use of a stream that has is no longer valid. This is a \ - bug in the application." - )] - ExpiredStream, - /// Reading from a stream that has not been written to. - #[error("Reading from a stream that has not been written to.")] - CannotReadFromUnwrittenStream, - /// Fatal internal error or network failure - #[error("Fatal internal error or network failure")] - NetworkFailure, + /// Problem finishing stream + #[error("Error finishing stream: {0}")] + Finish(#[from] quinn_proto::FinishError), /// Connection already being closed #[error("Connection already being closed")] ConnectionClosing, } +#[cfg(any())] impl From for Error { fn from(_: SendError) -> Error { Error::NetworkFailure @@ -76,15 +67,14 @@ impl From for io::Error { match e { Error::IO(e) => io::Error::new(e.kind(), Error::IO(e)), Error::ConnectionError(e) => e.into(), - e @ Error::NetworkFailure - | e @ Error::ConnectionClosing - | e @ Error::ConnectError(_) => io::Error::new(ErrorKind::Other, e), + e @ Error::ConnectionClosing | e @ Error::ConnectError(_) => { + io::Error::new(ErrorKind::Other, e) + } e @ Error::Stopped(_) | e @ Error::Reset(_) | e @ Error::ConnectionLost => { io::Error::new(ErrorKind::ConnectionAborted, e) } - e @ Error::ExpiredStream => io::Error::new(ErrorKind::BrokenPipe, e), + e @ Error::Finish(_) => io::Error::new(ErrorKind::BrokenPipe, e), e @ Error::AlreadyListening => io::Error::new(ErrorKind::AddrInUse, e), - e @ Error::CannotReadFromUnwrittenStream => io::Error::new(ErrorKind::NotConnected, e), } } } diff --git a/transports/quic/src/muxer.rs b/transports/quic/src/muxer.rs index 67b83755800..f9a7009afca 100644 --- a/transports/quic/src/muxer.rs +++ b/transports/quic/src/muxer.rs @@ -46,6 +46,8 @@ struct QuicMuxerInner { poll_substream_opened_waker: Option, /// Waker to wake if the connection is closed. poll_close_waker: Option, + /// Count of active (writable) substreams. + writable_substreams: usize, } /// State of a single substream. @@ -57,6 +59,10 @@ struct SubstreamState { write_waker: Option, /// Waker to wake if the substream becomes closed. finished_waker: Option, + /// `true` if and only if the substream has been closed for reading. + read_closed: bool, + /// `true` if and only if the substream has been closed for writing. + write_closed: bool, } impl QuicMuxer { @@ -74,6 +80,7 @@ impl QuicMuxer { substreams: Default::default(), poll_substream_opened_waker: None, poll_close_waker: None, + writable_substreams: 0, }), } } @@ -87,7 +94,8 @@ impl StreamMuxer for QuicMuxer { fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { // We use `poll_inbound` to perform the background processing of the entire connection. let mut inner = self.inner.lock(); - tracing::trace!("poll_inbound called for side {:?}", inner.connection.side()); + span!("poll_inbound", side = debug(inner.connection.side())); + tracing::trace!("poll_inbound called"); while let Poll::Ready(event) = inner.connection.poll_event(cx) { match event { @@ -130,7 +138,9 @@ impl StreamMuxer for QuicMuxer { if let Some(waker) = substream.finished_waker.take() { waker.wake(); } + substream.write_closed = true } + inner.writable_substreams -= 1; } // Do nothing as this is handled below. @@ -140,7 +150,15 @@ impl StreamMuxer for QuicMuxer { if let Some(substream) = inner.connection.pop_incoming_substream() { inner.substreams.insert(substream, Default::default()); + inner.writable_substreams += 1; + tracing::trace!("New substream"); Poll::Ready(Ok(substream)) + } else if inner.connection.is_drained() { + if let Some(w) = inner.poll_close_waker.take() { + tracing::trace!("Inner connection is drained, waking close waker"); + w.wake() + } + Poll::Ready(Err(Error::ConnectionLost)) } else { Poll::Pending } @@ -160,6 +178,7 @@ impl StreamMuxer for QuicMuxer { let mut inner = self.inner.lock(); if let Some(substream) = inner.connection.pop_outgoing_substream() { inner.substreams.insert(substream, Default::default()); + inner.writable_substreams += 1; return Poll::Ready(Ok(substream)); } @@ -251,14 +270,35 @@ impl StreamMuxer for QuicMuxer { fn shutdown_substream( &self, - _cx: &mut Context<'_>, - substream: &mut Self::Substream, + cx: &mut Context<'_>, + substream_id: &mut Self::Substream, ) -> Poll> { let mut inner = self.inner.lock(); + let QuicMuxerInner { + ref mut substreams, + ref mut connection, + .. + } = &mut *inner; + + let substream = substreams + .get_mut(substream_id) + .expect("using a destroyed substream"); + + if substream.write_closed { + return Poll::Ready(Ok(())); + } - // TODO: needs more work - inner.connection.shutdown_substream(*substream); - Poll::Ready(Ok(())) + if connection.shutdown_substream(*substream_id).is_err() { + substream.write_closed = true; + return Poll::Ready(Ok(())); + } + let waker = cx.waker(); + match substream.finished_waker.as_mut() { + None => substream.finished_waker = Some(waker.clone()), + Some(w) if w.will_wake(waker) && false => {} + Some(w) => std::mem::replace(w, waker.clone()).wake(), + } + Poll::Pending } fn destroy_substream(&self, substream: Self::Substream) { @@ -288,7 +328,33 @@ impl StreamMuxer for QuicMuxer { // TODO: poll if closed or something let mut inner = self.inner.lock(); - //self.connection.close(); + if inner.connection.close_reason().is_some() || inner.connection.is_drained() { + return Poll::Ready(Ok(())); + } + span!("closing", side = debug(inner.connection.side())); + if inner.writable_substreams == 0 { + tracing::debug!("closing connection"); + inner.connection.close(); + } else { + tracing::debug!("shutting down pending substreams"); + let QuicMuxerInner { + ref mut substreams, + ref mut connection, + .. + } = &mut *inner; + for (stream_id, waker) in substreams.iter_mut() { + tracing::debug!("shutting down substream {:?}", stream_id); + connection.shutdown_substream(*stream_id); + if let Some(w) = waker.write_waker.take() { + w.wake() + } + waker.write_waker = Some(cx.waker().clone()); + if let Some(w) = waker.finished_waker.take() { + w.wake() + } + waker.finished_waker = Some(cx.waker().clone()); + } + } // Register `cx.waker()` as being woken up if the connection closes. if !inner diff --git a/transports/quic/tests/tests.rs b/transports/quic/tests/tests.rs index 95f0ebab14b..496ad9b824c 100644 --- a/transports/quic/tests/tests.rs +++ b/transports/quic/tests/tests.rs @@ -46,7 +46,7 @@ impl<'a> AsyncWrite for QuicStream<'a> { fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { assert!(!self.shutdown, "written after close"); let Self { muxer, id, .. } = self.get_mut(); - let _ = muxer.poll_inbound(cx); + let _ = muxer.poll_inbound(cx).is_pending(); muxer .write_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) @@ -55,7 +55,7 @@ impl<'a> AsyncWrite for QuicStream<'a> { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.shutdown = true; let Self { muxer, id, .. } = self.get_mut(); - let _ = muxer.poll_inbound(cx); + let _ = muxer.poll_inbound(cx).is_pending(); debug!("trying to close {:?}", id); match muxer.shutdown_substream(cx, id.as_mut().unwrap()) { Poll::Pending => return Poll::Pending, @@ -77,7 +77,7 @@ impl<'a> AsyncRead for QuicStream<'a> { buf: &mut [u8], ) -> Poll> { let Self { id, muxer, .. } = self.get_mut(); - let _ = muxer.poll_inbound(cx); + let _ = muxer.poll_inbound(cx).is_pending(); muxer .read_substream(cx, id.as_mut().unwrap(), buf) .map_err(From::from) @@ -98,8 +98,8 @@ struct Outbound<'a>(&'a QuicMuxer); impl<'a> futures::Future for Outbound<'a> { type Output = Result>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let _ = self.0.poll_inbound(cx); let Outbound(conn) = &mut *self; + let _ = conn.poll_inbound(cx); conn.poll_outbound(cx, &mut ()) .map_ok(|id| QuicStream { id: Some(id), @@ -139,7 +139,6 @@ impl Future for Closer { type Output = Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let i = &mut self.get_mut().0; - let _ = i.poll_inbound(cx); i.close(cx).map_err(From::from) } } @@ -219,10 +218,6 @@ fn do_test(_i: u32) { let muxer = Arc::new(muxer); let mut socket: QuicStream = Inbound(&*muxer).next().await.expect("no incoming stream"); - { - let cloned = muxer.clone(); - async_std::task::spawn(future::poll_fn(move |cx| cloned.poll_inbound(cx))); - } let mut buf = [0u8; 3]; debug!("reading data from accepted stream!"); { @@ -236,7 +231,6 @@ fn do_test(_i: u32) { socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); debug!("data written!"); socket.close().await.unwrap(); - // debug!("socket closed!"); assert_eq!(socket.read(&mut buf).await.unwrap(), 0); debug!("end of stream"); drop(socket); @@ -261,10 +255,6 @@ fn do_test(_i: u32) { error!("Dialing a Connection: {:?}", addr); let (peer_id, connection) = quic_endpoint.dial(addr.clone()).unwrap().await.unwrap(); let connection = Arc::new(connection); - { - let cloned = connection.clone(); - async_std::task::spawn(future::poll_fn(move |cx| cloned.poll_inbound(cx))); - } trace!("Received a Connection: {:?}", connection); let () = connection.open_outbound(); let mut stream = Outbound(&*connection).await.expect("failed"); From aac03f84b35fc364194633e4fecea9d7ea976f32 Mon Sep 17 00:00:00 2001 From: "Demi M. Obenour" Date: Mon, 29 Jun 2020 20:59:37 -0400 Subject: [PATCH 202/202] Remove some disabled code --- transports/quic/src/error.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/transports/quic/src/error.rs b/transports/quic/src/error.rs index e9bc078ffea..4da9ec00c99 100644 --- a/transports/quic/src/error.rs +++ b/transports/quic/src/error.rs @@ -55,13 +55,6 @@ pub enum Error { ConnectionClosing, } -#[cfg(any())] -impl From for Error { - fn from(_: SendError) -> Error { - Error::NetworkFailure - } -} - impl From for io::Error { fn from(e: Error) -> Self { match e {