From 018347e9b0528c869e8c5fbac10ff8117bd4f0e4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Apr 2022 19:41:20 +0400 Subject: [PATCH 001/244] initial webrtc transport --- Cargo.toml | 2 + transports/webrtc/Cargo.toml | 35 + transports/webrtc/src/connection/mod.rs | 78 ++ .../src/connection/poll_data_channel.rs | 289 ++++++++ transports/webrtc/src/error.rs | 34 + transports/webrtc/src/lib.rs | 104 +++ transports/webrtc/src/sdp.rs | 186 +++++ transports/webrtc/src/transport.rs | 697 ++++++++++++++++++ transports/webrtc/src/udp_mux.rs | 415 +++++++++++ .../webrtc/src/udp_mux/socket_addr_ext.rs | 267 +++++++ transports/webrtc/src/udp_mux/udp_mux_conn.rs | 308 ++++++++ transports/webrtc/src/upgrade.rs | 139 ++++ 12 files changed, 2554 insertions(+) create mode 100644 transports/webrtc/Cargo.toml create mode 100644 transports/webrtc/src/connection/mod.rs create mode 100644 transports/webrtc/src/connection/poll_data_channel.rs create mode 100644 transports/webrtc/src/error.rs create mode 100644 transports/webrtc/src/lib.rs create mode 100644 transports/webrtc/src/sdp.rs create mode 100644 transports/webrtc/src/transport.rs create mode 100644 transports/webrtc/src/udp_mux.rs create mode 100644 transports/webrtc/src/udp_mux/socket_addr_ext.rs create mode 100644 transports/webrtc/src/udp_mux/udp_mux_conn.rs create mode 100644 transports/webrtc/src/upgrade.rs diff --git a/Cargo.toml b/Cargo.toml index d2f361cb1b4..28c421702d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ websocket = ["libp2p-websocket"] yamux = ["libp2p-yamux"] secp256k1 = ["libp2p-core/secp256k1"] serde = ["libp2p-core/serde", "libp2p-kad/serde", "libp2p-gossipsub/serde"] +webrtc = ["libp2p-webrtc"] [package.metadata.docs.rs] all-features = true @@ -97,6 +98,7 @@ libp2p-swarm-derive = { version = "0.27.0", path = "swarm-derive" } libp2p-uds = { version = "0.32.0", path = "transports/uds", optional = true } libp2p-wasm-ext = { version = "0.33.0", path = "transports/wasm-ext", default-features = false, optional = true } libp2p-yamux = { version = "0.37.0", path = "muxers/yamux", optional = true } +libp2p-webrtc = { version = "0.1.0", path = "transports/webrtc", optional = true } multiaddr = { version = "0.14.0" } parking_lot = "0.12.0" pin-project = "1.0.0" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml new file mode 100644 index 00000000000..9e4d6254016 --- /dev/null +++ b/transports/webrtc/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "libp2p-webrtc" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "WebRTC transport for libp2p" +repository = "https://github.com/libp2p/rust-libp2p" +license = "" +edition = "2021" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] +rust-version = "1.56.1" + +[dependencies] +bytes = "1" +env_logger = "0.9.0" +futures = "0.3.17" +hex = "0.4" +libp2p-core = { version = "0.33.0", path = "../../core", default-features = false } +log = "0.4.14" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1" +tinytemplate = "1.2" +tokio-crate = { package = "tokio", version = "1.17.0", default-features = false, features = ["net"]} +webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc", branch = "anton/168-allow-persistent-certificates" } +webrtc-ice = "0.6.6" +webrtc-data = "0.3.3" +webrtc-sctp = "0.4.3" +if-watch = "0.2.2" +futures-timer = "3.0" +stun = "0.4.2" +webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } +async-trait = "0.1.52" + +[dev-dependencies] +rcgen = "0.8.14" diff --git a/transports/webrtc/src/connection/mod.rs b/transports/webrtc/src/connection/mod.rs new file mode 100644 index 00000000000..58d0b12b623 --- /dev/null +++ b/transports/webrtc/src/connection/mod.rs @@ -0,0 +1,78 @@ +// Copyright 2022 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. + +mod poll_data_channel; + +use futures::prelude::*; +use webrtc::peer_connection::RTCPeerConnection; +use webrtc_data::data_channel::DataChannel; + +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use poll_data_channel::PollDataChannel; + +/// A WebRTC connection over a single data channel. See lib documentation for +/// the reasoning as to why a single data channel is being used. +pub struct Connection<'a> { + /// `RTCPeerConnection` to the remote peer. + pub inner: RTCPeerConnection, + /// A data channel. + pub data_channel: PollDataChannel<'a>, +} + +impl Connection<'_> { + pub fn new(peer_conn: RTCPeerConnection, data_channel: Arc) -> Self { + Self { + inner: peer_conn, + data_channel: PollDataChannel::new(data_channel), + } + } +} + +impl AsyncRead for Connection<'_> { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut self.data_channel).poll_read(cx, buf) + } +} + +impl AsyncWrite for Connection<'_> { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.data_channel).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.data_channel).poll_flush(cx) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.data_channel).poll_close(cx) + } +} diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs new file mode 100644 index 00000000000..14f56a53e9b --- /dev/null +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -0,0 +1,289 @@ +// Copyright 2022 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 bytes::Bytes; + +use futures::prelude::*; +use webrtc_data::data_channel::DataChannel; +use webrtc_data::Error; + +use std::fmt; +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +/// Default capacity of the temporary read buffer used by [`PollStream`]. +const DEFAULT_READ_BUF_SIZE: usize = 4096; + +/// State of the read `Future` in [`PollStream`]. +enum ReadFut<'a> { + /// Nothing in progress. + Idle, + /// Reading data from the underlying stream. + Reading(Pin, Error>> + Send + 'a>>), + /// Finished reading, but there's unread data in the temporary buffer. + RemainingData(Vec), +} + +impl<'a> ReadFut<'a> { + /// Gets a mutable reference to the future stored inside `Reading(future)`. + /// + /// # Panics + /// + /// Panics if `ReadFut` variant is not `Reading`. + fn get_reading_mut( + &mut self, + ) -> &mut Pin, Error>> + Send + 'a>> { + match self { + ReadFut::Reading(ref mut fut) => fut, + _ => panic!("expected ReadFut to be Reading"), + } + } +} + +/// A wrapper around around [`DataChannel`], which implements [`AsyncRead`] and +/// [`AsyncWrite`]. +/// +/// Both `poll_read` and `poll_write` calls allocate temporary buffers, which results in an +/// additional overhead. +pub struct PollDataChannel<'a> { + data_channel: Arc, + + read_fut: ReadFut<'a>, + write_fut: Option> + Send + 'a>>>, + shutdown_fut: Option> + Send + 'a>>>, + + read_buf_cap: usize, +} + +impl PollDataChannel<'_> { + /// Constructs a new `PollDataChannel`. + pub fn new(data_channel: Arc) -> Self { + Self { + data_channel, + read_fut: ReadFut::Idle, + write_fut: None, + shutdown_fut: None, + read_buf_cap: DEFAULT_READ_BUF_SIZE, + } + } + + /// Get back the inner data_channel. + pub fn into_inner(self) -> Arc { + self.data_channel + } + + /// Obtain a clone of the inner data_channel. + pub fn clone_inner(&self) -> Arc { + self.data_channel.clone() + } + + /// Set the capacity of the temporary read buffer (default: 4096). + pub fn set_read_buf_capacity(&mut self, capacity: usize) { + self.read_buf_cap = capacity + } +} + +impl AsyncRead for PollDataChannel<'_> { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let fut = match self.read_fut { + ReadFut::Idle => { + // read into a temporary buffer because `buf` has an unonymous lifetime, which can + // be shorter than the lifetime of `read_fut`. + let dc = self.data_channel.clone(); + let mut temp_buf = vec![0; self.read_buf_cap]; + self.read_fut = ReadFut::Reading(Box::pin(async move { + let res = dc.read(temp_buf.as_mut_slice()).await; + match res { + Ok(n) => { + temp_buf.truncate(n); + Ok(temp_buf) + }, + Err(e) => Err(e), + } + })); + self.read_fut.get_reading_mut() + }, + ReadFut::Reading(ref mut fut) => fut, + ReadFut::RemainingData(ref mut data) => { + let remaining = buf.len(); + let len = std::cmp::min(data.len(), remaining); + buf.copy_from_slice(&data[..len]); + if data.len() > remaining { + // ReadFut remains to be RemainingData + data.drain(0..len); + } else { + self.read_fut = ReadFut::Idle; + } + return Poll::Ready(Ok(len)); + }, + }; + + loop { + match fut.as_mut().poll(cx) { + Poll::Pending => return Poll::Pending, + // retry immediately upon empty data or incomplete chunks + // since there's no way to setup a waker. + Poll::Ready(Err(Error::Sctp(webrtc_sctp::Error::ErrTryAgain))) => {}, + // EOF has been reached => don't touch buf and just return Ok + Poll::Ready(Err(Error::Sctp(webrtc_sctp::Error::ErrEof))) => { + self.read_fut = ReadFut::Idle; + return Poll::Ready(Ok(0)); + }, + Poll::Ready(Err(e)) => { + self.read_fut = ReadFut::Idle; + return Poll::Ready(Err(webrtc_error_to_io(e))); + }, + Poll::Ready(Ok(mut temp_buf)) => { + let remaining = buf.len(); + let len = std::cmp::min(temp_buf.len(), remaining); + buf.copy_from_slice(&temp_buf[..len]); + if temp_buf.len() > remaining { + temp_buf.drain(0..len); + self.read_fut = ReadFut::RemainingData(temp_buf); + } else { + self.read_fut = ReadFut::Idle; + } + return Poll::Ready(Ok(len)); + }, + } + } + } +} + +impl AsyncWrite for PollDataChannel<'_> { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let (fut, fut_is_new) = match self.write_fut.as_mut() { + Some(fut) => (fut, false), + None => { + let dc = self.data_channel.clone(); + let bytes = Bytes::copy_from_slice(buf); + ( + self.write_fut + .get_or_insert(Box::pin(async move { dc.write(&bytes).await })), + true, + ) + }, + }; + + match fut.as_mut().poll(cx) { + Poll::Pending => { + // If it's the first time we're polling the future, `Poll::Pending` can't be + // returned because that would mean the `PollDataChannel` is not ready for writing. And + // this is not true since we've just created a future, which is going to write the + // buf to the underlying dc. + // + // It's okay to return `Poll::Ready` if the data is buffered (this is what the + // buffered writer and `File` do). + if fut_is_new { + Poll::Ready(Ok(buf.len())) + } else { + // If it's the subsequent poll, it's okay to return `Poll::Pending` as it + // indicates that the `PollDataChannel` is not ready for writing. Only one future + // can be in progress at the time. + Poll::Pending + } + }, + Poll::Ready(Err(e)) => { + self.write_fut = None; + Poll::Ready(Err(webrtc_error_to_io(e))) + }, + Poll::Ready(Ok(n)) => { + self.write_fut = None; + Poll::Ready(Ok(n)) + }, + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.write_fut.as_mut() { + Some(fut) => match fut.as_mut().poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => { + self.write_fut = None; + Poll::Ready(Err(webrtc_error_to_io(e))) + }, + Poll::Ready(Ok(_)) => { + self.write_fut = None; + Poll::Ready(Ok(())) + }, + }, + None => Poll::Ready(Ok(())), + } + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let fut = match self.shutdown_fut.as_mut() { + Some(fut) => fut, + None => { + let dc = self.data_channel.clone(); + self.shutdown_fut + .get_or_insert(Box::pin(async move { dc.close().await })) + }, + }; + + match fut.as_mut().poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => Poll::Ready(Err(webrtc_error_to_io(e))), + Poll::Ready(Ok(_)) => Poll::Ready(Ok(())), + } + } +} + +impl<'a> Clone for PollDataChannel<'a> { + fn clone(&self) -> PollDataChannel<'a> { + PollDataChannel::new(self.clone_inner()) + } +} + +impl fmt::Debug for PollDataChannel<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PollDataChannel") + .field("data_channel", &self.data_channel) + .finish() + } +} + +impl AsRef for PollDataChannel<'_> { + fn as_ref(&self) -> &DataChannel { + &*self.data_channel + } +} + +fn webrtc_error_to_io(error: Error) -> io::Error { + match error { + e @ Error::Sctp(webrtc_sctp::Error::ErrEof) => { + io::Error::new(io::ErrorKind::UnexpectedEof, e.to_string()) + }, + e @ Error::ErrStreamClosed => { + io::Error::new(io::ErrorKind::ConnectionAborted, e.to_string()) + }, + e => io::Error::new(io::ErrorKind::Other, e.to_string()), + } +} diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs new file mode 100644 index 00000000000..4b4e680a22d --- /dev/null +++ b/transports/webrtc/src/error.rs @@ -0,0 +1,34 @@ +// Copyright 2022 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 thiserror::Error; + +/// Error in WebRTC. +#[derive(Error, Debug)] +pub enum Error { + #[error("multi-address {0} is not supported")] + InvalidMultiaddr(libp2p_core::Multiaddr), + #[error("webrtc error: {0}")] + WebRTC(#[from] webrtc::Error), + #[error("io error: {0}")] + IoError(#[from] std::io::Error), + #[error("internal error: {0} (see debug logs)")] + InternalError(String), +} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs new file mode 100644 index 00000000000..52398e62d9a --- /dev/null +++ b/transports/webrtc/src/lib.rs @@ -0,0 +1,104 @@ +// Copyright 2022 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 WebRTC direct protocol. "direct" here means +//! communicating without a signaling server. +//! +//! # Overview +//! +//! ## ICE +//! +//! RFCs: 8839, 8445 See also: +//! https://tools.ietf.org/id/draft-ietf-rtcweb-sdp-08.html#rfc.section.5.2.3 +//! +//! The WebRTC protocol uses ICE in order to establish a connection. +//! +//! In a typical ICE setup, there are two endpoints, called agents, that want to communicate. One +//! of these two agents is the local browser, while the other agent is the target of the +//! connection. +//! +//! Even though in this specific context all we want is a simple client-server communication, it is +//! helpful to keep in mind that ICE was designed to solve the problem of NAT traversal. +//! +//! The ICE workflow works as follows: +//! +//! - An "offerer" determines ways in which it could be accessible (either an +//! IP address or through a relay using a TURN server), which are called "candidates". It then +//! generates a small text payload in a format called SDP, that describes the request for a +//! connection. +//! - The offerer sends this SDP-encoded message to the answerer. The medium through which this +//! exchange is done is out of scope of the ICE protocol. +//! - The answerer then finds its own candidates, and generates an answer, again in the SDP format. +//! This answer is sent back to the offerer. +//! - Each agent then tries to connect to the remote's candidates. +//! +//! We pretend to send the offer to the remote agent (the target of the connection), then pretend +//! that it has found a valid IP address for itself (i.e. a candidate), then pretend that the SDP +//! answer containing this candidate has been sent back. This will cause the offerer to execute +//! step 4: try to connect to the remote's candidate. +//! +//! ## 1 vs N data channels +//! +//! The SDP message generated by the offerer contains the list of so-called "media streams" that it +//! wants to open. In our specific use-case, we configure the transport to always request one data +//! stream. +//! +//! For this prototype, we have choosen to implement a single data channel and multiplex on top +//! because doing N channels will force us to rewrite a significant part of smoldot, which isn't +//! ready at all to have encryption and multiplexing handled "externally". +//! +//! ## TCP or UDP +//! +//! WebRTC by itself doesn't hardcode any specific protocol for these media streams. Instead, it is +//! the SDP message of the offerer that specifies which protocol to use. In our use case, one data +//! stream, we know that the offerer will always request either TCP+DTLS+SCTP, or UDP+DTLS+SCTP. +//! +//! ## DTLS+SCTP +//! +//! RFCs: 8841, 8832 +//! +//! In both cases (TCP or UDP), the next layer is DTLS. DTLS is similar to the well-known TLS +//! protocol, except that it doesn't guarantee ordering of delivery (as this is instead provided by +//! the SCTP layer on top of DTLS). In other words, once the TCP or UDP connection is established, +//! the browser will try to perform a DTLS handshake. +//! +//! During the ICE negotiation, each agent must include in its SDP packet a hash of the self-signed +//! certificate that it will use during the DTLS handshake. In our use-case, where we try to +//! hand-crate the SDP answer generated by the remote, this is problematic. One way to solve this +//! is to make the hash a part of the remote's multiaddr. +//! +//! ## PeerId +//! +//! Ideally, we would just add a field in the certificate that contains a signature of the +//! certificate made using the PeerId. But you can't do that, as it's a chicken and egg problem: +//! you can't modify the certificate anymore after you've created a signature of it. We could find +//! some semi-hacky system where the signature is made against the certificate but with this field +//! removed, but that's very hacky as well. +//! +//! For now we'll start an encryption protocol handshake on top of the single data channel. Once +//! this handshake has been successful, we stop it and just use DTLS. + +pub mod connection; +pub mod error; +pub mod transport; + +mod sdp; +mod udp_mux; +mod upgrade; diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs new file mode 100644 index 00000000000..2384b85d958 --- /dev/null +++ b/transports/webrtc/src/sdp.rs @@ -0,0 +1,186 @@ +// Copyright 2022 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 serde::Serialize; + +use std::net::IpAddr; + +// An SDP message that constitutes the offer. +// +// Main RFC: +// `sctp-port` and `max-message-size` attrs RFC: +// `group` and `mid` attrs RFC: +// `ice-ufrag`, `ice-pwd` and `ice-options` attrs RFC: +// `setup` attr RFC: +// +// Short description: +// +// v= -> always 0 +// o= +// +// identifies the creator of the SDP document. We are allowed to use dummy values +// (`-` and `0.0.0.0` as ) to remain anonymous, which we do. Note that "IN" means +// "Internet". +// +// s= +// +// We are allowed to pass a dummy `-`. +// +// c= +// +// Indicates the IP address of the remote. +// Note that "IN" means "Internet". +// +// t= +// +// Start and end of the validity of the session. `0 0` means that the session never expires. +// +// m= ... +// +// A `m=` line describes a request to establish a certain protocol. The protocol in this line +// (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be the same as the one in the offer. +// We know that this is true because we tweak the offer to match the protocol. The `` +// component must always be `webrtc-datachannel` for WebRTC. +// RFCs: 8839, 8866, 8841 +// +// a=mid: +// +// Media ID - uniquely identifies this media stream (RFC9143). +// +// a=ice-options:ice2 +// +// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). +// +// a=ice-ufrag: +// a=ice-pwd: +// +// ICE username and password, which are used for establishing and +// maintaining the ICE connection. (RFC8839) +// MUST match ones used by the answerer (server). +// +// a=fingerprint:sha-256 +// +// Fingerprint of the certificate that the remote will use during the TLS +// handshake. (RFC8122) +// TODO: do we verify fingerprint here? +// +// a=setup:actpass +// +// The endpoint that is the offerer MUST use the setup attribute value of setup:actpass and be +// prepared to receive a client_hello before it receives the answer. +// +// a=sctp-port: +// +// The SCTP port (RFC8841) +// Note it's different from the "m=" line port value, which indicates the port of the +// underlying transport-layer protocol (UDP or TCP). +// +// a=max-message-size: +// +// The maximum SCTP user message size (in bytes). (RFC8841) +pub const CLIENT_SESSION_DESCRIPTION: &'static str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +c=IN {ip_version} {target_ip} +t=0 0 + +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:sha-256 {fingerprint} +a=setup:actpass +a=sctp-port:5000 +a=max-message-size:100000 +"; + +// See [`CLIENT_SESSION_DESCRIPTION`]. +// +// a=ice-lite +// +// A lite implementation is only appropriate for devices that will *always* be connected to +// the public Internet and have a public IP address at which it can receive packets from any +// correspondent. ICE will not function when a lite implementation is placed behind a NAT +// (RFC8445). +// +// a=tls-id: +// +// "TLS ID" uniquely identifies a TLS association. +// The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection +// must be reopened in case of ICE renegotiation. Considering that ICE renegotiations +// never happen in our use case, we can simply put a random value and not care about +// it. Note however that the TLS ID in the answer must be present if and only if the +// offer contains one. (RFC8842) +// TODO: is it true that renegotiations never happen? what about a connection closing? +// "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839). +// XXX: but right now browsers don't send it. +// +// a=setup:passive +// +// "passive" indicates that the remote DTLS server will only listen for incoming +// connections. (RFC5763) +// The answerer (server) MUST not be located behind a NAT (RFC6135). +// +// The answerer MUST use either a setup attribute value of setup:active or setup:passive. +// Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until +// the answerer is received, which adds additional latency. setup:active allows the answer and +// the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. +// +// a=candidate: +// +// A transport address for a candidate that can be used for connectivity checks (RFC8839). +pub const SERVER_SESSION_DESCRIPTION: &'static str = "v=0 +o=- 0 0 IN {ip_version} {target_ip} +s=- +t=0 0 +a=ice-lite +m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel +c=IN {ip_version} {target_ip} +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:{ufrag} +a=ice-pwd:{pwd} +a=fingerprint:sha-256 {fingerprint} + +a=setup:passive +a=sctp-port:5000 +a=max-message-size:100000 +a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +"; + +/// Indicates the IP version used in WebRTC: `IP4` or `IP6`. +#[derive(Serialize)] +pub enum IpVersion { + IP4, + IP6, +} + +/// Context passed to the templating engine, which replaces the above placeholders (e.g. +/// `{IP_VERSION}`) with real values. +#[derive(Serialize)] +pub struct DescriptionContext { + pub ip_version: IpVersion, + pub target_ip: IpAddr, + pub target_port: u16, + pub fingerprint: String, + pub ufrag: String, + pub pwd: String, +} diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs new file mode 100644 index 00000000000..530e806dac0 --- /dev/null +++ b/transports/webrtc/src/transport.rs @@ -0,0 +1,697 @@ +// Copyright 2022 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 libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerEvent, TransportError}, + Transport, +}; + +use futures::{ + channel::{mpsc, oneshot}, + future::BoxFuture, + prelude::*, + ready, TryFutureExt, +}; +use futures_timer::Delay; +use if_watch::{IfEvent, IfWatcher}; +use log::{debug, error, trace}; +use tinytemplate::TinyTemplate; +use tokio_crate::net::{ToSocketAddrs, UdpSocket}; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::peer_connection::certificate::RTCCertificate; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc_data::data_channel::DataChannel as DetachedDataChannel; +use webrtc_ice::network_type::NetworkType; +use webrtc_ice::udp_mux::UDPMux; +use webrtc_ice::udp_network::UDPNetwork; + +use std::borrow::Cow; +use std::io; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use std::time::Duration; + +use crate::error::Error; +use crate::sdp; + +use crate::connection::Connection; +use crate::udp_mux::UDPMuxNewAddr; +use crate::udp_mux::UDPMuxParams; +use crate::upgrade::WebRTCUpgrade; + +enum IfWatch { + Pending(BoxFuture<'static, io::Result>), + Ready(IfWatcher), +} + +/// The listening addresses of a [`WebRTCTransport`]. +enum InAddr { + /// The stream accepts connections on a single interface. + One { out: Option }, + /// The stream accepts connections on all interfaces. + Any { if_watch: IfWatch }, +} + +/// A WebRTC transport with direct p2p communication (without a STUN server). +#[derive(Clone)] +pub struct WebRTCTransport { + /// A `RTCConfiguration` which holds this peer's certificate(s). + config: RTCConfiguration, + + /// The `UDPMux` that manages all ICE connections. + udp_mux: Arc, + + /// The local address of `udp_mux`. + udp_mux_addr: SocketAddr, + + /// The receiver for new `SocketAddr` connecting to this peer. + new_addr_rx: Arc>>, +} + +impl WebRTCTransport { + /// Create a new WebRTC transport. + /// + /// Creates a UDP socket bound to `listen_addr`. + pub async fn new( + certificate: RTCCertificate, + listen_addr: A, + ) -> Result> { + // bind to `listen_addr` and construct a UDP mux. + let socket = UdpSocket::bind(listen_addr) + .map_err(Error::IoError) + .map_err(TransportError::Other) + .await?; + // sender and receiver for new addresses + let (new_addr_tx, new_addr_rx) = mpsc::channel(1); + let udp_mux_addr = socket + .local_addr() + .map_err(Error::IoError) + .map_err(TransportError::Other)?; + let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket), new_addr_tx); + + Ok(Self { + config: RTCConfiguration { + certificates: vec![certificate], + ..Default::default() + }, + udp_mux, + udp_mux_addr, + new_addr_rx: Arc::new(Mutex::new(new_addr_rx)), + }) + } + + /// Returns the SHA-256 fingerprint of the certificate in lowercase hex string as expressed + /// utilizing the syntax of 'fingerprint' in . + fn cert_fingerprint(&self) -> String { + fingerprint_of_first_certificate(&self.config) + } +} + +impl Transport for WebRTCTransport { + type Output = Connection<'static>; + type Error = Error; + type Listener = WebRTCListenStream; + type ListenerUpgrade = BoxFuture<'static, Result>; + type Dial = BoxFuture<'static, Result>; + + fn listen_on(self, addr: Multiaddr) -> Result> { + debug!("listening on {} (ignoring {})", self.udp_mux_addr, addr); + Ok(WebRTCListenStream::new( + self.udp_mux_addr, + self.config.clone(), + self.udp_mux.clone(), + self.new_addr_rx.clone(), + )) + } + + fn dial(self, addr: Multiaddr) -> Result> { + Ok(Box::pin(self.do_dial(addr))) + } + + fn dial_as_listener(self, addr: Multiaddr) -> Result> { + // XXX: anything to do here? + self.dial(addr) + } + + fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { + // XXX: anything to do here? + libp2p_core::address_translation(server, observed) + } +} + +/// A stream of incoming connections on one or more interfaces. +pub struct WebRTCListenStream { + /// The socket address that the listening socket is bound to, + /// which may be a "wildcard address" like `INADDR_ANY` or `IN6ADDR_ANY` + /// when listening on all interfaces for IPv4 respectively IPv6 connections. + listen_addr: SocketAddr, + /// The IP addresses of network interfaces on which the listening socket + /// is accepting connections. + /// + /// If the listen socket listens on all interfaces, these may change over + /// time as interfaces become available or unavailable. + in_addr: InAddr, + /// How long to sleep after a (non-fatal) error while trying + /// to accept a new connection. + sleep_on_error: Duration, + /// The current pause, if any. + pause: Option, + + /// A `RTCConfiguration` which holds this peer's certificate(s). + config: RTCConfiguration, + /// The `UDPMux` that manages all ICE connections. + udp_mux: Arc, + /// The receiver for new `SocketAddr` connecting to this peer. + new_addr_rx: Arc>>, +} + +impl WebRTCListenStream { + /// Constructs a `WebRTCListenStream` for incoming connections around + /// the given `TcpListener`. + fn new( + listen_addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + new_addr_rx: Arc>>, + ) -> Self { + // Check whether the listening IP is set or not. + let in_addr = if match &listen_addr { + SocketAddr::V4(a) => a.ip().is_unspecified(), + SocketAddr::V6(a) => a.ip().is_unspecified(), + } { + // The `addrs` are populated via `if_watch` when the + // `WebRTCTransport` is polled. + InAddr::Any { + if_watch: IfWatch::Pending(IfWatcher::new().boxed()), + } + } else { + InAddr::One { + out: Some(ip_to_multiaddr(listen_addr.ip(), listen_addr.port())), + } + }; + + WebRTCListenStream { + listen_addr, + in_addr, + pause: None, + sleep_on_error: Duration::from_millis(100), + config, + udp_mux, + new_addr_rx, + } + } +} + +impl Stream for WebRTCListenStream { + type Item = + Result, Error>>, Error>, Error>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = Pin::into_inner(self); + + loop { + match &mut me.in_addr { + InAddr::Any { if_watch } => match if_watch { + // If we listen on all interfaces, wait for `if-watch` to be ready. + IfWatch::Pending(f) => match ready!(Pin::new(f).poll(cx)) { + Ok(w) => { + *if_watch = IfWatch::Ready(w); + continue; + }, + Err(err) => { + debug! { + "Failed to begin observing interfaces: {:?}. Scheduling retry.", + err + }; + *if_watch = IfWatch::Pending(IfWatcher::new().boxed()); + me.pause = Some(Delay::new(me.sleep_on_error)); + return Poll::Ready(Some(Ok(ListenerEvent::Error(Error::IoError( + err, + ))))); + }, + }, + // Consume all events for up/down interface changes. + IfWatch::Ready(watch) => { + while let Poll::Ready(ev) = watch.poll_unpin(cx) { + match ev { + Ok(IfEvent::Up(inet)) => { + let ip = inet.addr(); + if me.listen_addr.is_ipv4() == ip.is_ipv4() + || me.listen_addr.is_ipv6() == ip.is_ipv6() + { + let ma = ip_to_multiaddr(ip, me.listen_addr.port()); + debug!("New listen address: {}", ma); + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( + ma, + )))); + } + }, + Ok(IfEvent::Down(inet)) => { + let ip = inet.addr(); + if me.listen_addr.is_ipv4() == ip.is_ipv4() + || me.listen_addr.is_ipv6() == ip.is_ipv6() + { + let ma = ip_to_multiaddr(ip, me.listen_addr.port()); + debug!("Expired listen address: {}", ma); + return Poll::Ready(Some(Ok( + ListenerEvent::AddressExpired(ma), + ))); + } + }, + Err(err) => { + debug! { + "Failure polling interfaces: {:?}. Scheduling retry.", + err + }; + me.pause = Some(Delay::new(me.sleep_on_error)); + return Poll::Ready(Some(Ok(ListenerEvent::Error( + Error::IoError(err), + )))); + }, + } + } + }, + }, + // If the listener is bound to a single interface, make sure the address reported + // once. + InAddr::One { out } => { + if let Some(multiaddr) = out.take() { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(multiaddr)))); + } + }, + } + + if let Some(mut pause) = me.pause.take() { + match Pin::new(&mut pause).poll(cx) { + Poll::Ready(_) => {}, + Poll::Pending => { + me.pause = Some(pause); + return Poll::Pending; + }, + } + } + + // safe to unwrap here since this is the only place `new_addr_rx` is locked. + return match Pin::new(&mut *me.new_addr_rx.lock().unwrap()).poll_next(cx) { + Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), + remote_addr: addr.clone(), + upgrade: Box::pin(WebRTCUpgrade::new( + me.udp_mux.clone(), + me.config.clone(), + addr, + )) as BoxFuture<'static, _>, + }))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }; + } + } +} + +impl WebRTCTransport { + async fn do_dial(self, addr: Multiaddr) -> Result, Error> { + let socket_addr = + multiaddr_to_socketaddr(&addr).ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(Error::InvalidMultiaddr(addr.clone())); + } + + let config = self.config.clone(); + + let remote = addr.clone(); // used for logging + trace!("dialing address: {:?}", remote); + + let fingerprint = self.cert_fingerprint(); + let se = build_setting_engine(self.udp_mux.clone(), &socket_addr, &fingerprint); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api + .new_peer_connection(config) + .map_err(Error::WebRTC) + .await?; + + // Create a datachannel with label 'data' + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: None, + id: Some(1), + ordered: None, + max_retransmits: None, + max_packet_life_time: None, + protocol: None, + }), + ) + .await?; + + let (data_channel_rx, data_channel_tx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + let d = Arc::clone(&data_channel); + data_channel + .on_open(Box::new(move || { + debug!("Data channel '{}'-'{}' open.", d.label(), d.id()); + + let d2 = Arc::clone(&d); + Box::pin(async move { + match d2.detach().await { + Ok(detached) => { + if let Err(_) = data_channel_rx.send(detached) { + error!("data_channel_tx dropped"); + } + }, + Err(e) => { + error!("Can't detach data channel: {}", e); + }, + }; + }) + })) + .await; + + let offer = peer_connection + .create_offer(None) + .map_err(Error::WebRTC) + .await?; + debug!("OFFER: {:?}", offer.sdp); + peer_connection + .set_local_description(offer) + .map_err(Error::WebRTC) + .await?; + + // Set the remote description to the predefined SDP. + let fingerprint = match addr.iter().last() { + Some(Protocol::XWebRTC(f)) => f, + _ => { + return Err(Error::InvalidMultiaddr(addr)); + }, + }; + let server_session_description = render_description( + sdp::SERVER_SESSION_DESCRIPTION, + socket_addr, + &fingerprint_to_string(&fingerprint), + ); + debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + peer_connection + .set_remote_description(sdp) + .map_err(Error::WebRTC) + .await?; + + // wait until data channel is opened and ready to use + match tokio_crate::time::timeout(Duration::from_secs(10), data_channel_tx).await { + Ok(Ok(dc)) => Ok(Connection::new(peer_connection, dc)), + Ok(Err(e)) => Err(Error::InternalError(e.to_string())), + Err(_) => Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )), + } + } +} + +/// Creates a [`Multiaddr`] from the given IP address and port number. +fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { + Multiaddr::empty().with(ip.into()).with(Protocol::Udp(port)) +} + +/// Renders a [`TinyTemplate`] description using the provided arguments. +pub(crate) fn render_description(description: &str, addr: SocketAddr, fingerprint: &str) -> String { + let mut tt = TinyTemplate::new(); + tt.add_template("description", description).unwrap(); + + let f = fingerprint.to_owned().replace(":", ""); + let context = sdp::DescriptionContext { + ip_version: { + if addr.is_ipv4() { + sdp::IpVersion::IP4 + } else { + sdp::IpVersion::IP6 + } + }, + target_ip: addr.ip(), + target_port: addr.port(), + // hashing algorithm (SHA-256) is hardcoded for now + fingerprint: fingerprint.to_owned(), + // ufrag and pwd are both equal to the fingerprint (minus the `:` delimiter) + ufrag: f.clone(), + pwd: f, + }; + tt.render("description", &context).unwrap() +} + +/// Tries to turn a WebRTC multiaddress into a [`SocketAddr`]. Returns None if the format of the +/// multiaddr is wrong. +pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { + let mut iter = addr.iter(); + let proto1 = iter.next()?; + let proto2 = iter.next()?; + let proto3 = iter.next()?; + + while let Some(proto) = iter.next() { + match proto { + Protocol::P2p(_) => {}, // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + _ => return None, + } + } + + match (proto1, proto2, proto3) { + (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { + Some(SocketAddr::new(ip.into(), port)) + }, + (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { + Some(SocketAddr::new(ip.into(), port)) + }, + _ => None, + } +} + +/// Transforms a byte array fingerprint into a string. +pub(crate) fn fingerprint_to_string(f: &Cow<'_, [u8; 32]>) -> String { + let values: Vec = f.iter().map(|x| format! {"{:02x}", x}).collect(); + values.join(":") +} + +/// Returns a fingerprint of the first certificate. +/// +/// # Panics +/// +/// Panics if the config does not contain any certificates. +pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> String { + // safe to unwrap here because we require a certificate during construction. + let fingerprints = config + .certificates + .first() + .expect("at least one certificate") + .get_fingerprints() + .expect("fingerprints to succeed"); + fingerprints.first().unwrap().value.to_owned() +} + +/// Creates a new [`SettingEngine`] and configures it. +pub(crate) fn build_setting_engine( + udp_mux: Arc, + addr: &SocketAddr, + fingerprint: &str, +) -> SettingEngine { + let mut se = SettingEngine::default(); + // Set both ICE user and password to fingerprint. + // It will be checked by remote side when exchanging ICE messages. + let f = fingerprint.to_owned().replace(":", ""); + se.set_ice_credentials(f.clone(), f); + se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); + // Allow detaching data channels. + se.detach_data_channels(); + // Set the desired network type. + // + // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate + // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). + let network_type = if addr.is_ipv4() { + NetworkType::Udp4 + } else { + NetworkType::Udp6 + }; + se.set_network_types(vec![network_type]); + se +} + +// Tests ////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use libp2p_core::{multiaddr::Protocol, Multiaddr, Transport}; + use rcgen::KeyPair; + use std::net::IpAddr; + use std::net::{Ipv4Addr, Ipv6Addr}; + use tokio_crate as tokio; + + #[test] + fn multiaddr_to_socketaddr_conversion() { + assert!( + multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) + .is_none() + ); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + .parse::() + .unwrap() + ), + Some( SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 12345, + ) ) + ); + + assert!( + multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/tcp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + .parse::() + .unwrap() + ).is_none() + ); + + assert!(multiaddr_to_socketaddr( + &"/ip4/127.0.0.1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B/tcp/12345" + .parse::() + .unwrap() + ) + .is_none()); + + assert_eq!( + multiaddr_to_socketaddr( + &"/ip4/255.255.255.255/udp/8080/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + .parse::() + .unwrap() + ), + Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + 8080, + )) + ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/::1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + .parse::() + .unwrap() + ), + Some( 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/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + .parse::() + .unwrap() + ), + Some( SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + )), + 8080, + ) ) + ); + } + + fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { + let mut buf = [0; 32]; + hex::decode_to_slice(s, &mut buf).unwrap(); + Cow::Owned(buf) + } + + #[tokio::test] + async fn dialer_connects_to_listener_ipv4() { + let _ = env_logger::builder().is_test(true).try_init(); + let a = "127.0.0.1:0".parse().unwrap(); + connect(a).await + } + + #[tokio::test] + async fn dialer_connects_to_listener_ipv6() { + let _ = env_logger::builder().is_test(true).try_init(); + let a = "[::1]:0".parse().unwrap(); + connect(a).await; + } + + async fn connect(listen_addr: SocketAddr) { + let transport = { + let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); + let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); + WebRTCTransport::new(cert, listen_addr) + .await + .expect("transport") + }; + + let mut listener = transport + .clone() + .listen_on(ip_to_multiaddr(listen_addr.ip(), listen_addr.port())) + .expect("listener"); + + let addr = listener + .try_next() + .await + .expect("some event") + .expect("no error") + .into_new_address() + .expect("listen address"); + + assert_ne!(Some(Protocol::Udp(0)), addr.iter().nth(1)); + + let inbound = async move { + let (conn, _addr) = listener + .try_filter_map(|e| future::ready(Ok(e.into_upgrade()))) + .try_next() + .await + .unwrap() + .unwrap(); + conn.await + }; + + let transport2 = { + let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); + let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); + // okay to reuse `listen_addr` since the port is `0` (any). + WebRTCTransport::new(cert, listen_addr) + .await + .expect("transport") + }; + // TODO: make code cleaner wrt ":" + let f = &transport.cert_fingerprint().replace(":", ""); + let outbound = transport2 + .dial(addr.with(Protocol::XWebRTC(hex_to_cow(f)))) + .unwrap(); + + let (a, b) = futures::join!(inbound, outbound); + a.and(b).unwrap(); + } +} diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs new file mode 100644 index 00000000000..11a3b7d2a8a --- /dev/null +++ b/transports/webrtc/src/udp_mux.rs @@ -0,0 +1,415 @@ +// MIT License +// +// Copyright (c) 2021 WebRTC.rs +// +// 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 std::{ + borrow::Cow, + collections::{HashMap, HashSet}, + io::ErrorKind, + net::SocketAddr, + sync::Arc, +}; + +use futures::channel::mpsc; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use webrtc_ice::udp_mux::UDPMux; +use webrtc_util::{sync::RwLock, Conn, Error}; + +use tokio_crate as tokio; +use tokio_crate::sync::{watch, Mutex}; + +mod socket_addr_ext; + +mod udp_mux_conn; +use udp_mux_conn::{UDPMuxConn, UDPMuxConnParams}; + +use async_trait::async_trait; + +use stun::{ + attributes::ATTR_USERNAME, + message::{is_message as is_stun_message, Message as STUNMessage}, +}; + +const RECEIVE_MTU: usize = 8192; + +/// Normalize a target socket addr for sending over a given local socket addr. This is useful when +/// a dual stack socket is used, in which case an IPv4 target needs to be mapped to an IPv6 +/// address. +fn normalize_socket_addr(target: &SocketAddr, socket_addr: &SocketAddr) -> SocketAddr { + match (target, socket_addr) { + (SocketAddr::V4(target_ipv4), SocketAddr::V6(_)) => { + let ipv6_mapped = target_ipv4.ip().to_ipv6_mapped(); + + SocketAddr::new(std::net::IpAddr::V6(ipv6_mapped), target_ipv4.port()) + }, + // This will fail later if target is IPv6 and socket is IPv4, we ignore it here + (_, _) => *target, + } +} + +pub struct UDPMuxParams { + conn: Box, +} + +impl UDPMuxParams { + pub fn new(conn: C) -> Self + where + C: Conn + Send + Sync + 'static, + { + Self { + conn: Box::new(conn), + } + } +} + +/// This is a copy of `UDPMuxDefault` with the exception of ability to report new addresses via +/// `new_addr_tx`. +pub struct UDPMuxNewAddr { + /// The params this instance is configured with. + /// Contains the underlying UDP socket in use + params: UDPMuxParams, + + /// Maps from ufrag to the underlying connection. + conns: Mutex>, + + /// Maps from ip address to the underlying connection. + address_map: RwLock>, + + // Close sender + closed_watch_tx: Mutex>>, + + /// Close reciever + closed_watch_rx: watch::Receiver<()>, + + /// Set of the new IP addresses reported via `new_addr_tx` to avoid sending the same IP + /// multiple times. + new_addrs: RwLock>, +} + +impl UDPMuxNewAddr { + pub fn new(params: UDPMuxParams, new_addr_tx: mpsc::Sender) -> Arc { + let (closed_watch_tx, closed_watch_rx) = watch::channel(()); + + let mux = Arc::new(Self { + params, + conns: Mutex::default(), + address_map: RwLock::default(), + closed_watch_tx: Mutex::new(Some(closed_watch_tx)), + closed_watch_rx: closed_watch_rx.clone(), + new_addrs: RwLock::default(), + }); + + let cloned_mux = Arc::clone(&mux); + cloned_mux.start_conn_worker(closed_watch_rx, new_addr_tx); + + mux + } + + pub async fn is_closed(&self) -> bool { + self.closed_watch_tx.lock().await.is_none() + } + + async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result { + self.params + .conn + .send_to(buf, *target) + .await + .map_err(Into::into) + } + + /// Create a muxed connection for a given ufrag. + async fn create_muxed_conn(self: &Arc, ufrag: &str) -> Result { + let local_addr = self.params.conn.local_addr().await?; + + let params = UDPMuxConnParams { + local_addr, + key: ufrag.into(), + udp_mux: Arc::clone(self), + }; + + Ok(UDPMuxConn::new(params)) + } + + async fn register_conn_for_address(&self, conn: &UDPMuxConn, addr: SocketAddr) { + if self.is_closed().await { + return; + } + + let key = conn.key(); + { + let mut addresses = self.address_map.write(); + + addresses + .entry(addr) + .and_modify(|e| { + if e.key() != key { + e.remove_address(&addr); + *e = conn.clone() + } + }) + .or_insert_with(|| conn.clone()); + } + + // remove addr from new_addrs once conn is established + { + let mut new_addrs = self.new_addrs.write(); + new_addrs.remove(&addr); + } + + log::debug!("Registered {} for {}", addr, key); + } + + async fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { + match ufrag_from_stun_message(buffer, true) { + Ok(ufrag) => { + let conns = self.conns.lock().await; + conns.get(&ufrag).map(Clone::clone) + }, + Err(e) => { + log::error!("{} (addr: {})", e, addr); + None + }, + } + } + + fn start_conn_worker( + self: Arc, + mut closed_watch_rx: watch::Receiver<()>, + mut new_addr_tx: mpsc::Sender, + ) { + tokio::spawn(async move { + let mut buffer = [0u8; RECEIVE_MTU]; + + loop { + let loop_self = Arc::clone(&self); + let conn = &loop_self.params.conn; + + tokio::select! { + res = conn.recv_from(&mut buffer) => { + match res { + Ok((len, addr)) => { + // Find connection based on previously having seen this source address + let conn = { + let address_map = loop_self + .address_map + .read(); + + address_map.get(&addr).map(Clone::clone) + }; + + let conn = match conn { + // If we couldn't find the connection based on source address, see if + // this is a STUN mesage and if so if we can find the connection based on ufrag. + None if is_stun_message(&buffer) => { + loop_self.conn_from_stun_message(&buffer, &addr).await + } + s @ Some(_) => s, + _ => None, + }; + + match conn { + None => { + if !loop_self.new_addrs.read().contains(&addr) { + match ufrag_from_stun_message(&buffer, false) { + Ok(ufrag) => { + log::trace!("Notifying about new address {} from {}", &addr, ufrag); + let a = Multiaddr::empty() + .with(addr.ip().into()) + .with(Protocol::Udp(addr.port())) + .with(Protocol::XWebRTC(hex_to_cow(&ufrag.replace(":", "")))); + if let Err(err) = new_addr_tx.try_send(a) { + log::error!("Failed to send new address {}: {}", &addr, err); + } else { + let mut new_addrs = loop_self.new_addrs.write(); + new_addrs.insert(addr.clone()); + }; + } + Err(e) => { + log::trace!("Unknown address {} (non STUN packet: {})", &addr, e); + } + } + } + } + Some(conn) => { + if let Err(err) = conn.write_packet(&buffer[..len], addr).await { + log::error!("Failed to write packet: {}", err); + } + } + } + } + Err(Error::Io(err)) if err.0.kind() == ErrorKind::TimedOut => continue, + Err(err) => { + log::error!("Could not read udp packet: {}", err); + break; + } + } + } + _ = closed_watch_rx.changed() => { + return; + } + } + } + }); + } +} + +#[async_trait] +impl UDPMux for UDPMuxNewAddr { + async fn close(&self) -> Result<(), Error> { + if self.is_closed().await { + return Err(Error::ErrAlreadyClosed); + } + + let mut closed_tx = self.closed_watch_tx.lock().await; + + if let Some(tx) = closed_tx.take() { + let _ = tx.send(()); + drop(closed_tx); + + let old_conns = { + let mut conns = self.conns.lock().await; + + std::mem::take(&mut (*conns)) + }; + + // NOTE: We don't wait for these closure to complete + for (_, conn) in old_conns.into_iter() { + conn.close(); + } + + { + let mut address_map = self.address_map.write(); + + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + let _ = std::mem::take(&mut (*address_map)); + } + + { + let mut new_addrs = self.new_addrs.write(); + + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + let _ = std::mem::take(&mut (*new_addrs)); + } + } + + Ok(()) + } + + async fn get_conn(self: Arc, ufrag: &str) -> Result, Error> { + if self.is_closed().await { + return Err(Error::ErrUseClosedNetworkConn); + } + + { + let mut conns = self.conns.lock().await; + if let Some(conn) = conns.get(ufrag) { + // UDPMuxConn uses `Arc` internally so it's cheap to clone, but because + // we implement `Conn` we need to further wrap it in an `Arc` here. + return Ok(Arc::new(conn.clone()) as Arc); + } + + let muxed_conn = self.create_muxed_conn(ufrag).await?; + let mut close_rx = muxed_conn.close_rx(); + let cloned_self = Arc::clone(&self); + let cloned_ufrag = ufrag.to_string(); + tokio::spawn(async move { + let _ = close_rx.changed().await; + + // Arc needed + cloned_self.remove_conn_by_ufrag(&cloned_ufrag).await; + }); + + conns.insert(ufrag.into(), muxed_conn.clone()); + + Ok(Arc::new(muxed_conn) as Arc) + } + } + + async fn remove_conn_by_ufrag(&self, ufrag: &str) { + // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` + // is keyed on `ufrag` their implementation is equivalent. + + let removed_conn = { + let mut conns = self.conns.lock().await; + conns.remove(ufrag) + }; + + if let Some(conn) = removed_conn { + let mut address_map = self.address_map.write(); + + for address in conn.get_addresses() { + address_map.remove(&address); + } + } + } +} + +fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { + let mut buf = [0; 32]; + hex::decode_to_slice(s, &mut buf).unwrap(); + Cow::Owned(buf) +} + +/// Gets the ufrag from the given STUN message or returns an error, if failed to decode or the +/// username attribute is not present. +fn ufrag_from_stun_message(buffer: &[u8], local_ufrag: bool) -> Result { + let (result, message) = { + let mut m = STUNMessage::new(); + + (m.unmarshal_binary(buffer), m) + }; + + match result { + Err(err) => Err(Error::Other(format!( + "failed to handle decode ICE: {}", + err + ))), + Ok(_) => { + let (attr, found) = message.attributes.get(ATTR_USERNAME); + if !found { + return Err(Error::Other("no username attribute in STUN message".into())); + } + + match String::from_utf8(attr.value) { + // Per the RFC this shouldn't happen + // https://datatracker.ietf.org/doc/html/rfc5389#section-15.3 + Err(err) => Err(Error::Other(format!( + "failed to decode USERNAME from STUN message as UTF-8: {}", + err + ))), + Ok(s) => { + // s is a combination of the local_ufrag and the remote ufrag separated by `:`. + let res = if local_ufrag { + s.split(":").next() + } else { + s.split(":").last() + }; + match res { + Some(s) => Ok(s.to_owned()), + None => Err(Error::Other("can't get ufrag from username".into())), + } + }, + } + }, + } +} diff --git a/transports/webrtc/src/udp_mux/socket_addr_ext.rs b/transports/webrtc/src/udp_mux/socket_addr_ext.rs new file mode 100644 index 00000000000..d1d06ceb3bc --- /dev/null +++ b/transports/webrtc/src/udp_mux/socket_addr_ext.rs @@ -0,0 +1,267 @@ +// MIT License +// +// Copyright (c) 2021 WebRTC.rs +// +// 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 std::array::TryFromSliceError; +use std::convert::TryInto; +use std::net::SocketAddr; + +use webrtc_util::Error; + +pub(super) trait SocketAddrExt { + ///Encode a representation of `self` into the buffer and return the length of this encoded + ///version. + /// + /// The buffer needs to be at least 27 bytes in length. + fn encode(&self, buffer: &mut [u8]) -> Result; + + /// Decode a `SocketAddr` from a buffer. The encoding should have previously been done with + /// [`SocketAddrExt::encode`]. + fn decode(buffer: &[u8]) -> Result; +} + +const IPV4_MARKER: u8 = 4; +const IPV4_ADDRESS_SIZE: usize = 7; +const IPV6_MARKER: u8 = 6; +const IPV6_ADDRESS_SIZE: usize = 27; + +pub(super) const MAX_ADDR_SIZE: usize = IPV6_ADDRESS_SIZE; + +impl SocketAddrExt for SocketAddr { + fn encode(&self, buffer: &mut [u8]) -> Result { + use std::net::SocketAddr::*; + + if buffer.len() < MAX_ADDR_SIZE { + return Err(Error::ErrBufferShort); + } + + match self { + V4(addr) => { + let marker = IPV4_MARKER; + let ip: [u8; 4] = addr.ip().octets(); + let port: u16 = addr.port(); + + buffer[0] = marker; + buffer[1..5].copy_from_slice(&ip); + buffer[5..7].copy_from_slice(&port.to_le_bytes()); + + Ok(7) + }, + V6(addr) => { + let marker = IPV6_MARKER; + let ip: [u8; 16] = addr.ip().octets(); + let port: u16 = addr.port(); + let flowinfo = addr.flowinfo(); + let scope_id = addr.scope_id(); + + buffer[0] = marker; + buffer[1..17].copy_from_slice(&ip); + buffer[17..19].copy_from_slice(&port.to_le_bytes()); + buffer[19..23].copy_from_slice(&flowinfo.to_le_bytes()); + buffer[23..27].copy_from_slice(&scope_id.to_le_bytes()); + + Ok(MAX_ADDR_SIZE) + }, + } + } + + fn decode(buffer: &[u8]) -> Result { + use std::net::*; + + match buffer[0] { + IPV4_MARKER => { + if buffer.len() < IPV4_ADDRESS_SIZE { + return Err(Error::ErrBufferShort); + } + + let ip_parts = &buffer[1..5]; + let port = match &buffer[5..7].try_into() { + Err(_) => return Err(Error::ErrFailedToParseIpaddr), + Ok(input) => u16::from_le_bytes(*input), + }; + + let ip = Ipv4Addr::new(ip_parts[0], ip_parts[1], ip_parts[2], ip_parts[3]); + + Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) + }, + IPV6_MARKER => { + if buffer.len() < IPV6_ADDRESS_SIZE { + return Err(Error::ErrBufferShort); + } + + // Just to help the type system infer correctly + fn helper(b: &[u8]) -> Result<&[u8; 16], TryFromSliceError> { + b.try_into() + } + + let ip = match helper(&buffer[1..17]) { + Err(_) => return Err(Error::ErrFailedToParseIpaddr), + Ok(input) => Ipv6Addr::from(*input), + }; + let port = match &buffer[17..19].try_into() { + Err(_) => return Err(Error::ErrFailedToParseIpaddr), + Ok(input) => u16::from_le_bytes(*input), + }; + + let flowinfo = match &buffer[19..23].try_into() { + Err(_) => return Err(Error::ErrFailedToParseIpaddr), + Ok(input) => u32::from_le_bytes(*input), + }; + + let scope_id = match &buffer[23..27].try_into() { + Err(_) => return Err(Error::ErrFailedToParseIpaddr), + Ok(input) => u32::from_le_bytes(*input), + }; + + Ok(SocketAddr::V6(SocketAddrV6::new( + ip, port, flowinfo, scope_id, + ))) + }, + _ => Err(Error::ErrFailedToParseIpaddr), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::net::*; + + #[test] + fn test_ipv4() { + let ip = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([56, 128, 35, 5]), 0x1234)); + + let mut buffer = [0_u8; MAX_ADDR_SIZE]; + let encoded_len = ip.encode(&mut buffer); + + assert_eq!(encoded_len, Ok(7)); + assert_eq!( + &buffer[0..7], + &[IPV4_MARKER, 56, 128, 35, 5, 0x34, 0x12][..] + ); + + let decoded = SocketAddr::decode(&buffer); + + assert_eq!(decoded, Ok(ip)); + } + + #[test] + fn test_ipv6() { + let ip = SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::from([ + 92, 114, 235, 3, 244, 64, 38, 111, 20, 100, 199, 241, 19, 174, 220, 123, + ]), + 0x1234, + 0x12345678, + 0x87654321, + )); + + let mut buffer = [0_u8; MAX_ADDR_SIZE]; + let encoded_len = ip.encode(&mut buffer); + + assert_eq!(encoded_len, Ok(27)); + assert_eq!( + &buffer[0..27], + &[ + IPV6_MARKER, // marker + // Start of ipv6 address + 92, + 114, + 235, + 3, + 244, + 64, + 38, + 111, + 20, + 100, + 199, + 241, + 19, + 174, + 220, + 123, + // LE port + 0x34, + 0x12, + // LE flowinfo + 0x78, + 0x56, + 0x34, + 0x12, + // LE scope_id + 0x21, + 0x43, + 0x65, + 0x87, + ][..] + ); + + let decoded = SocketAddr::decode(&buffer); + + assert_eq!(decoded, Ok(ip)); + } + + #[test] + fn test_encode_ipv4_with_short_buffer() { + let mut buffer = vec![0u8; IPV4_ADDRESS_SIZE - 1]; + let ip = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([56, 128, 35, 5]), 0x1234)); + + let result = ip.encode(&mut buffer); + + assert_eq!(result, Err(Error::ErrBufferShort)); + } + + #[test] + fn test_encode_ipv6_with_short_buffer() { + let mut buffer = vec![0u8; MAX_ADDR_SIZE - 1]; + let ip = SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::from([ + 92, 114, 235, 3, 244, 64, 38, 111, 20, 100, 199, 241, 19, 174, 220, 123, + ]), + 0x1234, + 0x12345678, + 0x87654321, + )); + + let result = ip.encode(&mut buffer); + + assert_eq!(result, Err(Error::ErrBufferShort)); + } + + #[test] + fn test_decode_ipv4_with_short_buffer() { + let buffer = vec![IPV4_MARKER, 0]; + + let result = SocketAddr::decode(&buffer); + + assert_eq!(result, Err(Error::ErrBufferShort)); + } + + #[test] + fn test_decode_ipv6_with_short_buffer() { + let buffer = vec![IPV6_MARKER, 0]; + + let result = SocketAddr::decode(&buffer); + + assert_eq!(result, Err(Error::ErrBufferShort)); + } +} diff --git a/transports/webrtc/src/udp_mux/udp_mux_conn.rs b/transports/webrtc/src/udp_mux/udp_mux_conn.rs new file mode 100644 index 00000000000..89d070b8d8d --- /dev/null +++ b/transports/webrtc/src/udp_mux/udp_mux_conn.rs @@ -0,0 +1,308 @@ +// MIT License +// +// Copyright (c) 2021 WebRTC.rs +// +// 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 std::convert::TryInto; +use std::{collections::HashSet, io, net::SocketAddr, sync::Arc}; + +use async_trait::async_trait; +use tokio_crate as tokio; +use tokio_crate::sync::watch; + +use webrtc_util::{sync::Mutex, Buffer, Conn, Error}; + +use super::socket_addr_ext::{SocketAddrExt, MAX_ADDR_SIZE}; +use super::{normalize_socket_addr, UDPMuxNewAddr, RECEIVE_MTU}; + +#[inline(always)] +/// Create a buffer of appropriate size to fit both a packet with max RECEIVE_MTU and the +/// additional metadata used for muxing. +fn make_buffer() -> Vec { + // The 4 extra bytes are used to encode the length of the data and address respectively. + // See [`write_packet`] for details. + vec![0u8; RECEIVE_MTU + MAX_ADDR_SIZE + 2 + 2] +} + +pub(crate) struct UDPMuxConnParams { + pub(super) local_addr: SocketAddr, + + pub(super) key: String, + + // NOTE: This Arc exists in both directions which is liable to cause a retain cycle. This is + // accounted for in [`UDPMuxNewAddr::close`], which makes sure to drop all Arcs referencing any + // `UDPMuxConn`. + pub(super) udp_mux: Arc, +} + +struct UDPMuxConnInner { + pub(super) params: UDPMuxConnParams, + + /// Close Sender. We'll send a value on this channel when we close + closed_watch_tx: Mutex>>, + + /// Remote addresses we've seen on this connection. + addresses: Mutex>, + + buffer: Buffer, +} + +impl UDPMuxConnInner { + // Sending/Recieving + async fn recv_from(&self, buf: &mut [u8]) -> ConnResult<(usize, SocketAddr)> { + // NOTE: Pion/ice uses Sync.Pool to optimise this. + let mut buffer = make_buffer(); + let mut offset = 0; + + let len = self.buffer.read(&mut buffer, None).await?; + // We always have at least. + // + // * 2 bytes for data len + // * 2 bytes for addr len + // * 7 bytes for an Ipv4 addr + if len < 11 { + return Err(Error::ErrBufferShort); + } + + let data_len: usize = buffer[..2] + .try_into() + .map(u16::from_le_bytes) + .map(From::from) + .unwrap(); + offset += 2; + + let total = 2 + data_len + 2 + 7; + if data_len > buf.len() || total > len { + return Err(Error::ErrBufferShort); + } + + buf[..data_len].copy_from_slice(&buffer[offset..offset + data_len]); + offset += data_len; + + let address_len: usize = buffer[offset..offset + 2] + .try_into() + .map(u16::from_le_bytes) + .map(From::from) + .unwrap(); + offset += 2; + + let addr = SocketAddr::decode(&buffer[offset..offset + address_len])?; + + Ok((data_len, addr)) + } + + async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> ConnResult { + self.params.udp_mux.send_to(buf, target).await + } + + fn is_closed(&self) -> bool { + self.closed_watch_tx.lock().is_none() + } + + fn close(self: &Arc) { + let mut closed_tx = self.closed_watch_tx.lock(); + + if let Some(tx) = closed_tx.take() { + let _ = tx.send(true); + drop(closed_tx); + + let cloned_self = Arc::clone(self); + + { + let mut addresses = self.addresses.lock(); + *addresses = Default::default(); + } + + // NOTE: Alternatively we could wait on the buffer closing here so that + // our caller can wait for things to fully settle down + tokio::spawn(async move { + cloned_self.buffer.close().await; + }); + } + } + + fn local_addr(&self) -> SocketAddr { + self.params.local_addr + } + + // Address related methods + pub(super) fn get_addresses(&self) -> Vec { + let addresses = self.addresses.lock(); + + addresses.iter().cloned().collect() + } + + pub(super) fn add_address(self: &Arc, addr: SocketAddr) { + { + let mut addresses = self.addresses.lock(); + addresses.insert(addr); + } + } + + pub(super) fn remove_address(&self, addr: &SocketAddr) { + { + let mut addresses = self.addresses.lock(); + addresses.remove(addr); + } + } + + pub(super) fn contains_address(&self, addr: &SocketAddr) -> bool { + let addresses = self.addresses.lock(); + + addresses.contains(addr) + } +} + +#[derive(Clone)] +pub(crate) struct UDPMuxConn { + /// Close Receiver. A copy of this can be obtained via [`close_tx`]. + closed_watch_rx: watch::Receiver, + + inner: Arc, +} + +impl UDPMuxConn { + pub(crate) fn new(params: UDPMuxConnParams) -> Self { + let (closed_watch_tx, closed_watch_rx) = watch::channel(false); + + Self { + closed_watch_rx, + inner: Arc::new(UDPMuxConnInner { + params, + closed_watch_tx: Mutex::new(Some(closed_watch_tx)), + addresses: Default::default(), + buffer: Buffer::new(0, 0), + }), + } + } + + pub(crate) fn key(&self) -> &str { + &self.inner.params.key + } + + pub(crate) async fn write_packet(&self, data: &[u8], addr: SocketAddr) -> ConnResult<()> { + // NOTE: Pion/ice uses Sync.Pool to optimise this. + let mut buffer = make_buffer(); + let mut offset = 0; + + if (data.len() + MAX_ADDR_SIZE) > (RECEIVE_MTU + MAX_ADDR_SIZE) { + return Err(Error::ErrBufferShort); + } + + // Format of buffer: | data len(2) | data bytes(dn) | addr len(2) | addr bytes(an) | + // Where the number in parenthesis indicate the number of bytes used + // `dn` and `an` are the length in bytes of data and addr respectively. + + // SAFETY: `data.len()` is at most RECEIVE_MTU(8192) - MAX_ADDR_SIZE(27) + buffer[0..2].copy_from_slice(&(data.len() as u16).to_le_bytes()[..]); + offset += 2; + + buffer[offset..offset + data.len()].copy_from_slice(data); + offset += data.len(); + + let len = addr.encode(&mut buffer[offset + 2..])?; + buffer[offset..offset + 2].copy_from_slice(&(len as u16).to_le_bytes()[..]); + offset += 2 + len; + + self.inner.buffer.write(&buffer[..offset]).await?; + + Ok(()) + } + + pub(crate) fn is_closed(&self) -> bool { + self.inner.is_closed() + } + + /// Get a copy of the close [`tokio::sync::watch::Receiver`] that fires when this + /// connection is closed. + pub(crate) fn close_rx(&self) -> watch::Receiver { + self.closed_watch_rx.clone() + } + + /// Close this connection + pub(crate) fn close(&self) { + self.inner.close(); + } + + pub(super) fn get_addresses(&self) -> Vec { + self.inner.get_addresses() + } + + pub(super) async fn add_address(&self, addr: SocketAddr) { + self.inner.add_address(addr); + self.inner + .params + .udp_mux + .register_conn_for_address(self, addr) + .await; + } + + pub(super) fn remove_address(&self, addr: &SocketAddr) { + self.inner.remove_address(addr) + } + + pub(super) fn contains_address(&self, addr: &SocketAddr) -> bool { + self.inner.contains_address(addr) + } +} + +type ConnResult = Result; + +#[async_trait] +impl Conn for UDPMuxConn { + async fn connect(&self, _addr: SocketAddr) -> ConnResult<()> { + Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) + } + + async fn recv(&self, _buf: &mut [u8]) -> ConnResult { + Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) + } + + async fn recv_from(&self, buf: &mut [u8]) -> ConnResult<(usize, SocketAddr)> { + self.inner.recv_from(buf).await + } + + async fn send(&self, _buf: &[u8]) -> ConnResult { + Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) + } + + async fn send_to(&self, buf: &[u8], target: SocketAddr) -> ConnResult { + let normalized_target = normalize_socket_addr(&target, &self.inner.params.local_addr); + + if !self.contains_address(&normalized_target) { + self.add_address(normalized_target).await; + } + + self.inner.send_to(buf, &normalized_target).await + } + + async fn local_addr(&self) -> ConnResult { + Ok(self.inner.local_addr()) + } + + async fn remote_addr(&self) -> Option { + None + } + async fn close(&self) -> ConnResult<()> { + self.inner.close(); + + Ok(()) + } +} diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs new file mode 100644 index 00000000000..ee5f09060c1 --- /dev/null +++ b/transports/webrtc/src/upgrade.rs @@ -0,0 +1,139 @@ +// Copyright 2022 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 futures::channel::oneshot; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use log::{debug, error, trace}; +use webrtc::api::APIBuilder; +use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::dtls_transport::dtls_role::DTLSRole; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc_data::data_channel::DataChannel as DetachedDataChannel; +use webrtc_ice::udp_mux::UDPMux; + +use std::sync::Arc; +use std::time::Duration; + +use crate::connection::Connection; +use crate::error::Error; +use crate::sdp; +use crate::transport; + +pub struct WebRTCUpgrade {} + +impl WebRTCUpgrade { + pub async fn new( + udp_mux: Arc, + config: RTCConfiguration, + addr: Multiaddr, + ) -> Result, Error> { + trace!("upgrading {}", addr); + + let socket_addr = transport::multiaddr_to_socketaddr(&addr) + .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; + let fingerprint = transport::fingerprint_of_first_certificate(&config); + + let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &fingerprint); + { + // Act as a lite ICE (ICE which does not send additional candidates). + se.set_lite(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server) + .map_err(Error::WebRTC)?; + } + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; + + // Create a datachannel with label 'data'. + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: Some(true), + id: Some(1), + ordered: None, + max_retransmits: None, + max_packet_life_time: None, + protocol: None, + }), + ) + .await?; + + let (data_channel_rx, data_channel_tx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + let d = Arc::clone(&data_channel); + data_channel + .on_open(Box::new(move || { + debug!("Data channel '{}'-'{}' open.", d.label(), d.id()); + + let d2 = Arc::clone(&d); + Box::pin(async move { + match d2.detach().await { + Ok(detached) => { + if let Err(_) = data_channel_rx.send(detached) { + error!("data_channel_tx dropped"); + } + }, + Err(e) => { + error!("Can't detach data channel: {}", e); + }, + }; + }) + })) + .await; + + // Set the remote description to the predefined SDP. + let fingerprint = match addr.iter().last() { + Some(Protocol::XWebRTC(f)) => f, + _ => { + debug!("{} is not a WebRTC multiaddr", addr); + return Err(Error::InvalidMultiaddr(addr)); + }, + }; + let client_session_description = transport::render_description( + sdp::CLIENT_SESSION_DESCRIPTION, + socket_addr, + &transport::fingerprint_to_string(&fingerprint), + ); + debug!("OFFER: {:?}", client_session_description); + let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + peer_connection.set_remote_description(sdp).await?; + + let answer = peer_connection.create_answer(None).await?; + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + debug!("ANSWER: {:?}", answer.sdp); + peer_connection.set_local_description(answer).await?; + + // wait until data channel is opened and ready to use + match tokio_crate::time::timeout(Duration::from_secs(10), data_channel_tx).await { + Ok(Ok(dc)) => Ok(Connection::new(peer_connection, dc)), + Ok(Err(e)) => Err(Error::InternalError(e.to_string())), + Err(_) => Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )), + } + } +} From 654fc88cb759068b754562a2823ea4ad2e0b6f98 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 May 2022 11:11:20 +0400 Subject: [PATCH 002/244] add a comment about TCP status --- .../webrtc/src/{connection/mod.rs => connection.rs} | 0 transports/webrtc/src/connection/poll_data_channel.rs | 6 +++--- transports/webrtc/src/lib.rs | 9 ++++++--- transports/webrtc/src/transport.rs | 10 +++++----- 4 files changed, 14 insertions(+), 11 deletions(-) rename transports/webrtc/src/{connection/mod.rs => connection.rs} (100%) diff --git a/transports/webrtc/src/connection/mod.rs b/transports/webrtc/src/connection.rs similarity index 100% rename from transports/webrtc/src/connection/mod.rs rename to transports/webrtc/src/connection.rs diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 14f56a53e9b..4fd3b104913 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -75,7 +75,7 @@ pub struct PollDataChannel<'a> { } impl PollDataChannel<'_> { - /// Constructs a new `PollDataChannel`. + /// Constructs a new [`PollDataChannel`]. pub fn new(data_channel: Arc) -> Self { Self { data_channel, @@ -110,7 +110,7 @@ impl AsyncRead for PollDataChannel<'_> { ) -> Poll> { let fut = match self.read_fut { ReadFut::Idle => { - // read into a temporary buffer because `buf` has an unonymous lifetime, which can + // Read into a temporary buffer because `buf` has an unonymous lifetime, which can // be shorter than the lifetime of `read_fut`. let dc = self.data_channel.clone(); let mut temp_buf = vec![0; self.read_buf_cap]; @@ -144,7 +144,7 @@ impl AsyncRead for PollDataChannel<'_> { loop { match fut.as_mut().poll(cx) { Poll::Pending => return Poll::Pending, - // retry immediately upon empty data or incomplete chunks + // Retry immediately upon empty data or incomplete chunks // since there's no way to setup a waker. Poll::Ready(Err(Error::Sctp(webrtc_sctp::Error::ErrTryAgain))) => {}, // EOF has been reached => don't touch buf and just return Ok diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 52398e62d9a..0ee9ef1463c 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -18,15 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the [`Transport`] trait for WebRTC direct protocol. "direct" here means -//! communicating without a signaling server. +//! Implementation of the [`Transport`] trait for WebRTC protocol without a signaling server. //! //! # Overview //! //! ## ICE //! //! RFCs: 8839, 8445 See also: -//! https://tools.ietf.org/id/draft-ietf-rtcweb-sdp-08.html#rfc.section.5.2.3 +//! //! //! The WebRTC protocol uses ICE in order to establish a connection. //! @@ -70,6 +69,10 @@ //! the SDP message of the offerer that specifies which protocol to use. In our use case, one data //! stream, we know that the offerer will always request either TCP+DTLS+SCTP, or UDP+DTLS+SCTP. //! +//! The implementation only supports UDP at the moment, so if the offerer requests TCP+DTLS+SCTP, it +//! will not respond. Support for TCP may be added in the future (see +//! ). +//! //! ## DTLS+SCTP //! //! RFCs: 8841, 8832 diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 530e806dac0..5497b4b0054 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -100,12 +100,12 @@ impl WebRTCTransport { certificate: RTCCertificate, listen_addr: A, ) -> Result> { - // bind to `listen_addr` and construct a UDP mux. + // Bind to `listen_addr` and construct a UDP mux. let socket = UdpSocket::bind(listen_addr) .map_err(Error::IoError) .map_err(TransportError::Other) .await?; - // sender and receiver for new addresses + // Sender and receiver for new addresses let (new_addr_tx, new_addr_rx) = mpsc::channel(1); let udp_mux_addr = socket .local_addr() @@ -315,7 +315,7 @@ impl Stream for WebRTCListenStream { } } - // safe to unwrap here since this is the only place `new_addr_rx` is locked. + // Safe to unwrap here since this is the only place `new_addr_rx` is locked. return match Pin::new(&mut *me.new_addr_rx.lock().unwrap()).poll_next(cx) { Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), @@ -425,7 +425,7 @@ impl WebRTCTransport { .map_err(Error::WebRTC) .await?; - // wait until data channel is opened and ready to use + // Wait until data channel is opened and ready to use match tokio_crate::time::timeout(Duration::from_secs(10), data_channel_tx).await { Ok(Ok(dc)) => Ok(Connection::new(peer_connection, dc)), Ok(Err(e)) => Err(Error::InternalError(e.to_string())), @@ -457,7 +457,7 @@ pub(crate) fn render_description(description: &str, addr: SocketAddr, fingerprin }, target_ip: addr.ip(), target_port: addr.port(), - // hashing algorithm (SHA-256) is hardcoded for now + // Hashing algorithm (SHA-256) is hardcoded for now fingerprint: fingerprint.to_owned(), // ufrag and pwd are both equal to the fingerprint (minus the `:` delimiter) ufrag: f.clone(), From 7754e11b2c30bbc261cfb136fe9aca1b317e87fb Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 May 2022 13:25:37 +0400 Subject: [PATCH 003/244] remove lifetime from ReadFut also - make upgrade a fn - use select!+Delay instead of tokio_crate::timeout --- .../src/connection/poll_data_channel.rs | 50 +++--- transports/webrtc/src/transport.rs | 90 +++++----- transports/webrtc/src/upgrade.rs | 161 +++++++++--------- 3 files changed, 153 insertions(+), 148 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 4fd3b104913..e41a46f58e5 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -34,16 +34,16 @@ use std::task::{Context, Poll}; const DEFAULT_READ_BUF_SIZE: usize = 4096; /// State of the read `Future` in [`PollStream`]. -enum ReadFut<'a> { +enum ReadFut { /// Nothing in progress. Idle, /// Reading data from the underlying stream. - Reading(Pin, Error>> + Send + 'a>>), + Reading(Pin, Error>> + Send + 'static>>), /// Finished reading, but there's unread data in the temporary buffer. RemainingData(Vec), } -impl<'a> ReadFut<'a> { +impl ReadFut { /// Gets a mutable reference to the future stored inside `Reading(future)`. /// /// # Panics @@ -51,7 +51,7 @@ impl<'a> ReadFut<'a> { /// Panics if `ReadFut` variant is not `Reading`. fn get_reading_mut( &mut self, - ) -> &mut Pin, Error>> + Send + 'a>> { + ) -> &mut Pin, Error>> + Send + 'static>> { match self { ReadFut::Reading(ref mut fut) => fut, _ => panic!("expected ReadFut to be Reading"), @@ -59,15 +59,14 @@ impl<'a> ReadFut<'a> { } } -/// A wrapper around around [`DataChannel`], which implements [`AsyncRead`] and -/// [`AsyncWrite`]. +/// A wrapper around [`DataChannel`], which implements [`AsyncRead`] and [`AsyncWrite`]. /// /// Both `poll_read` and `poll_write` calls allocate temporary buffers, which results in an /// additional overhead. pub struct PollDataChannel<'a> { data_channel: Arc, - read_fut: ReadFut<'a>, + read_fut: ReadFut, write_fut: Option> + Send + 'a>>>, shutdown_fut: Option> + Send + 'a>>>, @@ -115,14 +114,10 @@ impl AsyncRead for PollDataChannel<'_> { let dc = self.data_channel.clone(); let mut temp_buf = vec![0; self.read_buf_cap]; self.read_fut = ReadFut::Reading(Box::pin(async move { - let res = dc.read(temp_buf.as_mut_slice()).await; - match res { - Ok(n) => { - temp_buf.truncate(n); - Ok(temp_buf) - }, - Err(e) => Err(e), - } + dc.read(temp_buf.as_mut_slice()).await.map(|n| { + temp_buf.truncate(n); + temp_buf + }) })); self.read_fut.get_reading_mut() }, @@ -239,14 +234,10 @@ impl AsyncWrite for PollDataChannel<'_> { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let fut = match self.shutdown_fut.as_mut() { - Some(fut) => fut, - None => { - let dc = self.data_channel.clone(); - self.shutdown_fut - .get_or_insert(Box::pin(async move { dc.close().await })) - }, - }; + let dc = self.data_channel.clone(); + let fut = self + .shutdown_fut + .get_or_insert_with(|| Box::pin(async move { dc.close().await })); match fut.as_mut().poll(cx) { Poll::Pending => Poll::Pending, @@ -276,14 +267,15 @@ impl AsRef for PollDataChannel<'_> { } } -fn webrtc_error_to_io(error: Error) -> io::Error { +fn webrtc_error_to_io(err: Error) -> io::Error { + let err_str = err.to_string(); match error { - e @ Error::Sctp(webrtc_sctp::Error::ErrEof) => { - io::Error::new(io::ErrorKind::UnexpectedEof, e.to_string()) + Error::Sctp(webrtc_sctp::Error::ErrEof) => { + io::Error::new(io::ErrorKind::UnexpectedEof, err_str) }, - e @ Error::ErrStreamClosed => { - io::Error::new(io::ErrorKind::ConnectionAborted, e.to_string()) + Error::ErrStreamClosed => { + io::Error::new(io::ErrorKind::ConnectionAborted, err_str) }, - e => io::Error::new(io::ErrorKind::Other, e.to_string()), + _ => io::Error::new(io::ErrorKind::Other, err_str), } } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 5497b4b0054..150237aeca3 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -29,6 +29,7 @@ use futures::{ future::BoxFuture, prelude::*, ready, TryFutureExt, + select, }; use futures_timer::Delay; use if_watch::{IfEvent, IfWatcher}; @@ -61,7 +62,7 @@ use crate::sdp; use crate::connection::Connection; use crate::udp_mux::UDPMuxNewAddr; use crate::udp_mux::UDPMuxParams; -use crate::upgrade::WebRTCUpgrade; +use crate::upgrade; enum IfWatch { Pending(BoxFuture<'static, io::Result>), @@ -241,7 +242,7 @@ impl Stream for WebRTCListenStream { Ok(w) => { *if_watch = IfWatch::Ready(w); continue; - }, + } Err(err) => { debug! { "Failed to begin observing interfaces: {:?}. Scheduling retry.", @@ -252,7 +253,7 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error(Error::IoError( err, ))))); - }, + } }, // Consume all events for up/down interface changes. IfWatch::Ready(watch) => { @@ -269,7 +270,7 @@ impl Stream for WebRTCListenStream { ma, )))); } - }, + } Ok(IfEvent::Down(inet)) => { let ip = inet.addr(); if me.listen_addr.is_ipv4() == ip.is_ipv4() @@ -281,7 +282,7 @@ impl Stream for WebRTCListenStream { ListenerEvent::AddressExpired(ma), ))); } - }, + } Err(err) => { debug! { "Failure polling interfaces: {:?}. Scheduling retry.", @@ -291,10 +292,10 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error( Error::IoError(err), )))); - }, + } } } - }, + } }, // If the listener is bound to a single interface, make sure the address reported // once. @@ -302,16 +303,16 @@ impl Stream for WebRTCListenStream { if let Some(multiaddr) = out.take() { return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(multiaddr)))); } - }, + } } if let Some(mut pause) = me.pause.take() { match Pin::new(&mut pause).poll(cx) { - Poll::Ready(_) => {}, + Poll::Ready(_) => {} Poll::Pending => { me.pause = Some(pause); return Poll::Pending; - }, + } } } @@ -320,11 +321,8 @@ impl Stream for WebRTCListenStream { Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), remote_addr: addr.clone(), - upgrade: Box::pin(WebRTCUpgrade::new( - me.udp_mux.clone(), - me.config.clone(), - addr, - )) as BoxFuture<'static, _>, + upgrade: Box::pin(upgrade::webrtc(me.udp_mux.clone(), me.config.clone(), addr)) + as BoxFuture<'static, _>, }))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, @@ -370,28 +368,34 @@ impl WebRTCTransport { ) .await?; - let (data_channel_rx, data_channel_tx) = oneshot::channel::>(); + let (data_channel_rx, mut data_channel_tx) = oneshot::channel::>(); // Wait until the data channel is opened and detach it. - let d = Arc::clone(&data_channel); data_channel - .on_open(Box::new(move || { - debug!("Data channel '{}'-'{}' open.", d.label(), d.id()); - - let d2 = Arc::clone(&d); - Box::pin(async move { - match d2.detach().await { - Ok(detached) => { - if let Err(_) = data_channel_rx.send(detached) { - error!("data_channel_tx dropped"); - } - }, - Err(e) => { - error!("Can't detach data channel: {}", e); - }, - }; + .on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + debug!( + "Data channel '{}'-'{}' open.", + data_channel.label(), + data_channel.id() + ); + + Box::pin(async move { + let data_channel = data_channel.clone(); + match data_channel.detach().await { + Ok(detached) => { + if let Err(_) = data_channel_rx.send(detached) { + error!("data_channel_tx dropped"); + } + }, + Err(e) => { + error!("Can't detach data channel: {}", e); + }, + }; + }) }) - })) + }) .await; let offer = peer_connection @@ -409,7 +413,7 @@ impl WebRTCTransport { Some(Protocol::XWebRTC(f)) => f, _ => { return Err(Error::InvalidMultiaddr(addr)); - }, + } }; let server_session_description = render_description( sdp::SERVER_SESSION_DESCRIPTION, @@ -426,12 +430,14 @@ impl WebRTCTransport { .await?; // Wait until data channel is opened and ready to use - match tokio_crate::time::timeout(Duration::from_secs(10), data_channel_tx).await { - Ok(Ok(dc)) => Ok(Connection::new(peer_connection, dc)), - Ok(Err(e)) => Err(Error::InternalError(e.to_string())), - Err(_) => Err(Error::InternalError( + select! { + res = data_channel_tx => match res { + Ok(dc) => Ok(Connection::new(peer_connection, dc)), + Err(e) => Err(Error::InternalError(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( "data channel opening took longer than 10 seconds (see logs)".into(), - )), + )) } } } @@ -476,7 +482,7 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { while let Some(proto) = iter.next() { match proto { - Protocol::P2p(_) => {}, // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. _ => return None, } } @@ -484,10 +490,10 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { match (proto1, proto2, proto3) { (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - }, + } (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - }, + } _ => None, } } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index ee5f09060c1..e7bf6320efc 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -19,6 +19,9 @@ // DEALINGS IN THE SOFTWARE. use futures::channel::oneshot; +use futures::select; +use futures::FutureExt; +use futures_timer::Delay; use libp2p_core::multiaddr::{Multiaddr, Protocol}; use log::{debug, error, trace}; use webrtc::api::APIBuilder; @@ -37,60 +40,62 @@ use crate::error::Error; use crate::sdp; use crate::transport; -pub struct WebRTCUpgrade {} +pub async fn webrtc( + udp_mux: Arc, + config: RTCConfiguration, + addr: Multiaddr, +) -> Result, Error> { + trace!("upgrading {}", addr); -impl WebRTCUpgrade { - pub async fn new( - udp_mux: Arc, - config: RTCConfiguration, - addr: Multiaddr, - ) -> Result, Error> { - trace!("upgrading {}", addr); + let socket_addr = transport::multiaddr_to_socketaddr(&addr) + .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; + let fingerprint = transport::fingerprint_of_first_certificate(&config); - let socket_addr = transport::multiaddr_to_socketaddr(&addr) - .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; - let fingerprint = transport::fingerprint_of_first_certificate(&config); - - let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &fingerprint); - { - // Act as a lite ICE (ICE which does not send additional candidates). - se.set_lite(true); - // Act as a DTLS server (one which waits for a connection). - // - // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, - // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server) - .map_err(Error::WebRTC)?; + let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &fingerprint); + { + // Act as a lite ICE (ICE which does not send additional candidates). + se.set_lite(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server) + .map_err(Error::WebRTC)?; } - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; - // Create a datachannel with label 'data'. - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: Some(true), - id: Some(1), - ordered: None, - max_retransmits: None, - max_packet_life_time: None, - protocol: None, - }), - ) - .await?; + // Create a datachannel with label 'data'. + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: Some(true), + id: Some(1), + ordered: None, + max_retransmits: None, + max_packet_life_time: None, + protocol: None, + }), + ) + .await?; - let (data_channel_rx, data_channel_tx) = oneshot::channel::>(); + let (data_channel_rx, mut data_channel_tx) = oneshot::channel::>(); - // Wait until the data channel is opened and detach it. - let d = Arc::clone(&data_channel); - data_channel - .on_open(Box::new(move || { - debug!("Data channel '{}'-'{}' open.", d.label(), d.id()); + // Wait until the data channel is opened and detach it. + data_channel + .on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + debug!( + "Data channel '{}'-'{}' open.", + data_channel.label(), + data_channel.id() + ); - let d2 = Arc::clone(&d); Box::pin(async move { - match d2.detach().await { + let data_channel = data_channel.clone(); + match data_channel.detach().await { Ok(detached) => { if let Err(_) = data_channel_rx.send(detached) { error!("data_channel_tx dropped"); @@ -101,39 +106,41 @@ impl WebRTCUpgrade { }, }; }) - })) - .await; + }) + }) + .await; - // Set the remote description to the predefined SDP. - let fingerprint = match addr.iter().last() { - Some(Protocol::XWebRTC(f)) => f, - _ => { - debug!("{} is not a WebRTC multiaddr", addr); - return Err(Error::InvalidMultiaddr(addr)); - }, - }; - let client_session_description = transport::render_description( - sdp::CLIENT_SESSION_DESCRIPTION, - socket_addr, - &transport::fingerprint_to_string(&fingerprint), - ); - debug!("OFFER: {:?}", client_session_description); - let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); - peer_connection.set_remote_description(sdp).await?; + // Set the remote description to the predefined SDP. + let fingerprint = match addr.iter().last() { + Some(Protocol::XWebRTC(f)) => f, + _ => { + debug!("{} is not a WebRTC multiaddr", addr); + return Err(Error::InvalidMultiaddr(addr)); + }, + }; + let client_session_description = transport::render_description( + sdp::CLIENT_SESSION_DESCRIPTION, + socket_addr, + &transport::fingerprint_to_string(&fingerprint), + ); + debug!("OFFER: {:?}", client_session_description); + let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + peer_connection.set_remote_description(sdp).await?; - let answer = peer_connection.create_answer(None).await?; - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - debug!("ANSWER: {:?}", answer.sdp); - peer_connection.set_local_description(answer).await?; + let answer = peer_connection.create_answer(None).await?; + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + debug!("ANSWER: {:?}", answer.sdp); + peer_connection.set_local_description(answer).await?; - // wait until data channel is opened and ready to use - match tokio_crate::time::timeout(Duration::from_secs(10), data_channel_tx).await { - Ok(Ok(dc)) => Ok(Connection::new(peer_connection, dc)), - Ok(Err(e)) => Err(Error::InternalError(e.to_string())), - Err(_) => Err(Error::InternalError( - "data channel opening took longer than 10 seconds (see logs)".into(), - )), - } + // wait until data channel is opened and ready to use + select! { + res = data_channel_tx => match res { + Ok(dc) => Ok(Connection::new(peer_connection, dc)), + Err(e) => Err(Error::InternalError(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) } } From 0b47656231cf89f7fdcb2da9e617a5e4a622d1bf Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 May 2022 15:12:14 +0400 Subject: [PATCH 004/244] use ready macro --- .../src/connection/poll_data_channel.rs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index e41a46f58e5..e444cea83a8 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -21,6 +21,7 @@ use bytes::Bytes; use futures::prelude::*; +use futures::ready; use webrtc_data::data_channel::DataChannel; use webrtc_data::Error; @@ -137,21 +138,20 @@ impl AsyncRead for PollDataChannel<'_> { }; loop { - match fut.as_mut().poll(cx) { - Poll::Pending => return Poll::Pending, + match ready!(fut.as_mut().poll(cx)) { // Retry immediately upon empty data or incomplete chunks // since there's no way to setup a waker. - Poll::Ready(Err(Error::Sctp(webrtc_sctp::Error::ErrTryAgain))) => {}, + Err(Error::Sctp(webrtc_sctp::Error::ErrTryAgain)) => {}, // EOF has been reached => don't touch buf and just return Ok - Poll::Ready(Err(Error::Sctp(webrtc_sctp::Error::ErrEof))) => { + Err(Error::Sctp(webrtc_sctp::Error::ErrEof)) => { self.read_fut = ReadFut::Idle; return Poll::Ready(Ok(0)); }, - Poll::Ready(Err(e)) => { + Err(e) => { self.read_fut = ReadFut::Idle; return Poll::Ready(Err(webrtc_error_to_io(e))); }, - Poll::Ready(Ok(mut temp_buf)) => { + Ok(mut temp_buf) => { let remaining = buf.len(); let len = std::cmp::min(temp_buf.len(), remaining); buf.copy_from_slice(&temp_buf[..len]); @@ -218,13 +218,12 @@ impl AsyncWrite for PollDataChannel<'_> { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.write_fut.as_mut() { - Some(fut) => match fut.as_mut().poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { + Some(fut) => match ready!(fut.as_mut().poll(cx)) { + Err(e) => { self.write_fut = None; Poll::Ready(Err(webrtc_error_to_io(e))) }, - Poll::Ready(Ok(_)) => { + Ok(_) => { self.write_fut = None; Poll::Ready(Ok(())) }, @@ -239,10 +238,9 @@ impl AsyncWrite for PollDataChannel<'_> { .shutdown_fut .get_or_insert_with(|| Box::pin(async move { dc.close().await })); - match fut.as_mut().poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => Poll::Ready(Err(webrtc_error_to_io(e))), - Poll::Ready(Ok(_)) => Poll::Ready(Ok(())), + match ready!(fut.as_mut().poll(cx)) { + Err(e) => Poll::Ready(Err(webrtc_error_to_io(e))), + Ok(_) => Poll::Ready(Ok(())), } } } From 06bb28721f6f5b491fc84fb2f1ec3a6e85862942 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 May 2022 16:18:28 +0400 Subject: [PATCH 005/244] remove static keyword --- transports/webrtc/src/connection/poll_data_channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index e444cea83a8..b066ba58f9f 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -39,7 +39,7 @@ enum ReadFut { /// Nothing in progress. Idle, /// Reading data from the underlying stream. - Reading(Pin, Error>> + Send + 'static>>), + Reading(Pin, Error>> + Send>>), /// Finished reading, but there's unread data in the temporary buffer. RemainingData(Vec), } @@ -52,7 +52,7 @@ impl ReadFut { /// Panics if `ReadFut` variant is not `Reading`. fn get_reading_mut( &mut self, - ) -> &mut Pin, Error>> + Send + 'static>> { + ) -> &mut Pin, Error>> + Send>> { match self { ReadFut::Reading(ref mut fut) => fut, _ => panic!("expected ReadFut to be Reading"), From 8578225c27d6cea5cbc603af9f50844fd97e2e44 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 20 Jun 2022 11:56:32 +0400 Subject: [PATCH 006/244] implement StreamMuxer trait for Connection Copied from https://github.com/melekes/libp2p-webrtc-direct/pull/2. This code does not yet compiles because of `XWebRTC` protocol. if you want to see the working version, please visit https://github.com/melekes/libp2p-webrtc-direct. --- transports/webrtc/Cargo.toml | 28 +- transports/webrtc/src/connection.rs | 315 ++++++++++++++++-- .../src/connection/poll_data_channel.rs | 275 ++++----------- transports/webrtc/src/error.rs | 13 + transports/webrtc/src/transport.rs | 267 +++++++++------ transports/webrtc/src/udp_mux.rs | 151 ++++----- .../webrtc/src/udp_mux/socket_addr_ext.rs | 267 --------------- transports/webrtc/src/udp_mux/udp_mux_conn.rs | 308 ----------------- transports/webrtc/src/upgrade.rs | 152 +++++---- transports/webrtc/tests/smoke.rs | 305 +++++++++++++++++ 10 files changed, 1014 insertions(+), 1067 deletions(-) delete mode 100644 transports/webrtc/src/udp_mux/socket_addr_ext.rs delete mode 100644 transports/webrtc/src/udp_mux/udp_mux_conn.rs create mode 100644 transports/webrtc/tests/smoke.rs diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 9e4d6254016..2335d007e1d 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -8,28 +8,38 @@ license = "" edition = "2021" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] -rust-version = "1.56.1" [dependencies] +async-trait = "0.1.52" bytes = "1" -env_logger = "0.9.0" +fnv = "1.0" futures = "0.3.17" +futures-lite = "1.12.0" +futures-timer = "3.0" hex = "0.4" +if-watch = "0.2.2" libp2p-core = { version = "0.33.0", path = "../../core", default-features = false } +libp2p-noise = { version = "0.36.0", path = "../../transports/noise" } log = "0.4.14" serde = { version = "1.0", features = ["derive"] } +stun = "0.4.2" thiserror = "1" tinytemplate = "1.2" -tokio-crate = { package = "tokio", version = "1.17.0", default-features = false, features = ["net"]} +tokio-crate = { package = "tokio", version = "1.18.2", features = ["net"]} webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc", branch = "anton/168-allow-persistent-certificates" } -webrtc-ice = "0.6.6" webrtc-data = "0.3.3" -webrtc-sctp = "0.4.3" -if-watch = "0.2.2" -futures-timer = "3.0" -stun = "0.4.2" +webrtc-ice = "0.7.0" +webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } -async-trait = "0.1.52" [dev-dependencies] +anyhow = "1.0.41" +env_logger = "0.9.0" +libp2p-request-response = { version = "0.18.0", path = "../../protocols/request-response" } +libp2p-swarm = { version = "0.36.0", path = "../../swarm" } +rand = "0.8.4" +rand_core = "0.5.1" rcgen = "0.8.14" + +[patch.crates-io] +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 58d0b12b623..73c4fe97e5c 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -20,59 +20,316 @@ mod poll_data_channel; -use futures::prelude::*; +use fnv::FnvHashMap; +use futures::channel::mpsc; +use futures::channel::oneshot::{self, Sender}; +use futures::lock::Mutex as FutMutex; +use futures::{future::BoxFuture, prelude::*, ready}; +use futures_lite::stream::StreamExt; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use log::{debug, error, trace}; +use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; -use webrtc_data::data_channel::DataChannel; +use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use std::io; use std::pin::Pin; -use std::sync::Arc; +use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; -use poll_data_channel::PollDataChannel; +pub(crate) use poll_data_channel::PollDataChannel; -/// A WebRTC connection over a single data channel. See lib documentation for -/// the reasoning as to why a single data channel is being used. -pub struct Connection<'a> { +/// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. +pub struct Connection { + connection_inner: Arc>, // uses futures mutex because used in async code (see open_outbound) + data_channels_inner: StdMutex, +} + +struct ConnectionInner { /// `RTCPeerConnection` to the remote peer. - pub inner: RTCPeerConnection, - /// A data channel. - pub data_channel: PollDataChannel<'a>, + rtc_conn: RTCPeerConnection, +} + +struct DataChannelsInner { + /// A map of data channels. + map: FnvHashMap, + /// Channel onto which incoming data channels are put. + incoming_data_channels_rx: mpsc::Receiver>, + /// Temporary read buffer's capacity (equal for all data channels). + /// See [`PollDataChannel`] `read_buf_cap`. + read_buf_cap: Option, } -impl Connection<'_> { - pub fn new(peer_conn: RTCPeerConnection, data_channel: Arc) -> Self { +impl Connection { + /// Creates a new connection. + pub async fn new(rtc_conn: RTCPeerConnection) -> Self { + let (data_channel_tx, data_channel_rx) = mpsc::channel(10); + + Connection::register_incoming_data_channels_handler(&rtc_conn, data_channel_tx).await; + Self { - inner: peer_conn, - data_channel: PollDataChannel::new(data_channel), + connection_inner: Arc::new(FutMutex::new(ConnectionInner { rtc_conn })), + data_channels_inner: StdMutex::new(DataChannelsInner { + map: FnvHashMap::default(), + incoming_data_channels_rx: data_channel_rx, + read_buf_cap: None, + }), } } + + /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). + pub fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + data_channels_inner.read_buf_cap = Some(cap); + } + + /// Registers a handler for incoming data channels. + async fn register_incoming_data_channels_handler( + rtc_conn: &RTCPeerConnection, + tx: mpsc::Sender>, + ) { + rtc_conn + .on_data_channel(Box::new(move |data_channel: Arc| { + debug!( + "Incoming data channel '{}'-'{}'", + data_channel.label(), + data_channel.id() + ); + + let data_channel = data_channel.clone(); + let mut tx = tx.clone(); + + Box::pin(async move { + data_channel + .on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + debug!( + "Data channel '{}'-'{}' open", + data_channel.label(), + data_channel.id() + ); + + Box::pin(async move { + let data_channel = data_channel.clone(); + match data_channel.detach().await { + Ok(detached) => { + if let Err(e) = tx.try_send(detached) { + // This can happen if the client is not reading + // events (using `poll_event`) fast enough, which + // generally shouldn't be the case. + error!("Can't send data channel: {}", e); + } + }, + Err(e) => { + error!("Can't detach data channel: {}", e); + }, + }; + }) + }) + }) + .await; + }) + })) + .await; + } } -impl AsyncRead for Connection<'_> { - fn poll_read( - mut self: Pin<&mut Self>, +impl<'a> StreamMuxer for Connection { + type Substream = PollDataChannel; + type OutboundSubstream = BoxFuture<'static, Result, Self::Error>>; + type Error = io::Error; + + fn poll_event( + &self, cx: &mut Context<'_>, + ) -> Poll, Self::Error>> { + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + match ready!(data_channels_inner.incoming_data_channels_rx.poll_next(cx)) { + Some(detached) => { + trace!("Incoming substream {}", detached.stream_identifier()); + + let ch = PollDataChannel::new(detached); + if let Some(cap) = data_channels_inner.read_buf_cap { + ch.set_read_buf_capacity(cap); + } + + data_channels_inner + .map + .insert(ch.stream_identifier(), ch.clone()); + + Poll::Ready(Ok(StreamMuxerEvent::InboundSubstream(ch))) + }, + None => Poll::Ready(Err(io::Error::new( + io::ErrorKind::Other, + "incoming_data_channels_rx is closed (no messages left)", + ))), + } + } + + fn open_outbound(&self) -> Self::OutboundSubstream { + let connection_inner = self.connection_inner.clone(); + + Box::pin(async move { + let connection_inner = connection_inner.lock().await; + + // Create a datachannel with label 'data' + let data_channel = connection_inner + .rtc_conn + .create_data_channel("data", None) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("webrtc error: {}", e))) + .await?; + + trace!("Opening outbound substream {}", data_channel.id()); + + // No need to hold the lock during the DTLS handshake. + drop(connection_inner); + + let (tx, rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + register_data_channel_open_handler(data_channel, tx).await; + + // Wait until data channel is opened and ready to use + match rx.await { + Ok(detached) => Ok(detached), + Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())), + } + }) + } + + fn poll_outbound( + &self, + cx: &mut Context<'_>, + s: &mut Self::OutboundSubstream, + ) -> Poll> { + match ready!(s.as_mut().poll(cx)) { + Ok(detached) => { + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + + let ch = PollDataChannel::new(detached); + if let Some(cap) = data_channels_inner.read_buf_cap { + ch.set_read_buf_capacity(cap); + } + + data_channels_inner + .map + .insert(ch.stream_identifier(), ch.clone()); + + Poll::Ready(Ok(ch)) + }, + Err(e) => Poll::Ready(Err(e)), + } + } + + /// NOTE: `_s` might be waiting at one of the await points, and dropping the future will + /// abruptly interrupt the execution. + fn destroy_outbound(&self, _s: Self::OutboundSubstream) {} + + fn read_substream( + &self, + cx: &mut Context<'_>, + s: &mut Self::Substream, buf: &mut [u8], - ) -> Poll> { - Pin::new(&mut self.data_channel).poll_read(cx, buf) + ) -> Poll> { + Pin::new(s).poll_read(cx, buf) } -} -impl AsyncWrite for Connection<'_> { - fn poll_write( - mut self: Pin<&mut Self>, + fn write_substream( + &self, cx: &mut Context<'_>, + s: &mut Self::Substream, buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.data_channel).poll_write(cx, buf) + ) -> Poll> { + Pin::new(s).poll_write(cx, buf) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.data_channel).poll_flush(cx) + fn flush_substream( + &self, + cx: &mut Context<'_>, + s: &mut Self::Substream, + ) -> Poll> { + Pin::new(s).poll_flush(cx) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.data_channel).poll_close(cx) + fn shutdown_substream( + &self, + cx: &mut Context<'_>, + s: &mut Self::Substream, + ) -> Poll> { + trace!("Closing substream {}", s.stream_identifier()); + Pin::new(s).poll_close(cx) } + + fn destroy_substream(&self, s: Self::Substream) { + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + data_channels_inner.map.remove(&s.stream_identifier()); + } + + fn close(&self, cx: &mut Context<'_>) -> Poll> { + debug!("Closing connection"); + + // First, flush all the buffered data. + match ready!(self.flush_all(cx)) { + Ok(_) => { + // Second, shutdown all the substreams. + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + for (_, ch) in &mut data_channels_inner.map { + match ready!(self.shutdown_substream(cx, ch)) { + Ok(_) => continue, + Err(e) => return Poll::Ready(Err(e)), + } + } + + // Third, close `incoming_data_channels_rx` + data_channels_inner.incoming_data_channels_rx.close(); + + Poll::Ready(Ok(())) + }, + Err(e) => Poll::Ready(Err(e)), + } + } + + fn flush_all(&self, cx: &mut Context<'_>) -> Poll> { + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + for (_, ch) in &mut data_channels_inner.map { + match ready!(self.flush_substream(cx, ch)) { + Ok(_) => continue, + Err(e) => return Poll::Ready(Err(e)), + } + } + Poll::Ready(Ok(())) + } +} + +pub(crate) async fn register_data_channel_open_handler( + data_channel: Arc, + data_channel_tx: Sender>, +) { + data_channel + .on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + debug!( + "Data channel '{}'-'{}' open", + data_channel.label(), + data_channel.id() + ); + + Box::pin(async move { + let data_channel = data_channel.clone(); + match data_channel.detach().await { + Ok(detached) => { + if let Err(e) = data_channel_tx.send(detached) { + error!("Can't send data channel: {:?}", e); + } + }, + Err(e) => { + error!("Can't detach data channel: {}", e); + }, + }; + }) + }) + }) + .await; } diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index b066ba58f9f..40aa8dec8b6 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -18,262 +18,121 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use bytes::Bytes; - use futures::prelude::*; -use futures::ready; use webrtc_data::data_channel::DataChannel; -use webrtc_data::Error; +use webrtc_data::data_channel::PollDataChannel as RTCPollDataChannel; -use std::fmt; use std::io; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; -/// Default capacity of the temporary read buffer used by [`PollStream`]. -const DEFAULT_READ_BUF_SIZE: usize = 4096; +#[derive(Debug)] +pub struct PollDataChannel(RTCPollDataChannel); -/// State of the read `Future` in [`PollStream`]. -enum ReadFut { - /// Nothing in progress. - Idle, - /// Reading data from the underlying stream. - Reading(Pin, Error>> + Send>>), - /// Finished reading, but there's unread data in the temporary buffer. - RemainingData(Vec), -} +impl PollDataChannel { + /// Constructs a new `PollDataChannel`. + pub fn new(data_channel: Arc) -> Self { + Self(RTCPollDataChannel::new(data_channel)) + } -impl ReadFut { - /// Gets a mutable reference to the future stored inside `Reading(future)`. - /// - /// # Panics - /// - /// Panics if `ReadFut` variant is not `Reading`. - fn get_reading_mut( - &mut self, - ) -> &mut Pin, Error>> + Send>> { - match self { - ReadFut::Reading(ref mut fut) => fut, - _ => panic!("expected ReadFut to be Reading"), - } + /// Get back the inner data_channel. + pub fn into_inner(self) -> RTCPollDataChannel { + self.0 + } + + /// Obtain a clone of the inner data_channel. + pub fn clone_inner(&self) -> RTCPollDataChannel { + self.0.clone() } -} -/// A wrapper around [`DataChannel`], which implements [`AsyncRead`] and [`AsyncWrite`]. -/// -/// Both `poll_read` and `poll_write` calls allocate temporary buffers, which results in an -/// additional overhead. -pub struct PollDataChannel<'a> { - data_channel: Arc, + /// MessagesSent returns the number of messages sent + pub fn messages_sent(&self) -> usize { + self.0.messages_sent() + } - read_fut: ReadFut, - write_fut: Option> + Send + 'a>>>, - shutdown_fut: Option> + Send + 'a>>>, + /// MessagesReceived returns the number of messages received + pub fn messages_received(&self) -> usize { + self.0.messages_received() + } - read_buf_cap: usize, -} + /// BytesSent returns the number of bytes sent + pub fn bytes_sent(&self) -> usize { + self.0.bytes_sent() + } -impl PollDataChannel<'_> { - /// Constructs a new [`PollDataChannel`]. - pub fn new(data_channel: Arc) -> Self { - Self { - data_channel, - read_fut: ReadFut::Idle, - write_fut: None, - shutdown_fut: None, - read_buf_cap: DEFAULT_READ_BUF_SIZE, - } + /// BytesReceived returns the number of bytes received + pub fn bytes_received(&self) -> usize { + self.0.bytes_received() } - /// Get back the inner data_channel. - pub fn into_inner(self) -> Arc { - self.data_channel + /// StreamIdentifier returns the Stream identifier associated to the stream. + pub fn stream_identifier(&self) -> u16 { + self.0.stream_identifier() } - /// Obtain a clone of the inner data_channel. - pub fn clone_inner(&self) -> Arc { - self.data_channel.clone() + /// BufferedAmount returns the number of bytes of data currently queued to be + /// sent over this stream. + pub fn buffered_amount(&self) -> usize { + self.0.buffered_amount() } - /// Set the capacity of the temporary read buffer (default: 4096). + /// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing + /// data that is considered "low." Defaults to 0. + pub fn buffered_amount_low_threshold(&self) -> usize { + self.0.buffered_amount_low_threshold() + } + + /// Set the capacity of the temporary read buffer (default: 8192). pub fn set_read_buf_capacity(&mut self, capacity: usize) { - self.read_buf_cap = capacity + self.0.set_read_buf_capacity(capacity) } } -impl AsyncRead for PollDataChannel<'_> { +impl AsyncRead for PollDataChannel { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - let fut = match self.read_fut { - ReadFut::Idle => { - // Read into a temporary buffer because `buf` has an unonymous lifetime, which can - // be shorter than the lifetime of `read_fut`. - let dc = self.data_channel.clone(); - let mut temp_buf = vec![0; self.read_buf_cap]; - self.read_fut = ReadFut::Reading(Box::pin(async move { - dc.read(temp_buf.as_mut_slice()).await.map(|n| { - temp_buf.truncate(n); - temp_buf - }) - })); - self.read_fut.get_reading_mut() - }, - ReadFut::Reading(ref mut fut) => fut, - ReadFut::RemainingData(ref mut data) => { - let remaining = buf.len(); - let len = std::cmp::min(data.len(), remaining); - buf.copy_from_slice(&data[..len]); - if data.len() > remaining { - // ReadFut remains to be RemainingData - data.drain(0..len); - } else { - self.read_fut = ReadFut::Idle; - } - return Poll::Ready(Ok(len)); - }, - }; - - loop { - match ready!(fut.as_mut().poll(cx)) { - // Retry immediately upon empty data or incomplete chunks - // since there's no way to setup a waker. - Err(Error::Sctp(webrtc_sctp::Error::ErrTryAgain)) => {}, - // EOF has been reached => don't touch buf and just return Ok - Err(Error::Sctp(webrtc_sctp::Error::ErrEof)) => { - self.read_fut = ReadFut::Idle; - return Poll::Ready(Ok(0)); - }, - Err(e) => { - self.read_fut = ReadFut::Idle; - return Poll::Ready(Err(webrtc_error_to_io(e))); - }, - Ok(mut temp_buf) => { - let remaining = buf.len(); - let len = std::cmp::min(temp_buf.len(), remaining); - buf.copy_from_slice(&temp_buf[..len]); - if temp_buf.len() > remaining { - temp_buf.drain(0..len); - self.read_fut = ReadFut::RemainingData(temp_buf); - } else { - self.read_fut = ReadFut::Idle; - } - return Poll::Ready(Ok(len)); - }, - } - } + let mut read_buf = tokio_crate::io::ReadBuf::new(buf); + futures::ready!(tokio_crate::io::AsyncRead::poll_read( + Pin::new(&mut self.0), + cx, + &mut read_buf + ))?; + Poll::Ready(Ok(read_buf.filled().len())) } } -impl AsyncWrite for PollDataChannel<'_> { +impl AsyncWrite for PollDataChannel { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - let (fut, fut_is_new) = match self.write_fut.as_mut() { - Some(fut) => (fut, false), - None => { - let dc = self.data_channel.clone(); - let bytes = Bytes::copy_from_slice(buf); - ( - self.write_fut - .get_or_insert(Box::pin(async move { dc.write(&bytes).await })), - true, - ) - }, - }; - - match fut.as_mut().poll(cx) { - Poll::Pending => { - // If it's the first time we're polling the future, `Poll::Pending` can't be - // returned because that would mean the `PollDataChannel` is not ready for writing. And - // this is not true since we've just created a future, which is going to write the - // buf to the underlying dc. - // - // It's okay to return `Poll::Ready` if the data is buffered (this is what the - // buffered writer and `File` do). - if fut_is_new { - Poll::Ready(Ok(buf.len())) - } else { - // If it's the subsequent poll, it's okay to return `Poll::Pending` as it - // indicates that the `PollDataChannel` is not ready for writing. Only one future - // can be in progress at the time. - Poll::Pending - } - }, - Poll::Ready(Err(e)) => { - self.write_fut = None; - Poll::Ready(Err(webrtc_error_to_io(e))) - }, - Poll::Ready(Ok(n)) => { - self.write_fut = None; - Poll::Ready(Ok(n)) - }, - } + tokio_crate::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.write_fut.as_mut() { - Some(fut) => match ready!(fut.as_mut().poll(cx)) { - Err(e) => { - self.write_fut = None; - Poll::Ready(Err(webrtc_error_to_io(e))) - }, - Ok(_) => { - self.write_fut = None; - Poll::Ready(Ok(())) - }, - }, - None => Poll::Ready(Ok(())), - } + tokio_crate::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let dc = self.data_channel.clone(); - let fut = self - .shutdown_fut - .get_or_insert_with(|| Box::pin(async move { dc.close().await })); - - match ready!(fut.as_mut().poll(cx)) { - Err(e) => Poll::Ready(Err(webrtc_error_to_io(e))), - Ok(_) => Poll::Ready(Ok(())), - } + tokio_crate::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx) } -} -impl<'a> Clone for PollDataChannel<'a> { - fn clone(&self) -> PollDataChannel<'a> { - PollDataChannel::new(self.clone_inner()) - } -} - -impl fmt::Debug for PollDataChannel<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PollDataChannel") - .field("data_channel", &self.data_channel) - .finish() - } -} - -impl AsRef for PollDataChannel<'_> { - fn as_ref(&self) -> &DataChannel { - &*self.data_channel + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + tokio_crate::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs) } } -fn webrtc_error_to_io(err: Error) -> io::Error { - let err_str = err.to_string(); - match error { - Error::Sctp(webrtc_sctp::Error::ErrEof) => { - io::Error::new(io::ErrorKind::UnexpectedEof, err_str) - }, - Error::ErrStreamClosed => { - io::Error::new(io::ErrorKind::ConnectionAborted, err_str) - }, - _ => io::Error::new(io::ErrorKind::Other, err_str), +impl Clone for PollDataChannel { + fn clone(&self) -> PollDataChannel { + PollDataChannel(self.clone_inner()) } } diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 4b4e680a22d..7a73da660bb 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use libp2p_core::PeerId; use thiserror::Error; /// Error in WebRTC. @@ -29,6 +30,18 @@ pub enum Error { WebRTC(#[from] webrtc::Error), #[error("io error: {0}")] IoError(#[from] std::io::Error), + #[error("noise error: {0}")] + Noise(#[from] libp2p_noise::NoiseError), + + // Authentication errors. + #[error("invalid fingerprint (expected {expected:?}, got {got:?})")] + InvalidFingerprint { expected: String, got: String }, + #[error("invalid peer ID (expected {expected:?}, got {got:?})")] + InvalidPeerID { + expected: Option, + got: PeerId, + }, + #[error("internal error: {0} (see debug logs)")] InternalError(String), } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 150237aeca3..a25ed4559d9 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -18,22 +18,26 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - transport::{ListenerEvent, TransportError}, - Transport, -}; - +use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::{ channel::{mpsc, oneshot}, + future, future::BoxFuture, prelude::*, - ready, TryFutureExt, - select, + ready, select, TryFutureExt, }; use futures_timer::Delay; use if_watch::{IfEvent, IfWatcher}; -use log::{debug, error, trace}; +use libp2p_core::identity; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + muxing::StreamMuxerBox, + transport::{Boxed, ListenerEvent, TransportError}, + PeerId, Transport, +}; +use libp2p_core::{OutboundUpgrade, UpgradeInfo}; +use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; +use log::{debug, trace}; use tinytemplate::TinyTemplate; use tokio_crate::net::{ToSocketAddrs, UdpSocket}; use webrtc::api::setting_engine::SettingEngine; @@ -60,6 +64,7 @@ use crate::error::Error; use crate::sdp; use crate::connection::Connection; +use crate::connection::PollDataChannel; use crate::udp_mux::UDPMuxNewAddr; use crate::udp_mux::UDPMuxParams; use crate::upgrade; @@ -69,7 +74,7 @@ enum IfWatch { Ready(IfWatcher), } -/// The listening addresses of a [`WebRTCTransport`]. +/// The listening addresses of a [`WebRTCDirectTransport`]. enum InAddr { /// The stream accepts connections on a single interface. One { out: Option }, @@ -79,7 +84,7 @@ enum InAddr { /// A WebRTC transport with direct p2p communication (without a STUN server). #[derive(Clone)] -pub struct WebRTCTransport { +pub struct WebRTCDirectTransport { /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, @@ -91,14 +96,18 @@ pub struct WebRTCTransport { /// The receiver for new `SocketAddr` connecting to this peer. new_addr_rx: Arc>>, + + /// `Keypair` identifying this peer + id_keys: identity::Keypair, } -impl WebRTCTransport { +impl WebRTCDirectTransport { /// Create a new WebRTC transport. /// /// Creates a UDP socket bound to `listen_addr`. pub async fn new( certificate: RTCCertificate, + id_keys: identity::Keypair, listen_addr: A, ) -> Result> { // Bind to `listen_addr` and construct a UDP mux. @@ -117,23 +126,32 @@ impl WebRTCTransport { Ok(Self { config: RTCConfiguration { certificates: vec![certificate], - ..Default::default() + ..RTCConfiguration::default() }, udp_mux, udp_mux_addr, new_addr_rx: Arc::new(Mutex::new(new_addr_rx)), + id_keys, }) } /// Returns the SHA-256 fingerprint of the certificate in lowercase hex string as expressed /// utilizing the syntax of 'fingerprint' in . - fn cert_fingerprint(&self) -> String { + pub fn cert_fingerprint(&self) -> String { fingerprint_of_first_certificate(&self.config) } + + /// Creates a boxed libp2p transport. + pub fn boxed(self) -> Boxed<(PeerId, StreamMuxerBox)> { + Transport::map(self, |(peer_id, conn), _| { + (peer_id, StreamMuxerBox::new(conn)) + }) + .boxed() + } } -impl Transport for WebRTCTransport { - type Output = Connection<'static>; +impl Transport for WebRTCDirectTransport { + type Output = (PeerId, Connection); type Error = Error; type Listener = WebRTCListenStream; type ListenerUpgrade = BoxFuture<'static, Result>; @@ -146,6 +164,7 @@ impl Transport for WebRTCTransport { self.config.clone(), self.udp_mux.clone(), self.new_addr_rx.clone(), + self.id_keys.clone(), )) } @@ -188,6 +207,8 @@ pub struct WebRTCListenStream { udp_mux: Arc, /// The receiver for new `SocketAddr` connecting to this peer. new_addr_rx: Arc>>, + /// `Keypair` identifying this peer + id_keys: identity::Keypair, } impl WebRTCListenStream { @@ -198,6 +219,7 @@ impl WebRTCListenStream { config: RTCConfiguration, udp_mux: Arc, new_addr_rx: Arc>>, + id_keys: identity::Keypair, ) -> Self { // Check whether the listening IP is set or not. let in_addr = if match &listen_addr { @@ -205,7 +227,7 @@ impl WebRTCListenStream { SocketAddr::V6(a) => a.ip().is_unspecified(), } { // The `addrs` are populated via `if_watch` when the - // `WebRTCTransport` is polled. + // `WebRTCDirectTransport` is polled. InAddr::Any { if_watch: IfWatch::Pending(IfWatcher::new().boxed()), } @@ -223,13 +245,16 @@ impl WebRTCListenStream { config, udp_mux, new_addr_rx, + id_keys, } } } impl Stream for WebRTCListenStream { - type Item = - Result, Error>>, Error>, Error>; + type Item = Result< + ListenerEvent>, Error>, + Error, + >; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let me = Pin::into_inner(self); @@ -242,7 +267,7 @@ impl Stream for WebRTCListenStream { Ok(w) => { *if_watch = IfWatch::Ready(w); continue; - } + }, Err(err) => { debug! { "Failed to begin observing interfaces: {:?}. Scheduling retry.", @@ -253,7 +278,7 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error(Error::IoError( err, ))))); - } + }, }, // Consume all events for up/down interface changes. IfWatch::Ready(watch) => { @@ -270,7 +295,7 @@ impl Stream for WebRTCListenStream { ma, )))); } - } + }, Ok(IfEvent::Down(inet)) => { let ip = inet.addr(); if me.listen_addr.is_ipv4() == ip.is_ipv4() @@ -282,7 +307,7 @@ impl Stream for WebRTCListenStream { ListenerEvent::AddressExpired(ma), ))); } - } + }, Err(err) => { debug! { "Failure polling interfaces: {:?}. Scheduling retry.", @@ -292,10 +317,10 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error( Error::IoError(err), )))); - } + }, } } - } + }, }, // If the listener is bound to a single interface, make sure the address reported // once. @@ -303,16 +328,16 @@ impl Stream for WebRTCListenStream { if let Some(multiaddr) = out.take() { return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(multiaddr)))); } - } + }, } if let Some(mut pause) = me.pause.take() { match Pin::new(&mut pause).poll(cx) { - Poll::Ready(_) => {} + Poll::Ready(_) => {}, Poll::Pending => { me.pause = Some(pause); return Poll::Pending; - } + }, } } @@ -321,8 +346,12 @@ impl Stream for WebRTCListenStream { Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), remote_addr: addr.clone(), - upgrade: Box::pin(upgrade::webrtc(me.udp_mux.clone(), me.config.clone(), addr)) - as BoxFuture<'static, _>, + upgrade: Box::pin(upgrade::webrtc( + me.udp_mux.clone(), + me.config.clone(), + addr, + me.id_keys.clone(), + )) as BoxFuture<'static, _>, }))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, @@ -331,8 +360,8 @@ impl Stream for WebRTCListenStream { } } -impl WebRTCTransport { - async fn do_dial(self, addr: Multiaddr) -> Result, Error> { +impl WebRTCDirectTransport { + async fn do_dial(self, addr: Multiaddr) -> Result<(PeerId, Connection), Error> { let socket_addr = multiaddr_to_socketaddr(&addr).ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { @@ -344,8 +373,8 @@ impl WebRTCTransport { let remote = addr.clone(); // used for logging trace!("dialing address: {:?}", remote); - let fingerprint = self.cert_fingerprint(); - let se = build_setting_engine(self.udp_mux.clone(), &socket_addr, &fingerprint); + let our_fingerprint = self.cert_fingerprint(); + let se = build_setting_engine(self.udp_mux.clone(), &socket_addr, &our_fingerprint); let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api @@ -353,51 +382,6 @@ impl WebRTCTransport { .map_err(Error::WebRTC) .await?; - // Create a datachannel with label 'data' - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: None, - id: Some(1), - ordered: None, - max_retransmits: None, - max_packet_life_time: None, - protocol: None, - }), - ) - .await?; - - let (data_channel_rx, mut data_channel_tx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - data_channel - .on_open({ - let data_channel = data_channel.clone(); - Box::new(move || { - debug!( - "Data channel '{}'-'{}' open.", - data_channel.label(), - data_channel.id() - ); - - Box::pin(async move { - let data_channel = data_channel.clone(); - match data_channel.detach().await { - Ok(detached) => { - if let Err(_) = data_channel_rx.send(detached) { - error!("data_channel_tx dropped"); - } - }, - Err(e) => { - error!("Can't detach data channel: {}", e); - }, - }; - }) - }) - }) - .await; - let offer = peer_connection .create_offer(None) .map_err(Error::WebRTC) @@ -409,16 +393,14 @@ impl WebRTCTransport { .await?; // Set the remote description to the predefined SDP. - let fingerprint = match addr.iter().last() { - Some(Protocol::XWebRTC(f)) => f, - _ => { - return Err(Error::InvalidMultiaddr(addr)); - } + let remote_fingerprint = match fingerprint_from_addr(&addr) { + Some(f) => fingerprint_to_string(&f), + None => return Err(Error::InvalidMultiaddr(addr.clone())), }; let server_session_description = render_description( sdp::SERVER_SESSION_DESCRIPTION, socket_addr, - &fingerprint_to_string(&fingerprint), + &remote_fingerprint, ); debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); @@ -429,16 +411,80 @@ impl WebRTCTransport { .map_err(Error::WebRTC) .await?; + // Create a datachannel with label 'data' + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + id: Some(1), + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + // Wait until data channel is opened and ready to use - select! { - res = data_channel_tx => match res { - Ok(dc) => Ok(Connection::new(peer_connection, dc)), - Err(e) => Err(Error::InternalError(e.to_string())), + let detached = select! { + res = rx => match res { + Ok(detached) => detached, + Err(e) => return Err(Error::InternalError(e.to_string())), }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( + _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( "data channel opening took longer than 10 seconds (see logs)".into(), )) + }; + + trace!("noise handshake with {}", remote); + let dh_keys = Keypair::::new() + .into_authentic(&self.id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_outbound(PollDataChannel::new(detached), info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await + .map_err(Error::Noise)?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + trace!("exchanging TLS certificate fingerprints with {}", remote); + let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint, + got: fingerprint_from_noise, + }); + } + + trace!("verifying peer's identity {}", remote); + let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); + if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { + return Err(Error::InvalidPeerID { + expected: peer_id_from_addr, + got: peer_id, + }); } + + // Close the initial data channel after noise handshake is done. + // https://github.com/webrtc-rs/sctp/pull/14 + // detached + // .close() + // .await + // .map_err(|e| Error::WebRTC(e.into()))?; + + Ok((peer_id, Connection::new(peer_connection).await)) } } @@ -452,7 +498,7 @@ pub(crate) fn render_description(description: &str, addr: SocketAddr, fingerprin let mut tt = TinyTemplate::new(); tt.add_template("description", description).unwrap(); - let f = fingerprint.to_owned().replace(":", ""); + let f = fingerprint.to_owned().replace(':', ""); let context = sdp::DescriptionContext { ip_version: { if addr.is_ipv4() { @@ -480,9 +526,9 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { let proto2 = iter.next()?; let proto3 = iter.next()?; - while let Some(proto) = iter.next() { + for proto in iter { match proto { - Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(_) => {}, // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. _ => return None, } } @@ -490,10 +536,10 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { match (proto1, proto2, proto3) { (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - } + }, (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - } + }, _ => None, } } @@ -517,7 +563,7 @@ pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> Str .expect("at least one certificate") .get_fingerprints() .expect("fingerprints to succeed"); - fingerprints.first().unwrap().value.to_owned() + fingerprints.first().unwrap().value.clone() } /// Creates a new [`SettingEngine`] and configures it. @@ -529,7 +575,7 @@ pub(crate) fn build_setting_engine( let mut se = SettingEngine::default(); // Set both ICE user and password to fingerprint. // It will be checked by remote side when exchanging ICE messages. - let f = fingerprint.to_owned().replace(":", ""); + let f = fingerprint.to_owned().replace(':', ""); se.set_ice_credentials(f.clone(), f); se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); // Allow detaching data channels. @@ -547,6 +593,17 @@ pub(crate) fn build_setting_engine( se } +fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { + let iter = addr.iter(); + for proto in iter { + match proto { + Protocol::XWebRTC(f) => return Some(f), + _ => continue, + } + } + None +} + // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] @@ -639,7 +696,7 @@ mod tests { async fn dialer_connects_to_listener_ipv4() { let _ = env_logger::builder().is_test(true).try_init(); let a = "127.0.0.1:0".parse().unwrap(); - connect(a).await + connect(a).await; } #[tokio::test] @@ -650,10 +707,12 @@ mod tests { } async fn connect(listen_addr: SocketAddr) { + let id_keys = identity::Keypair::generate_ed25519(); + let t1_peer_id = PeerId::from_public_key(&id_keys.public()); let transport = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - WebRTCTransport::new(cert, listen_addr) + WebRTCDirectTransport::new(cert, id_keys, listen_addr) .await .expect("transport") }; @@ -685,16 +744,20 @@ mod tests { let transport2 = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); + let id_keys = identity::Keypair::generate_ed25519(); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); // okay to reuse `listen_addr` since the port is `0` (any). - WebRTCTransport::new(cert, listen_addr) + WebRTCDirectTransport::new(cert, id_keys, listen_addr) .await .expect("transport") }; // TODO: make code cleaner wrt ":" - let f = &transport.cert_fingerprint().replace(":", ""); + let f = &transport.cert_fingerprint().replace(':', ""); let outbound = transport2 - .dial(addr.with(Protocol::XWebRTC(hex_to_cow(f)))) + .dial( + addr.with(Protocol::XWebRTC(hex_to_cow(f))) + .with(Protocol::P2p(t1_peer_id.into())), + ) .unwrap(); let (a, b) = futures::join!(inbound, outbound); diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 11a3b7d2a8a..a9938645ee5 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -25,22 +25,17 @@ use std::{ collections::{HashMap, HashSet}, io::ErrorKind, net::SocketAddr, - sync::Arc, + sync::{Arc, Weak}, }; use futures::channel::mpsc; use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use webrtc_ice::udp_mux::UDPMux; +use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc_util::{sync::RwLock, Conn, Error}; use tokio_crate as tokio; use tokio_crate::sync::{watch, Mutex}; -mod socket_addr_ext; - -mod udp_mux_conn; -use udp_mux_conn::{UDPMuxConn, UDPMuxConnParams}; - use async_trait::async_trait; use stun::{ @@ -127,14 +122,6 @@ impl UDPMuxNewAddr { self.closed_watch_tx.lock().await.is_none() } - async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result { - self.params - .conn - .send_to(buf, *target) - .await - .map_err(Into::into) - } - /// Create a muxed connection for a given ufrag. async fn create_muxed_conn(self: &Arc, ufrag: &str) -> Result { let local_addr = self.params.conn.local_addr().await?; @@ -142,41 +129,12 @@ impl UDPMuxNewAddr { let params = UDPMuxConnParams { local_addr, key: ufrag.into(), - udp_mux: Arc::clone(self), + udp_mux: Arc::downgrade(self) as Weak, }; Ok(UDPMuxConn::new(params)) } - async fn register_conn_for_address(&self, conn: &UDPMuxConn, addr: SocketAddr) { - if self.is_closed().await { - return; - } - - let key = conn.key(); - { - let mut addresses = self.address_map.write(); - - addresses - .entry(addr) - .and_modify(|e| { - if e.key() != key { - e.remove_address(&addr); - *e = conn.clone() - } - }) - .or_insert_with(|| conn.clone()); - } - - // remove addr from new_addrs once conn is established - { - let mut new_addrs = self.new_addrs.write(); - new_addrs.remove(&addr); - } - - log::debug!("Registered {} for {}", addr, key); - } - async fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { match ufrag_from_stun_message(buffer, true) { Ok(ufrag) => { @@ -234,12 +192,12 @@ impl UDPMuxNewAddr { let a = Multiaddr::empty() .with(addr.ip().into()) .with(Protocol::Udp(addr.port())) - .with(Protocol::XWebRTC(hex_to_cow(&ufrag.replace(":", "")))); + .with(Protocol::XWebRTC(hex_to_cow(&ufrag.replace(':', "")))); if let Err(err) = new_addr_tx.try_send(a) { log::error!("Failed to send new address {}: {}", &addr, err); } else { let mut new_addrs = loop_self.new_addrs.write(); - new_addrs.insert(addr.clone()); + new_addrs.insert(addr); }; } Err(e) => { @@ -291,7 +249,7 @@ impl UDPMux for UDPMuxNewAddr { }; // NOTE: We don't wait for these closure to complete - for (_, conn) in old_conns.into_iter() { + for (_, conn) in old_conns { conn.close(); } @@ -364,6 +322,46 @@ impl UDPMux for UDPMuxNewAddr { } } +#[async_trait] +impl UDPMuxWriter for UDPMuxNewAddr { + async fn register_conn_for_address(&self, conn: &UDPMuxConn, addr: SocketAddr) { + if self.is_closed().await { + return; + } + + let key = conn.key(); + { + let mut addresses = self.address_map.write(); + + addresses + .entry(addr) + .and_modify(|e| { + if e.key() != key { + e.remove_address(&addr); + *e = conn.clone(); + } + }) + .or_insert_with(|| conn.clone()); + } + + // remove addr from new_addrs once conn is established + { + let mut new_addrs = self.new_addrs.write(); + new_addrs.remove(&addr); + } + + log::debug!("Registered {} for {}", addr, key); + } + + async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result { + self.params + .conn + .send_to(buf, *target) + .await + .map_err(Into::into) + } +} + fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { let mut buf = [0; 32]; hex::decode_to_slice(s, &mut buf).unwrap(); @@ -379,37 +377,36 @@ fn ufrag_from_stun_message(buffer: &[u8], local_ufrag: bool) -> Result Err(Error::Other(format!( + if let Err(err) = result { + Err(Error::Other(format!( "failed to handle decode ICE: {}", err - ))), - Ok(_) => { - let (attr, found) = message.attributes.get(ATTR_USERNAME); - if !found { - return Err(Error::Other("no username attribute in STUN message".into())); - } + ))) + } else { + let (attr, found) = message.attributes.get(ATTR_USERNAME); + if !found { + return Err(Error::Other("no username attribute in STUN message".into())); + } - match String::from_utf8(attr.value) { - // Per the RFC this shouldn't happen - // https://datatracker.ietf.org/doc/html/rfc5389#section-15.3 - Err(err) => Err(Error::Other(format!( - "failed to decode USERNAME from STUN message as UTF-8: {}", - err - ))), - Ok(s) => { - // s is a combination of the local_ufrag and the remote ufrag separated by `:`. - let res = if local_ufrag { - s.split(":").next() - } else { - s.split(":").last() - }; - match res { - Some(s) => Ok(s.to_owned()), - None => Err(Error::Other("can't get ufrag from username".into())), - } - }, - } - }, + match String::from_utf8(attr.value) { + // Per the RFC this shouldn't happen + // https://datatracker.ietf.org/doc/html/rfc5389#section-15.3 + Err(err) => Err(Error::Other(format!( + "failed to decode USERNAME from STUN message as UTF-8: {}", + err + ))), + Ok(s) => { + // s is a combination of the local_ufrag and the remote ufrag separated by `:`. + let res = if local_ufrag { + s.split(':').next() + } else { + s.split(':').last() + }; + match res { + Some(s) => Ok(s.to_owned()), + None => Err(Error::Other("can't get ufrag from username".into())), + } + }, + } } } diff --git a/transports/webrtc/src/udp_mux/socket_addr_ext.rs b/transports/webrtc/src/udp_mux/socket_addr_ext.rs deleted file mode 100644 index d1d06ceb3bc..00000000000 --- a/transports/webrtc/src/udp_mux/socket_addr_ext.rs +++ /dev/null @@ -1,267 +0,0 @@ -// MIT License -// -// Copyright (c) 2021 WebRTC.rs -// -// 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 std::array::TryFromSliceError; -use std::convert::TryInto; -use std::net::SocketAddr; - -use webrtc_util::Error; - -pub(super) trait SocketAddrExt { - ///Encode a representation of `self` into the buffer and return the length of this encoded - ///version. - /// - /// The buffer needs to be at least 27 bytes in length. - fn encode(&self, buffer: &mut [u8]) -> Result; - - /// Decode a `SocketAddr` from a buffer. The encoding should have previously been done with - /// [`SocketAddrExt::encode`]. - fn decode(buffer: &[u8]) -> Result; -} - -const IPV4_MARKER: u8 = 4; -const IPV4_ADDRESS_SIZE: usize = 7; -const IPV6_MARKER: u8 = 6; -const IPV6_ADDRESS_SIZE: usize = 27; - -pub(super) const MAX_ADDR_SIZE: usize = IPV6_ADDRESS_SIZE; - -impl SocketAddrExt for SocketAddr { - fn encode(&self, buffer: &mut [u8]) -> Result { - use std::net::SocketAddr::*; - - if buffer.len() < MAX_ADDR_SIZE { - return Err(Error::ErrBufferShort); - } - - match self { - V4(addr) => { - let marker = IPV4_MARKER; - let ip: [u8; 4] = addr.ip().octets(); - let port: u16 = addr.port(); - - buffer[0] = marker; - buffer[1..5].copy_from_slice(&ip); - buffer[5..7].copy_from_slice(&port.to_le_bytes()); - - Ok(7) - }, - V6(addr) => { - let marker = IPV6_MARKER; - let ip: [u8; 16] = addr.ip().octets(); - let port: u16 = addr.port(); - let flowinfo = addr.flowinfo(); - let scope_id = addr.scope_id(); - - buffer[0] = marker; - buffer[1..17].copy_from_slice(&ip); - buffer[17..19].copy_from_slice(&port.to_le_bytes()); - buffer[19..23].copy_from_slice(&flowinfo.to_le_bytes()); - buffer[23..27].copy_from_slice(&scope_id.to_le_bytes()); - - Ok(MAX_ADDR_SIZE) - }, - } - } - - fn decode(buffer: &[u8]) -> Result { - use std::net::*; - - match buffer[0] { - IPV4_MARKER => { - if buffer.len() < IPV4_ADDRESS_SIZE { - return Err(Error::ErrBufferShort); - } - - let ip_parts = &buffer[1..5]; - let port = match &buffer[5..7].try_into() { - Err(_) => return Err(Error::ErrFailedToParseIpaddr), - Ok(input) => u16::from_le_bytes(*input), - }; - - let ip = Ipv4Addr::new(ip_parts[0], ip_parts[1], ip_parts[2], ip_parts[3]); - - Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) - }, - IPV6_MARKER => { - if buffer.len() < IPV6_ADDRESS_SIZE { - return Err(Error::ErrBufferShort); - } - - // Just to help the type system infer correctly - fn helper(b: &[u8]) -> Result<&[u8; 16], TryFromSliceError> { - b.try_into() - } - - let ip = match helper(&buffer[1..17]) { - Err(_) => return Err(Error::ErrFailedToParseIpaddr), - Ok(input) => Ipv6Addr::from(*input), - }; - let port = match &buffer[17..19].try_into() { - Err(_) => return Err(Error::ErrFailedToParseIpaddr), - Ok(input) => u16::from_le_bytes(*input), - }; - - let flowinfo = match &buffer[19..23].try_into() { - Err(_) => return Err(Error::ErrFailedToParseIpaddr), - Ok(input) => u32::from_le_bytes(*input), - }; - - let scope_id = match &buffer[23..27].try_into() { - Err(_) => return Err(Error::ErrFailedToParseIpaddr), - Ok(input) => u32::from_le_bytes(*input), - }; - - Ok(SocketAddr::V6(SocketAddrV6::new( - ip, port, flowinfo, scope_id, - ))) - }, - _ => Err(Error::ErrFailedToParseIpaddr), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::net::*; - - #[test] - fn test_ipv4() { - let ip = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([56, 128, 35, 5]), 0x1234)); - - let mut buffer = [0_u8; MAX_ADDR_SIZE]; - let encoded_len = ip.encode(&mut buffer); - - assert_eq!(encoded_len, Ok(7)); - assert_eq!( - &buffer[0..7], - &[IPV4_MARKER, 56, 128, 35, 5, 0x34, 0x12][..] - ); - - let decoded = SocketAddr::decode(&buffer); - - assert_eq!(decoded, Ok(ip)); - } - - #[test] - fn test_ipv6() { - let ip = SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::from([ - 92, 114, 235, 3, 244, 64, 38, 111, 20, 100, 199, 241, 19, 174, 220, 123, - ]), - 0x1234, - 0x12345678, - 0x87654321, - )); - - let mut buffer = [0_u8; MAX_ADDR_SIZE]; - let encoded_len = ip.encode(&mut buffer); - - assert_eq!(encoded_len, Ok(27)); - assert_eq!( - &buffer[0..27], - &[ - IPV6_MARKER, // marker - // Start of ipv6 address - 92, - 114, - 235, - 3, - 244, - 64, - 38, - 111, - 20, - 100, - 199, - 241, - 19, - 174, - 220, - 123, - // LE port - 0x34, - 0x12, - // LE flowinfo - 0x78, - 0x56, - 0x34, - 0x12, - // LE scope_id - 0x21, - 0x43, - 0x65, - 0x87, - ][..] - ); - - let decoded = SocketAddr::decode(&buffer); - - assert_eq!(decoded, Ok(ip)); - } - - #[test] - fn test_encode_ipv4_with_short_buffer() { - let mut buffer = vec![0u8; IPV4_ADDRESS_SIZE - 1]; - let ip = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([56, 128, 35, 5]), 0x1234)); - - let result = ip.encode(&mut buffer); - - assert_eq!(result, Err(Error::ErrBufferShort)); - } - - #[test] - fn test_encode_ipv6_with_short_buffer() { - let mut buffer = vec![0u8; MAX_ADDR_SIZE - 1]; - let ip = SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::from([ - 92, 114, 235, 3, 244, 64, 38, 111, 20, 100, 199, 241, 19, 174, 220, 123, - ]), - 0x1234, - 0x12345678, - 0x87654321, - )); - - let result = ip.encode(&mut buffer); - - assert_eq!(result, Err(Error::ErrBufferShort)); - } - - #[test] - fn test_decode_ipv4_with_short_buffer() { - let buffer = vec![IPV4_MARKER, 0]; - - let result = SocketAddr::decode(&buffer); - - assert_eq!(result, Err(Error::ErrBufferShort)); - } - - #[test] - fn test_decode_ipv6_with_short_buffer() { - let buffer = vec![IPV6_MARKER, 0]; - - let result = SocketAddr::decode(&buffer); - - assert_eq!(result, Err(Error::ErrBufferShort)); - } -} diff --git a/transports/webrtc/src/udp_mux/udp_mux_conn.rs b/transports/webrtc/src/udp_mux/udp_mux_conn.rs deleted file mode 100644 index 89d070b8d8d..00000000000 --- a/transports/webrtc/src/udp_mux/udp_mux_conn.rs +++ /dev/null @@ -1,308 +0,0 @@ -// MIT License -// -// Copyright (c) 2021 WebRTC.rs -// -// 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 std::convert::TryInto; -use std::{collections::HashSet, io, net::SocketAddr, sync::Arc}; - -use async_trait::async_trait; -use tokio_crate as tokio; -use tokio_crate::sync::watch; - -use webrtc_util::{sync::Mutex, Buffer, Conn, Error}; - -use super::socket_addr_ext::{SocketAddrExt, MAX_ADDR_SIZE}; -use super::{normalize_socket_addr, UDPMuxNewAddr, RECEIVE_MTU}; - -#[inline(always)] -/// Create a buffer of appropriate size to fit both a packet with max RECEIVE_MTU and the -/// additional metadata used for muxing. -fn make_buffer() -> Vec { - // The 4 extra bytes are used to encode the length of the data and address respectively. - // See [`write_packet`] for details. - vec![0u8; RECEIVE_MTU + MAX_ADDR_SIZE + 2 + 2] -} - -pub(crate) struct UDPMuxConnParams { - pub(super) local_addr: SocketAddr, - - pub(super) key: String, - - // NOTE: This Arc exists in both directions which is liable to cause a retain cycle. This is - // accounted for in [`UDPMuxNewAddr::close`], which makes sure to drop all Arcs referencing any - // `UDPMuxConn`. - pub(super) udp_mux: Arc, -} - -struct UDPMuxConnInner { - pub(super) params: UDPMuxConnParams, - - /// Close Sender. We'll send a value on this channel when we close - closed_watch_tx: Mutex>>, - - /// Remote addresses we've seen on this connection. - addresses: Mutex>, - - buffer: Buffer, -} - -impl UDPMuxConnInner { - // Sending/Recieving - async fn recv_from(&self, buf: &mut [u8]) -> ConnResult<(usize, SocketAddr)> { - // NOTE: Pion/ice uses Sync.Pool to optimise this. - let mut buffer = make_buffer(); - let mut offset = 0; - - let len = self.buffer.read(&mut buffer, None).await?; - // We always have at least. - // - // * 2 bytes for data len - // * 2 bytes for addr len - // * 7 bytes for an Ipv4 addr - if len < 11 { - return Err(Error::ErrBufferShort); - } - - let data_len: usize = buffer[..2] - .try_into() - .map(u16::from_le_bytes) - .map(From::from) - .unwrap(); - offset += 2; - - let total = 2 + data_len + 2 + 7; - if data_len > buf.len() || total > len { - return Err(Error::ErrBufferShort); - } - - buf[..data_len].copy_from_slice(&buffer[offset..offset + data_len]); - offset += data_len; - - let address_len: usize = buffer[offset..offset + 2] - .try_into() - .map(u16::from_le_bytes) - .map(From::from) - .unwrap(); - offset += 2; - - let addr = SocketAddr::decode(&buffer[offset..offset + address_len])?; - - Ok((data_len, addr)) - } - - async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> ConnResult { - self.params.udp_mux.send_to(buf, target).await - } - - fn is_closed(&self) -> bool { - self.closed_watch_tx.lock().is_none() - } - - fn close(self: &Arc) { - let mut closed_tx = self.closed_watch_tx.lock(); - - if let Some(tx) = closed_tx.take() { - let _ = tx.send(true); - drop(closed_tx); - - let cloned_self = Arc::clone(self); - - { - let mut addresses = self.addresses.lock(); - *addresses = Default::default(); - } - - // NOTE: Alternatively we could wait on the buffer closing here so that - // our caller can wait for things to fully settle down - tokio::spawn(async move { - cloned_self.buffer.close().await; - }); - } - } - - fn local_addr(&self) -> SocketAddr { - self.params.local_addr - } - - // Address related methods - pub(super) fn get_addresses(&self) -> Vec { - let addresses = self.addresses.lock(); - - addresses.iter().cloned().collect() - } - - pub(super) fn add_address(self: &Arc, addr: SocketAddr) { - { - let mut addresses = self.addresses.lock(); - addresses.insert(addr); - } - } - - pub(super) fn remove_address(&self, addr: &SocketAddr) { - { - let mut addresses = self.addresses.lock(); - addresses.remove(addr); - } - } - - pub(super) fn contains_address(&self, addr: &SocketAddr) -> bool { - let addresses = self.addresses.lock(); - - addresses.contains(addr) - } -} - -#[derive(Clone)] -pub(crate) struct UDPMuxConn { - /// Close Receiver. A copy of this can be obtained via [`close_tx`]. - closed_watch_rx: watch::Receiver, - - inner: Arc, -} - -impl UDPMuxConn { - pub(crate) fn new(params: UDPMuxConnParams) -> Self { - let (closed_watch_tx, closed_watch_rx) = watch::channel(false); - - Self { - closed_watch_rx, - inner: Arc::new(UDPMuxConnInner { - params, - closed_watch_tx: Mutex::new(Some(closed_watch_tx)), - addresses: Default::default(), - buffer: Buffer::new(0, 0), - }), - } - } - - pub(crate) fn key(&self) -> &str { - &self.inner.params.key - } - - pub(crate) async fn write_packet(&self, data: &[u8], addr: SocketAddr) -> ConnResult<()> { - // NOTE: Pion/ice uses Sync.Pool to optimise this. - let mut buffer = make_buffer(); - let mut offset = 0; - - if (data.len() + MAX_ADDR_SIZE) > (RECEIVE_MTU + MAX_ADDR_SIZE) { - return Err(Error::ErrBufferShort); - } - - // Format of buffer: | data len(2) | data bytes(dn) | addr len(2) | addr bytes(an) | - // Where the number in parenthesis indicate the number of bytes used - // `dn` and `an` are the length in bytes of data and addr respectively. - - // SAFETY: `data.len()` is at most RECEIVE_MTU(8192) - MAX_ADDR_SIZE(27) - buffer[0..2].copy_from_slice(&(data.len() as u16).to_le_bytes()[..]); - offset += 2; - - buffer[offset..offset + data.len()].copy_from_slice(data); - offset += data.len(); - - let len = addr.encode(&mut buffer[offset + 2..])?; - buffer[offset..offset + 2].copy_from_slice(&(len as u16).to_le_bytes()[..]); - offset += 2 + len; - - self.inner.buffer.write(&buffer[..offset]).await?; - - Ok(()) - } - - pub(crate) fn is_closed(&self) -> bool { - self.inner.is_closed() - } - - /// Get a copy of the close [`tokio::sync::watch::Receiver`] that fires when this - /// connection is closed. - pub(crate) fn close_rx(&self) -> watch::Receiver { - self.closed_watch_rx.clone() - } - - /// Close this connection - pub(crate) fn close(&self) { - self.inner.close(); - } - - pub(super) fn get_addresses(&self) -> Vec { - self.inner.get_addresses() - } - - pub(super) async fn add_address(&self, addr: SocketAddr) { - self.inner.add_address(addr); - self.inner - .params - .udp_mux - .register_conn_for_address(self, addr) - .await; - } - - pub(super) fn remove_address(&self, addr: &SocketAddr) { - self.inner.remove_address(addr) - } - - pub(super) fn contains_address(&self, addr: &SocketAddr) -> bool { - self.inner.contains_address(addr) - } -} - -type ConnResult = Result; - -#[async_trait] -impl Conn for UDPMuxConn { - async fn connect(&self, _addr: SocketAddr) -> ConnResult<()> { - Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) - } - - async fn recv(&self, _buf: &mut [u8]) -> ConnResult { - Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) - } - - async fn recv_from(&self, buf: &mut [u8]) -> ConnResult<(usize, SocketAddr)> { - self.inner.recv_from(buf).await - } - - async fn send(&self, _buf: &[u8]) -> ConnResult { - Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into()) - } - - async fn send_to(&self, buf: &[u8], target: SocketAddr) -> ConnResult { - let normalized_target = normalize_socket_addr(&target, &self.inner.params.local_addr); - - if !self.contains_address(&normalized_target) { - self.add_address(normalized_target).await; - } - - self.inner.send_to(buf, &normalized_target).await - } - - async fn local_addr(&self) -> ConnResult { - Ok(self.inner.local_addr()) - } - - async fn remote_addr(&self) -> Option { - None - } - async fn close(&self) -> ConnResult<()> { - self.inner.close(); - - Ok(()) - } -} diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index e7bf6320efc..baf1a02df2a 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -18,12 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::channel::oneshot; -use futures::select; -use futures::FutureExt; +use futures::io::{AsyncReadExt, AsyncWriteExt}; +use futures::{channel::oneshot, future, select, FutureExt, TryFutureExt}; use futures_timer::Delay; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use log::{debug, error, trace}; +use libp2p_core::identity; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + PeerId, +}; +use libp2p_core::{InboundUpgrade, UpgradeInfo}; +use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; +use log::{debug, trace}; use webrtc::api::APIBuilder; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; use webrtc::dtls_transport::dtls_role::DTLSRole; @@ -36,6 +41,7 @@ use std::sync::Arc; use std::time::Duration; use crate::connection::Connection; +use crate::connection::PollDataChannel; use crate::error::Error; use crate::sdp; use crate::transport; @@ -44,14 +50,15 @@ pub async fn webrtc( udp_mux: Arc, config: RTCConfiguration, addr: Multiaddr, -) -> Result, Error> { + id_keys: identity::Keypair, +) -> Result<(PeerId, Connection), Error> { trace!("upgrading {}", addr); let socket_addr = transport::multiaddr_to_socketaddr(&addr) .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; - let fingerprint = transport::fingerprint_of_first_certificate(&config); + let our_fingerprint = transport::fingerprint_of_first_certificate(&config); - let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &fingerprint); + let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &our_fingerprint); { // Act as a lite ICE (ICE which does not send additional candidates). se.set_lite(true); @@ -61,67 +68,21 @@ pub async fn webrtc( // but none end up responding). se.set_answering_dtls_role(DTLSRole::Server) .map_err(Error::WebRTC)?; - } + } let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; - // Create a datachannel with label 'data'. - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: Some(true), - id: Some(1), - ordered: None, - max_retransmits: None, - max_packet_life_time: None, - protocol: None, - }), - ) - .await?; - - let (data_channel_rx, mut data_channel_tx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - data_channel - .on_open({ - let data_channel = data_channel.clone(); - Box::new(move || { - debug!( - "Data channel '{}'-'{}' open.", - data_channel.label(), - data_channel.id() - ); - - Box::pin(async move { - let data_channel = data_channel.clone(); - match data_channel.detach().await { - Ok(detached) => { - if let Err(_) = data_channel_rx.send(detached) { - error!("data_channel_tx dropped"); - } - }, - Err(e) => { - error!("Can't detach data channel: {}", e); - }, - }; - }) - }) - }) - .await; - // Set the remote description to the predefined SDP. - let fingerprint = match addr.iter().last() { - Some(Protocol::XWebRTC(f)) => f, - _ => { - debug!("{} is not a WebRTC multiaddr", addr); - return Err(Error::InvalidMultiaddr(addr)); - }, + let remote_fingerprint = if let Some(Protocol::XWebRTC(f)) = addr.iter().last() { + transport::fingerprint_to_string(&f) + } else { + debug!("{} is not a WebRTC multiaddr", addr); + return Err(Error::InvalidMultiaddr(addr)); }; let client_session_description = transport::render_description( sdp::CLIENT_SESSION_DESCRIPTION, socket_addr, - &transport::fingerprint_to_string(&fingerprint), + &remote_fingerprint, ); debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); @@ -133,14 +94,71 @@ pub async fn webrtc( debug!("ANSWER: {:?}", answer.sdp); peer_connection.set_local_description(answer).await?; - // wait until data channel is opened and ready to use - select! { - res = data_channel_tx => match res { - Ok(dc) => Ok(Connection::new(peer_connection, dc)), - Err(e) => Err(Error::InternalError(e.to_string())), + // Create a datachannel with label 'data'. + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: Some(true), + id: Some(1), + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + + // Wait until data channel is opened and ready to use + let detached = select! { + res = rx => match res { + Ok(detached) => detached, + Err(e) => return Err(Error::InternalError(e.to_string())), }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( + _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( "data channel opening took longer than 10 seconds (see logs)".into(), )) + }; + + trace!("noise handshake with {}", addr); + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_inbound(PollDataChannel::new(detached.clone()), info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await + .map_err(Error::Noise)?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + trace!("exchanging TLS certificate fingerprints with {}", addr); + let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint, + got: fingerprint_from_noise, + }); } + + // Close the initial data channel after noise handshake is done. + // https://github.com/webrtc-rs/sctp/pull/14 + // detached + // .close() + // .await + // .map_err(|e| Error::WebRTC(e.into()))?; + + Ok((peer_id, Connection::new(peer_connection).await)) } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs new file mode 100644 index 00000000000..00be9e7d39f --- /dev/null +++ b/transports/webrtc/tests/smoke.rs @@ -0,0 +1,305 @@ +use anyhow::Result; +use async_trait::async_trait; +use futures::future::FutureExt; +use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; +use futures::stream::StreamExt; +use libp2p_core::identity; +use libp2p_core::multiaddr::Protocol; +use libp2p_core::upgrade; +use libp2p_request_response::{ + ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, +}; +use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_webrtc_direct::transport::WebRTCDirectTransport; +use log::trace; +use rand::RngCore; +use rcgen::KeyPair; +use tokio_crate as tokio; +use webrtc::peer_connection::certificate::RTCCertificate; + +use std::borrow::Cow; +use std::{io, iter}; + +fn generate_certificate() -> RTCCertificate { + let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); + RTCCertificate::from_key_pair(kp).expect("certificate") +} + +fn generate_tls_keypair() -> identity::Keypair { + identity::Keypair::generate_ed25519() +} + +async fn create_swarm() -> Result<(Swarm>, String)> { + let cert = generate_certificate(); + let keypair = generate_tls_keypair(); + let peer_id = keypair.public().to_peer_id(); + let transport = WebRTCDirectTransport::new(cert, keypair, "127.0.0.1:0").await?; + let fingerprint = transport.cert_fingerprint(); + let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); + let cfg = RequestResponseConfig::default(); + let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); + trace!("{}", peer_id); + Ok(( + SwarmBuilder::new(transport.boxed(), behaviour, peer_id) + .executor(Box::new(|fut| { + tokio::spawn(fut); + })) + .build(), + fingerprint, + )) +} + +#[tokio::test] +async fn smoke() -> Result<()> { + let _ = env_logger::builder().is_test(true).try_init(); + + let mut rng = rand::thread_rng(); + + let (mut a, a_fingerprint) = create_swarm().await?; + let (mut b, _b_fingerprint) = create_swarm().await?; + + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0".parse()?)?; + + let addr = match a.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + let addr = addr.with(Protocol::XWebRTC(hex_to_cow( + &a_fingerprint.replace(":", ""), + ))); + + let mut data = vec![0; 4096]; + rng.fill_bytes(&mut data); + + b.behaviour_mut() + .add_address(&Swarm::local_peer_id(&a), addr); + b.behaviour_mut() + .send_request(&Swarm::local_peer_id(&a), Ping(data.clone())); + + match b.next().await { + Some(SwarmEvent::Dialing(_)) => {}, + e => panic!("{:?}", e), + } + + match a.next().await { + Some(SwarmEvent::IncomingConnection { .. }) => {}, + e => panic!("{:?}", e), + }; + + match b.next().await { + Some(SwarmEvent::ConnectionEstablished { .. }) => {}, + e => panic!("{:?}", e), + }; + + match a.next().await { + Some(SwarmEvent::ConnectionEstablished { .. }) => {}, + e => panic!("{:?}", e), + }; + + assert!(b.next().now_or_never().is_none()); + + match a.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request: Ping(ping), + channel, + .. + }, + .. + })) => { + a.behaviour_mut() + .send_response(channel, Pong(ping)) + .unwrap(); + }, + e => panic!("{:?}", e), + } + + match a.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {}, + e => panic!("{:?}", e), + } + + match b.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(pong), + .. + }, + .. + })) => assert_eq!(data, pong), + e => panic!("{:?}", e), + } + + a.behaviour_mut().send_request( + &Swarm::local_peer_id(&b), + Ping(b"another substream".to_vec()), + ); + + assert!(a.next().now_or_never().is_none()); + + match b.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request: Ping(data), + channel, + .. + }, + .. + })) => { + b.behaviour_mut() + .send_response(channel, Pong(data)) + .unwrap(); + }, + e => panic!("{:?}", e), + } + + match b.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {}, + e => panic!("{:?}", e), + } + + match a.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(data), + .. + }, + .. + })) => assert_eq!(data, b"another substream".to_vec()), + e => panic!("{:?}", e), + } + + Ok(()) +} + +#[derive(Debug, Clone)] +struct PingProtocol(); + +#[derive(Clone)] +struct PingCodec(); + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Ping(Vec); + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Pong(Vec); + +impl ProtocolName for PingProtocol { + fn protocol_name(&self) -> &[u8] { + "/ping/1".as_bytes() + } +} + +#[async_trait] +impl RequestResponseCodec for PingCodec { + type Protocol = PingProtocol; + type Request = Ping; + type Response = Pong; + + async fn read_request(&mut self, _: &PingProtocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + upgrade::read_length_prefixed(io, 4096) + .map(|res| match res { + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), + Ok(vec) => Ok(Ping(vec)), + }) + .await + } + + async fn read_response(&mut self, _: &PingProtocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + upgrade::read_length_prefixed(io, 4096) + .map(|res| match res { + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), + Ok(vec) => Ok(Pong(vec)), + }) + .await + } + + async fn write_request( + &mut self, + _: &PingProtocol, + io: &mut T, + Ping(data): Ping, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + upgrade::write_length_prefixed(io, data).await?; + io.close().await?; + Ok(()) + } + + async fn write_response( + &mut self, + _: &PingProtocol, + io: &mut T, + Pong(data): Pong, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + upgrade::write_length_prefixed(io, data).await?; + io.close().await?; + Ok(()) + } +} + +#[tokio::test] +async fn dial_failure() -> Result<()> { + let _ = env_logger::builder().is_test(true).try_init(); + + let (mut a, a_fingerprint) = create_swarm().await?; + let (mut b, _b_fingerprint) = create_swarm().await?; + + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0".parse()?)?; + + let addr = match a.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + let addr = addr.with(Protocol::XWebRTC(hex_to_cow( + &a_fingerprint.replace(":", ""), + ))); + + let a_peer_id = &Swarm::local_peer_id(&a).clone(); + drop(a); // stop a swarm so b can never reach it + + b.behaviour_mut().add_address(a_peer_id, addr); + b.behaviour_mut() + .send_request(a_peer_id, Ping(b"hello world".to_vec())); + + match b.next().await { + Some(SwarmEvent::Dialing(_)) => {}, + e => panic!("{:?}", e), + } + + match b.next().await { + Some(SwarmEvent::OutgoingConnectionError { .. }) => {}, + e => panic!("{:?}", e), + }; + + match b.next().await { + Some(SwarmEvent::Behaviour(RequestResponseEvent::OutboundFailure { .. })) => {}, + e => panic!("{:?}", e), + }; + + Ok(()) +} + +fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { + let mut buf = [0; 32]; + hex::decode_to_slice(s, &mut buf).unwrap(); + Cow::Owned(buf) +} From 659565530f9dea1ae12d8e9a64b5637a1392c2a1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 21 Jun 2022 16:19:29 +0400 Subject: [PATCH 007/244] use multiaddr fork for now so the code compiles also rename WebRTCDirectTransport to WebRTCTransport --- Cargo.toml | 4 + transports/webrtc/Cargo.toml | 4 +- transports/webrtc/src/connection.rs | 18 +- transports/webrtc/src/transport.rs | 301 +++++++++++++++------------- transports/webrtc/src/udp_mux.rs | 8 +- transports/webrtc/tests/smoke.rs | 26 +-- 6 files changed, 191 insertions(+), 170 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28c421702d0..ab1bb1527dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,10 @@ libp2p-websocket = { version = "0.35.0", path = "transports/websocket", optional [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.38.0", path = "protocols/gossipsub", optional = true } +# TODO: use upstream once Protocol for WebRTC is there. +[patch.crates-io] +multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } + [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } async-trait = "0.1" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 2335d007e1d..454f4b2d7ff 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -26,8 +26,8 @@ stun = "0.4.2" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18.2", features = ["net"]} -webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc", branch = "anton/168-allow-persistent-certificates" } -webrtc-data = "0.3.3" +webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 73c4fe97e5c..df299a056fe 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -120,10 +120,10 @@ impl Connection { // generally shouldn't be the case. error!("Can't send data channel: {}", e); } - }, + } Err(e) => { error!("Can't detach data channel: {}", e); - }, + } }; }) }) @@ -149,7 +149,7 @@ impl<'a> StreamMuxer for Connection { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); - let ch = PollDataChannel::new(detached); + let mut ch = PollDataChannel::new(detached); if let Some(cap) = data_channels_inner.read_buf_cap { ch.set_read_buf_capacity(cap); } @@ -159,7 +159,7 @@ impl<'a> StreamMuxer for Connection { .insert(ch.stream_identifier(), ch.clone()); Poll::Ready(Ok(StreamMuxerEvent::InboundSubstream(ch))) - }, + } None => Poll::Ready(Err(io::Error::new( io::ErrorKind::Other, "incoming_data_channels_rx is closed (no messages left)", @@ -207,7 +207,7 @@ impl<'a> StreamMuxer for Connection { Ok(detached) => { let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - let ch = PollDataChannel::new(detached); + let mut ch = PollDataChannel::new(detached); if let Some(cap) = data_channels_inner.read_buf_cap { ch.set_read_buf_capacity(cap); } @@ -217,7 +217,7 @@ impl<'a> StreamMuxer for Connection { .insert(ch.stream_identifier(), ch.clone()); Poll::Ready(Ok(ch)) - }, + } Err(e) => Poll::Ready(Err(e)), } } @@ -285,7 +285,7 @@ impl<'a> StreamMuxer for Connection { data_channels_inner.incoming_data_channels_rx.close(); Poll::Ready(Ok(())) - }, + } Err(e) => Poll::Ready(Err(e)), } } @@ -323,10 +323,10 @@ pub(crate) async fn register_data_channel_open_handler( if let Err(e) = data_channel_tx.send(detached) { error!("Can't send data channel: {:?}", e); } - }, + } Err(e) => { error!("Can't detach data channel: {}", e); - }, + } }; }) }) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index a25ed4559d9..c0221c919f8 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -74,7 +74,7 @@ enum IfWatch { Ready(IfWatcher), } -/// The listening addresses of a [`WebRTCDirectTransport`]. +/// The listening addresses of a [`WebRTCTransport`]. enum InAddr { /// The stream accepts connections on a single interface. One { out: Option }, @@ -84,7 +84,7 @@ enum InAddr { /// A WebRTC transport with direct p2p communication (without a STUN server). #[derive(Clone)] -pub struct WebRTCDirectTransport { +pub struct WebRTCTransport { /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, @@ -101,7 +101,7 @@ pub struct WebRTCDirectTransport { id_keys: identity::Keypair, } -impl WebRTCDirectTransport { +impl WebRTCTransport { /// Create a new WebRTC transport. /// /// Creates a UDP socket bound to `listen_addr`. @@ -150,14 +150,17 @@ impl WebRTCDirectTransport { } } -impl Transport for WebRTCDirectTransport { +impl Transport for WebRTCTransport { type Output = (PeerId, Connection); type Error = Error; type Listener = WebRTCListenStream; type ListenerUpgrade = BoxFuture<'static, Result>; type Dial = BoxFuture<'static, Result>; - fn listen_on(self, addr: Multiaddr) -> Result> { + fn listen_on( + &mut self, + addr: Multiaddr, + ) -> Result> { debug!("listening on {} (ignoring {})", self.udp_mux_addr, addr); Ok(WebRTCListenStream::new( self.udp_mux_addr, @@ -168,11 +171,24 @@ impl Transport for WebRTCDirectTransport { )) } - fn dial(self, addr: Multiaddr) -> Result> { - Ok(Box::pin(self.do_dial(addr))) + fn dial(&mut self, addr: Multiaddr) -> Result> { + let config = self.config.clone(); + let our_fingerprint = self.cert_fingerprint(); + let udp_mux = self.udp_mux.clone(); + let id_keys = self.id_keys.clone(); + Ok(Box::pin(do_dial( + config, + our_fingerprint, + udp_mux, + id_keys, + addr, + ))) } - fn dial_as_listener(self, addr: Multiaddr) -> Result> { + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { // XXX: anything to do here? self.dial(addr) } @@ -227,7 +243,7 @@ impl WebRTCListenStream { SocketAddr::V6(a) => a.ip().is_unspecified(), } { // The `addrs` are populated via `if_watch` when the - // `WebRTCDirectTransport` is polled. + // `WebRTCTransport` is polled. InAddr::Any { if_watch: IfWatch::Pending(IfWatcher::new().boxed()), } @@ -267,7 +283,7 @@ impl Stream for WebRTCListenStream { Ok(w) => { *if_watch = IfWatch::Ready(w); continue; - }, + } Err(err) => { debug! { "Failed to begin observing interfaces: {:?}. Scheduling retry.", @@ -278,7 +294,7 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error(Error::IoError( err, ))))); - }, + } }, // Consume all events for up/down interface changes. IfWatch::Ready(watch) => { @@ -295,7 +311,7 @@ impl Stream for WebRTCListenStream { ma, )))); } - }, + } Ok(IfEvent::Down(inet)) => { let ip = inet.addr(); if me.listen_addr.is_ipv4() == ip.is_ipv4() @@ -307,7 +323,7 @@ impl Stream for WebRTCListenStream { ListenerEvent::AddressExpired(ma), ))); } - }, + } Err(err) => { debug! { "Failure polling interfaces: {:?}. Scheduling retry.", @@ -317,10 +333,10 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(Ok(ListenerEvent::Error( Error::IoError(err), )))); - }, + } } } - }, + } }, // If the listener is bound to a single interface, make sure the address reported // once. @@ -328,16 +344,16 @@ impl Stream for WebRTCListenStream { if let Some(multiaddr) = out.take() { return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(multiaddr)))); } - }, + } } if let Some(mut pause) = me.pause.take() { match Pin::new(&mut pause).poll(cx) { - Poll::Ready(_) => {}, + Poll::Ready(_) => {} Poll::Pending => { me.pause = Some(pause); return Poll::Pending; - }, + } } } @@ -360,132 +376,133 @@ impl Stream for WebRTCListenStream { } } -impl WebRTCDirectTransport { - async fn do_dial(self, addr: Multiaddr) -> Result<(PeerId, Connection), Error> { - let socket_addr = - multiaddr_to_socketaddr(&addr).ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - return Err(Error::InvalidMultiaddr(addr.clone())); - } - - let config = self.config.clone(); - - let remote = addr.clone(); // used for logging - trace!("dialing address: {:?}", remote); - - let our_fingerprint = self.cert_fingerprint(); - let se = build_setting_engine(self.udp_mux.clone(), &socket_addr, &our_fingerprint); - let api = APIBuilder::new().with_setting_engine(se).build(); - - let peer_connection = api - .new_peer_connection(config) - .map_err(Error::WebRTC) - .await?; - - let offer = peer_connection - .create_offer(None) - .map_err(Error::WebRTC) - .await?; - debug!("OFFER: {:?}", offer.sdp); - peer_connection - .set_local_description(offer) - .map_err(Error::WebRTC) - .await?; - - // Set the remote description to the predefined SDP. - let remote_fingerprint = match fingerprint_from_addr(&addr) { - Some(f) => fingerprint_to_string(&f), - None => return Err(Error::InvalidMultiaddr(addr.clone())), - }; - let server_session_description = render_description( - sdp::SERVER_SESSION_DESCRIPTION, - socket_addr, - &remote_fingerprint, - ); - debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - peer_connection - .set_remote_description(sdp) - .map_err(Error::WebRTC) - .await?; +async fn do_dial( + config: RTCConfiguration, + our_fingerprint: String, + udp_mux: Arc, + id_keys: identity::Keypair, + addr: Multiaddr, +) -> Result<(PeerId, Connection), Error> { + let socket_addr = + multiaddr_to_socketaddr(&addr).ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(Error::InvalidMultiaddr(addr.clone())); + } - // Create a datachannel with label 'data' - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - id: Some(1), - ..RTCDataChannelInit::default() - }), - ) - .await?; + let remote = addr.clone(); // used for logging + trace!("dialing address: {:?}", remote); + + let se = build_setting_engine(udp_mux.clone(), &socket_addr, &our_fingerprint); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api + .new_peer_connection(config) + .map_err(Error::WebRTC) + .await?; + + let offer = peer_connection + .create_offer(None) + .map_err(Error::WebRTC) + .await?; + debug!("OFFER: {:?}", offer.sdp); + peer_connection + .set_local_description(offer) + .map_err(Error::WebRTC) + .await?; + + // Set the remote description to the predefined SDP. + let remote_fingerprint = match fingerprint_from_addr(&addr) { + Some(f) => fingerprint_to_string(&f), + None => return Err(Error::InvalidMultiaddr(addr.clone())), + }; + let server_session_description = render_description( + sdp::SERVER_SESSION_DESCRIPTION, + socket_addr, + &remote_fingerprint, + ); + debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + peer_connection + .set_remote_description(sdp) + .map_err(Error::WebRTC) + .await?; + + // Create a datachannel with label 'data' + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + id: Some(1), + ..RTCDataChannelInit::default() + }), + ) + .await?; - let (tx, mut rx) = oneshot::channel::>(); + let (tx, mut rx) = oneshot::channel::>(); - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; - // Wait until data channel is opened and ready to use - let detached = select! { - res = rx => match res { - Ok(detached) => detached, - Err(e) => return Err(Error::InternalError(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - }; + // Wait until data channel is opened and ready to use + let detached = select! { + res = rx => match res { + Ok(detached) => detached, + Err(e) => return Err(Error::InternalError(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + }; - trace!("noise handshake with {}", remote); - let dh_keys = Keypair::::new() - .into_authentic(&self.id_keys) - .unwrap(); - let noise = NoiseConfig::xx(dh_keys); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(detached), info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) - .await - .map_err(Error::Noise)?; - - // Exchange TLS certificate fingerprints to prevent MiM attacks. - trace!("exchanging TLS certificate fingerprints with {}", remote); - let n = noise_io.write(&our_fingerprint.into_bytes()).await?; - noise_io.flush().await?; - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint, - got: fingerprint_from_noise, - }); - } + trace!("noise handshake with {}", remote); + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_outbound(PollDataChannel::new(detached), info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await + .map_err(Error::Noise)?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + trace!("exchanging TLS certificate fingerprints with {}", remote); + let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint, + got: fingerprint_from_noise, + }); + } - trace!("verifying peer's identity {}", remote); - let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); - if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { - return Err(Error::InvalidPeerID { - expected: peer_id_from_addr, - got: peer_id, - }); - } + trace!("verifying peer's identity {}", remote); + let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); + if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { + return Err(Error::InvalidPeerID { + expected: peer_id_from_addr, + got: peer_id, + }); + } - // Close the initial data channel after noise handshake is done. - // https://github.com/webrtc-rs/sctp/pull/14 - // detached - // .close() - // .await - // .map_err(|e| Error::WebRTC(e.into()))?; + // Close the initial data channel after noise handshake is done. + // https://github.com/webrtc-rs/sctp/pull/14 + // detached + // .close() + // .await + // .map_err(|e| Error::WebRTC(e.into()))?; - Ok((peer_id, Connection::new(peer_connection).await)) - } + Ok((peer_id, Connection::new(peer_connection).await)) } /// Creates a [`Multiaddr`] from the given IP address and port number. @@ -528,7 +545,7 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { for proto in iter { match proto { - Protocol::P2p(_) => {}, // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. _ => return None, } } @@ -536,10 +553,10 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { match (proto1, proto2, proto3) { (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - }, + } (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { Some(SocketAddr::new(ip.into(), port)) - }, + } _ => None, } } @@ -712,7 +729,7 @@ mod tests { let transport = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - WebRTCDirectTransport::new(cert, id_keys, listen_addr) + WebRTCTransport::new(cert, id_keys, listen_addr) .await .expect("transport") }; @@ -742,12 +759,12 @@ mod tests { conn.await }; - let transport2 = { + let mut transport2 = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); let id_keys = identity::Keypair::generate_ed25519(); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); // okay to reuse `listen_addr` since the port is `0` (any). - WebRTCDirectTransport::new(cert, id_keys, listen_addr) + WebRTCTransport::new(cert, id_keys, listen_addr) .await .expect("transport") }; diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index a9938645ee5..ba67347c126 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -54,7 +54,7 @@ fn normalize_socket_addr(target: &SocketAddr, socket_addr: &SocketAddr) -> Socke let ipv6_mapped = target_ipv4.ip().to_ipv6_mapped(); SocketAddr::new(std::net::IpAddr::V6(ipv6_mapped), target_ipv4.port()) - }, + } // This will fail later if target is IPv6 and socket is IPv4, we ignore it here (_, _) => *target, } @@ -140,11 +140,11 @@ impl UDPMuxNewAddr { Ok(ufrag) => { let conns = self.conns.lock().await; conns.get(&ufrag).map(Clone::clone) - }, + } Err(e) => { log::error!("{} (addr: {})", e, addr); None - }, + } } } @@ -406,7 +406,7 @@ fn ufrag_from_stun_message(buffer: &[u8], local_ufrag: bool) -> Result Ok(s.to_owned()), None => Err(Error::Other("can't get ufrag from username".into())), } - }, + } } } } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 00be9e7d39f..ab1f1914abc 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -11,7 +11,7 @@ use libp2p_request_response::{ RequestResponseEvent, RequestResponseMessage, }; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use libp2p_webrtc_direct::transport::WebRTCDirectTransport; +use libp2p_webrtc::transport::WebRTCTransport; use log::trace; use rand::RngCore; use rcgen::KeyPair; @@ -34,7 +34,7 @@ async fn create_swarm() -> Result<(Swarm>, String)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let transport = WebRTCDirectTransport::new(cert, keypair, "127.0.0.1:0").await?; + let transport = WebRTCTransport::new(cert, keypair, "127.0.0.1:0").await?; let fingerprint = transport.cert_fingerprint(); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -78,22 +78,22 @@ async fn smoke() -> Result<()> { .send_request(&Swarm::local_peer_id(&a), Ping(data.clone())); match b.next().await { - Some(SwarmEvent::Dialing(_)) => {}, + Some(SwarmEvent::Dialing(_)) => {} e => panic!("{:?}", e), } match a.next().await { - Some(SwarmEvent::IncomingConnection { .. }) => {}, + Some(SwarmEvent::IncomingConnection { .. }) => {} e => panic!("{:?}", e), }; match b.next().await { - Some(SwarmEvent::ConnectionEstablished { .. }) => {}, + Some(SwarmEvent::ConnectionEstablished { .. }) => {} e => panic!("{:?}", e), }; match a.next().await { - Some(SwarmEvent::ConnectionEstablished { .. }) => {}, + Some(SwarmEvent::ConnectionEstablished { .. }) => {} e => panic!("{:?}", e), }; @@ -112,12 +112,12 @@ async fn smoke() -> Result<()> { a.behaviour_mut() .send_response(channel, Pong(ping)) .unwrap(); - }, + } e => panic!("{:?}", e), } match a.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {}, + Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {} e => panic!("{:?}", e), } @@ -153,12 +153,12 @@ async fn smoke() -> Result<()> { b.behaviour_mut() .send_response(channel, Pong(data)) .unwrap(); - }, + } e => panic!("{:?}", e), } match b.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {}, + Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => {} e => panic!("{:?}", e), } @@ -281,17 +281,17 @@ async fn dial_failure() -> Result<()> { .send_request(a_peer_id, Ping(b"hello world".to_vec())); match b.next().await { - Some(SwarmEvent::Dialing(_)) => {}, + Some(SwarmEvent::Dialing(_)) => {} e => panic!("{:?}", e), } match b.next().await { - Some(SwarmEvent::OutgoingConnectionError { .. }) => {}, + Some(SwarmEvent::OutgoingConnectionError { .. }) => {} e => panic!("{:?}", e), }; match b.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::OutboundFailure { .. })) => {}, + Some(SwarmEvent::Behaviour(RequestResponseEvent::OutboundFailure { .. })) => {} e => panic!("{:?}", e), }; From 174cab9c8e38698b945d5ffdf2144384bda283ec Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 22 Jun 2022 13:27:14 +0400 Subject: [PATCH 008/244] remove flush_all and rename close to poll_close --- transports/webrtc/src/connection.rs | 36 ++++++++++++----------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index df299a056fe..0e7a4b24f2e 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -249,6 +249,7 @@ impl<'a> StreamMuxer for Connection { cx: &mut Context<'_>, s: &mut Self::Substream, ) -> Poll> { + trace!("Flushing substream {}", s.stream_identifier()); Pin::new(s).poll_flush(cx) } @@ -262,42 +263,35 @@ impl<'a> StreamMuxer for Connection { } fn destroy_substream(&self, s: Self::Substream) { + trace!("Destroying substream {}", s.stream_identifier()); let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); data_channels_inner.map.remove(&s.stream_identifier()); } - fn close(&self, cx: &mut Context<'_>) -> Poll> { + fn poll_close(&self, cx: &mut Context<'_>) -> Poll> { debug!("Closing connection"); - // First, flush all the buffered data. - match ready!(self.flush_all(cx)) { - Ok(_) => { - // Second, shutdown all the substreams. - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - for (_, ch) in &mut data_channels_inner.map { - match ready!(self.shutdown_substream(cx, ch)) { - Ok(_) => continue, - Err(e) => return Poll::Ready(Err(e)), - } - } - - // Third, close `incoming_data_channels_rx` - data_channels_inner.incoming_data_channels_rx.close(); + let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - Poll::Ready(Ok(())) + // First, flush all the buffered data. + for (_, ch) in &mut data_channels_inner.map { + match ready!(self.flush_substream(cx, ch)) { + Ok(_) => continue, + Err(e) => return Poll::Ready(Err(e)), } - Err(e) => Poll::Ready(Err(e)), } - } - fn flush_all(&self, cx: &mut Context<'_>) -> Poll> { - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + // Second, shutdown all the substreams. for (_, ch) in &mut data_channels_inner.map { - match ready!(self.flush_substream(cx, ch)) { + match ready!(self.shutdown_substream(cx, ch)) { Ok(_) => continue, Err(e) => return Poll::Ready(Err(e)), } } + + // Third, close `incoming_data_channels_rx` + data_channels_inner.incoming_data_channels_rx.close(); + Poll::Ready(Ok(())) } } From 4de01b096fef00fe644c0393a99053d3e0c33986 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 22 Jun 2022 17:05:24 +0400 Subject: [PATCH 009/244] loosen the deps version requirements --- transports/webrtc/Cargo.toml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 454f4b2d7ff..dbfe019fe73 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -10,22 +10,22 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-trait = "0.1.52" +async-trait = "0.1" bytes = "1" fnv = "1.0" -futures = "0.3.17" -futures-lite = "1.12.0" -futures-timer = "3.0" +futures = "0.3" +futures-lite = "1" +futures-timer = "3" hex = "0.4" -if-watch = "0.2.2" +if-watch = "0.2" libp2p-core = { version = "0.33.0", path = "../../core", default-features = false } libp2p-noise = { version = "0.36.0", path = "../../transports/noise" } -log = "0.4.14" +log = "0.4" serde = { version = "1.0", features = ["derive"] } -stun = "0.4.2" +stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio-crate = { package = "tokio", version = "1.18.2", features = ["net"]} +tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } webrtc-ice = "0.7.0" @@ -33,13 +33,13 @@ webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } [dev-dependencies] -anyhow = "1.0.41" -env_logger = "0.9.0" +anyhow = "1.0" +env_logger = "0.9" libp2p-request-response = { version = "0.18.0", path = "../../protocols/request-response" } libp2p-swarm = { version = "0.36.0", path = "../../swarm" } -rand = "0.8.4" -rand_core = "0.5.1" -rcgen = "0.8.14" +rand = "0.8" +rand_core = "0.5" +rcgen = "0.8" [patch.crates-io] webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } From 44e69590d4998197af321a8d77f94cf84bb61120 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 28 Jun 2022 12:53:33 +0400 Subject: [PATCH 010/244] use patched versions of ice, sctp, dtls and data until proper versions are released --- Cargo.toml | 7 ++++++- core/Cargo.toml | 2 +- protocols/gossipsub/Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 11 ++++------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca34ffc971d..33c24dd448c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,9 +114,14 @@ libp2p-websocket = { version = "0.35.0", path = "transports/websocket", optional [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.38.0", path = "protocols/gossipsub", optional = true } -# TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] +# TODO: use upstream once Protocol for WebRTC is there. multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } +# TODO: remove once webrtc-* crates have new versions +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "anton/new-sctp" } +webrtc-ice = { version = "0.7.0", git = "https://github.com/webrtc-rs/ice.git", branch = "main" } +webrtc-sctp = { version = "0.5.0", git = "https://github.com/webrtc-rs/sctp.git", branch = "main" } +webrtc-dtls = { version = "0.5.3", git = "https://github.com/melekes/webrtc-dtls.git", branch = "anton/old-elliptic-curve" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/core/Cargo.toml b/core/Cargo.toml index b6a45248a56..08e009eb33c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ log = "0.4" multiaddr = { version = "0.14.0" } multihash = { version = "0.16", default-features = false, features = ["std", "multihash-impl", "identity", "sha2"] } multistream-select = { version = "0.11", path = "../misc/multistream-select" } -p256 = { version = "0.10.0", default-features = false, features = ["ecdsa"], optional = true } +p256 = { version = "0.10.1", default-features = false, features = ["ecdsa"], optional = true } parking_lot = "0.12.0" pin-project = "1.0.0" prost = "0.10" diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 73f6f4180a2..e8add666745 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -42,7 +42,7 @@ libp2p-mplex = { path = "../../muxers/mplex" } libp2p-noise = { path = "../../transports/noise" } quickcheck = "0.9.2" hex = "0.4.2" -derive_builder = "0.11.1" +derive_builder = "0.11.2" [build-dependencies] prost-build = "0.10" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index dbfe019fe73..fd5b2395f5f 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -25,12 +25,12 @@ serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} +tokio-crate = { package = "tokio", version = "1.19", features = ["net"]} webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } +webrtc-data = "0.3.3" webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" -webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } +webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } [dev-dependencies] anyhow = "1.0" @@ -39,7 +39,4 @@ libp2p-request-response = { version = "0.18.0", path = "../../protocols/request- libp2p-swarm = { version = "0.36.0", path = "../../swarm" } rand = "0.8" rand_core = "0.5" -rcgen = "0.8" - -[patch.crates-io] -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } +rcgen = "0.9" From eb494b773659323a5fa538f8400076bfb43002af Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 28 Jun 2022 14:09:19 +0400 Subject: [PATCH 011/244] Revert "use patched versions of ice, sctp, dtls and data" This reverts commit 44e69590d4998197af321a8d77f94cf84bb61120. --- Cargo.toml | 7 +------ core/Cargo.toml | 2 +- protocols/gossipsub/Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 11 +++++++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33c24dd448c..ca34ffc971d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,14 +114,9 @@ libp2p-websocket = { version = "0.35.0", path = "transports/websocket", optional [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.38.0", path = "protocols/gossipsub", optional = true } -[patch.crates-io] # TODO: use upstream once Protocol for WebRTC is there. +[patch.crates-io] multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } -# TODO: remove once webrtc-* crates have new versions -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "anton/new-sctp" } -webrtc-ice = { version = "0.7.0", git = "https://github.com/webrtc-rs/ice.git", branch = "main" } -webrtc-sctp = { version = "0.5.0", git = "https://github.com/webrtc-rs/sctp.git", branch = "main" } -webrtc-dtls = { version = "0.5.3", git = "https://github.com/melekes/webrtc-dtls.git", branch = "anton/old-elliptic-curve" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/core/Cargo.toml b/core/Cargo.toml index 08e009eb33c..b6a45248a56 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ log = "0.4" multiaddr = { version = "0.14.0" } multihash = { version = "0.16", default-features = false, features = ["std", "multihash-impl", "identity", "sha2"] } multistream-select = { version = "0.11", path = "../misc/multistream-select" } -p256 = { version = "0.10.1", default-features = false, features = ["ecdsa"], optional = true } +p256 = { version = "0.10.0", default-features = false, features = ["ecdsa"], optional = true } parking_lot = "0.12.0" pin-project = "1.0.0" prost = "0.10" diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index e8add666745..73f6f4180a2 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -42,7 +42,7 @@ libp2p-mplex = { path = "../../muxers/mplex" } libp2p-noise = { path = "../../transports/noise" } quickcheck = "0.9.2" hex = "0.4.2" -derive_builder = "0.11.2" +derive_builder = "0.11.1" [build-dependencies] prost-build = "0.10" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index fd5b2395f5f..dbfe019fe73 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -25,12 +25,12 @@ serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio-crate = { package = "tokio", version = "1.19", features = ["net"]} +tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } -webrtc-data = "0.3.3" +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" -webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } +webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } [dev-dependencies] anyhow = "1.0" @@ -39,4 +39,7 @@ libp2p-request-response = { version = "0.18.0", path = "../../protocols/request- libp2p-swarm = { version = "0.36.0", path = "../../swarm" } rand = "0.8" rand_core = "0.5" -rcgen = "0.9" +rcgen = "0.8" + +[patch.crates-io] +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } From c53bb436b40b4f3282e4d6acac5545a337137bd1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 28 Jun 2022 14:56:20 +0400 Subject: [PATCH 012/244] update data crate --- transports/webrtc/Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index dbfe019fe73..f721e36891a 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -27,7 +27,7 @@ thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", rev = "9656a255a69365268a5b1e0978b84e56f4d7114d" } webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } @@ -39,7 +39,4 @@ libp2p-request-response = { version = "0.18.0", path = "../../protocols/request- libp2p-swarm = { version = "0.36.0", path = "../../swarm" } rand = "0.8" rand_core = "0.5" -rcgen = "0.8" - -[patch.crates-io] -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", branch = "main" } +rcgen = "0.9" From 490e7ab54602c5e78196b73acdc2228add960915 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 29 Jun 2022 13:25:13 +0400 Subject: [PATCH 013/244] cargo: add webrtc to members --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ca34ffc971d..2ffacc40c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,8 @@ members = [ "transports/tcp", "transports/uds", "transports/websocket", - "transports/wasm-ext" + "transports/wasm-ext", + "transports/webrtc" ] [[example]] From 3c7f796705605aa7006146b5eced91312c9f3ebd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 29 Jun 2022 16:15:04 +0400 Subject: [PATCH 014/244] fix feature syntax err --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2ffacc40c60..413ac311c55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ websocket = ["dep:libp2p-websocket"] yamux = ["dep:libp2p-yamux"] secp256k1 = ["libp2p-core/secp256k1"] serde = ["libp2p-core/serde", "libp2p-kad?/serde", "libp2p-gossipsub?/serde"] -webrtc = ["libp2p-webrtc"] +webrtc = ["dep:libp2p-webrtc"] [package.metadata.docs.rs] all-features = true From d5b2e096ff5527140eb017adcdd8f38130d753a9 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 29 Jun 2022 16:54:26 +0400 Subject: [PATCH 015/244] enable webrtc by default (temporary) --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 413ac311c55..2d86c6e2c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ default = [ "wasm-ext", "websocket", "yamux", + "webrtc" ] autonat = ["dep:libp2p-autonat"] dcutr = ["dep:libp2p-dcutr", "libp2p-metrics?/dcutr"] From bd8091fcf743eb5e99fcefa8713c8ce65988fdda Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 30 Jun 2022 14:47:49 +0400 Subject: [PATCH 016/244] Revert "enable webrtc by default (temporary)" This reverts commit d5b2e096ff5527140eb017adcdd8f38130d753a9. --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2d86c6e2c0b..413ac311c55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ default = [ "wasm-ext", "websocket", "yamux", - "webrtc" ] autonat = ["dep:libp2p-autonat"] dcutr = ["dep:libp2p-dcutr", "libp2p-metrics?/dcutr"] From 6d703f88fcb099ebff22cfa6c5b17aed4c36ff6d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 30 Jun 2022 14:59:59 +0400 Subject: [PATCH 017/244] add libp2p_webrtc to libp2p --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 45174b66af7..2bfa29e1617 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,10 @@ pub use libp2p_websocket as websocket; #[cfg_attr(docsrs, doc(cfg(feature = "yamux")))] #[doc(inline)] pub use libp2p_yamux as yamux; +#[cfg(feature = "webrtc")] +#[cfg_attr(docsrs, doc(cfg(feature = "webrtc")))] +#[doc(inline)] +pub use libp2p_webrtc as webrtc; mod transport_ext; From 64d60e9a391f53f3a94a61fa6cf8740ef94dc8bc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 11 Jul 2022 11:49:17 +0400 Subject: [PATCH 018/244] fix ErrShortBuffer error by increasing the temporary buffer size Refs https://github.com/webrtc-rs/sctp/issues/28 --- transports/webrtc/src/transport.rs | 6 +++++- transports/webrtc/src/upgrade.rs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index c0221c919f8..3a2d7fa84b2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -502,7 +502,11 @@ async fn do_dial( // .await // .map_err(|e| Error::WebRTC(e.into()))?; - Ok((peer_id, Connection::new(peer_connection).await)) + let mut c = Connection::new(peer_connection).await; + // XXX: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) } /// Creates a [`Multiaddr`] from the given IP address and port number. diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index baf1a02df2a..3fc68581c57 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -160,5 +160,9 @@ pub async fn webrtc( // .await // .map_err(|e| Error::WebRTC(e.into()))?; - Ok((peer_id, Connection::new(peer_connection).await)) + let mut c = Connection::new(peer_connection).await; + // XXX: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) } From d96b499ce780ce5a0d762233a53d2a879000c225 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 11 Jul 2022 17:44:56 +0400 Subject: [PATCH 019/244] update to support new Transport interface Refs https://github.com/libp2p/rust-libp2p/pull/2652 --- transports/webrtc/Cargo.toml | 8 +- transports/webrtc/src/connection.rs | 107 +++-- transports/webrtc/src/error.rs | 3 + transports/webrtc/src/transport.rs | 607 ++++++++++++++++------------ transports/webrtc/tests/smoke.rs | 4 +- 5 files changed, 418 insertions(+), 311 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index f721e36891a..d7adb8e7feb 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -18,8 +18,8 @@ futures-lite = "1" futures-timer = "3" hex = "0.4" if-watch = "0.2" -libp2p-core = { version = "0.33.0", path = "../../core", default-features = false } -libp2p-noise = { version = "0.36.0", path = "../../transports/noise" } +libp2p-core = { version = "0.34.0", path = "../../core", default-features = false } +libp2p-noise = { version = "0.37.0", path = "../../transports/noise" } log = "0.4" serde = { version = "1.0", features = ["derive"] } stun = "0.4" @@ -35,8 +35,8 @@ webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", [dev-dependencies] anyhow = "1.0" env_logger = "0.9" -libp2p-request-response = { version = "0.18.0", path = "../../protocols/request-response" } -libp2p-swarm = { version = "0.36.0", path = "../../swarm" } +libp2p-request-response = { version = "0.20.0", path = "../../protocols/request-response" } +libp2p-swarm = { version = "0.38.0", path = "../../swarm" } rand = "0.8" rand_core = "0.5" rcgen = "0.9" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 0e7a4b24f2e..0de8b50c586 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -33,7 +33,6 @@ use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use std::io; -use std::pin::Pin; use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; @@ -226,47 +225,47 @@ impl<'a> StreamMuxer for Connection { /// abruptly interrupt the execution. fn destroy_outbound(&self, _s: Self::OutboundSubstream) {} - fn read_substream( - &self, - cx: &mut Context<'_>, - s: &mut Self::Substream, - buf: &mut [u8], - ) -> Poll> { - Pin::new(s).poll_read(cx, buf) - } - - fn write_substream( - &self, - cx: &mut Context<'_>, - s: &mut Self::Substream, - buf: &[u8], - ) -> Poll> { - Pin::new(s).poll_write(cx, buf) - } - - fn flush_substream( - &self, - cx: &mut Context<'_>, - s: &mut Self::Substream, - ) -> Poll> { - trace!("Flushing substream {}", s.stream_identifier()); - Pin::new(s).poll_flush(cx) - } - - fn shutdown_substream( - &self, - cx: &mut Context<'_>, - s: &mut Self::Substream, - ) -> Poll> { - trace!("Closing substream {}", s.stream_identifier()); - Pin::new(s).poll_close(cx) - } - - fn destroy_substream(&self, s: Self::Substream) { - trace!("Destroying substream {}", s.stream_identifier()); - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - data_channels_inner.map.remove(&s.stream_identifier()); - } + // fn read_substream( + // &self, + // cx: &mut Context<'_>, + // s: &mut Self::Substream, + // buf: &mut [u8], + // ) -> Poll> { + // Pin::new(s).poll_read(cx, buf) + // } + + // fn write_substream( + // &self, + // cx: &mut Context<'_>, + // s: &mut Self::Substream, + // buf: &[u8], + // ) -> Poll> { + // Pin::new(s).poll_write(cx, buf) + // } + + // fn flush_substream( + // &self, + // cx: &mut Context<'_>, + // s: &mut Self::Substream, + // ) -> Poll> { + // trace!("Flushing substream {}", s.stream_identifier()); + // Pin::new(s).poll_flush(cx) + // } + + // fn shutdown_substream( + // &self, + // cx: &mut Context<'_>, + // s: &mut Self::Substream, + // ) -> Poll> { + // trace!("Closing substream {}", s.stream_identifier()); + // Pin::new(s).poll_close(cx) + // } + + // fn destroy_substream(&self, s: Self::Substream) { + // trace!("Destroying substream {}", s.stream_identifier()); + // let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); + // data_channels_inner.map.remove(&s.stream_identifier()); + // } fn poll_close(&self, cx: &mut Context<'_>) -> Poll> { debug!("Closing connection"); @@ -274,20 +273,20 @@ impl<'a> StreamMuxer for Connection { let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); // First, flush all the buffered data. - for (_, ch) in &mut data_channels_inner.map { - match ready!(self.flush_substream(cx, ch)) { - Ok(_) => continue, - Err(e) => return Poll::Ready(Err(e)), - } - } + // for (_, ch) in &mut data_channels_inner.map { + // match ready!(self.flush_substream(cx, ch)) { + // Ok(_) => continue, + // Err(e) => return Poll::Ready(Err(e)), + // } + // } // Second, shutdown all the substreams. - for (_, ch) in &mut data_channels_inner.map { - match ready!(self.shutdown_substream(cx, ch)) { - Ok(_) => continue, - Err(e) => return Poll::Ready(Err(e)), - } - } + // for (_, ch) in &mut data_channels_inner.map { + // match ready!(self.shutdown_substream(cx, ch)) { + // Ok(_) => continue, + // Err(e) => return Poll::Ready(Err(e)), + // } + // } // Third, close `incoming_data_channels_rx` data_channels_inner.incoming_data_channels_rx.close(); diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 7a73da660bb..8b62279a12c 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -42,6 +42,9 @@ pub enum Error { got: PeerId, }, + #[error("no active listeners")] + NoListeners, + #[error("internal error: {0} (see debug logs)")] InternalError(String), } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 3a2d7fa84b2..f521743ef8c 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -32,14 +32,14 @@ use libp2p_core::identity; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, muxing::StreamMuxerBox, - transport::{Boxed, ListenerEvent, TransportError}, + transport::{Boxed, ListenerId, TransportError, TransportEvent}, PeerId, Transport, }; use libp2p_core::{OutboundUpgrade, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; use tinytemplate::TinyTemplate; -use tokio_crate::net::{ToSocketAddrs, UdpSocket}; +use tokio_crate::net::UdpSocket; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; @@ -52,19 +52,19 @@ use webrtc_ice::udp_mux::UDPMux; use webrtc_ice::udp_network::UDPNetwork; use std::borrow::Cow; +use std::collections::VecDeque; use std::io; use std::net::IpAddr; use std::net::SocketAddr; use std::pin::Pin; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; -use crate::error::Error; -use crate::sdp; - use crate::connection::Connection; use crate::connection::PollDataChannel; +use crate::error::Error; +use crate::sdp; use crate::udp_mux::UDPMuxNewAddr; use crate::udp_mux::UDPMuxParams; use crate::upgrade; @@ -83,56 +83,31 @@ enum InAddr { } /// A WebRTC transport with direct p2p communication (without a STUN server). -#[derive(Clone)] pub struct WebRTCTransport { /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, - - /// The `UDPMux` that manages all ICE connections. - udp_mux: Arc, - - /// The local address of `udp_mux`. - udp_mux_addr: SocketAddr, - - /// The receiver for new `SocketAddr` connecting to this peer. - new_addr_rx: Arc>>, - /// `Keypair` identifying this peer id_keys: identity::Keypair, + /// All the active listeners. + /// The `WebRTCListenStream` struct contains a stream that we want to be pinned. Since the `VecDeque` + /// can be resized, the only way is to use a `Pin>`. + listeners: VecDeque>>, + /// Pending transport events to return from [`WebRTCTransport::poll`]. + pending_events: VecDeque::ListenerUpgrade, Error>>, } impl WebRTCTransport { /// Create a new WebRTC transport. - /// - /// Creates a UDP socket bound to `listen_addr`. - pub async fn new( - certificate: RTCCertificate, - id_keys: identity::Keypair, - listen_addr: A, - ) -> Result> { - // Bind to `listen_addr` and construct a UDP mux. - let socket = UdpSocket::bind(listen_addr) - .map_err(Error::IoError) - .map_err(TransportError::Other) - .await?; - // Sender and receiver for new addresses - let (new_addr_tx, new_addr_rx) = mpsc::channel(1); - let udp_mux_addr = socket - .local_addr() - .map_err(Error::IoError) - .map_err(TransportError::Other)?; - let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket), new_addr_tx); - - Ok(Self { + pub fn new(certificate: RTCCertificate, id_keys: identity::Keypair) -> Self { + Self { config: RTCConfiguration { certificates: vec![certificate], ..RTCConfiguration::default() }, - udp_mux, - udp_mux_addr, - new_addr_rx: Arc::new(Mutex::new(new_addr_rx)), id_keys, - }) + listeners: VecDeque::new(), + pending_events: VecDeque::new(), + } } /// Returns the SHA-256 fingerprint of the certificate in lowercase hex string as expressed @@ -148,59 +123,330 @@ impl WebRTCTransport { }) .boxed() } + + fn do_listen( + &self, + listener_id: ListenerId, + addr: Multiaddr, + ) -> Result> { + let sock_addr = multiaddr_to_socketaddr(&addr) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?; + + let std_sock = std::net::UdpSocket::bind(sock_addr) + .map_err(Error::IoError) + .map_err(TransportError::Other)?; + std_sock + .set_nonblocking(true) + .map_err(Error::IoError) + .map_err(TransportError::Other)?; + let socket = UdpSocket::from_std(std_sock) + .map_err(Error::IoError) + .map_err(TransportError::Other)?; + + let listen_addr = socket + .local_addr() + .map_err(Error::IoError) + .map_err(TransportError::Other)?; + + debug!("listening on {}", listen_addr); + + // Sender and receiver for new addresses + let (new_addr_tx, new_addr_rx) = mpsc::channel(1); + let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket), new_addr_tx); + + Ok(WebRTCListenStream::new( + listener_id, + listen_addr, + self.config.clone(), + udp_mux, + new_addr_rx, + self.id_keys.clone(), + )) + } } impl Transport for WebRTCTransport { type Output = (PeerId, Connection); type Error = Error; - type Listener = WebRTCListenStream; type ListenerUpgrade = BoxFuture<'static, Result>; type Dial = BoxFuture<'static, Result>; - fn listen_on( - &mut self, - addr: Multiaddr, - ) -> Result> { - debug!("listening on {} (ignoring {})", self.udp_mux_addr, addr); - Ok(WebRTCListenStream::new( - self.udp_mux_addr, - self.config.clone(), - self.udp_mux.clone(), - self.new_addr_rx.clone(), - self.id_keys.clone(), - )) + fn listen_on(&mut self, addr: Multiaddr) -> Result> { + let id = ListenerId::new(); + let listener = self.do_listen(id, addr)?; + self.listeners.push_back(Box::pin(listener)); + Ok(id) + } + + fn remove_listener(&mut self, id: ListenerId) -> bool { + if let Some(index) = self.listeners.iter().position(|l| l.listener_id != id) { + self.listeners.remove(index); + self.pending_events + .push_back(TransportEvent::ListenerClosed { + listener_id: id, + reason: Ok(()), + }); + true + } else { + false + } + } + + /// Poll all listeners. + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // Return pending events from closed listeners. + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + // We remove each element from `listeners` one by one and add them back. + let mut remaining = self.listeners.len(); + while let Some(mut listener) = self.listeners.pop_back() { + match TryStream::try_poll_next(listener.as_mut(), cx) { + Poll::Pending => { + self.listeners.push_front(listener); + remaining -= 1; + if remaining == 0 { + break; + } + } + Poll::Ready(Some(Ok(WebRTCListenerEvent::Upgrade { + upgrade, + local_addr, + remote_addr, + }))) => { + let id = listener.listener_id; + self.listeners.push_front(listener); + return Poll::Ready(TransportEvent::Incoming { + listener_id: id, + upgrade, + local_addr, + send_back_addr: remote_addr, + }); + } + Poll::Ready(Some(Ok(WebRTCListenerEvent::NewAddress(a)))) => { + let id = listener.listener_id; + self.listeners.push_front(listener); + return Poll::Ready(TransportEvent::NewAddress { + listener_id: id, + listen_addr: a, + }); + } + Poll::Ready(Some(Ok(WebRTCListenerEvent::AddressExpired(a)))) => { + let id = listener.listener_id; + self.listeners.push_front(listener); + return Poll::Ready(TransportEvent::AddressExpired { + listener_id: id, + listen_addr: a, + }); + } + Poll::Ready(Some(Ok(WebRTCListenerEvent::Error(error)))) => { + let id = listener.listener_id; + self.listeners.push_front(listener); + return Poll::Ready(TransportEvent::ListenerError { + listener_id: id, + error: error.into(), + }); + } + Poll::Ready(None) => { + return Poll::Ready(TransportEvent::ListenerClosed { + listener_id: listener.listener_id, + reason: Ok(()), + }); + } + Poll::Ready(Some(Err(err))) => { + return Poll::Ready(TransportEvent::ListenerClosed { + listener_id: listener.listener_id, + reason: Err(err), + }); + } + } + } + Poll::Pending } fn dial(&mut self, addr: Multiaddr) -> Result> { - let config = self.config.clone(); - let our_fingerprint = self.cert_fingerprint(); - let udp_mux = self.udp_mux.clone(); - let id_keys = self.id_keys.clone(); - Ok(Box::pin(do_dial( - config, - our_fingerprint, - udp_mux, - id_keys, - addr, - ))) + self.dial_as_listener(addr) } fn dial_as_listener( &mut self, addr: Multiaddr, ) -> Result> { - // XXX: anything to do here? - self.dial(addr) + let config = self.config.clone(); + let our_fingerprint = self.cert_fingerprint(); + let id_keys = self.id_keys.clone(); + let udp_mux = if let Some(l) = self.listeners.back() { + l.udp_mux.clone() + } else { + return Err(TransportError::Other(Error::NoListeners)); + }; + + let sock_addr = multiaddr_to_socketaddr(&addr) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + + let remote = addr.clone(); // used for logging + trace!("dialing address: {:?}", remote); + + let se = build_setting_engine(udp_mux.clone(), &sock_addr, &our_fingerprint); + let api = APIBuilder::new().with_setting_engine(se).build(); + + // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus + // do the `set_remote_description` call within the [`Future`]. + Ok(async move { + let peer_connection = api + .new_peer_connection(config) + .map_err(Error::WebRTC) + .await?; + + let offer = peer_connection + .create_offer(None) + .map_err(Error::WebRTC) + .await?; + debug!("OFFER: {:?}", offer.sdp); + peer_connection + .set_local_description(offer) + .map_err(Error::WebRTC) + .await?; + + // Set the remote description to the predefined SDP. + let remote_fingerprint = match fingerprint_from_addr(&addr) { + Some(f) => fingerprint_to_string(&f), + None => return Err(Error::InvalidMultiaddr(addr.clone())), + }; + let server_session_description = render_description( + sdp::SERVER_SESSION_DESCRIPTION, + sock_addr, + &remote_fingerprint, + ); + debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + peer_connection + .set_remote_description(sdp) + .map_err(Error::WebRTC) + .await?; + + // Create a datachannel with label 'data' + let data_channel = peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + id: Some(1), + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + + // Wait until data channel is opened and ready to use + let detached = select! { + res = rx => match res { + Ok(detached) => detached, + Err(e) => return Err(Error::InternalError(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + }; + + trace!("noise handshake with {}", remote); + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_outbound(PollDataChannel::new(detached), info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await + .map_err(Error::Noise)?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + trace!("exchanging TLS certificate fingerprints with {}", remote); + let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = String::from_utf8(buf) + .map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint, + got: fingerprint_from_noise, + }); + } + + trace!("verifying peer's identity {}", remote); + let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); + if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { + return Err(Error::InvalidPeerID { + expected: peer_id_from_addr, + got: peer_id, + }); + } + + // Close the initial data channel after noise handshake is done. + // https://github.com/webrtc-rs/sctp/pull/14 + // detached + // .close() + // .await + // .map_err(|e| Error::WebRTC(e.into()))?; + + let mut c = Connection::new(peer_connection).await; + // XXX: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) + } + .boxed()) } fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - // XXX: anything to do here? libp2p_core::address_translation(server, observed) } } +/// Event produced by a [`WebRTCListenStream`]. +#[derive(Debug)] +pub enum WebRTCListenerEvent { + /// The listener is listening on a new additional [`Multiaddr`]. + NewAddress(Multiaddr), + /// An upgrade, consisting of the upgrade future, the listener address and the remote address. + Upgrade { + /// The upgrade. + upgrade: S, + /// The local address which produced this upgrade. + local_addr: Multiaddr, + /// The remote address which produced this upgrade. + remote_addr: Multiaddr, + }, + /// A [`Multiaddr`] is no longer used for listening. + AddressExpired(Multiaddr), + /// A non-fatal error has happened on the listener. + /// + /// This event should be generated in order to notify the user that something wrong has + /// happened. The listener, however, continues to run. + Error(Error), +} + /// A stream of incoming connections on one or more interfaces. pub struct WebRTCListenStream { + /// The ID of this listener. + listener_id: ListenerId, /// The socket address that the listening socket is bound to, /// which may be a "wildcard address" like `INADDR_ANY` or `IN6ADDR_ANY` /// when listening on all interfaces for IPv4 respectively IPv6 connections. @@ -216,25 +462,24 @@ pub struct WebRTCListenStream { sleep_on_error: Duration, /// The current pause, if any. pause: Option, - /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, /// The `UDPMux` that manages all ICE connections. udp_mux: Arc, /// The receiver for new `SocketAddr` connecting to this peer. - new_addr_rx: Arc>>, + new_addr_rx: mpsc::Receiver, /// `Keypair` identifying this peer id_keys: identity::Keypair, } impl WebRTCListenStream { - /// Constructs a `WebRTCListenStream` for incoming connections around - /// the given `TcpListener`. + /// Constructs a `WebRTCListenStream` for incoming connections. fn new( + listener_id: ListenerId, listen_addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - new_addr_rx: Arc>>, + new_addr_rx: mpsc::Receiver, id_keys: identity::Keypair, ) -> Self { // Check whether the listening IP is set or not. @@ -254,6 +499,7 @@ impl WebRTCListenStream { }; WebRTCListenStream { + listener_id, listen_addr, in_addr, pause: None, @@ -267,10 +513,8 @@ impl WebRTCListenStream { } impl Stream for WebRTCListenStream { - type Item = Result< - ListenerEvent>, Error>, - Error, - >; + type Item = + Result>>, Error>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let me = Pin::into_inner(self); @@ -291,9 +535,9 @@ impl Stream for WebRTCListenStream { }; *if_watch = IfWatch::Pending(IfWatcher::new().boxed()); me.pause = Some(Delay::new(me.sleep_on_error)); - return Poll::Ready(Some(Ok(ListenerEvent::Error(Error::IoError( - err, - ))))); + return Poll::Ready(Some(Ok(WebRTCListenerEvent::Error( + Error::IoError(err), + )))); } }, // Consume all events for up/down interface changes. @@ -307,9 +551,9 @@ impl Stream for WebRTCListenStream { { let ma = ip_to_multiaddr(ip, me.listen_addr.port()); debug!("New listen address: {}", ma); - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( - ma, - )))); + return Poll::Ready(Some(Ok( + WebRTCListenerEvent::NewAddress(ma), + ))); } } Ok(IfEvent::Down(inet)) => { @@ -320,7 +564,7 @@ impl Stream for WebRTCListenStream { let ma = ip_to_multiaddr(ip, me.listen_addr.port()); debug!("Expired listen address: {}", ma); return Poll::Ready(Some(Ok( - ListenerEvent::AddressExpired(ma), + WebRTCListenerEvent::AddressExpired(ma), ))); } } @@ -330,7 +574,7 @@ impl Stream for WebRTCListenStream { err }; me.pause = Some(Delay::new(me.sleep_on_error)); - return Poll::Ready(Some(Ok(ListenerEvent::Error( + return Poll::Ready(Some(Ok(WebRTCListenerEvent::Error( Error::IoError(err), )))); } @@ -342,7 +586,7 @@ impl Stream for WebRTCListenStream { // once. InAddr::One { out } => { if let Some(multiaddr) = out.take() { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(multiaddr)))); + return Poll::Ready(Some(Ok(WebRTCListenerEvent::NewAddress(multiaddr)))); } } } @@ -358,8 +602,8 @@ impl Stream for WebRTCListenStream { } // Safe to unwrap here since this is the only place `new_addr_rx` is locked. - return match Pin::new(&mut *me.new_addr_rx.lock().unwrap()).poll_next(cx) { - Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + return match Pin::new(&mut me.new_addr_rx).poll_next(cx) { + Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(WebRTCListenerEvent::Upgrade { local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), remote_addr: addr.clone(), upgrade: Box::pin(upgrade::webrtc( @@ -376,139 +620,6 @@ impl Stream for WebRTCListenStream { } } -async fn do_dial( - config: RTCConfiguration, - our_fingerprint: String, - udp_mux: Arc, - id_keys: identity::Keypair, - addr: Multiaddr, -) -> Result<(PeerId, Connection), Error> { - let socket_addr = - multiaddr_to_socketaddr(&addr).ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - return Err(Error::InvalidMultiaddr(addr.clone())); - } - - let remote = addr.clone(); // used for logging - trace!("dialing address: {:?}", remote); - - let se = build_setting_engine(udp_mux.clone(), &socket_addr, &our_fingerprint); - let api = APIBuilder::new().with_setting_engine(se).build(); - - let peer_connection = api - .new_peer_connection(config) - .map_err(Error::WebRTC) - .await?; - - let offer = peer_connection - .create_offer(None) - .map_err(Error::WebRTC) - .await?; - debug!("OFFER: {:?}", offer.sdp); - peer_connection - .set_local_description(offer) - .map_err(Error::WebRTC) - .await?; - - // Set the remote description to the predefined SDP. - let remote_fingerprint = match fingerprint_from_addr(&addr) { - Some(f) => fingerprint_to_string(&f), - None => return Err(Error::InvalidMultiaddr(addr.clone())), - }; - let server_session_description = render_description( - sdp::SERVER_SESSION_DESCRIPTION, - socket_addr, - &remote_fingerprint, - ); - debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - peer_connection - .set_remote_description(sdp) - .map_err(Error::WebRTC) - .await?; - - // Create a datachannel with label 'data' - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - id: Some(1), - ..RTCDataChannelInit::default() - }), - ) - .await?; - - let (tx, mut rx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; - - // Wait until data channel is opened and ready to use - let detached = select! { - res = rx => match res { - Ok(detached) => detached, - Err(e) => return Err(Error::InternalError(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - }; - - trace!("noise handshake with {}", remote); - let dh_keys = Keypair::::new() - .into_authentic(&id_keys) - .unwrap(); - let noise = NoiseConfig::xx(dh_keys); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(detached), info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) - .await - .map_err(Error::Noise)?; - - // Exchange TLS certificate fingerprints to prevent MiM attacks. - trace!("exchanging TLS certificate fingerprints with {}", remote); - let n = noise_io.write(&our_fingerprint.into_bytes()).await?; - noise_io.flush().await?; - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint, - got: fingerprint_from_noise, - }); - } - - trace!("verifying peer's identity {}", remote); - let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); - if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { - return Err(Error::InvalidPeerID { - expected: peer_id_from_addr, - got: peer_id, - }); - } - - // Close the initial data channel after noise handshake is done. - // https://github.com/webrtc-rs/sctp/pull/14 - // detached - // .close() - // .await - // .map_err(|e| Error::WebRTC(e.into()))?; - - let mut c = Connection::new(peer_connection).await; - // XXX: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - Ok((peer_id, c)) -} - /// Creates a [`Multiaddr`] from the given IP address and port number. fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { Multiaddr::empty().with(ip.into()).with(Protocol::Udp(port)) @@ -630,7 +741,7 @@ fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { #[cfg(test)] mod tests { use super::*; - use libp2p_core::{multiaddr::Protocol, Multiaddr, Transport}; + use libp2p_core::{multiaddr::Protocol, Multiaddr}; use rcgen::KeyPair; use std::net::IpAddr; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -716,37 +827,31 @@ mod tests { #[tokio::test] async fn dialer_connects_to_listener_ipv4() { let _ = env_logger::builder().is_test(true).try_init(); - let a = "127.0.0.1:0".parse().unwrap(); - connect(a).await; + let a = "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap(); + futures::executor::block_on(connect(a)); } #[tokio::test] async fn dialer_connects_to_listener_ipv6() { let _ = env_logger::builder().is_test(true).try_init(); - let a = "[::1]:0".parse().unwrap(); - connect(a).await; + let a = "/ip6/::1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap(); + futures::executor::block_on(connect(a)); } - async fn connect(listen_addr: SocketAddr) { + async fn connect(listen_addr: Multiaddr) { let id_keys = identity::Keypair::generate_ed25519(); let t1_peer_id = PeerId::from_public_key(&id_keys.public()); - let transport = { + let mut transport = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - WebRTCTransport::new(cert, id_keys, listen_addr) - .await - .expect("transport") + WebRTCTransport::new(cert, id_keys).boxed() }; - let mut listener = transport - .clone() - .listen_on(ip_to_multiaddr(listen_addr.ip(), listen_addr.port())) - .expect("listener"); + transport.listen_on(listen_addr.clone()).expect("listener"); - let addr = listener - .try_next() + let addr = transport + .next() .await - .expect("some event") .expect("no error") .into_new_address() .expect("listen address"); @@ -754,29 +859,29 @@ mod tests { assert_ne!(Some(Protocol::Udp(0)), addr.iter().nth(1)); let inbound = async move { - let (conn, _addr) = listener - .try_filter_map(|e| future::ready(Ok(e.into_upgrade()))) - .try_next() + let (conn, _addr) = transport + .select_next_some() + .map(|ev| ev.into_incoming()) .await - .unwrap() .unwrap(); conn.await }; - let mut transport2 = { + let (mut transport2, f) = { let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); let id_keys = identity::Keypair::generate_ed25519(); let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - // okay to reuse `listen_addr` since the port is `0` (any). - WebRTCTransport::new(cert, id_keys, listen_addr) - .await - .expect("transport") + let t = WebRTCTransport::new(cert, id_keys); + // TODO: make code cleaner wrt ":" + let f = t.cert_fingerprint().replace(':', ""); + (t.boxed(), f) }; - // TODO: make code cleaner wrt ":" - let f = &transport.cert_fingerprint().replace(':', ""); + + transport2.listen_on(listen_addr).expect("listener"); + let outbound = transport2 .dial( - addr.with(Protocol::XWebRTC(hex_to_cow(f))) + addr.with(Protocol::XWebRTC(hex_to_cow(&f))) .with(Protocol::P2p(t1_peer_id.into())), ) .unwrap(); diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index ab1f1914abc..3cdcbabd140 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -34,7 +34,7 @@ async fn create_swarm() -> Result<(Swarm>, String)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let transport = WebRTCTransport::new(cert, keypair, "127.0.0.1:0").await?; + let transport = WebRTCTransport::new(cert, keypair); let fingerprint = transport.cert_fingerprint(); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -59,7 +59,7 @@ async fn smoke() -> Result<()> { let (mut a, a_fingerprint) = create_swarm().await?; let (mut b, _b_fingerprint) = create_swarm().await?; - Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0".parse()?)?; + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; let addr = match a.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, From 0e5b812f00387f0db0a87d1d9d68a482c6b759b5 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 12 Jul 2022 11:56:57 +0400 Subject: [PATCH 020/244] remove unused func and cleanup imports --- transports/webrtc/src/udp_mux.rs | 42 +++++++++----------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index ba67347c126..8f3e4305e96 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -20,6 +20,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use async_trait::async_trait; +use futures::channel::mpsc; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use stun::{ + attributes::ATTR_USERNAME, + message::{is_message as is_stun_message, Message as STUNMessage}, +}; +use tokio_crate as tokio; +use tokio_crate::sync::{watch, Mutex}; +use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; +use webrtc_util::{sync::RwLock, Conn, Error}; + use std::{ borrow::Cow, collections::{HashMap, HashSet}, @@ -28,38 +40,8 @@ use std::{ sync::{Arc, Weak}, }; -use futures::channel::mpsc; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; -use webrtc_util::{sync::RwLock, Conn, Error}; - -use tokio_crate as tokio; -use tokio_crate::sync::{watch, Mutex}; - -use async_trait::async_trait; - -use stun::{ - attributes::ATTR_USERNAME, - message::{is_message as is_stun_message, Message as STUNMessage}, -}; - const RECEIVE_MTU: usize = 8192; -/// Normalize a target socket addr for sending over a given local socket addr. This is useful when -/// a dual stack socket is used, in which case an IPv4 target needs to be mapped to an IPv6 -/// address. -fn normalize_socket_addr(target: &SocketAddr, socket_addr: &SocketAddr) -> SocketAddr { - match (target, socket_addr) { - (SocketAddr::V4(target_ipv4), SocketAddr::V6(_)) => { - let ipv6_mapped = target_ipv4.ip().to_ipv6_mapped(); - - SocketAddr::new(std::net::IpAddr::V6(ipv6_mapped), target_ipv4.port()) - } - // This will fail later if target is IPv6 and socket is IPv4, we ignore it here - (_, _) => *target, - } -} - pub struct UDPMuxParams { conn: Box, } From 809d23eef8335b630954210ec15031f08788f364 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 12 Jul 2022 12:29:29 +0400 Subject: [PATCH 021/244] remove commented methods except destroy_substream which needs to be ported --- transports/webrtc/src/connection.rs | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 0de8b50c586..22544e09d01 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -225,42 +225,6 @@ impl<'a> StreamMuxer for Connection { /// abruptly interrupt the execution. fn destroy_outbound(&self, _s: Self::OutboundSubstream) {} - // fn read_substream( - // &self, - // cx: &mut Context<'_>, - // s: &mut Self::Substream, - // buf: &mut [u8], - // ) -> Poll> { - // Pin::new(s).poll_read(cx, buf) - // } - - // fn write_substream( - // &self, - // cx: &mut Context<'_>, - // s: &mut Self::Substream, - // buf: &[u8], - // ) -> Poll> { - // Pin::new(s).poll_write(cx, buf) - // } - - // fn flush_substream( - // &self, - // cx: &mut Context<'_>, - // s: &mut Self::Substream, - // ) -> Poll> { - // trace!("Flushing substream {}", s.stream_identifier()); - // Pin::new(s).poll_flush(cx) - // } - - // fn shutdown_substream( - // &self, - // cx: &mut Context<'_>, - // s: &mut Self::Substream, - // ) -> Poll> { - // trace!("Closing substream {}", s.stream_identifier()); - // Pin::new(s).poll_close(cx) - // } - // fn destroy_substream(&self, s: Self::Substream) { // trace!("Destroying substream {}", s.stream_identifier()); // let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); From a31f1bf2b536e417acab5b8c32e47e0c82b00803 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 12 Jul 2022 13:30:02 +0400 Subject: [PATCH 022/244] uncomment flushing and closing in poll_close --- transports/webrtc/src/connection.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 22544e09d01..838dc1b55ff 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -32,6 +32,7 @@ use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; +use std::pin::Pin; use std::io; use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; @@ -237,20 +238,23 @@ impl<'a> StreamMuxer for Connection { let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); // First, flush all the buffered data. - // for (_, ch) in &mut data_channels_inner.map { - // match ready!(self.flush_substream(cx, ch)) { - // Ok(_) => continue, - // Err(e) => return Poll::Ready(Err(e)), - // } - // } + for (_, ch) in &mut data_channels_inner.map { + match ready!(Pin::new(ch).poll_flush(cx)) { + Ok(_) => continue, + Err(e) => return Poll::Ready(Err(e)), + } + } // Second, shutdown all the substreams. - // for (_, ch) in &mut data_channels_inner.map { - // match ready!(self.shutdown_substream(cx, ch)) { - // Ok(_) => continue, - // Err(e) => return Poll::Ready(Err(e)), - // } - // } + for (_, ch) in &mut data_channels_inner.map { + match ready!(Pin::new(ch).poll_close(cx)) { + Ok(_) => continue, + Err(e) => return Poll::Ready(Err(e)), + } + } + + // Done with data channels. + data_channels_inner.map.clear(); // Third, close `incoming_data_channels_rx` data_channels_inner.incoming_data_channels_rx.close(); From 414f945007405f17232dde9a430b401ff9929324 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 13 Jul 2022 13:31:26 +0400 Subject: [PATCH 023/244] refactor in_addr and listeners --- transports/webrtc/src/error.rs | 3 + transports/webrtc/src/in_addr.rs | 120 ++++++++ transports/webrtc/src/lib.rs | 1 + transports/webrtc/src/transport.rs | 426 +++++++++++------------------ transports/webrtc/tests/smoke.rs | 7 + 5 files changed, 294 insertions(+), 263 deletions(-) create mode 100644 transports/webrtc/src/in_addr.rs diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 8b62279a12c..15870931743 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -45,6 +45,9 @@ pub enum Error { #[error("no active listeners")] NoListeners, + #[error("UDP mux is closed")] + UDPMuxIsClosed, + #[error("internal error: {0} (see debug logs)")] InternalError(String), } diff --git a/transports/webrtc/src/in_addr.rs b/transports/webrtc/src/in_addr.rs new file mode 100644 index 00000000000..fd642d16dc5 --- /dev/null +++ b/transports/webrtc/src/in_addr.rs @@ -0,0 +1,120 @@ +// Copyright 2022 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 if_watch::{IfEvent, IfWatcher}; + +use futures::{ + future::{BoxFuture, FutureExt}, + stream::Stream, +}; + +use std::{ + io::Result, + net::IpAddr, + ops::DerefMut, + pin::Pin, + task::{Context, Poll}, +}; + +/// Watches for interface changes. +#[derive(Debug)] +pub enum InAddr { + /// The socket accepts connections on a single interface. + One { ip: Option }, + /// The socket accepts connections on all interfaces. + Any { if_watch: Box }, +} + +impl InAddr { + /// If ip is specified then only one `IfEvent::Up` with IpNet(ip)/32 will be generated. + /// If ip is unspecified then `IfEvent::Up/Down` events will be generated for all interfaces. + pub fn new(ip: IpAddr) -> Self { + if ip.is_unspecified() { + let watcher = IfWatch::Pending(IfWatcher::new().boxed()); + InAddr::Any { + if_watch: Box::new(watcher), + } + } else { + InAddr::One { ip: Some(ip) } + } + } +} + +pub enum IfWatch { + Pending(BoxFuture<'static, std::io::Result>), + Ready(Box), +} + +impl std::fmt::Debug for IfWatch { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + IfWatch::Pending(_) => write!(f, "Pending"), + IfWatch::Ready(_) => write!(f, "Ready"), + } + } +} +impl Stream for InAddr { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = Pin::into_inner(self); + loop { + match me { + // If the listener is bound to a single interface, make sure the + // address is reported once. + InAddr::One { ip } => { + if let Some(ip) = ip.take() { + return Poll::Ready(Some(Ok(IfEvent::Up(ip.into())))); + } + } + InAddr::Any { if_watch } => { + match if_watch.deref_mut() { + // If we listen on all interfaces, wait for `if-watch` to be ready. + IfWatch::Pending(f) => match futures::ready!(f.poll_unpin(cx)) { + Ok(watcher) => { + *if_watch = Box::new(IfWatch::Ready(Box::new(watcher))); + continue; + } + Err(err) => { + *if_watch = Box::new(IfWatch::Pending(IfWatcher::new().boxed())); + return Poll::Ready(Some(Err(err))); + } + }, + // Consume all events for up/down interface changes. + IfWatch::Ready(watcher) => { + if let Poll::Ready(ev) = watcher.poll_unpin(cx) { + match ev { + Ok(event) => { + return Poll::Ready(Some(Ok(event))); + } + Err(err) => { + return Poll::Ready(Some(Err(err))); + } + } + } + } + } + } + } + break; + } + Poll::Pending + } +} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 0ee9ef1463c..3433f803a40 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -102,6 +102,7 @@ pub mod connection; pub mod error; pub mod transport; +mod in_addr; mod sdp; mod udp_mux; mod upgrade; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index f521743ef8c..3810ea152c3 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -24,10 +24,13 @@ use futures::{ future, future::BoxFuture, prelude::*, - ready, select, TryFutureExt, + select, + stream::SelectAll, + stream::Stream, + TryFutureExt, }; use futures_timer::Delay; -use if_watch::{IfEvent, IfWatcher}; +use if_watch::IfEvent; use libp2p_core::identity; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, @@ -51,37 +54,24 @@ use webrtc_ice::network_type::NetworkType; use webrtc_ice::udp_mux::UDPMux; use webrtc_ice::udp_network::UDPNetwork; -use std::borrow::Cow; -use std::collections::VecDeque; -use std::io; -use std::net::IpAddr; -use std::net::SocketAddr; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; +use std::{ + borrow::Cow, + net::SocketAddr, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; use crate::connection::Connection; use crate::connection::PollDataChannel; use crate::error::Error; +use crate::in_addr::InAddr; use crate::sdp; use crate::udp_mux::UDPMuxNewAddr; use crate::udp_mux::UDPMuxParams; use crate::upgrade; -enum IfWatch { - Pending(BoxFuture<'static, io::Result>), - Ready(IfWatcher), -} - -/// The listening addresses of a [`WebRTCTransport`]. -enum InAddr { - /// The stream accepts connections on a single interface. - One { out: Option }, - /// The stream accepts connections on all interfaces. - Any { if_watch: IfWatch }, -} - /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct WebRTCTransport { /// A `RTCConfiguration` which holds this peer's certificate(s). @@ -89,11 +79,7 @@ pub struct WebRTCTransport { /// `Keypair` identifying this peer id_keys: identity::Keypair, /// All the active listeners. - /// The `WebRTCListenStream` struct contains a stream that we want to be pinned. Since the `VecDeque` - /// can be resized, the only way is to use a `Pin>`. - listeners: VecDeque>>, - /// Pending transport events to return from [`WebRTCTransport::poll`]. - pending_events: VecDeque::ListenerUpgrade, Error>>, + listeners: SelectAll, } impl WebRTCTransport { @@ -105,8 +91,7 @@ impl WebRTCTransport { ..RTCConfiguration::default() }, id_keys, - listeners: VecDeque::new(), - pending_events: VecDeque::new(), + listeners: SelectAll::new(), } } @@ -174,18 +159,13 @@ impl Transport for WebRTCTransport { fn listen_on(&mut self, addr: Multiaddr) -> Result> { let id = ListenerId::new(); let listener = self.do_listen(id, addr)?; - self.listeners.push_back(Box::pin(listener)); + self.listeners.push(listener); Ok(id) } fn remove_listener(&mut self, id: ListenerId) -> bool { - if let Some(index) = self.listeners.iter().position(|l| l.listener_id != id) { - self.listeners.remove(index); - self.pending_events - .push_back(TransportEvent::ListenerClosed { - listener_id: id, - reason: Ok(()), - }); + if let Some(listener) = self.listeners.iter_mut().find(|l| l.listener_id == id) { + listener.close(Ok(())); true } else { false @@ -197,103 +177,31 @@ impl Transport for WebRTCTransport { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - // Return pending events from closed listeners. - if let Some(event) = self.pending_events.pop_front() { - return Poll::Ready(event); + match self.listeners.poll_next_unpin(cx) { + Poll::Ready(Some(ev)) => Poll::Ready(ev), + _ => Poll::Pending, } - // We remove each element from `listeners` one by one and add them back. - let mut remaining = self.listeners.len(); - while let Some(mut listener) = self.listeners.pop_back() { - match TryStream::try_poll_next(listener.as_mut(), cx) { - Poll::Pending => { - self.listeners.push_front(listener); - remaining -= 1; - if remaining == 0 { - break; - } - } - Poll::Ready(Some(Ok(WebRTCListenerEvent::Upgrade { - upgrade, - local_addr, - remote_addr, - }))) => { - let id = listener.listener_id; - self.listeners.push_front(listener); - return Poll::Ready(TransportEvent::Incoming { - listener_id: id, - upgrade, - local_addr, - send_back_addr: remote_addr, - }); - } - Poll::Ready(Some(Ok(WebRTCListenerEvent::NewAddress(a)))) => { - let id = listener.listener_id; - self.listeners.push_front(listener); - return Poll::Ready(TransportEvent::NewAddress { - listener_id: id, - listen_addr: a, - }); - } - Poll::Ready(Some(Ok(WebRTCListenerEvent::AddressExpired(a)))) => { - let id = listener.listener_id; - self.listeners.push_front(listener); - return Poll::Ready(TransportEvent::AddressExpired { - listener_id: id, - listen_addr: a, - }); - } - Poll::Ready(Some(Ok(WebRTCListenerEvent::Error(error)))) => { - let id = listener.listener_id; - self.listeners.push_front(listener); - return Poll::Ready(TransportEvent::ListenerError { - listener_id: id, - error: error.into(), - }); - } - Poll::Ready(None) => { - return Poll::Ready(TransportEvent::ListenerClosed { - listener_id: listener.listener_id, - reason: Ok(()), - }); - } - Poll::Ready(Some(Err(err))) => { - return Poll::Ready(TransportEvent::ListenerClosed { - listener_id: listener.listener_id, - reason: Err(err), - }); - } - } - } - Poll::Pending } fn dial(&mut self, addr: Multiaddr) -> Result> { - self.dial_as_listener(addr) - } - - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { let config = self.config.clone(); let our_fingerprint = self.cert_fingerprint(); let id_keys = self.id_keys.clone(); - let udp_mux = if let Some(l) = self.listeners.back() { - l.udp_mux.clone() - } else { - return Err(TransportError::Other(Error::NoListeners)); - }; let sock_addr = multiaddr_to_socketaddr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } - let remote = addr.clone(); // used for logging trace!("dialing address: {:?}", remote); - let se = build_setting_engine(udp_mux.clone(), &sock_addr, &our_fingerprint); + let udp_mux = if let Some(l) = self.listeners.iter_mut().next() { + l.udp_mux.clone() + } else { + return Err(TransportError::Other(Error::NoListeners)); + }; + let se = build_setting_engine(udp_mux, &sock_addr, &our_fingerprint); let api = APIBuilder::new().with_setting_engine(se).build(); // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus @@ -415,61 +323,55 @@ impl Transport for WebRTCTransport { .boxed()) } + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { + // TODO: As the listener of a WebRTC hole punch, we need to send a random UDP packet to the + // `addr`. See DCUtR specification below. + // + // https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol + self.dial(addr) + } + fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { libp2p_core::address_translation(server, observed) } } -/// Event produced by a [`WebRTCListenStream`]. -#[derive(Debug)] -pub enum WebRTCListenerEvent { - /// The listener is listening on a new additional [`Multiaddr`]. - NewAddress(Multiaddr), - /// An upgrade, consisting of the upgrade future, the listener address and the remote address. - Upgrade { - /// The upgrade. - upgrade: S, - /// The local address which produced this upgrade. - local_addr: Multiaddr, - /// The remote address which produced this upgrade. - remote_addr: Multiaddr, - }, - /// A [`Multiaddr`] is no longer used for listening. - AddressExpired(Multiaddr), - /// A non-fatal error has happened on the listener. - /// - /// This event should be generated in order to notify the user that something wrong has - /// happened. The listener, however, continues to run. - Error(Error), -} - /// A stream of incoming connections on one or more interfaces. pub struct WebRTCListenStream { /// The ID of this listener. listener_id: ListenerId, + /// The socket address that the listening socket is bound to, /// which may be a "wildcard address" like `INADDR_ANY` or `IN6ADDR_ANY` /// when listening on all interfaces for IPv4 respectively IPv6 connections. listen_addr: SocketAddr, + /// The IP addresses of network interfaces on which the listening socket /// is accepting connections. /// /// If the listen socket listens on all interfaces, these may change over /// time as interfaces become available or unavailable. in_addr: InAddr, - /// How long to sleep after a (non-fatal) error while trying - /// to accept a new connection. - sleep_on_error: Duration, - /// The current pause, if any. - pause: Option, + /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, + /// The `UDPMux` that manages all ICE connections. udp_mux: Arc, + /// The receiver for new `SocketAddr` connecting to this peer. new_addr_rx: mpsc::Receiver, + /// `Keypair` identifying this peer id_keys: identity::Keypair, + + /// Set to `Some` if this [`Listener`] should close. + /// Optionally contains a [`TransportEvent::ListenerClosed`] that should be + /// reported before the listener's stream is terminated. + report_closed: Option::Item>>, } impl WebRTCListenStream { @@ -482,147 +384,145 @@ impl WebRTCListenStream { new_addr_rx: mpsc::Receiver, id_keys: identity::Keypair, ) -> Self { - // Check whether the listening IP is set or not. - let in_addr = if match &listen_addr { - SocketAddr::V4(a) => a.ip().is_unspecified(), - SocketAddr::V6(a) => a.ip().is_unspecified(), - } { - // The `addrs` are populated via `if_watch` when the - // `WebRTCTransport` is polled. - InAddr::Any { - if_watch: IfWatch::Pending(IfWatcher::new().boxed()), - } - } else { - InAddr::One { - out: Some(ip_to_multiaddr(listen_addr.ip(), listen_addr.port())), - } - }; + let in_addr = InAddr::new(listen_addr.ip()); WebRTCListenStream { listener_id, listen_addr, in_addr, - pause: None, - sleep_on_error: Duration::from_millis(100), config, udp_mux, new_addr_rx, id_keys, + report_closed: None, } } -} -impl Stream for WebRTCListenStream { - type Item = - Result>>, Error>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let me = Pin::into_inner(self); - - loop { - match &mut me.in_addr { - InAddr::Any { if_watch } => match if_watch { - // If we listen on all interfaces, wait for `if-watch` to be ready. - IfWatch::Pending(f) => match ready!(Pin::new(f).poll(cx)) { - Ok(w) => { - *if_watch = IfWatch::Ready(w); - continue; + /// Report the listener as closed in a [`TransportEvent::ListenerClosed`] and + /// terminate the stream. + fn close(&mut self, reason: Result<(), Error>) { + match self.report_closed { + Some(_) => debug!("Listener was already closed."), + None => { + // Report the listener event as closed. + let _ = self + .report_closed + .insert(Some(TransportEvent::ListenerClosed { + listener_id: self.listener_id, + reason, + })); + } + } + } + + /// Poll for a next If Event. + fn poll_if_addr(&mut self, cx: &mut Context<'_>) -> Option<::Item> { + match self.in_addr.poll_next_unpin(cx) { + Poll::Ready(mut item) => { + if let Some(item) = item.take() { + // Consume all events for up/down interface changes. + match item { + Ok(IfEvent::Up(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + debug!("New listen address: {}", ma); + Some(TransportEvent::NewAddress { + listener_id: self.listener_id, + listen_addr: ma, + }) + } else { + self.poll_if_addr(cx) + } + } + Ok(IfEvent::Down(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + debug!("Expired listen address: {}", ma); + Some(TransportEvent::AddressExpired { + listener_id: self.listener_id, + listen_addr: ma, + }) + } else { + self.poll_if_addr(cx) + } } Err(err) => { debug! { - "Failed to begin observing interfaces: {:?}. Scheduling retry.", + "Failure polling interfaces: {:?}.", err }; - *if_watch = IfWatch::Pending(IfWatcher::new().boxed()); - me.pause = Some(Delay::new(me.sleep_on_error)); - return Poll::Ready(Some(Ok(WebRTCListenerEvent::Error( - Error::IoError(err), - )))); + Some(TransportEvent::ListenerError { + listener_id: self.listener_id, + error: err.into(), + }) } - }, - // Consume all events for up/down interface changes. - IfWatch::Ready(watch) => { - while let Poll::Ready(ev) = watch.poll_unpin(cx) { - match ev { - Ok(IfEvent::Up(inet)) => { - let ip = inet.addr(); - if me.listen_addr.is_ipv4() == ip.is_ipv4() - || me.listen_addr.is_ipv6() == ip.is_ipv6() - { - let ma = ip_to_multiaddr(ip, me.listen_addr.port()); - debug!("New listen address: {}", ma); - return Poll::Ready(Some(Ok( - WebRTCListenerEvent::NewAddress(ma), - ))); - } - } - Ok(IfEvent::Down(inet)) => { - let ip = inet.addr(); - if me.listen_addr.is_ipv4() == ip.is_ipv4() - || me.listen_addr.is_ipv6() == ip.is_ipv6() - { - let ma = ip_to_multiaddr(ip, me.listen_addr.port()); - debug!("Expired listen address: {}", ma); - return Poll::Ready(Some(Ok( - WebRTCListenerEvent::AddressExpired(ma), - ))); - } - } - Err(err) => { - debug! { - "Failure polling interfaces: {:?}. Scheduling retry.", - err - }; - me.pause = Some(Delay::new(me.sleep_on_error)); - return Poll::Ready(Some(Ok(WebRTCListenerEvent::Error( - Error::IoError(err), - )))); - } - } - } - } - }, - // If the listener is bound to a single interface, make sure the address reported - // once. - InAddr::One { out } => { - if let Some(multiaddr) = out.take() { - return Poll::Ready(Some(Ok(WebRTCListenerEvent::NewAddress(multiaddr)))); } + } else { + self.poll_if_addr(cx) } } + Poll::Pending => None, + } + } +} - if let Some(mut pause) = me.pause.take() { - match Pin::new(&mut pause).poll(cx) { - Poll::Ready(_) => {} - Poll::Pending => { - me.pause = Some(pause); - return Poll::Pending; - } - } +impl Stream for WebRTCListenStream { + type Item = TransportEvent<::ListenerUpgrade, Error>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + if let Some(closed) = self.report_closed.as_mut() { + // Listener was closed. + // Report the transport event if there is one. On the next iteration, return + // `Poll::Ready(None)` to terminate the stream. + return Poll::Ready(closed.take()); + } + if let Some(event) = self.poll_if_addr(cx) { + return Poll::Ready(Some(event)); + } + + let addr = match futures::ready!(self.new_addr_rx.poll_next_unpin(cx)) { + Some(addr) => addr, + None => { + self.close(Err(Error::UDPMuxIsClosed)); + return self.poll_next(cx); } + }; - // Safe to unwrap here since this is the only place `new_addr_rx` is locked. - return match Pin::new(&mut me.new_addr_rx).poll_next(cx) { - Poll::Ready(Some(addr)) => Poll::Ready(Some(Ok(WebRTCListenerEvent::Upgrade { - local_addr: ip_to_multiaddr(me.listen_addr.ip(), me.listen_addr.port()), - remote_addr: addr.clone(), - upgrade: Box::pin(upgrade::webrtc( - me.udp_mux.clone(), - me.config.clone(), - addr, - me.id_keys.clone(), - )) as BoxFuture<'static, _>, - }))), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - }; - } + let local_addr = socketaddr_to_multiaddr(&self.listen_addr); + let event = TransportEvent::Incoming { + upgrade: Box::pin(upgrade::webrtc( + self.udp_mux.clone(), + self.config.clone(), + addr.clone(), + self.id_keys.clone(), + )) as BoxFuture<'static, _>, + local_addr, + send_back_addr: addr, + listener_id: self.listener_id, + }; + Poll::Ready(Some(event)) } } -/// Creates a [`Multiaddr`] from the given IP address and port number. -fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { - Multiaddr::empty().with(ip.into()).with(Protocol::Udp(port)) +// TODO: remove +fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { + let mut buf = [0; 32]; + hex::decode_to_slice(s, &mut buf).unwrap(); + Cow::Owned(buf) +} + +/// Turns an IP address and port into the corresponding WebRTC multiaddr. +pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { + // TODO: remove + let f = "ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B"; + Multiaddr::empty() + .with(socket_addr.ip().into()) + .with(Protocol::Udp(socket_addr.port())) + .with(Protocol::XWebRTC(hex_to_cow(&f))) } /// Renders a [`TinyTemplate`] description using the provided arguments. @@ -881,7 +781,7 @@ mod tests { let outbound = transport2 .dial( - addr.with(Protocol::XWebRTC(hex_to_cow(&f))) + addr.replace(2, |_| Some(Protocol::XWebRTC(hex_to_cow(&f)))).unwrap() .with(Protocol::P2p(t1_peer_id.into())), ) .unwrap(); diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 3cdcbabd140..96d8559b018 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -60,11 +60,18 @@ async fn smoke() -> Result<()> { let (mut b, _b_fingerprint) = create_swarm().await?; Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; + Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; let addr = match a.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; + + let _ = match b.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + let addr = addr.with(Protocol::XWebRTC(hex_to_cow( &a_fingerprint.replace(":", ""), ))); From 84c54614a9a835ae75ce7b5a303c7724a1f416fd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 13 Jul 2022 13:34:39 +0400 Subject: [PATCH 024/244] support ipv6 as well --- transports/webrtc/src/transport.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 3810ea152c3..f2b4b4b9feb 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -424,7 +424,9 @@ impl WebRTCListenStream { match item { Ok(IfEvent::Up(inet)) => { let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() { + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr); debug!("New listen address: {}", ma); @@ -438,7 +440,9 @@ impl WebRTCListenStream { } Ok(IfEvent::Down(inet)) => { let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() { + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr); debug!("Expired listen address: {}", ma); From 66f6c78954f4e72568368e2403a882edf58c3b6e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 14 Jul 2022 09:34:43 +0400 Subject: [PATCH 025/244] format code --- transports/webrtc/src/transport.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index f2b4b4b9feb..667d7fbd623 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -785,7 +785,8 @@ mod tests { let outbound = transport2 .dial( - addr.replace(2, |_| Some(Protocol::XWebRTC(hex_to_cow(&f)))).unwrap() + addr.replace(2, |_| Some(Protocol::XWebRTC(hex_to_cow(&f)))) + .unwrap() .with(Protocol::P2p(t1_peer_id.into())), ) .unwrap(); From 8ca428c29af77fc07314086f1f305defa19b3475 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 15 Jul 2022 15:21:50 +0400 Subject: [PATCH 026/244] disable fingerprint verification on server side --- src/lib.rs | 8 ++--- transports/webrtc/Cargo.toml | 7 ++-- transports/webrtc/src/connection.rs | 2 +- transports/webrtc/src/transport.rs | 37 +++++++++++--------- transports/webrtc/src/udp_mux.rs | 23 +++++-------- transports/webrtc/src/upgrade.rs | 52 ++++++++++++++++++----------- 6 files changed, 72 insertions(+), 57 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3f67c46f155..3925d809680 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,10 @@ pub use libp2p_uds as uds; #[cfg_attr(docsrs, doc(cfg(feature = "wasm-ext")))] #[doc(inline)] pub use libp2p_wasm_ext as wasm_ext; +#[cfg(feature = "webrtc")] +#[cfg_attr(docsrs, doc(cfg(feature = "webrtc")))] +#[doc(inline)] +pub use libp2p_webrtc as webrtc; #[cfg(feature = "websocket")] #[cfg_attr(docsrs, doc(cfg(feature = "websocket")))] #[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] @@ -144,10 +148,6 @@ pub use libp2p_websocket as websocket; #[cfg_attr(docsrs, doc(cfg(feature = "yamux")))] #[doc(inline)] pub use libp2p_yamux as yamux; -#[cfg(feature = "webrtc")] -#[cfg_attr(docsrs, doc(cfg(feature = "webrtc")))] -#[doc(inline)] -pub use libp2p_webrtc as webrtc; mod transport_ext; diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index d7adb8e7feb..f2fe802b351 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -26,11 +26,12 @@ stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} -webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "anton/168-allow-persistent-certificates-plus-deps" } -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", rev = "9656a255a69365268a5b1e0978b84e56f4d7114d" } +webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "always-set-remote-certificate" } +webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", rev = "f630dcd2b90ed8faf57d1b36c18ec5aa715325c0" } webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" -webrtc-util = { version = "0.5.3", default-features = false, features = ["conn", "vnet", "sync"] } +webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } +multihash = { version = "0.16", default-features = false, features = ["sha2"] } [dev-dependencies] anyhow = "1.0" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 838dc1b55ff..4c840e42194 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -32,8 +32,8 @@ use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; -use std::pin::Pin; use std::io; +use std::pin::Pin; use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 667d7fbd623..a7a502a34bb 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -68,8 +68,7 @@ use crate::connection::PollDataChannel; use crate::error::Error; use crate::in_addr::InAddr; use crate::sdp; -use crate::udp_mux::UDPMuxNewAddr; -use crate::udp_mux::UDPMuxParams; +use crate::udp_mux::{NewAddr, UDPMuxNewAddr, UDPMuxParams}; use crate::upgrade; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -231,6 +230,7 @@ impl Transport for WebRTCTransport { sdp::SERVER_SESSION_DESCRIPTION, sock_addr, &remote_fingerprint, + &remote_fingerprint.to_owned().replace(':', ""), ); debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); @@ -363,7 +363,7 @@ pub struct WebRTCListenStream { udp_mux: Arc, /// The receiver for new `SocketAddr` connecting to this peer. - new_addr_rx: mpsc::Receiver, + new_addr_rx: mpsc::Receiver, /// `Keypair` identifying this peer id_keys: identity::Keypair, @@ -381,7 +381,7 @@ impl WebRTCListenStream { listen_addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - new_addr_rx: mpsc::Receiver, + new_addr_rx: mpsc::Receiver, id_keys: identity::Keypair, ) -> Self { let in_addr = InAddr::new(listen_addr.ip()); @@ -488,8 +488,8 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(event)); } - let addr = match futures::ready!(self.new_addr_rx.poll_next_unpin(cx)) { - Some(addr) => addr, + let new_addr = match futures::ready!(self.new_addr_rx.poll_next_unpin(cx)) { + Some(a) => a, None => { self.close(Err(Error::UDPMuxIsClosed)); return self.poll_next(cx); @@ -497,15 +497,17 @@ impl Stream for WebRTCListenStream { }; let local_addr = socketaddr_to_multiaddr(&self.listen_addr); + let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); let event = TransportEvent::Incoming { upgrade: Box::pin(upgrade::webrtc( self.udp_mux.clone(), self.config.clone(), - addr.clone(), + new_addr.addr, + new_addr.ufrag, self.id_keys.clone(), )) as BoxFuture<'static, _>, local_addr, - send_back_addr: addr, + send_back_addr, listener_id: self.listener_id, }; Poll::Ready(Some(event)) @@ -530,11 +532,15 @@ pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { } /// Renders a [`TinyTemplate`] description using the provided arguments. -pub(crate) fn render_description(description: &str, addr: SocketAddr, fingerprint: &str) -> String { +pub(crate) fn render_description( + description: &str, + addr: SocketAddr, + fingerprint: &str, + ufrag: &str, +) -> String { let mut tt = TinyTemplate::new(); tt.add_template("description", description).unwrap(); - let f = fingerprint.to_owned().replace(':', ""); let context = sdp::DescriptionContext { ip_version: { if addr.is_ipv4() { @@ -545,11 +551,10 @@ pub(crate) fn render_description(description: &str, addr: SocketAddr, fingerprin }, target_ip: addr.ip(), target_port: addr.port(), - // Hashing algorithm (SHA-256) is hardcoded for now fingerprint: fingerprint.to_owned(), - // ufrag and pwd are both equal to the fingerprint (minus the `:` delimiter) - ufrag: f.clone(), - pwd: f, + // NOTE: ufrag is equal to pwd. + ufrag: ufrag.to_owned(), + pwd: ufrag.to_owned(), }; tt.render("description", &context).unwrap() } @@ -586,7 +591,7 @@ pub(crate) fn fingerprint_to_string(f: &Cow<'_, [u8; 32]>) -> String { values.join(":") } -/// Returns a fingerprint of the first certificate. +/// Returns a SHA-256 fingerprint of the first certificate. /// /// # Panics /// @@ -599,6 +604,7 @@ pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> Str .expect("at least one certificate") .get_fingerprints() .expect("fingerprints to succeed"); + debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); fingerprints.first().unwrap().value.clone() } @@ -633,6 +639,7 @@ fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { let iter = addr.iter(); for proto in iter { match proto { + // TODO: check hash is one of https://datatracker.ietf.org/doc/html/rfc8122#section-5 Protocol::XWebRTC(f) => return Some(f), _ => continue, } diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 8f3e4305e96..aa90e2c0aa4 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -22,7 +22,6 @@ use async_trait::async_trait; use futures::channel::mpsc; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, @@ -33,7 +32,6 @@ use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc_util::{sync::RwLock, Conn, Error}; use std::{ - borrow::Cow, collections::{HashMap, HashSet}, io::ErrorKind, net::SocketAddr, @@ -42,6 +40,11 @@ use std::{ const RECEIVE_MTU: usize = 8192; +pub struct NewAddr { + pub addr: SocketAddr, + pub ufrag: String, +} + pub struct UDPMuxParams { conn: Box, } @@ -82,7 +85,7 @@ pub struct UDPMuxNewAddr { } impl UDPMuxNewAddr { - pub fn new(params: UDPMuxParams, new_addr_tx: mpsc::Sender) -> Arc { + pub fn new(params: UDPMuxParams, new_addr_tx: mpsc::Sender) -> Arc { let (closed_watch_tx, closed_watch_rx) = watch::channel(()); let mux = Arc::new(Self { @@ -133,7 +136,7 @@ impl UDPMuxNewAddr { fn start_conn_worker( self: Arc, mut closed_watch_rx: watch::Receiver<()>, - mut new_addr_tx: mpsc::Sender, + mut new_addr_tx: mpsc::Sender, ) { tokio::spawn(async move { let mut buffer = [0u8; RECEIVE_MTU]; @@ -171,11 +174,7 @@ impl UDPMuxNewAddr { match ufrag_from_stun_message(&buffer, false) { Ok(ufrag) => { log::trace!("Notifying about new address {} from {}", &addr, ufrag); - let a = Multiaddr::empty() - .with(addr.ip().into()) - .with(Protocol::Udp(addr.port())) - .with(Protocol::XWebRTC(hex_to_cow(&ufrag.replace(':', "")))); - if let Err(err) = new_addr_tx.try_send(a) { + if let Err(err) = new_addr_tx.try_send(NewAddr { addr, ufrag }) { log::error!("Failed to send new address {}: {}", &addr, err); } else { let mut new_addrs = loop_self.new_addrs.write(); @@ -344,12 +343,6 @@ impl UDPMuxWriter for UDPMuxNewAddr { } } -fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { - let mut buf = [0; 32]; - hex::decode_to_slice(s, &mut buf).unwrap(); - Cow::Owned(buf) -} - /// Gets the ufrag from the given STUN message or returns an error, if failed to decode or the /// username attribute is not present. fn ufrag_from_stun_message(buffer: &[u8], local_ufrag: bool) -> Result { diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 3fc68581c57..a313df9b5e4 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -22,13 +22,11 @@ use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::{channel::oneshot, future, select, FutureExt, TryFutureExt}; use futures_timer::Delay; use libp2p_core::identity; -use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - PeerId, -}; +use libp2p_core::PeerId; use libp2p_core::{InboundUpgrade, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; +use multihash::Hasher; use webrtc::api::APIBuilder; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; use webrtc::dtls_transport::dtls_role::DTLSRole; @@ -37,6 +35,7 @@ use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use webrtc_ice::udp_mux::UDPMux; +use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -49,19 +48,18 @@ use crate::transport; pub async fn webrtc( udp_mux: Arc, config: RTCConfiguration, - addr: Multiaddr, + socket_addr: SocketAddr, + ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - trace!("upgrading {}", addr); + trace!("upgrading {} (ufrag: {})", socket_addr, ufrag); - let socket_addr = transport::multiaddr_to_socketaddr(&addr) - .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; let our_fingerprint = transport::fingerprint_of_first_certificate(&config); let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &our_fingerprint); { - // Act as a lite ICE (ICE which does not send additional candidates). se.set_lite(true); + se.disable_certificate_fingerprint_verification(true); // Act as a DTLS server (one which waits for a connection). // // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, @@ -72,17 +70,11 @@ pub async fn webrtc( let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; - // Set the remote description to the predefined SDP. - let remote_fingerprint = if let Some(Protocol::XWebRTC(f)) = addr.iter().last() { - transport::fingerprint_to_string(&f) - } else { - debug!("{} is not a WebRTC multiaddr", addr); - return Err(Error::InvalidMultiaddr(addr)); - }; let client_session_description = transport::render_description( sdp::CLIENT_SESSION_DESCRIPTION, socket_addr, - &remote_fingerprint, + "UNKNOWN", + &ufrag, ); debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); @@ -123,7 +115,7 @@ pub async fn webrtc( )) }; - trace!("noise handshake with {}", addr); + trace!("noise handshake with {} (ufrag: {})", socket_addr, ufrag); let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); @@ -139,13 +131,35 @@ pub async fn webrtc( .map_err(Error::Noise)?; // Exchange TLS certificate fingerprints to prevent MiM attacks. - trace!("exchanging TLS certificate fingerprints with {}", addr); + trace!( + "exchanging TLS certificate fingerprints with {} (ufrag: {})", + socket_addr, + ufrag + ); + + // 1. Submit SHA-256 fingerprint let n = noise_io.write(&our_fingerprint.into_bytes()).await?; noise_io.flush().await?; + + // 2. Receive one too and compare it to the fingerprint of the remote DTLS certificate. let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. noise_io.read_exact(buf.as_mut_slice()).await?; let fingerprint_from_noise = String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + let remote_fingerprint = { + let remote_cert_bytes = + peer_connection + .get_remote_dtls_certificate() + .await + .ok_or(Error::InternalError( + "No remote certificate found".to_owned(), + ))?; + let mut h = multihash::Sha2_256::default(); + h.update(&remote_cert_bytes); + let hashed = h.finalize(); + let values: Vec = hashed.iter().map(|x| format! {"{:02x}", x}).collect(); + values.join(":") + }; if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { expected: remote_fingerprint, From cf2c11d213a3aba40faf92f5c9aa2c433399073a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 18 Jul 2022 13:16:46 +0400 Subject: [PATCH 027/244] flatten StreamMuxer interface reflects changes made in https://github.com/libp2p/rust-libp2p/pull/2724/files --- transports/webrtc/Cargo.toml | 4 +- transports/webrtc/src/connection.rs | 182 ++++++++++++---------------- 2 files changed, 82 insertions(+), 104 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index f2fe802b351..842fdb68c04 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -18,8 +18,8 @@ futures-lite = "1" futures-timer = "3" hex = "0.4" if-watch = "0.2" -libp2p-core = { version = "0.34.0", path = "../../core", default-features = false } -libp2p-noise = { version = "0.37.0", path = "../../transports/noise" } +libp2p-core = { version = "0.35.0", path = "../../core", default-features = false } +libp2p-noise = { version = "0.38.0", path = "../../transports/noise" } log = "0.4" serde = { version = "1.0", features = ["derive"] } stun = "0.4" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 4c840e42194..c2f38acb58a 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -20,44 +20,49 @@ mod poll_data_channel; -use fnv::FnvHashMap; use futures::channel::mpsc; use futures::channel::oneshot::{self, Sender}; use futures::lock::Mutex as FutMutex; use futures::{future::BoxFuture, prelude::*, ready}; use futures_lite::stream::StreamExt; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; +use libp2p_core::{ muxing::StreamMuxer, Multiaddr }; use log::{debug, error, trace}; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; -use std::io; -use std::pin::Pin; use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; pub(crate) use poll_data_channel::PollDataChannel; +use crate::error::Error; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. pub struct Connection { - connection_inner: Arc>, // uses futures mutex because used in async code (see open_outbound) - data_channels_inner: StdMutex, -} - -struct ConnectionInner { /// `RTCPeerConnection` to the remote peer. - rtc_conn: RTCPeerConnection, + /// + /// Uses futures mutex because used in async code (see poll_outbound and poll_close). + peer_conn: Arc>, + + inner: StdMutex, } -struct DataChannelsInner { - /// A map of data channels. - map: FnvHashMap, +struct ConnectionInner { /// Channel onto which incoming data channels are put. incoming_data_channels_rx: mpsc::Receiver>, + /// Temporary read buffer's capacity (equal for all data channels). /// See [`PollDataChannel`] `read_buf_cap`. read_buf_cap: Option, + + /// Future, which, once polled, will result in an outbound substream. + /// + /// NOTE: future might be waiting at one of the await points, and dropping the future will + /// abruptly interrupt the execution. + outbound_fut: Option, Error>>>, + + /// Future, which, once polled, will result in closing the entire connection. + close_fut: Option>>, } impl Connection { @@ -68,19 +73,20 @@ impl Connection { Connection::register_incoming_data_channels_handler(&rtc_conn, data_channel_tx).await; Self { - connection_inner: Arc::new(FutMutex::new(ConnectionInner { rtc_conn })), - data_channels_inner: StdMutex::new(DataChannelsInner { - map: FnvHashMap::default(), + peer_conn: Arc::new(FutMutex::new(rtc_conn)), + inner: StdMutex::new(ConnectionInner { incoming_data_channels_rx: data_channel_rx, read_buf_cap: None, + outbound_fut: None, + close_fut: None, }), } } /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). pub fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - data_channels_inner.read_buf_cap = Some(cap); + let mut inner = self.inner.lock().unwrap(); + inner.read_buf_cap = Some(cap); } /// Registers a handler for incoming data channels. @@ -137,129 +143,101 @@ impl Connection { impl<'a> StreamMuxer for Connection { type Substream = PollDataChannel; - type OutboundSubstream = BoxFuture<'static, Result, Self::Error>>; - type Error = io::Error; + type Error = Error; - fn poll_event( + fn poll_inbound( &self, cx: &mut Context<'_>, - ) -> Poll, Self::Error>> { - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - match ready!(data_channels_inner.incoming_data_channels_rx.poll_next(cx)) { + ) -> Poll> { + let mut inner = self.inner.lock().unwrap(); + match ready!(inner.incoming_data_channels_rx.poll_next(cx)) { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); let mut ch = PollDataChannel::new(detached); - if let Some(cap) = data_channels_inner.read_buf_cap { + if let Some(cap) = inner.read_buf_cap { ch.set_read_buf_capacity(cap); } - data_channels_inner - .map - .insert(ch.stream_identifier(), ch.clone()); - - Poll::Ready(Ok(StreamMuxerEvent::InboundSubstream(ch))) + Poll::Ready(Ok(ch)) } - None => Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - "incoming_data_channels_rx is closed (no messages left)", + None => Poll::Ready(Err(Error::InternalError( + "incoming_data_channels_rx is closed (no messages left)".to_string(), ))), } } - fn open_outbound(&self) -> Self::OutboundSubstream { - let connection_inner = self.connection_inner.clone(); + fn poll_address_change(&self, _cx: &mut Context<'_>) -> Poll> { + return Poll::Pending; + } - Box::pin(async move { - let connection_inner = connection_inner.lock().await; + fn poll_outbound( + &self, + cx: &mut Context<'_>, + ) -> Poll> { + let mut inner = self.inner.lock().unwrap(); + let peer_conn = self.peer_conn.clone(); + let fut = inner.outbound_fut.get_or_insert( + Box::pin(async move { + let peer_conn = peer_conn.lock().await; - // Create a datachannel with label 'data' - let data_channel = connection_inner - .rtc_conn - .create_data_channel("data", None) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("webrtc error: {}", e))) - .await?; + // Create a datachannel with label 'data' + let data_channel = peer_conn + .create_data_channel("data", None) + .map_err(Error::WebRTC) + .await?; - trace!("Opening outbound substream {}", data_channel.id()); + trace!("Opening outbound substream {}", data_channel.id()); - // No need to hold the lock during the DTLS handshake. - drop(connection_inner); + // No need to hold the lock during the DTLS handshake. + drop(peer_conn); - let (tx, rx) = oneshot::channel::>(); + let (tx, rx) = oneshot::channel::>(); - // Wait until the data channel is opened and detach it. - register_data_channel_open_handler(data_channel, tx).await; + // Wait until the data channel is opened and detach it. + register_data_channel_open_handler(data_channel, tx).await; - // Wait until data channel is opened and ready to use - match rx.await { - Ok(detached) => Ok(detached), - Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())), - } - }) - } + // Wait until data channel is opened and ready to use + match rx.await { + Ok(detached) => Ok(detached), + Err(e) => Err(Error::InternalError(e.to_string())), + } + })); - fn poll_outbound( - &self, - cx: &mut Context<'_>, - s: &mut Self::OutboundSubstream, - ) -> Poll> { - match ready!(s.as_mut().poll(cx)) { + match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - let mut ch = PollDataChannel::new(detached); - if let Some(cap) = data_channels_inner.read_buf_cap { + if let Some(cap) = inner.read_buf_cap { ch.set_read_buf_capacity(cap); } - data_channels_inner - .map - .insert(ch.stream_identifier(), ch.clone()); - Poll::Ready(Ok(ch)) } Err(e) => Poll::Ready(Err(e)), } } - /// NOTE: `_s` might be waiting at one of the await points, and dropping the future will - /// abruptly interrupt the execution. - fn destroy_outbound(&self, _s: Self::OutboundSubstream) {} - - // fn destroy_substream(&self, s: Self::Substream) { - // trace!("Destroying substream {}", s.stream_identifier()); - // let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - // data_channels_inner.map.remove(&s.stream_identifier()); - // } - fn poll_close(&self, cx: &mut Context<'_>) -> Poll> { debug!("Closing connection"); - let mut data_channels_inner = self.data_channels_inner.lock().unwrap(); - - // First, flush all the buffered data. - for (_, ch) in &mut data_channels_inner.map { - match ready!(Pin::new(ch).poll_flush(cx)) { - Ok(_) => continue, - Err(e) => return Poll::Ready(Err(e)), - } - } - - // Second, shutdown all the substreams. - for (_, ch) in &mut data_channels_inner.map { - match ready!(Pin::new(ch).poll_close(cx)) { - Ok(_) => continue, - Err(e) => return Poll::Ready(Err(e)), + let mut inner = self.inner.lock().unwrap(); + let peer_conn = self.peer_conn.clone(); + let fut = inner.close_fut.get_or_insert( + Box::pin(async move { + let peer_conn = peer_conn.lock().await; + peer_conn + .close() + .await + .map_err(Error::WebRTC) + })); + + match ready!(fut.as_mut().poll(cx)) { + Ok(()) => { + inner.incoming_data_channels_rx.close(); + Poll::Ready(Ok(())) } + Err(e) => Poll::Ready(Err(e)), } - - // Done with data channels. - data_channels_inner.map.clear(); - - // Third, close `incoming_data_channels_rx` - data_channels_inner.incoming_data_channels_rx.close(); - - Poll::Ready(Ok(())) } } From 35bc4eb6661bf0e385eeab7dc18eedb00ea5cdc4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 19 Jul 2022 12:52:24 +0400 Subject: [PATCH 028/244] get rid of `loop` in UDPMuxNewAddr by moving a reading future into Transport and polling it in a newly added `poll`. https://github.com/libp2p/rust-libp2p/pull/2622/files#r876945521 --- transports/webrtc/src/connection.rs | 65 ++++---- transports/webrtc/src/error.rs | 4 +- transports/webrtc/src/transport.rs | 85 +++++----- transports/webrtc/src/udp_mux.rs | 230 +++++++++++++--------------- 4 files changed, 181 insertions(+), 203 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index c2f38acb58a..db853e8aae8 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -25,7 +25,7 @@ use futures::channel::oneshot::{self, Sender}; use futures::lock::Mutex as FutMutex; use futures::{future::BoxFuture, prelude::*, ready}; use futures_lite::stream::StreamExt; -use libp2p_core::{ muxing::StreamMuxer, Multiaddr }; +use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; @@ -34,8 +34,8 @@ use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use std::sync::{Arc, Mutex as StdMutex}; use std::task::{Context, Poll}; -pub(crate) use poll_data_channel::PollDataChannel; use crate::error::Error; +pub(crate) use poll_data_channel::PollDataChannel; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. pub struct Connection { @@ -145,10 +145,7 @@ impl<'a> StreamMuxer for Connection { type Substream = PollDataChannel; type Error = Error; - fn poll_inbound( - &self, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { let mut inner = self.inner.lock().unwrap(); match ready!(inner.incoming_data_channels_rx.poll_next(cx)) { Some(detached) => { @@ -171,38 +168,34 @@ impl<'a> StreamMuxer for Connection { return Poll::Pending; } - fn poll_outbound( - &self, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_outbound(&self, cx: &mut Context<'_>) -> Poll> { let mut inner = self.inner.lock().unwrap(); let peer_conn = self.peer_conn.clone(); - let fut = inner.outbound_fut.get_or_insert( - Box::pin(async move { - let peer_conn = peer_conn.lock().await; + let fut = inner.outbound_fut.get_or_insert(Box::pin(async move { + let peer_conn = peer_conn.lock().await; - // Create a datachannel with label 'data' - let data_channel = peer_conn - .create_data_channel("data", None) - .map_err(Error::WebRTC) - .await?; + // Create a datachannel with label 'data' + let data_channel = peer_conn + .create_data_channel("data", None) + .map_err(Error::WebRTC) + .await?; - trace!("Opening outbound substream {}", data_channel.id()); + trace!("Opening outbound substream {}", data_channel.id()); - // No need to hold the lock during the DTLS handshake. - drop(peer_conn); + // No need to hold the lock during the DTLS handshake. + drop(peer_conn); - let (tx, rx) = oneshot::channel::>(); + let (tx, rx) = oneshot::channel::>(); - // Wait until the data channel is opened and detach it. - register_data_channel_open_handler(data_channel, tx).await; + // Wait until the data channel is opened and detach it. + register_data_channel_open_handler(data_channel, tx).await; - // Wait until data channel is opened and ready to use - match rx.await { - Ok(detached) => Ok(detached), - Err(e) => Err(Error::InternalError(e.to_string())), - } - })); + // Wait until data channel is opened and ready to use + match rx.await { + Ok(detached) => Ok(detached), + Err(e) => Err(Error::InternalError(e.to_string())), + } + })); match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { @@ -222,14 +215,10 @@ impl<'a> StreamMuxer for Connection { let mut inner = self.inner.lock().unwrap(); let peer_conn = self.peer_conn.clone(); - let fut = inner.close_fut.get_or_insert( - Box::pin(async move { - let peer_conn = peer_conn.lock().await; - peer_conn - .close() - .await - .map_err(Error::WebRTC) - })); + let fut = inner.close_fut.get_or_insert(Box::pin(async move { + let peer_conn = peer_conn.lock().await; + peer_conn.close().await.map_err(Error::WebRTC) + })); match ready!(fut.as_mut().poll(cx)) { Ok(()) => { diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 15870931743..00365ce2db6 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -45,8 +45,8 @@ pub enum Error { #[error("no active listeners")] NoListeners, - #[error("UDP mux is closed")] - UDPMuxIsClosed, + #[error("UDP mux error: {0}")] + UDPMuxError(webrtc_util::Error), #[error("internal error: {0} (see debug logs)")] InternalError(String), diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index a7a502a34bb..a143f57e005 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -20,14 +20,8 @@ use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::{ - channel::{mpsc, oneshot}, - future, - future::BoxFuture, - prelude::*, - select, - stream::SelectAll, - stream::Stream, - TryFutureExt, + channel::oneshot, future, future::BoxFuture, prelude::*, select, stream::SelectAll, + stream::Stream, TryFutureExt, }; use futures_timer::Delay; use if_watch::IfEvent; @@ -68,7 +62,7 @@ use crate::connection::PollDataChannel; use crate::error::Error; use crate::in_addr::InAddr; use crate::sdp; -use crate::udp_mux::{NewAddr, UDPMuxNewAddr, UDPMuxParams}; +use crate::udp_mux::{UDPMuxEvent, UDPMuxNewAddr, UDPMuxParams}; use crate::upgrade; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -135,15 +129,13 @@ impl WebRTCTransport { debug!("listening on {}", listen_addr); // Sender and receiver for new addresses - let (new_addr_tx, new_addr_rx) = mpsc::channel(1); - let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket), new_addr_tx); + let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket)); Ok(WebRTCListenStream::new( listener_id, listen_addr, self.config.clone(), udp_mux, - new_addr_rx, self.id_keys.clone(), )) } @@ -359,11 +351,11 @@ pub struct WebRTCListenStream { /// A `RTCConfiguration` which holds this peer's certificate(s). config: RTCConfiguration, - /// The `UDPMux` that manages all ICE connections. - udp_mux: Arc, + /// The UDP muxer that manages all ICE connections. + udp_mux: Arc, - /// The receiver for new `SocketAddr` connecting to this peer. - new_addr_rx: mpsc::Receiver, + /// Future that drives reading from the UDP socket. + udp_mux_read_fut: Option>, /// `Keypair` identifying this peer id_keys: identity::Keypair, @@ -380,8 +372,7 @@ impl WebRTCListenStream { listener_id: ListenerId, listen_addr: SocketAddr, config: RTCConfiguration, - udp_mux: Arc, - new_addr_rx: mpsc::Receiver, + udp_mux: Arc, id_keys: identity::Keypair, ) -> Self { let in_addr = InAddr::new(listen_addr.ip()); @@ -392,7 +383,7 @@ impl WebRTCListenStream { in_addr, config, udp_mux, - new_addr_rx, + udp_mux_read_fut: None, id_keys, report_closed: None, } @@ -488,29 +479,41 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(event)); } - let new_addr = match futures::ready!(self.new_addr_rx.poll_next_unpin(cx)) { - Some(a) => a, - None => { - self.close(Err(Error::UDPMuxIsClosed)); - return self.poll_next(cx); + let udp_mux = self.udp_mux.clone(); + let udp_mux_read_fut = self + .udp_mux_read_fut + .get_or_insert(Box::pin(async move { udp_mux.read_from_conn().await })); + match udp_mux_read_fut.as_mut().poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(event) => { + self.udp_mux_read_fut = None; + + match event { + UDPMuxEvent::NewAddr(new_addr) => { + let local_addr = socketaddr_to_multiaddr(&self.listen_addr); + let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); + let event = TransportEvent::Incoming { + upgrade: Box::pin(upgrade::webrtc( + self.udp_mux.clone(), + self.config.clone(), + new_addr.addr, + new_addr.ufrag, + self.id_keys.clone(), + )) as BoxFuture<'static, _>, + local_addr, + send_back_addr, + listener_id: self.listener_id, + }; + Poll::Ready(Some(event)) + } + UDPMuxEvent::Error(e) => { + self.close(Err(Error::UDPMuxError(e))); + return self.poll_next(cx); + } + _ => return self.poll_next(cx), + } } - }; - - let local_addr = socketaddr_to_multiaddr(&self.listen_addr); - let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); - let event = TransportEvent::Incoming { - upgrade: Box::pin(upgrade::webrtc( - self.udp_mux.clone(), - self.config.clone(), - new_addr.addr, - new_addr.ufrag, - self.id_keys.clone(), - )) as BoxFuture<'static, _>, - local_addr, - send_back_addr, - listener_id: self.listener_id, - }; - Poll::Ready(Some(event)) + } } } diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index aa90e2c0aa4..462725e6078 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -21,13 +21,12 @@ // SOFTWARE. use async_trait::async_trait; -use futures::channel::mpsc; use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, }; use tokio_crate as tokio; -use tokio_crate::sync::{watch, Mutex}; +use tokio_crate::sync::Mutex; use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc_util::{sync::RwLock, Conn, Error}; @@ -35,7 +34,10 @@ use std::{ collections::{HashMap, HashSet}, io::ErrorKind, net::SocketAddr, - sync::{Arc, Weak}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Weak, + }, }; const RECEIVE_MTU: usize = 8192; @@ -60,6 +62,16 @@ impl UDPMuxParams { } } +/// An event emitted by [`UDPMuxNewAddr`] when it's polled. +pub enum UDPMuxEvent { + /// Connection error. UDP mux should be stopped. + Error(Error), + /// Got a [`NewAddr`] from the socket. + NewAddr(NewAddr), + /// Non-important event. Can be ignored. + None, +} + /// This is a copy of `UDPMuxDefault` with the exception of ability to report new addresses via /// `new_addr_tx`. pub struct UDPMuxNewAddr { @@ -73,38 +85,25 @@ pub struct UDPMuxNewAddr { /// Maps from ip address to the underlying connection. address_map: RwLock>, - // Close sender - closed_watch_tx: Mutex>>, + /// Set of the new IP addresses to avoid sending the same IP multiple times. + new_addrs: RwLock>, - /// Close reciever - closed_watch_rx: watch::Receiver<()>, + /// `true` when UDP mux is closed. + is_closed: AtomicBool, - /// Set of the new IP addresses reported via `new_addr_tx` to avoid sending the same IP - /// multiple times. - new_addrs: RwLock>, + read_buffer: [u8; RECEIVE_MTU], } impl UDPMuxNewAddr { - pub fn new(params: UDPMuxParams, new_addr_tx: mpsc::Sender) -> Arc { - let (closed_watch_tx, closed_watch_rx) = watch::channel(()); - - let mux = Arc::new(Self { + pub fn new(params: UDPMuxParams) -> Arc { + Arc::new(Self { params, conns: Mutex::default(), address_map: RwLock::default(), - closed_watch_tx: Mutex::new(Some(closed_watch_tx)), - closed_watch_rx: closed_watch_rx.clone(), new_addrs: RwLock::default(), - }); - - let cloned_mux = Arc::clone(&mux); - cloned_mux.start_conn_worker(closed_watch_rx, new_addr_tx); - - mux - } - - pub async fn is_closed(&self) -> bool { - self.closed_watch_tx.lock().await.is_none() + is_closed: AtomicBool::new(false), + read_buffer: [0u8; RECEIVE_MTU], + }) } /// Create a muxed connection for a given ufrag. @@ -133,129 +132,116 @@ impl UDPMuxNewAddr { } } - fn start_conn_worker( - self: Arc, - mut closed_watch_rx: watch::Receiver<()>, - mut new_addr_tx: mpsc::Sender, - ) { - tokio::spawn(async move { - let mut buffer = [0u8; RECEIVE_MTU]; - - loop { - let loop_self = Arc::clone(&self); - let conn = &loop_self.params.conn; - - tokio::select! { - res = conn.recv_from(&mut buffer) => { - match res { - Ok((len, addr)) => { - // Find connection based on previously having seen this source address - let conn = { - let address_map = loop_self - .address_map - .read(); - - address_map.get(&addr).map(Clone::clone) - }; - - let conn = match conn { - // If we couldn't find the connection based on source address, see if - // this is a STUN mesage and if so if we can find the connection based on ufrag. - None if is_stun_message(&buffer) => { - loop_self.conn_from_stun_message(&buffer, &addr).await - } - s @ Some(_) => s, - _ => None, - }; - - match conn { - None => { - if !loop_self.new_addrs.read().contains(&addr) { - match ufrag_from_stun_message(&buffer, false) { - Ok(ufrag) => { - log::trace!("Notifying about new address {} from {}", &addr, ufrag); - if let Err(err) = new_addr_tx.try_send(NewAddr { addr, ufrag }) { - log::error!("Failed to send new address {}: {}", &addr, err); - } else { - let mut new_addrs = loop_self.new_addrs.write(); - new_addrs.insert(addr); - }; - } - Err(e) => { - log::trace!("Unknown address {} (non STUN packet: {})", &addr, e); - } - } - } - } - Some(conn) => { - if let Err(err) = conn.write_packet(&buffer[..len], addr).await { - log::error!("Failed to write packet: {}", err); - } - } + pub fn is_closed(&self) -> bool { + return self.is_closed.load(Ordering::Relaxed); + } + + pub async fn read_from_conn(&self) -> UDPMuxEvent { + let mut buffer = self.read_buffer; + let conn = &self.params.conn; + + let res = conn.recv_from(&mut buffer).await; + match res { + Ok((len, addr)) => { + // Find connection based on previously having seen this source address + let conn = { + let address_map = self.address_map.read(); + + address_map.get(&addr).map(Clone::clone) + }; + + let conn = match conn { + // If we couldn't find the connection based on source address, see if + // this is a STUN mesage and if so if we can find the connection based on ufrag. + None if is_stun_message(&buffer) => { + self.conn_from_stun_message(&buffer, &addr).await + } + s @ Some(_) => s, + _ => None, + }; + + match conn { + None => { + if !self.new_addrs.read().contains(&addr) { + match ufrag_from_stun_message(&buffer, false) { + Ok(ufrag) => { + log::trace!( + "Notifying about new address {} from {}", + &addr, + ufrag + ); + let mut new_addrs = self.new_addrs.write(); + new_addrs.insert(addr); + return UDPMuxEvent::NewAddr(NewAddr { addr, ufrag }); + } + Err(e) => { + log::trace!( + "Unknown address {} (non STUN packet: {})", + &addr, + e + ); } - } - Err(Error::Io(err)) if err.0.kind() == ErrorKind::TimedOut => continue, - Err(err) => { - log::error!("Could not read udp packet: {}", err); - break; } } } - _ = closed_watch_rx.changed() => { - return; + Some(conn) => { + tokio::spawn(async move { + if let Err(err) = conn.write_packet(&buffer[..len], addr).await { + log::error!("Failed to write packet: {}", err); + } + }); } } } - }); + Err(Error::Io(err)) if err.0.kind() == ErrorKind::TimedOut => {} + Err(err) => { + log::error!("Could not read udp packet: {}", err); + return UDPMuxEvent::Error(err); + } + } + UDPMuxEvent::None } } #[async_trait] impl UDPMux for UDPMuxNewAddr { async fn close(&self) -> Result<(), Error> { - if self.is_closed().await { + if self.is_closed() { return Err(Error::ErrAlreadyClosed); } - let mut closed_tx = self.closed_watch_tx.lock().await; - - if let Some(tx) = closed_tx.take() { - let _ = tx.send(()); - drop(closed_tx); - - let old_conns = { - let mut conns = self.conns.lock().await; + let old_conns = { + let mut conns = self.conns.lock().await; - std::mem::take(&mut (*conns)) - }; + std::mem::take(&mut (*conns)) + }; - // NOTE: We don't wait for these closure to complete - for (_, conn) in old_conns { - conn.close(); - } + // NOTE: We don't wait for these closure to complete + for (_, conn) in old_conns { + conn.close(); + } - { - let mut address_map = self.address_map.write(); + { + let mut address_map = self.address_map.write(); - // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to - // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. - let _ = std::mem::take(&mut (*address_map)); - } + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + let _ = std::mem::take(&mut (*address_map)); + } - { - let mut new_addrs = self.new_addrs.write(); + { + let mut new_addrs = self.new_addrs.write(); - // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to - // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. - let _ = std::mem::take(&mut (*new_addrs)); - } + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + let _ = std::mem::take(&mut (*new_addrs)); } Ok(()) } async fn get_conn(self: Arc, ufrag: &str) -> Result, Error> { - if self.is_closed().await { + if self.is_closed() { return Err(Error::ErrUseClosedNetworkConn); } @@ -306,7 +292,7 @@ impl UDPMux for UDPMuxNewAddr { #[async_trait] impl UDPMuxWriter for UDPMuxNewAddr { async fn register_conn_for_address(&self, conn: &UDPMuxConn, addr: SocketAddr) { - if self.is_closed().await { + if self.is_closed() { return; } From 212936fcc798076ecb108a3e37add0a35f6b74fa Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 21 Jul 2022 14:53:22 +0400 Subject: [PATCH 029/244] brush up code --- transports/webrtc/Cargo.toml | 4 +- transports/webrtc/src/connection.rs | 22 ++++-- .../src/connection/poll_data_channel.rs | 1 + transports/webrtc/src/lib.rs | 35 +++------- transports/webrtc/src/transport.rs | 58 +++++++++------- transports/webrtc/src/udp_mux.rs | 24 +++++-- transports/webrtc/src/upgrade.rs | 67 ++++++++++--------- 7 files changed, 109 insertions(+), 102 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 842fdb68c04..1b57eb16cb7 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -26,8 +26,8 @@ stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} -webrtc = { version = "0.4.0", git = "https://github.com/melekes/webrtc.git", branch = "always-set-remote-certificate" } -webrtc-data = { version = "0.3.3", git = "https://github.com/webrtc-rs/data.git", rev = "f630dcd2b90ed8faf57d1b36c18ec5aa715325c0" } +webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", branch = "master" } +webrtc-data = "0.4.0" webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index db853e8aae8..8219f0d6b1a 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -20,10 +20,14 @@ mod poll_data_channel; -use futures::channel::mpsc; -use futures::channel::oneshot::{self, Sender}; -use futures::lock::Mutex as FutMutex; -use futures::{future::BoxFuture, prelude::*, ready}; +use futures::{ + channel::{ + mpsc, + oneshot::{self, Sender}, + }, + lock::Mutex as FutMutex, + {future::BoxFuture, prelude::*, ready}, +}; use futures_lite::stream::StreamExt; use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; @@ -31,12 +35,16 @@ use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; -use std::sync::{Arc, Mutex as StdMutex}; -use std::task::{Context, Poll}; +use std::{ + sync::{Arc, Mutex as StdMutex}, + task::{Context, Poll}, +}; use crate::error::Error; pub(crate) use poll_data_channel::PollDataChannel; +const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; + /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. pub struct Connection { /// `RTCPeerConnection` to the remote peer. @@ -68,7 +76,7 @@ struct ConnectionInner { impl Connection { /// Creates a new connection. pub async fn new(rtc_conn: RTCPeerConnection) -> Self { - let (data_channel_tx, data_channel_rx) = mpsc::channel(10); + let (data_channel_tx, data_channel_rx) = mpsc::channel(MAX_DATA_CHANNELS_IN_FLIGHT); Connection::register_incoming_data_channels_handler(&rtc_conn, data_channel_tx).await; diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 40aa8dec8b6..08d44a83657 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -27,6 +27,7 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +/// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. #[derive(Debug)] pub struct PollDataChannel(RTCPollDataChannel); diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 3433f803a40..fe9b4896936 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -30,7 +30,7 @@ //! The WebRTC protocol uses ICE in order to establish a connection. //! //! In a typical ICE setup, there are two endpoints, called agents, that want to communicate. One -//! of these two agents is the local browser, while the other agent is the target of the +//! of these two agents can be the local browser, while the other agent is the target of the //! connection. //! //! Even though in this specific context all we want is a simple client-server communication, it is @@ -53,21 +53,12 @@ //! answer containing this candidate has been sent back. This will cause the offerer to execute //! step 4: try to connect to the remote's candidate. //! -//! ## 1 vs N data channels -//! -//! The SDP message generated by the offerer contains the list of so-called "media streams" that it -//! wants to open. In our specific use-case, we configure the transport to always request one data -//! stream. -//! -//! For this prototype, we have choosen to implement a single data channel and multiplex on top -//! because doing N channels will force us to rewrite a significant part of smoldot, which isn't -//! ready at all to have encryption and multiplexing handled "externally". -//! //! ## TCP or UDP //! -//! WebRTC by itself doesn't hardcode any specific protocol for these media streams. Instead, it is -//! the SDP message of the offerer that specifies which protocol to use. In our use case, one data -//! stream, we know that the offerer will always request either TCP+DTLS+SCTP, or UDP+DTLS+SCTP. +//! WebRTC by itself doesn't hardcode any specific protocol for media streams. Instead, it is the +//! SDP message of the offerer that specifies which protocol to use. In our use case (one or more +//! data channels), we know that the offerer will always request either TCP+DTLS+SCTP, or +//! UDP+DTLS+SCTP. //! //! The implementation only supports UDP at the moment, so if the offerer requests TCP+DTLS+SCTP, it //! will not respond. Support for TCP may be added in the future (see @@ -84,19 +75,9 @@ //! //! During the ICE negotiation, each agent must include in its SDP packet a hash of the self-signed //! certificate that it will use during the DTLS handshake. In our use-case, where we try to -//! hand-crate the SDP answer generated by the remote, this is problematic. One way to solve this -//! is to make the hash a part of the remote's multiaddr. -//! -//! ## PeerId -//! -//! Ideally, we would just add a field in the certificate that contains a signature of the -//! certificate made using the PeerId. But you can't do that, as it's a chicken and egg problem: -//! you can't modify the certificate anymore after you've created a signature of it. We could find -//! some semi-hacky system where the signature is made against the certificate but with this field -//! removed, but that's very hacky as well. -//! -//! For now we'll start an encryption protocol handshake on top of the single data channel. Once -//! this handshake has been successful, we stop it and just use DTLS. +//! hand-crate the SDP answer generated by the remote, this is problematic. A way to solve this +//! is to make the hash a part of the remote's multiaddr. On the server side, we turn +//! certificate verification off. pub mod connection; pub mod error; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index a143f57e005..754f6a3a7ba 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -18,15 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::{ - channel::oneshot, future, future::BoxFuture, prelude::*, select, stream::SelectAll, - stream::Stream, TryFutureExt, + channel::oneshot, + future, + future::BoxFuture, + io::{AsyncReadExt, AsyncWriteExt}, + prelude::*, + select, + stream::SelectAll, + stream::Stream, + TryFutureExt, }; use futures_timer::Delay; use if_watch::IfEvent; -use libp2p_core::identity; use libp2p_core::{ + identity, multiaddr::{Multiaddr, Protocol}, muxing::StreamMuxerBox, transport::{Boxed, ListenerId, TransportError, TransportEvent}, @@ -57,13 +63,15 @@ use std::{ time::Duration, }; -use crate::connection::Connection; -use crate::connection::PollDataChannel; -use crate::error::Error; -use crate::in_addr::InAddr; -use crate::sdp; -use crate::udp_mux::{UDPMuxEvent, UDPMuxNewAddr, UDPMuxParams}; -use crate::upgrade; +use crate::{ + connection::Connection, + connection::PollDataChannel, + error::Error, + in_addr::InAddr, + sdp, + udp_mux::{UDPMuxEvent, UDPMuxNewAddr, UDPMuxParams}, + upgrade, +}; /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct WebRTCTransport { @@ -76,7 +84,7 @@ pub struct WebRTCTransport { } impl WebRTCTransport { - /// Create a new WebRTC transport. + /// Creates a new WebRTC transport. pub fn new(certificate: RTCCertificate, id_keys: identity::Keypair) -> Self { Self { config: RTCConfiguration { @@ -110,6 +118,7 @@ impl WebRTCTransport { let sock_addr = multiaddr_to_socketaddr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?; + // XXX: `UdpSocket::bind` is async, so use a std socket and convert let std_sock = std::net::UdpSocket::bind(sock_addr) .map_err(Error::IoError) .map_err(TransportError::Other)?; @@ -125,10 +134,8 @@ impl WebRTCTransport { .local_addr() .map_err(Error::IoError) .map_err(TransportError::Other)?; - debug!("listening on {}", listen_addr); - // Sender and receiver for new addresses let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket)); Ok(WebRTCListenStream::new( @@ -233,12 +240,12 @@ impl Transport for WebRTCTransport { .map_err(Error::WebRTC) .await?; - // Create a datachannel with label 'data' + // Open a data channel to do Noise on top and verify the remote. let data_channel = peer_connection .create_data_channel( "data", Some(RTCDataChannelInit { - id: Some(1), + id: Some(1), // id MUST match one used during upgrade ..RTCDataChannelInit::default() }), ) @@ -248,8 +255,6 @@ impl Transport for WebRTCTransport { // Wait until the data channel is opened and detach it. crate::connection::register_data_channel_open_handler(data_channel, tx).await; - - // Wait until data channel is opened and ready to use let detached = select! { res = rx => match res { Ok(detached) => detached, @@ -267,7 +272,7 @@ impl Transport for WebRTCTransport { let noise = NoiseConfig::xx(dh_keys); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(detached), info) + .upgrade_outbound(PollDataChannel::new(detached.clone()), info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), @@ -300,14 +305,13 @@ impl Transport for WebRTCTransport { } // Close the initial data channel after noise handshake is done. - // https://github.com/webrtc-rs/sctp/pull/14 - // detached - // .close() - // .await - // .map_err(|e| Error::WebRTC(e.into()))?; + detached + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; let mut c = Connection::new(peer_connection).await; - // XXX: default buffer size is too small to fit some messages. Possibly remove once + // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/sctp/issues/28 is fixed. c.set_data_channels_read_buf_capacity(8192 * 10); Ok((peer_id, c)) @@ -606,7 +610,7 @@ pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> Str .first() .expect("at least one certificate") .get_fingerprints() - .expect("fingerprints to succeed"); + .expect("get_fingerprints to succeed"); debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); fingerprints.first().unwrap().value.clone() } @@ -638,6 +642,8 @@ pub(crate) fn build_setting_engine( se } +/// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not +/// contain one. fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { let iter = addr.iter(); for proto in iter { diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 462725e6078..32001bfef61 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -42,16 +42,19 @@ use std::{ const RECEIVE_MTU: usize = 8192; +/// A previously unseen address of a remote who've sent us an ICE binding request. pub struct NewAddr { pub addr: SocketAddr, pub ufrag: String, } +/// Parameters for [`UDPMuxNewAddr`]. pub struct UDPMuxParams { conn: Box, } impl UDPMuxParams { + /// Creates new params. pub fn new(conn: C) -> Self where C: Conn + Send + Sync + 'static, @@ -72,8 +75,8 @@ pub enum UDPMuxEvent { None, } -/// This is a copy of `UDPMuxDefault` with the exception of ability to report new addresses via -/// `new_addr_tx`. +/// A modified version of [`webrtc_ice::udp_mux::UDPMuxDefault`], which reports previously unseen +/// addresses instead of ignoring them. pub struct UDPMuxNewAddr { /// The params this instance is configured with. /// Contains the underlying UDP socket in use @@ -82,19 +85,21 @@ pub struct UDPMuxNewAddr { /// Maps from ufrag to the underlying connection. conns: Mutex>, - /// Maps from ip address to the underlying connection. + /// Maps from socket address to the underlying connection. address_map: RwLock>, - /// Set of the new IP addresses to avoid sending the same IP multiple times. + /// Set of the new addresses to avoid sending the same address multiple times. new_addrs: RwLock>, /// `true` when UDP mux is closed. is_closed: AtomicBool, + /// Buffer used when reading from the underlying UDP socket. read_buffer: [u8; RECEIVE_MTU], } impl UDPMuxNewAddr { + /// Creates a new UDP muxer. pub fn new(params: UDPMuxParams) -> Arc { Arc::new(Self { params, @@ -119,6 +124,8 @@ impl UDPMuxNewAddr { Ok(UDPMuxConn::new(params)) } + /// Returns a muxed connection if the `ufrag` from the given STUN message matches an existing + /// connection. async fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { match ufrag_from_stun_message(buffer, true) { Ok(ufrag) => { @@ -126,16 +133,19 @@ impl UDPMuxNewAddr { conns.get(&ufrag).map(Clone::clone) } Err(e) => { - log::error!("{} (addr: {})", e, addr); + log::debug!("{} (addr: {})", e, addr); None } } } + /// Returns true if the UDP muxer is closed. pub fn is_closed(&self) -> bool { return self.is_closed.load(Ordering::Relaxed); } + /// Reads from the underlying UDP socket and either reports a new address or proxies data to the + /// muxed connection. pub async fn read_from_conn(&self) -> UDPMuxEvent { let mut buffer = self.read_buffer; let conn = &self.params.conn; @@ -175,7 +185,7 @@ impl UDPMuxNewAddr { return UDPMuxEvent::NewAddr(NewAddr { addr, ufrag }); } Err(e) => { - log::trace!( + log::debug!( "Unknown address {} (non STUN packet: {})", &addr, e @@ -187,7 +197,7 @@ impl UDPMuxNewAddr { Some(conn) => { tokio::spawn(async move { if let Err(err) = conn.write_packet(&buffer[..len], addr).await { - log::error!("Failed to write packet: {}", err); + log::error!("Failed to write packet: {} (addr: {})", err, addr); } }); } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index a313df9b5e4..5c4c1a65f58 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -18,12 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::io::{AsyncReadExt, AsyncWriteExt}; -use futures::{channel::oneshot, future, select, FutureExt, TryFutureExt}; +use futures::{ + channel::oneshot, + future, + io::{AsyncReadExt, AsyncWriteExt}, + select, FutureExt, TryFutureExt, +}; use futures_timer::Delay; -use libp2p_core::identity; -use libp2p_core::PeerId; -use libp2p_core::{InboundUpgrade, UpgradeInfo}; +use libp2p_core::{ + identity, PeerId, {InboundUpgrade, UpgradeInfo}, +}; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; use multihash::Hasher; @@ -35,15 +39,13 @@ use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use webrtc_ice::udp_mux::UDPMux; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; +use std::{net::SocketAddr, sync::Arc, time::Duration}; -use crate::connection::Connection; -use crate::connection::PollDataChannel; -use crate::error::Error; -use crate::sdp; -use crate::transport; +use crate::{ + connection::{Connection, PollDataChannel}, + error::Error, + sdp, transport, +}; pub async fn webrtc( udp_mux: Arc, @@ -73,7 +75,7 @@ pub async fn webrtc( let client_session_description = transport::render_description( sdp::CLIENT_SESSION_DESCRIPTION, socket_addr, - "UNKNOWN", + "UNKNOWN", // certificate verification is disabled, so any value is okay. &ufrag, ); debug!("OFFER: {:?}", client_session_description); @@ -86,13 +88,13 @@ pub async fn webrtc( debug!("ANSWER: {:?}", answer.sdp); peer_connection.set_local_description(answer).await?; - // Create a datachannel with label 'data'. + // Open a data channel to do Noise on top and verify the remote. let data_channel = peer_connection .create_data_channel( "data", Some(RTCDataChannelInit { - negotiated: Some(true), - id: Some(1), + negotiated: Some(true), // channel is already negotiated by the client + id: Some(1), // id MUST match one used during dial ..RTCDataChannelInit::default() }), ) @@ -100,11 +102,8 @@ pub async fn webrtc( let (tx, mut rx) = oneshot::channel::>(); - // Wait until the data channel is opened and detach it. // Wait until the data channel is opened and detach it. crate::connection::register_data_channel_open_handler(data_channel, tx).await; - - // Wait until data channel is opened and ready to use let detached = select! { res = rx => match res { Ok(detached) => detached, @@ -147,13 +146,16 @@ pub async fn webrtc( let fingerprint_from_noise = String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; let remote_fingerprint = { - let remote_cert_bytes = - peer_connection - .get_remote_dtls_certificate() - .await - .ok_or(Error::InternalError( - "No remote certificate found".to_owned(), - ))?; + let remote_cert_bytes = peer_connection + .sctp() + .transport() + .get_remote_certificate() + .await; + if remote_cert_bytes.is_empty() { + return Err(Error::InternalError( + "No remote certificate found".to_owned(), + )); + } let mut h = multihash::Sha2_256::default(); h.update(&remote_cert_bytes); let hashed = h.finalize(); @@ -168,14 +170,13 @@ pub async fn webrtc( } // Close the initial data channel after noise handshake is done. - // https://github.com/webrtc-rs/sctp/pull/14 - // detached - // .close() - // .await - // .map_err(|e| Error::WebRTC(e.into()))?; + detached + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; let mut c = Connection::new(peer_connection).await; - // XXX: default buffer size is too small to fit some messages. Possibly remove once + // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/sctp/issues/28 is fixed. c.set_data_channels_read_buf_capacity(8192 * 10); Ok((peer_id, c)) From 464335951a5205119697fcb56902dd5a0967db91 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 21 Jul 2022 15:55:54 +0400 Subject: [PATCH 030/244] add close_listener test --- transports/webrtc/src/transport.rs | 67 ++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 754f6a3a7ba..ec3fef16d92 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -660,13 +660,15 @@ fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { #[cfg(test)] mod tests { - use super::*; + use futures::future::poll_fn; use libp2p_core::{multiaddr::Protocol, Multiaddr}; use rcgen::KeyPair; - use std::net::IpAddr; - use std::net::{Ipv4Addr, Ipv6Addr}; use tokio_crate as tokio; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + use super::*; + #[test] fn multiaddr_to_socketaddr_conversion() { assert!( @@ -810,4 +812,63 @@ mod tests { let (a, b) = futures::join!(inbound, outbound); a.and(b).unwrap(); } + + #[tokio::test] + async fn close_listener() { + let id_keys = identity::Keypair::generate_ed25519(); + let mut transport = { + let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); + let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); + WebRTCTransport::new(cert, id_keys) + }; + + assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) + .now_or_never() + .is_none()); + + // Run test twice to check that there is no unexpected behaviour if `QuicTransport.listener` + // is temporarily empty. + for _ in 0..2 { + let listener = transport + .listen_on("/ip4/0.0.0.0/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()) + .unwrap(); + match poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)).await { + TransportEvent::NewAddress { + listener_id, + listen_addr, + } => { + assert_eq!(listener_id, listener); + assert!( + matches!(listen_addr.iter().next(), Some(Protocol::Ip4(a)) if !a.is_unspecified()) + ); + assert!( + matches!(listen_addr.iter().nth(1), Some(Protocol::Udp(port)) if port != 0) + ); + assert!( + matches!(listen_addr.iter().nth(2), Some(Protocol::XWebRTC(f)) if !f.is_empty()) + ); + } + e => panic!("Unexpected event: {:?}", e), + } + assert!( + transport.remove_listener(listener), + "Expect listener to exist." + ); + match poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)).await { + TransportEvent::ListenerClosed { + listener_id, + reason: Ok(()), + } => { + assert_eq!(listener_id, listener); + } + e => panic!("Unexpected event: {:?}", e), + } + // Poll once again so that the listener has the chance to return `Poll::Ready(None)` and + // be removed from the list of listeners. + assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) + .now_or_never() + .is_none()); + assert!(transport.listeners.is_empty()); + } + } } From e32aa5756139f93cd97c1b1e27d2799a925428a0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 22 Jul 2022 16:28:13 +0400 Subject: [PATCH 031/244] updates after @thomaseizinger review --- transports/webrtc/src/transport.rs | 48 ++++++++---------------------- transports/webrtc/src/upgrade.rs | 6 ++-- transports/webrtc/tests/smoke.rs | 10 ++++--- 3 files changed, 21 insertions(+), 43 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index ec3fef16d92..072e3dc63ba 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -34,11 +34,9 @@ use if_watch::IfEvent; use libp2p_core::{ identity, multiaddr::{Multiaddr, Protocol}, - muxing::StreamMuxerBox, - transport::{Boxed, ListenerId, TransportError, TransportEvent}, - PeerId, Transport, + transport::{ListenerId, TransportError, TransportEvent}, + OutboundUpgrade, PeerId, Transport, UpgradeInfo, }; -use libp2p_core::{OutboundUpgrade, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; use tinytemplate::TinyTemplate; @@ -102,14 +100,6 @@ impl WebRTCTransport { fingerprint_of_first_certificate(&self.config) } - /// Creates a boxed libp2p transport. - pub fn boxed(self) -> Boxed<(PeerId, StreamMuxerBox)> { - Transport::map(self, |(peer_id, conn), _| { - (peer_id, StreamMuxerBox::new(conn)) - }) - .boxed() - } - fn do_listen( &self, listener_id: ListenerId, @@ -194,31 +184,23 @@ impl Transport for WebRTCTransport { let remote = addr.clone(); // used for logging trace!("dialing address: {:?}", remote); - let udp_mux = if let Some(l) = self.listeners.iter_mut().next() { - l.udp_mux.clone() - } else { - return Err(TransportError::Other(Error::NoListeners)); - }; + let first_listener = self + .listeners + .iter_mut() + .next() + .ok_or(TransportError::Other(Error::NoListeners))?; + let udp_mux = first_listener.udp_mux.clone(); let se = build_setting_engine(udp_mux, &sock_addr, &our_fingerprint); let api = APIBuilder::new().with_setting_engine(se).build(); // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let peer_connection = api - .new_peer_connection(config) - .map_err(Error::WebRTC) - .await?; + let peer_connection = api.new_peer_connection(config).await?; - let offer = peer_connection - .create_offer(None) - .map_err(Error::WebRTC) - .await?; + let offer = peer_connection.create_offer(None).await?; debug!("OFFER: {:?}", offer.sdp); - peer_connection - .set_local_description(offer) - .map_err(Error::WebRTC) - .await?; + peer_connection.set_local_description(offer).await?; // Set the remote description to the predefined SDP. let remote_fingerprint = match fingerprint_from_addr(&addr) { @@ -235,10 +217,7 @@ impl Transport for WebRTCTransport { let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); // Set the local description and start UDP listeners // Note: this will start the gathering of ICE candidates - peer_connection - .set_remote_description(sdp) - .map_err(Error::WebRTC) - .await?; + peer_connection.set_remote_description(sdp).await?; // Open a data channel to do Noise on top and verify the remote. let data_channel = peer_connection @@ -277,8 +256,7 @@ impl Transport for WebRTCTransport { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), }) - .await - .map_err(Error::Noise)?; + .await?; // Exchange TLS certificate fingerprints to prevent MiM attacks. trace!("exchanging TLS certificate fingerprints with {}", remote); diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 5c4c1a65f58..aa6cb53e9db 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -66,8 +66,7 @@ pub async fn webrtc( // // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server) - .map_err(Error::WebRTC)?; + se.set_answering_dtls_role(DTLSRole::Server)?; } let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; @@ -126,8 +125,7 @@ pub async fn webrtc( RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), }) - .await - .map_err(Error::Noise)?; + .await?; // Exchange TLS certificate fingerprints to prevent MiM attacks. trace!( diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 96d8559b018..fcc785e1a81 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -3,9 +3,7 @@ use async_trait::async_trait; use futures::future::FutureExt; use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use futures::stream::StreamExt; -use libp2p_core::identity; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::upgrade; +use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; use libp2p_request_response::{ ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, @@ -40,8 +38,12 @@ async fn create_swarm() -> Result<(Swarm>, String)> { let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); trace!("{}", peer_id); + let transport = Transport::map(transport, |(peer_id, conn), _| { + (peer_id, StreamMuxerBox::new(conn)) + }) + .boxed(); Ok(( - SwarmBuilder::new(transport.boxed(), behaviour, peer_id) + SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(|fut| { tokio::spawn(fut); })) From cd7786f5c534bd9c014ec4eee580ed1725c14fcd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 23 Jul 2022 10:08:53 +0400 Subject: [PATCH 032/244] extract webrtc related logic into a sep mod --- transports/webrtc/src/lib.rs | 1 + transports/webrtc/src/transport.rs | 183 ++++------------ transports/webrtc/src/upgrade.rs | 96 ++------- transports/webrtc/src/webrtc_connection.rs | 233 +++++++++++++++++++++ 4 files changed, 291 insertions(+), 222 deletions(-) create mode 100644 transports/webrtc/src/webrtc_connection.rs diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index fe9b4896936..424cc2cbd8e 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -87,3 +87,4 @@ mod in_addr; mod sdp; mod udp_mux; mod upgrade; +mod webrtc_connection; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 072e3dc63ba..eee1c4a5398 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -19,17 +19,14 @@ // DEALINGS IN THE SOFTWARE. use futures::{ - channel::oneshot, future, future::BoxFuture, io::{AsyncReadExt, AsyncWriteExt}, prelude::*, - select, stream::SelectAll, stream::Stream, TryFutureExt, }; -use futures_timer::Delay; use if_watch::IfEvent; use libp2p_core::{ identity, @@ -39,18 +36,9 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; -use tinytemplate::TinyTemplate; use tokio_crate::net::UdpSocket; -use webrtc::api::setting_engine::SettingEngine; -use webrtc::api::APIBuilder; -use webrtc::data_channel::data_channel_init::RTCDataChannelInit; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; -use webrtc_data::data_channel::DataChannel as DetachedDataChannel; -use webrtc_ice::network_type::NetworkType; -use webrtc_ice::udp_mux::UDPMux; -use webrtc_ice::udp_network::UDPNetwork; use std::{ borrow::Cow, @@ -58,7 +46,6 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::Duration, }; use crate::{ @@ -66,9 +53,9 @@ use crate::{ connection::PollDataChannel, error::Error, in_addr::InAddr, - sdp, udp_mux::{UDPMuxEvent, UDPMuxNewAddr, UDPMuxParams}, upgrade, + webrtc_connection::WebRTCConnection, }; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -172,77 +159,44 @@ impl Transport for WebRTCTransport { } fn dial(&mut self, addr: Multiaddr) -> Result> { - let config = self.config.clone(); - let our_fingerprint = self.cert_fingerprint(); - let id_keys = self.id_keys.clone(); - let sock_addr = multiaddr_to_socketaddr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } + let remote = addr.clone(); // used for logging trace!("dialing address: {:?}", remote); + let config = self.config.clone(); + let our_fingerprint = self.cert_fingerprint(); + let id_keys = self.id_keys.clone(); + let first_listener = self .listeners .iter_mut() .next() .ok_or(TransportError::Other(Error::NoListeners))?; let udp_mux = first_listener.udp_mux.clone(); - let se = build_setting_engine(udp_mux, &sock_addr, &our_fingerprint); - let api = APIBuilder::new().with_setting_engine(se).build(); // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let peer_connection = api.new_peer_connection(config).await?; - - let offer = peer_connection.create_offer(None).await?; - debug!("OFFER: {:?}", offer.sdp); - peer_connection.set_local_description(offer).await?; - - // Set the remote description to the predefined SDP. - let remote_fingerprint = match fingerprint_from_addr(&addr) { - Some(f) => fingerprint_to_string(&f), - None => return Err(Error::InvalidMultiaddr(addr.clone())), - }; - let server_session_description = render_description( - sdp::SERVER_SESSION_DESCRIPTION, + let remote_fingerprint = fingerprint_from_addr(&addr) + .map(|f| fingerprint_to_string(f.iter())) + .ok_or(Error::InvalidMultiaddr(addr.clone()))?; + + let conn = WebRTCConnection::connect( sock_addr, + config, + udp_mux, + &our_fingerprint, &remote_fingerprint, - &remote_fingerprint.to_owned().replace(':', ""), - ); - debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - peer_connection.set_remote_description(sdp).await?; + ) + .await?; // Open a data channel to do Noise on top and verify the remote. - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - id: Some(1), // id MUST match one used during upgrade - ..RTCDataChannelInit::default() - }), - ) - .await?; - - let (tx, mut rx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; - let detached = select! { - res = rx => match res { - Ok(detached) => detached, - Err(e) => return Err(Error::InternalError(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - }; + let data_channel = conn.create_initial_upgrade_data_channel(None).await?; trace!("noise handshake with {}", remote); let dh_keys = Keypair::::new() @@ -251,7 +205,7 @@ impl Transport for WebRTCTransport { let noise = NoiseConfig::xx(dh_keys); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(detached.clone()), info) + .upgrade_outbound(PollDataChannel::new(data_channel.clone()), info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), @@ -283,12 +237,12 @@ impl Transport for WebRTCTransport { } // Close the initial data channel after noise handshake is done. - detached + data_channel .close() .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(peer_connection).await; + let mut c = Connection::new(conn.into_inner()).await; // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/sctp/issues/28 is fixed. c.set_data_channels_read_buf_capacity(8192 * 10); @@ -516,37 +470,33 @@ pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { .with(Protocol::XWebRTC(hex_to_cow(&f))) } -/// Renders a [`TinyTemplate`] description using the provided arguments. -pub(crate) fn render_description( - description: &str, - addr: SocketAddr, - fingerprint: &str, - ufrag: &str, -) -> String { - let mut tt = TinyTemplate::new(); - tt.add_template("description", description).unwrap(); - - let context = sdp::DescriptionContext { - ip_version: { - if addr.is_ipv4() { - sdp::IpVersion::IP4 - } else { - sdp::IpVersion::IP6 - } - }, - target_ip: addr.ip(), - target_port: addr.port(), - fingerprint: fingerprint.to_owned(), - // NOTE: ufrag is equal to pwd. - ufrag: ufrag.to_owned(), - pwd: ufrag.to_owned(), - }; - tt.render("description", &context).unwrap() +/// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not +/// contain one. +fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { + let iter = addr.iter(); + for proto in iter { + match proto { + // TODO: check hash is one of https://datatracker.ietf.org/doc/html/rfc8122#section-5 + Protocol::XWebRTC(f) => return Some(f), + _ => continue, + } + } + None +} + +/// Transforms a byte array fingerprint into a string. +pub(crate) fn fingerprint_to_string(f: T) -> String +where + T: IntoIterator, + ::Item: core::fmt::LowerHex, +{ + let values: Vec = f.into_iter().map(|x| format! {"{:02x}", x}).collect(); + values.join(":") } /// Tries to turn a WebRTC multiaddress into a [`SocketAddr`]. Returns None if the format of the /// multiaddr is wrong. -pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { +fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { let mut iter = addr.iter(); let proto1 = iter.next()?; let proto2 = iter.next()?; @@ -570,12 +520,6 @@ pub(crate) fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { } } -/// Transforms a byte array fingerprint into a string. -pub(crate) fn fingerprint_to_string(f: &Cow<'_, [u8; 32]>) -> String { - let values: Vec = f.iter().map(|x| format! {"{:02x}", x}).collect(); - values.join(":") -} - /// Returns a SHA-256 fingerprint of the first certificate. /// /// # Panics @@ -593,47 +537,6 @@ pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> Str fingerprints.first().unwrap().value.clone() } -/// Creates a new [`SettingEngine`] and configures it. -pub(crate) fn build_setting_engine( - udp_mux: Arc, - addr: &SocketAddr, - fingerprint: &str, -) -> SettingEngine { - let mut se = SettingEngine::default(); - // Set both ICE user and password to fingerprint. - // It will be checked by remote side when exchanging ICE messages. - let f = fingerprint.to_owned().replace(':', ""); - se.set_ice_credentials(f.clone(), f); - se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); - // Allow detaching data channels. - se.detach_data_channels(); - // Set the desired network type. - // - // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate - // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). - let network_type = if addr.is_ipv4() { - NetworkType::Udp4 - } else { - NetworkType::Udp6 - }; - se.set_network_types(vec![network_type]); - se -} - -/// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not -/// contain one. -fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { - let iter = addr.iter(); - for proto in iter { - match proto { - // TODO: check hash is one of https://datatracker.ietf.org/doc/html/rfc8122#section-5 - Protocol::XWebRTC(f) => return Some(f), - _ => continue, - } - } - None -} - // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index aa6cb53e9db..b3ba4af5095 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -19,32 +19,25 @@ // DEALINGS IN THE SOFTWARE. use futures::{ - channel::oneshot, future, io::{AsyncReadExt, AsyncWriteExt}, - select, FutureExt, TryFutureExt, + TryFutureExt, }; -use futures_timer::Delay; use libp2p_core::{ identity, PeerId, {InboundUpgrade, UpgradeInfo}, }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; -use log::{debug, trace}; -use multihash::Hasher; -use webrtc::api::APIBuilder; -use webrtc::data_channel::data_channel_init::RTCDataChannelInit; -use webrtc::dtls_transport::dtls_role::DTLSRole; +use log::trace; use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; -use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use webrtc_ice::udp_mux::UDPMux; -use std::{net::SocketAddr, sync::Arc, time::Duration}; +use std::{net::SocketAddr, sync::Arc}; use crate::{ connection::{Connection, PollDataChannel}, error::Error, - sdp, transport, + transport, + webrtc_connection::WebRTCConnection, }; pub async fn webrtc( @@ -58,60 +51,12 @@ pub async fn webrtc( let our_fingerprint = transport::fingerprint_of_first_certificate(&config); - let mut se = transport::build_setting_engine(udp_mux, &socket_addr, &our_fingerprint); - { - se.set_lite(true); - se.disable_certificate_fingerprint_verification(true); - // Act as a DTLS server (one which waits for a connection). - // - // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, - // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server)?; - } - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; - - let client_session_description = transport::render_description( - sdp::CLIENT_SESSION_DESCRIPTION, - socket_addr, - "UNKNOWN", // certificate verification is disabled, so any value is okay. - &ufrag, - ); - debug!("OFFER: {:?}", client_session_description); - let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); - peer_connection.set_remote_description(sdp).await?; - - let answer = peer_connection.create_answer(None).await?; - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - debug!("ANSWER: {:?}", answer.sdp); - peer_connection.set_local_description(answer).await?; + let conn = + WebRTCConnection::accept(socket_addr, config, udp_mux, &our_fingerprint, &ufrag).await?; // Open a data channel to do Noise on top and verify the remote. - let data_channel = peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: Some(true), // channel is already negotiated by the client - id: Some(1), // id MUST match one used during dial - ..RTCDataChannelInit::default() - }), - ) - .await?; - - let (tx, mut rx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; - let detached = select! { - res = rx => match res { - Ok(detached) => detached, - Err(e) => return Err(Error::InternalError(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - }; + // NOTE: channel is already negotiated by the client + let data_channel = conn.create_initial_upgrade_data_channel(Some(true)).await?; trace!("noise handshake with {} (ufrag: {})", socket_addr, ufrag); let dh_keys = Keypair::::new() @@ -120,7 +65,7 @@ pub async fn webrtc( let noise = NoiseConfig::xx(dh_keys); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut noise_io) = noise - .upgrade_inbound(PollDataChannel::new(detached.clone()), info) + .upgrade_inbound(PollDataChannel::new(data_channel.clone()), info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), @@ -144,21 +89,8 @@ pub async fn webrtc( let fingerprint_from_noise = String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; let remote_fingerprint = { - let remote_cert_bytes = peer_connection - .sctp() - .transport() - .get_remote_certificate() - .await; - if remote_cert_bytes.is_empty() { - return Err(Error::InternalError( - "No remote certificate found".to_owned(), - )); - } - let mut h = multihash::Sha2_256::default(); - h.update(&remote_cert_bytes); - let hashed = h.finalize(); - let values: Vec = hashed.iter().map(|x| format! {"{:02x}", x}).collect(); - values.join(":") + let f = conn.get_remote_fingerprint().await; + crate::transport::fingerprint_to_string(f.iter()) }; if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { @@ -168,12 +100,12 @@ pub async fn webrtc( } // Close the initial data channel after noise handshake is done. - detached + data_channel .close() .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(peer_connection).await; + let mut c = Connection::new(conn.into_inner()).await; // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/sctp/issues/28 is fixed. c.set_data_channels_read_buf_capacity(8192 * 10); diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs new file mode 100644 index 00000000000..7d96fb1d545 --- /dev/null +++ b/transports/webrtc/src/webrtc_connection.rs @@ -0,0 +1,233 @@ +// Copyright 2022 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 futures::{channel::oneshot, prelude::*, select}; +use futures_timer::Delay; +use multihash::Hasher; +use tinytemplate::TinyTemplate; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::dtls_transport::dtls_role::DTLSRole; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc::peer_connection::RTCPeerConnection; +use webrtc_data::data_channel::DataChannel; +use webrtc_ice::network_type::NetworkType; +use webrtc_ice::udp_mux::UDPMux; +use webrtc_ice::udp_network::UDPNetwork; + +use std::{net::SocketAddr, sync::Arc, time::Duration}; + +use crate::error::Error; +use crate::sdp; + +pub(crate) struct WebRTCConnection { + peer_connection: RTCPeerConnection, +} + +impl WebRTCConnection { + /// Creates a new WebRTC peer connection to the remote. + /// + /// # Panics + /// + /// Panics if the given address is not valid WebRTC dialing address. + pub async fn connect( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: &str, + remote_fingerprint: &str, + ) -> Result { + let se = Self::setting_engine(udp_mux, our_fingerprint, addr.is_ipv4()); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api.new_peer_connection(config).await?; + + // 1. OFFER + let offer = peer_connection.create_offer(None).await?; + log::debug!("OFFER: {:?}", offer.sdp); + peer_connection.set_local_description(offer).await?; + + // 2. ANSWER + // Set the remote description to the predefined SDP. + let server_session_description = render_description( + sdp::SERVER_SESSION_DESCRIPTION, + addr, + &remote_fingerprint, + &remote_fingerprint.to_owned().replace(':', ""), + ); + log::debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // NOTE: this will start the gathering of ICE candidates + peer_connection.set_remote_description(sdp).await?; + + return Ok(Self { peer_connection }); + } + + pub async fn accept( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: &str, + ufrag: &str, + ) -> Result { + let mut se = Self::setting_engine(udp_mux, our_fingerprint, addr.is_ipv4()); + { + se.set_lite(true); + se.disable_certificate_fingerprint_verification(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server)?; + } + + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; + + let client_session_description = render_description( + sdp::CLIENT_SESSION_DESCRIPTION, + addr, + "UNKNOWN", // certificate verification is disabled, so any value is okay. + &ufrag, + ); + log::debug!("OFFER: {:?}", client_session_description); + let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + peer_connection.set_remote_description(sdp).await?; + + let answer = peer_connection.create_answer(None).await?; + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + log::debug!("ANSWER: {:?}", answer.sdp); + peer_connection.set_local_description(answer).await?; + + return Ok(Self { peer_connection }); + } + + pub async fn create_initial_upgrade_data_channel( + &self, + negotiated: Option, + ) -> Result, Error> { + // Open a data channel to do Noise on top and verify the remote. + let data_channel = self + .peer_connection + .create_data_channel( + "data", + Some(RTCDataChannelInit { + id: Some(1), // id MUST match one used during upgrade + negotiated, + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + select! { + res = rx => match res { + Ok(detached) => Ok(detached), + Err(e) => return Err(Error::InternalError(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + } + } + + pub fn into_inner(self) -> RTCPeerConnection { + self.peer_connection + } + + pub async fn get_remote_fingerprint(&self) -> [u8; 32] { + let cert_bytes = self + .peer_connection + .sctp() + .transport() + .get_remote_certificate() + .await; + let mut h = multihash::Sha2_256::default(); + h.update(&cert_bytes); + let mut bytes: [u8; 32] = [0; 32]; + bytes.copy_from_slice(h.finalize()); + bytes + } + + fn setting_engine( + udp_mux: Arc, + fingerprint: &str, + is_ipv4: bool, + ) -> SettingEngine { + let mut se = SettingEngine::default(); + + // Set both ICE user and password to fingerprint. + // It will be checked by remote side when exchanging ICE messages. + let f = fingerprint.to_owned().replace(':', ""); + se.set_ice_credentials(f.clone(), f); + + se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); + + // Allow detaching data channels. + se.detach_data_channels(); + + // Set the desired network type. + // + // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate + // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). + let network_type = if is_ipv4 { + NetworkType::Udp4 + } else { + NetworkType::Udp6 + }; + se.set_network_types(vec![network_type]); + + se + } +} + +/// Renders a [`TinyTemplate`] description using the provided arguments. +pub(crate) fn render_description( + description: &str, + addr: SocketAddr, + fingerprint: &str, + ufrag: &str, +) -> String { + let mut tt = TinyTemplate::new(); + tt.add_template("description", description).unwrap(); + + let context = sdp::DescriptionContext { + ip_version: { + if addr.is_ipv4() { + sdp::IpVersion::IP4 + } else { + sdp::IpVersion::IP6 + } + }, + target_ip: addr.ip(), + target_port: addr.port(), + fingerprint: fingerprint.to_owned(), + // NOTE: ufrag is equal to pwd. + ufrag: ufrag.to_owned(), + pwd: ufrag.to_owned(), + }; + tt.render("description", &context).unwrap() +} From 8306743e6b3286d156f0ed52c15913a9777bb87d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 25 Jul 2022 12:49:33 +0400 Subject: [PATCH 033/244] extract perform_noise_handshake fn --- transports/webrtc/src/transport.rs | 81 +++++++++++++++++++----------- transports/webrtc/src/upgrade.rs | 65 ++++++++++++++++-------- 2 files changed, 94 insertions(+), 52 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index eee1c4a5398..d620a979a30 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -39,6 +39,7 @@ use log::{debug, trace}; use tokio_crate::net::UdpSocket; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc_data::data_channel::DataChannel; use std::{ borrow::Cow, @@ -166,7 +167,7 @@ impl Transport for WebRTCTransport { } let remote = addr.clone(); // used for logging - trace!("dialing address: {:?}", remote); + trace!("dialing addr={}", remote); let config = self.config.clone(); let our_fingerprint = self.cert_fingerprint(); @@ -198,36 +199,16 @@ impl Transport for WebRTCTransport { // Open a data channel to do Noise on top and verify the remote. let data_channel = conn.create_initial_upgrade_data_channel(None).await?; - trace!("noise handshake with {}", remote); - let dh_keys = Keypair::::new() - .into_authentic(&id_keys) - .unwrap(); - let noise = NoiseConfig::xx(dh_keys); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(data_channel.clone()), info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) - .await?; - - // Exchange TLS certificate fingerprints to prevent MiM attacks. - trace!("exchanging TLS certificate fingerprints with {}", remote); - let n = noise_io.write(&our_fingerprint.into_bytes()).await?; - noise_io.flush().await?; - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = String::from_utf8(buf) - .map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint, - got: fingerprint_from_noise, - }); - } + trace!("noise handshake with addr={}", remote); + let peer_id = perform_noise_handshake( + id_keys, + data_channel.clone(), + our_fingerprint, + remote_fingerprint, + ) + .await?; - trace!("verifying peer's identity {}", remote); + trace!("verifying peer's identity addr={}", remote); let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { return Err(Error::InvalidPeerID { @@ -537,6 +518,46 @@ pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> Str fingerprints.first().unwrap().value.clone() } +async fn perform_noise_handshake( + id_keys: identity::Keypair, + data_channel: Arc, + our_fingerprint: String, + remote_fingerprint: String, +) -> Result { + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_outbound(PollDataChannel::new(data_channel), info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + debug!( + "exchanging TLS certificate fingerprints with peer_id={}", + peer_id + ); + let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint, + got: fingerprint_from_noise, + }); + } + + Ok(peer_id) +} + // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index b3ba4af5095..95f814732ae 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -27,8 +27,9 @@ use libp2p_core::{ identity, PeerId, {InboundUpgrade, UpgradeInfo}, }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; -use log::trace; +use log::{debug, trace}; use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc_data::data_channel::DataChannel; use webrtc_ice::udp_mux::UDPMux; use std::{net::SocketAddr, sync::Arc}; @@ -47,7 +48,7 @@ pub async fn webrtc( ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - trace!("upgrading {} (ufrag: {})", socket_addr, ufrag); + trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); let our_fingerprint = transport::fingerprint_of_first_certificate(&config); @@ -58,7 +59,42 @@ pub async fn webrtc( // NOTE: channel is already negotiated by the client let data_channel = conn.create_initial_upgrade_data_channel(Some(true)).await?; - trace!("noise handshake with {} (ufrag: {})", socket_addr, ufrag); + trace!( + "noise handshake with addr={} (ufrag={})", + socket_addr, + ufrag + ); + let remote_fingerprint = { + let f = conn.get_remote_fingerprint().await; + crate::transport::fingerprint_to_string(f.iter()) + }; + let peer_id = perform_noise_handshake( + id_keys, + data_channel.clone(), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(conn.into_inner()).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) +} + +async fn perform_noise_handshake( + id_keys: identity::Keypair, + data_channel: Arc, + our_fingerprint: String, + remote_fingerprint: String, +) -> Result { let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); @@ -73,10 +109,9 @@ pub async fn webrtc( .await?; // Exchange TLS certificate fingerprints to prevent MiM attacks. - trace!( - "exchanging TLS certificate fingerprints with {} (ufrag: {})", - socket_addr, - ufrag + debug!( + "exchanging TLS certificate fingerprints with peer_id={}", + peer_id ); // 1. Submit SHA-256 fingerprint @@ -88,10 +123,6 @@ pub async fn webrtc( noise_io.read_exact(buf.as_mut_slice()).await?; let fingerprint_from_noise = String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; - let remote_fingerprint = { - let f = conn.get_remote_fingerprint().await; - crate::transport::fingerprint_to_string(f.iter()) - }; if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { expected: remote_fingerprint, @@ -99,15 +130,5 @@ pub async fn webrtc( }); } - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - Ok((peer_id, c)) + Ok(peer_id) } From 136edc26d831e2df035a45f964d49e03caca23f2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 25 Jul 2022 16:19:02 +0400 Subject: [PATCH 034/244] fix addr in smoke test --- transports/webrtc/tests/smoke.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index fcc785e1a81..d3f4f3052ab 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -74,9 +74,13 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), }; - let addr = addr.with(Protocol::XWebRTC(hex_to_cow( - &a_fingerprint.replace(":", ""), - ))); + let addr = addr + .replace(2, |_| { + Some(Protocol::XWebRTC(hex_to_cow( + &a_fingerprint.replace(":", ""), + ))) + }) + .unwrap(); let mut data = vec![0; 4096]; rng.fill_bytes(&mut data); From 6a11aa032774747f14bf1002cdb1b133bf994f9d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 25 Jul 2022 19:45:47 +0400 Subject: [PATCH 035/244] generate random ufrag when connecting also, remove dialer_connects_to_listener_ipv4/6 tests in favor of the smoke test. --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/transport.rs | 83 +--------------------- transports/webrtc/src/upgrade.rs | 1 + transports/webrtc/src/webrtc_connection.rs | 31 ++++---- transports/webrtc/tests/smoke.rs | 39 ++++++---- 5 files changed, 48 insertions(+), 108 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 1b57eb16cb7..9f1e68db989 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -32,12 +32,12 @@ webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } multihash = { version = "0.16", default-features = false, features = ["sha2"] } +rand = "0.8" [dev-dependencies] anyhow = "1.0" env_logger = "0.9" libp2p-request-response = { version = "0.20.0", path = "../../protocols/request-response" } libp2p-swarm = { version = "0.38.0", path = "../../swarm" } -rand = "0.8" rand_core = "0.5" rcgen = "0.9" diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index d620a979a30..95b29a07f11 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -187,14 +187,8 @@ impl Transport for WebRTCTransport { .map(|f| fingerprint_to_string(f.iter())) .ok_or(Error::InvalidMultiaddr(addr.clone()))?; - let conn = WebRTCConnection::connect( - sock_addr, - config, - udp_mux, - &our_fingerprint, - &remote_fingerprint, - ) - .await?; + let conn = + WebRTCConnection::connect(sock_addr, config, udp_mux, &remote_fingerprint).await?; // Open a data channel to do Noise on top and verify the remote. let data_channel = conn.create_initial_upgrade_data_channel(None).await?; @@ -642,79 +636,6 @@ mod tests { ); } - fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { - let mut buf = [0; 32]; - hex::decode_to_slice(s, &mut buf).unwrap(); - Cow::Owned(buf) - } - - #[tokio::test] - async fn dialer_connects_to_listener_ipv4() { - let _ = env_logger::builder().is_test(true).try_init(); - let a = "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap(); - futures::executor::block_on(connect(a)); - } - - #[tokio::test] - async fn dialer_connects_to_listener_ipv6() { - let _ = env_logger::builder().is_test(true).try_init(); - let a = "/ip6/::1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap(); - futures::executor::block_on(connect(a)); - } - - async fn connect(listen_addr: Multiaddr) { - let id_keys = identity::Keypair::generate_ed25519(); - let t1_peer_id = PeerId::from_public_key(&id_keys.public()); - let mut transport = { - let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); - let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - WebRTCTransport::new(cert, id_keys).boxed() - }; - - transport.listen_on(listen_addr.clone()).expect("listener"); - - let addr = transport - .next() - .await - .expect("no error") - .into_new_address() - .expect("listen address"); - - assert_ne!(Some(Protocol::Udp(0)), addr.iter().nth(1)); - - let inbound = async move { - let (conn, _addr) = transport - .select_next_some() - .map(|ev| ev.into_incoming()) - .await - .unwrap(); - conn.await - }; - - let (mut transport2, f) = { - let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); - let id_keys = identity::Keypair::generate_ed25519(); - let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - let t = WebRTCTransport::new(cert, id_keys); - // TODO: make code cleaner wrt ":" - let f = t.cert_fingerprint().replace(':', ""); - (t.boxed(), f) - }; - - transport2.listen_on(listen_addr).expect("listener"); - - let outbound = transport2 - .dial( - addr.replace(2, |_| Some(Protocol::XWebRTC(hex_to_cow(&f)))) - .unwrap() - .with(Protocol::P2p(t1_peer_id.into())), - ) - .unwrap(); - - let (a, b) = futures::join!(inbound, outbound); - a.and(b).unwrap(); - } - #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 95f814732ae..42db1768d27 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -86,6 +86,7 @@ pub async fn webrtc( // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/sctp/issues/28 is fixed. c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) } diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 7d96fb1d545..9cc8a7a4bcc 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -21,6 +21,8 @@ use futures::{channel::oneshot, prelude::*, select}; use futures_timer::Delay; use multihash::Hasher; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; use tinytemplate::TinyTemplate; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; @@ -53,10 +55,14 @@ impl WebRTCConnection { addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - our_fingerprint: &str, remote_fingerprint: &str, ) -> Result { - let se = Self::setting_engine(udp_mux, our_fingerprint, addr.is_ipv4()); + let ufrag: String = thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect(); + let se = Self::setting_engine(udp_mux, &ufrag, addr.is_ipv4()); let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; @@ -68,11 +74,12 @@ impl WebRTCConnection { // 2. ANSWER // Set the remote description to the predefined SDP. + let remote_ufrag = remote_fingerprint.to_owned().replace(":", ""); let server_session_description = render_description( sdp::SERVER_SESSION_DESCRIPTION, addr, &remote_fingerprint, - &remote_fingerprint.to_owned().replace(':', ""), + &remote_ufrag, ); log::debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); @@ -87,9 +94,12 @@ impl WebRTCConnection { config: RTCConfiguration, udp_mux: Arc, our_fingerprint: &str, - ufrag: &str, + remote_ufrag: &str, ) -> Result { - let mut se = Self::setting_engine(udp_mux, our_fingerprint, addr.is_ipv4()); + // Set both ICE user and password to our fingerprint because that's what the client is + // expecting (see [`Self::connect`] "2. ANSWER"). + let ufrag = our_fingerprint.to_owned().replace(':', ""); + let mut se = Self::setting_engine(udp_mux, &ufrag, addr.is_ipv4()); { se.set_lite(true); se.disable_certificate_fingerprint_verification(true); @@ -107,7 +117,7 @@ impl WebRTCConnection { sdp::CLIENT_SESSION_DESCRIPTION, addr, "UNKNOWN", // certificate verification is disabled, so any value is okay. - &ufrag, + &remote_ufrag, ); log::debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); @@ -132,7 +142,7 @@ impl WebRTCConnection { .create_data_channel( "data", Some(RTCDataChannelInit { - id: Some(1), // id MUST match one used during upgrade + id: Some(1), negotiated, ..RTCDataChannelInit::default() }), @@ -174,15 +184,12 @@ impl WebRTCConnection { fn setting_engine( udp_mux: Arc, - fingerprint: &str, + ufrag: &str, is_ipv4: bool, ) -> SettingEngine { let mut se = SettingEngine::default(); - // Set both ICE user and password to fingerprint. - // It will be checked by remote side when exchanging ICE messages. - let f = fingerprint.to_owned().replace(':', ""); - se.set_ice_credentials(f.clone(), f); + se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index d3f4f3052ab..36b92ca92ba 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -1,8 +1,10 @@ use anyhow::Result; use async_trait::async_trait; -use futures::future::FutureExt; -use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; -use futures::stream::StreamExt; +use futures::{ + future::FutureExt, + io::{AsyncRead, AsyncWrite, AsyncWriteExt}, + stream::StreamExt, +}; use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; use libp2p_request_response::{ ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, @@ -10,7 +12,6 @@ use libp2p_request_response::{ }; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p_webrtc::transport::WebRTCTransport; -use log::trace; use rand::RngCore; use rcgen::KeyPair; use tokio_crate as tokio; @@ -37,7 +38,6 @@ async fn create_swarm() -> Result<(Swarm>, String)> { let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); - trace!("{}", peer_id); let transport = Transport::map(transport, |(peer_id, conn), _| { (peer_id, StreamMuxerBox::new(conn)) }) @@ -69,11 +69,6 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), }; - let _ = match b.next().await { - Some(SwarmEvent::NewListenAddr { address, .. }) => address, - e => panic!("{:?}", e), - }; - let addr = addr .replace(2, |_| { Some(Protocol::XWebRTC(hex_to_cow( @@ -82,6 +77,11 @@ async fn smoke() -> Result<()> { }) .unwrap(); + let _ = match b.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + let mut data = vec![0; 4096]; rng.fill_bytes(&mut data); @@ -276,15 +276,26 @@ async fn dial_failure() -> Result<()> { let (mut a, a_fingerprint) = create_swarm().await?; let (mut b, _b_fingerprint) = create_swarm().await?; - Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0".parse()?)?; + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; + Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; let addr = match a.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; - let addr = addr.with(Protocol::XWebRTC(hex_to_cow( - &a_fingerprint.replace(":", ""), - ))); + + let addr = addr + .replace(2, |_| { + Some(Protocol::XWebRTC(hex_to_cow( + &a_fingerprint.replace(":", ""), + ))) + }) + .unwrap(); + + let _ = match b.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; let a_peer_id = &Swarm::local_peer_id(&a).clone(); drop(a); // stop a swarm so b can never reach it From d0c95a461db2e64aca5f247b5de854cf555a7fdf Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Jul 2022 11:45:20 +0400 Subject: [PATCH 036/244] updates after @mxinden review --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/sdp.rs | 1 - transports/webrtc/src/transport.rs | 14 +++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 9f1e68db989..25c92f5e8f5 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Parity Technologies "] description = "WebRTC transport for libp2p" repository = "https://github.com/libp2p/rust-libp2p" -license = "" +license = "MIT" edition = "2021" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 2384b85d958..f0626a75b18 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -79,7 +79,6 @@ use std::net::IpAddr; // // Fingerprint of the certificate that the remote will use during the TLS // handshake. (RFC8122) -// TODO: do we verify fingerprint here? // // a=setup:actpass // diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 95b29a07f11..c50bb9b67a8 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -175,7 +175,7 @@ impl Transport for WebRTCTransport { let first_listener = self .listeners - .iter_mut() + .iter() .next() .ok_or(TransportError::Other(Error::NoListeners))?; let udp_mux = first_listener.udp_mux.clone(); @@ -318,7 +318,7 @@ impl WebRTCListenStream { } /// Poll for a next If Event. - fn poll_if_addr(&mut self, cx: &mut Context<'_>) -> Option<::Item> { + fn poll_if_addr(&mut self, cx: &mut Context<'_>) -> Poll<::Item> { match self.in_addr.poll_next_unpin(cx) { Poll::Ready(mut item) => { if let Some(item) = item.take() { @@ -332,7 +332,7 @@ impl WebRTCListenStream { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr); debug!("New listen address: {}", ma); - Some(TransportEvent::NewAddress { + Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, listen_addr: ma, }) @@ -348,7 +348,7 @@ impl WebRTCListenStream { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr); debug!("Expired listen address: {}", ma); - Some(TransportEvent::AddressExpired { + Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, listen_addr: ma, }) @@ -361,7 +361,7 @@ impl WebRTCListenStream { "Failure polling interfaces: {:?}.", err }; - Some(TransportEvent::ListenerError { + Poll::Ready(TransportEvent::ListenerError { listener_id: self.listener_id, error: err.into(), }) @@ -371,7 +371,7 @@ impl WebRTCListenStream { self.poll_if_addr(cx) } } - Poll::Pending => None, + Poll::Pending => Poll::Pending, } } } @@ -386,7 +386,7 @@ impl Stream for WebRTCListenStream { // `Poll::Ready(None)` to terminate the stream. return Poll::Ready(closed.take()); } - if let Some(event) = self.poll_if_addr(cx) { + if let Poll::Ready(event) = self.poll_if_addr(cx) { return Poll::Ready(Some(event)); } From ef5e30652fad6a5a2ef648f8727b98c1a573f5e5 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Jul 2022 11:46:30 +0400 Subject: [PATCH 037/244] return None in WebRTCListenStream when udp_mux is closed to indicate that no more events will be coming from the stream --- transports/webrtc/src/transport.rs | 2 +- transports/webrtc/src/udp_mux.rs | 17 ++++++++--------- transports/webrtc/src/webrtc_connection.rs | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index c50bb9b67a8..cff72a923b2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -419,7 +419,7 @@ impl Stream for WebRTCListenStream { } UDPMuxEvent::Error(e) => { self.close(Err(Error::UDPMuxError(e))); - return self.poll_next(cx); + return Poll::Ready(None); } _ => return self.poll_next(cx), } diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 32001bfef61..2ab34e8f178 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -43,6 +43,7 @@ use std::{ const RECEIVE_MTU: usize = 8192; /// A previously unseen address of a remote who've sent us an ICE binding request. +#[derive(Debug)] pub struct NewAddr { pub addr: SocketAddr, pub ufrag: String, @@ -66,6 +67,7 @@ impl UDPMuxParams { } /// An event emitted by [`UDPMuxNewAddr`] when it's polled. +#[derive(Debug)] pub enum UDPMuxEvent { /// Connection error. UDP mux should be stopped. Error(Error), @@ -93,9 +95,6 @@ pub struct UDPMuxNewAddr { /// `true` when UDP mux is closed. is_closed: AtomicBool, - - /// Buffer used when reading from the underlying UDP socket. - read_buffer: [u8; RECEIVE_MTU], } impl UDPMuxNewAddr { @@ -107,7 +106,6 @@ impl UDPMuxNewAddr { address_map: RwLock::default(), new_addrs: RwLock::default(), is_closed: AtomicBool::new(false), - read_buffer: [0u8; RECEIVE_MTU], }) } @@ -133,7 +131,7 @@ impl UDPMuxNewAddr { conns.get(&ufrag).map(Clone::clone) } Err(e) => { - log::debug!("{} (addr: {})", e, addr); + log::debug!("{} (addr={})", e, addr); None } } @@ -147,7 +145,8 @@ impl UDPMuxNewAddr { /// Reads from the underlying UDP socket and either reports a new address or proxies data to the /// muxed connection. pub async fn read_from_conn(&self) -> UDPMuxEvent { - let mut buffer = self.read_buffer; + // TODO: avoid reallocating the buffer + let mut buffer = [0u8; RECEIVE_MTU]; let conn = &self.params.conn; let res = conn.recv_from(&mut buffer).await; @@ -176,7 +175,7 @@ impl UDPMuxNewAddr { match ufrag_from_stun_message(&buffer, false) { Ok(ufrag) => { log::trace!( - "Notifying about new address {} from {}", + "Notifying about new address addr={} from ufrag={}", &addr, ufrag ); @@ -186,7 +185,7 @@ impl UDPMuxNewAddr { } Err(e) => { log::debug!( - "Unknown address {} (non STUN packet: {})", + "Unknown address addr={} (non STUN packet: {})", &addr, e ); @@ -197,7 +196,7 @@ impl UDPMuxNewAddr { Some(conn) => { tokio::spawn(async move { if let Err(err) = conn.write_packet(&buffer[..len], addr).await { - log::error!("Failed to write packet: {} (addr: {})", err, addr); + log::error!("Failed to write packet: {} (addr={})", err, addr); } }); } diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 9cc8a7a4bcc..57a17be4adf 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -57,6 +57,7 @@ impl WebRTCConnection { udp_mux: Arc, remote_fingerprint: &str, ) -> Result { + // TODO: at least 128 bit of entropy let ufrag: String = thread_rng() .sample_iter(&Alphanumeric) .take(64) @@ -116,7 +117,7 @@ impl WebRTCConnection { let client_session_description = render_description( sdp::CLIENT_SESSION_DESCRIPTION, addr, - "UNKNOWN", // certificate verification is disabled, so any value is okay. + "NONE", // certificate verification is disabled, so any value is okay. &remote_ufrag, ); log::debug!("OFFER: {:?}", client_session_description); From 04542f8207b95109af70ccf6f0c517e0fb4bcf5a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Jul 2022 11:49:30 +0400 Subject: [PATCH 038/244] revert ef5e3065 the logic was correct there --- transports/webrtc/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index cff72a923b2..c50bb9b67a8 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -419,7 +419,7 @@ impl Stream for WebRTCListenStream { } UDPMuxEvent::Error(e) => { self.close(Err(Error::UDPMuxError(e))); - return Poll::Ready(None); + return self.poll_next(cx); } _ => return self.poll_next(cx), } From 273bd2cc9ba0f0ecf3534e14b6143a717ff5ec99 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Jul 2022 12:27:38 +0400 Subject: [PATCH 039/244] fix doc links --- transports/webrtc/src/lib.rs | 3 ++- transports/webrtc/src/transport.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 424cc2cbd8e..0f29df2be42 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -18,7 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the [`Transport`] trait for WebRTC protocol without a signaling server. +//! Implementation of the [`libp2p_core::Transport`] trait for WebRTC protocol without a signaling +//! server. //! //! # Overview //! diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index c50bb9b67a8..662e4574fd8 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -271,7 +271,8 @@ pub struct WebRTCListenStream { /// `Keypair` identifying this peer id_keys: identity::Keypair, - /// Set to `Some` if this [`Listener`] should close. + /// Set to `Some` if this listener should close. + /// /// Optionally contains a [`TransportEvent::ListenerClosed`] that should be /// reported before the listener's stream is terminated. report_closed: Option::Item>>, From 5abaec4f93399427c70b454482122bcb2a18603d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 27 Jul 2022 09:59:27 +0400 Subject: [PATCH 040/244] introduce WebRTCConfiguration wrapper around RTCConfiguration --- transports/webrtc/src/transport.rs | 148 +++++++++++++++++------------ transports/webrtc/src/upgrade.rs | 19 ++-- 2 files changed, 98 insertions(+), 69 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 662e4574fd8..aa8cd153b8c 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -23,6 +23,7 @@ use futures::{ future::BoxFuture, io::{AsyncReadExt, AsyncWriteExt}, prelude::*, + ready, stream::SelectAll, stream::Stream, TryFutureExt, @@ -61,8 +62,8 @@ use crate::{ /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct WebRTCTransport { - /// A `RTCConfiguration` which holds this peer's certificate(s). - config: RTCConfiguration, + /// The config which holds this peer's certificate(s). + config: WebRTCConfiguration, /// `Keypair` identifying this peer id_keys: identity::Keypair, /// All the active listeners. @@ -73,10 +74,7 @@ impl WebRTCTransport { /// Creates a new WebRTC transport. pub fn new(certificate: RTCCertificate, id_keys: identity::Keypair) -> Self { Self { - config: RTCConfiguration { - certificates: vec![certificate], - ..RTCConfiguration::default() - }, + config: WebRTCConfiguration::new(certificate), id_keys, listeners: SelectAll::new(), } @@ -85,7 +83,7 @@ impl WebRTCTransport { /// Returns the SHA-256 fingerprint of the certificate in lowercase hex string as expressed /// utilizing the syntax of 'fingerprint' in . pub fn cert_fingerprint(&self) -> String { - fingerprint_of_first_certificate(&self.config) + self.config.fingerprint_of_first_certificate() } fn do_listen( @@ -187,8 +185,13 @@ impl Transport for WebRTCTransport { .map(|f| fingerprint_to_string(f.iter())) .ok_or(Error::InvalidMultiaddr(addr.clone()))?; - let conn = - WebRTCConnection::connect(sock_addr, config, udp_mux, &remote_fingerprint).await?; + let conn = WebRTCConnection::connect( + sock_addr, + config.into_inner(), + udp_mux, + &remote_fingerprint, + ) + .await?; // Open a data channel to do Noise on top and verify the remote. let data_channel = conn.create_initial_upgrade_data_channel(None).await?; @@ -259,8 +262,8 @@ pub struct WebRTCListenStream { /// time as interfaces become available or unavailable. in_addr: InAddr, - /// A `RTCConfiguration` which holds this peer's certificate(s). - config: RTCConfiguration, + /// The config which holds this peer's certificate(s). + config: WebRTCConfiguration, /// The UDP muxer that manages all ICE connections. udp_mux: Arc, @@ -283,7 +286,7 @@ impl WebRTCListenStream { fn new( listener_id: ListenerId, listen_addr: SocketAddr, - config: RTCConfiguration, + config: WebRTCConfiguration, udp_mux: Arc, id_keys: identity::Keypair, ) -> Self { @@ -391,44 +394,82 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(event)); } - let udp_mux = self.udp_mux.clone(); - let udp_mux_read_fut = self - .udp_mux_read_fut - .get_or_insert(Box::pin(async move { udp_mux.read_from_conn().await })); - match udp_mux_read_fut.as_mut().poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(event) => { - self.udp_mux_read_fut = None; - - match event { - UDPMuxEvent::NewAddr(new_addr) => { - let local_addr = socketaddr_to_multiaddr(&self.listen_addr); - let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); - let event = TransportEvent::Incoming { - upgrade: Box::pin(upgrade::webrtc( - self.udp_mux.clone(), - self.config.clone(), - new_addr.addr, - new_addr.ufrag, - self.id_keys.clone(), - )) as BoxFuture<'static, _>, - local_addr, - send_back_addr, - listener_id: self.listener_id, - }; - Poll::Ready(Some(event)) - } - UDPMuxEvent::Error(e) => { - self.close(Err(Error::UDPMuxError(e))); - return self.poll_next(cx); - } - _ => return self.poll_next(cx), - } + // Poll UDP muxer for new addresses or incoming data for streams. + let udp_mux_read_fut = { + let udp_mux = self.udp_mux.clone(); + self.udp_mux_read_fut + .get_or_insert(Box::pin(async move { udp_mux.read_from_conn().await })) + }; + let event = ready!(udp_mux_read_fut.as_mut().poll(cx)); + self.udp_mux_read_fut = None; + match event { + UDPMuxEvent::NewAddr(new_addr) => { + let local_addr = socketaddr_to_multiaddr(&self.listen_addr); + let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); + let event = TransportEvent::Incoming { + upgrade: Box::pin(upgrade::webrtc( + self.udp_mux.clone(), + self.config.clone(), + new_addr.addr, + new_addr.ufrag, + self.id_keys.clone(), + )) as BoxFuture<'static, _>, + local_addr, + send_back_addr, + listener_id: self.listener_id, + }; + Poll::Ready(Some(event)) } + UDPMuxEvent::Error(e) => { + self.close(Err(Error::UDPMuxError(e))); + return self.poll_next(cx); + } + _ => return self.poll_next(cx), } } } +/// A wrapper around [`RTCConfiguration`]. +#[derive(Clone)] +pub(crate) struct WebRTCConfiguration { + inner: RTCConfiguration, +} + +impl WebRTCConfiguration { + /// Creates a new config. + pub fn new(certificate: RTCCertificate) -> Self { + Self { + inner: RTCConfiguration { + certificates: vec![certificate], + ..RTCConfiguration::default() + }, + } + } + + /// Returns a SHA-256 fingerprint of the first certificate. + /// + /// # Panics + /// + /// Panics if the config does not contain any certificates. + pub fn fingerprint_of_first_certificate(&self) -> String { + // safe to unwrap here because we require a certificate during construction. + let fingerprints = self + .inner + .certificates + .first() + .expect("at least one certificate") + .get_fingerprints() + .expect("get_fingerprints to succeed"); + debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); + fingerprints.first().unwrap().value.clone() + } + + /// Consumes the `WebRTCConfiguration`, returning its inner configuration. + pub fn into_inner(self) -> RTCConfiguration { + self.inner + } +} + // TODO: remove fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { let mut buf = [0; 32]; @@ -496,23 +537,6 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { } } -/// Returns a SHA-256 fingerprint of the first certificate. -/// -/// # Panics -/// -/// Panics if the config does not contain any certificates. -pub(crate) fn fingerprint_of_first_certificate(config: &RTCConfiguration) -> String { - // safe to unwrap here because we require a certificate during construction. - let fingerprints = config - .certificates - .first() - .expect("at least one certificate") - .get_fingerprints() - .expect("get_fingerprints to succeed"); - debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); - fingerprints.first().unwrap().value.clone() -} - async fn perform_noise_handshake( id_keys: identity::Keypair, data_channel: Arc, diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 42db1768d27..1a67c68333f 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -28,7 +28,6 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; -use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc_data::data_channel::DataChannel; use webrtc_ice::udp_mux::UDPMux; @@ -37,23 +36,29 @@ use std::{net::SocketAddr, sync::Arc}; use crate::{ connection::{Connection, PollDataChannel}, error::Error, - transport, + transport::WebRTCConfiguration, webrtc_connection::WebRTCConnection, }; -pub async fn webrtc( +pub(crate) async fn webrtc( udp_mux: Arc, - config: RTCConfiguration, + config: WebRTCConfiguration, socket_addr: SocketAddr, ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - let our_fingerprint = transport::fingerprint_of_first_certificate(&config); + let our_fingerprint = config.fingerprint_of_first_certificate(); - let conn = - WebRTCConnection::accept(socket_addr, config, udp_mux, &our_fingerprint, &ufrag).await?; + let conn = WebRTCConnection::accept( + socket_addr, + config.into_inner(), + udp_mux, + &our_fingerprint, + &ufrag, + ) + .await?; // Open a data channel to do Noise on top and verify the remote. // NOTE: channel is already negotiated by the client From a4e7be4ae6e6a5c571dd334ca6f0bcb4a6d42989 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 27 Jul 2022 10:11:25 +0400 Subject: [PATCH 041/244] perform_noise_handshake over abstract AsyncRead/Write https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r930375287 --- transports/webrtc/src/transport.rs | 16 +++++++++------- transports/webrtc/src/upgrade.rs | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index aa8cd153b8c..d4e8b6b63ef 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -21,7 +21,7 @@ use futures::{ future, future::BoxFuture, - io::{AsyncReadExt, AsyncWriteExt}, + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, prelude::*, ready, stream::SelectAll, @@ -40,7 +40,6 @@ use log::{debug, trace}; use tokio_crate::net::UdpSocket; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc_data::data_channel::DataChannel; use std::{ borrow::Cow, @@ -199,7 +198,7 @@ impl Transport for WebRTCTransport { trace!("noise handshake with addr={}", remote); let peer_id = perform_noise_handshake( id_keys, - data_channel.clone(), + PollDataChannel::new(data_channel.clone()), our_fingerprint, remote_fingerprint, ) @@ -537,19 +536,22 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { } } -async fn perform_noise_handshake( +async fn perform_noise_handshake( id_keys: identity::Keypair, - data_channel: Arc, + poll_data_channel: T, our_fingerprint: String, remote_fingerprint: String, -) -> Result { +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); let noise = NoiseConfig::xx(dh_keys); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut noise_io) = noise - .upgrade_outbound(PollDataChannel::new(data_channel), info) + .upgrade_outbound(poll_data_channel, info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 1a67c68333f..20ca6b231b7 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -20,7 +20,7 @@ use futures::{ future, - io::{AsyncReadExt, AsyncWriteExt}, + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, TryFutureExt, }; use libp2p_core::{ @@ -28,7 +28,6 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; -use webrtc_data::data_channel::DataChannel; use webrtc_ice::udp_mux::UDPMux; use std::{net::SocketAddr, sync::Arc}; @@ -75,7 +74,7 @@ pub(crate) async fn webrtc( }; let peer_id = perform_noise_handshake( id_keys, - data_channel.clone(), + PollDataChannel::new(data_channel.clone()), our_fingerprint, remote_fingerprint, ) @@ -95,19 +94,22 @@ pub(crate) async fn webrtc( Ok((peer_id, c)) } -async fn perform_noise_handshake( +async fn perform_noise_handshake( id_keys: identity::Keypair, - data_channel: Arc, + poll_data_channel: T, our_fingerprint: String, remote_fingerprint: String, -) -> Result { +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); let noise = NoiseConfig::xx(dh_keys); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut noise_io) = noise - .upgrade_inbound(PollDataChannel::new(data_channel.clone()), info) + .upgrade_inbound(poll_data_channel, info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), From 29ff7c9f420439bb18b49c63f879a5a2977b7e77 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 28 Jul 2022 16:06:32 +0400 Subject: [PATCH 042/244] poll API fro UDPMuxNewAddr --- transports/webrtc/src/connection.rs | 2 +- transports/webrtc/src/error.rs | 2 +- transports/webrtc/src/transport.rs | 17 +---- transports/webrtc/src/udp_mux.rs | 102 +++++++++++++--------------- 4 files changed, 51 insertions(+), 72 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 8219f0d6b1a..6701619232d 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -28,7 +28,7 @@ use futures::{ lock::Mutex as FutMutex, {future::BoxFuture, prelude::*, ready}, }; -use futures_lite::stream::StreamExt; +use futures_lite::StreamExt; use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; use webrtc::data_channel::RTCDataChannel; diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 00365ce2db6..7dd29c1ab9d 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -46,7 +46,7 @@ pub enum Error { NoListeners, #[error("UDP mux error: {0}")] - UDPMuxError(webrtc_util::Error), + UDPMuxError(std::io::Error), #[error("internal error: {0} (see debug logs)")] InternalError(String), diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index d4e8b6b63ef..030a3137756 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -54,7 +54,7 @@ use crate::{ connection::PollDataChannel, error::Error, in_addr::InAddr, - udp_mux::{UDPMuxEvent, UDPMuxNewAddr, UDPMuxParams}, + udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, upgrade, webrtc_connection::WebRTCConnection, }; @@ -111,7 +111,7 @@ impl WebRTCTransport { .map_err(TransportError::Other)?; debug!("listening on {}", listen_addr); - let udp_mux = UDPMuxNewAddr::new(UDPMuxParams::new(socket)); + let udp_mux = UDPMuxNewAddr::new(socket); Ok(WebRTCListenStream::new( listener_id, @@ -267,9 +267,6 @@ pub struct WebRTCListenStream { /// The UDP muxer that manages all ICE connections. udp_mux: Arc, - /// Future that drives reading from the UDP socket. - udp_mux_read_fut: Option>, - /// `Keypair` identifying this peer id_keys: identity::Keypair, @@ -297,7 +294,6 @@ impl WebRTCListenStream { in_addr, config, udp_mux, - udp_mux_read_fut: None, id_keys, report_closed: None, } @@ -394,14 +390,7 @@ impl Stream for WebRTCListenStream { } // Poll UDP muxer for new addresses or incoming data for streams. - let udp_mux_read_fut = { - let udp_mux = self.udp_mux.clone(); - self.udp_mux_read_fut - .get_or_insert(Box::pin(async move { udp_mux.read_from_conn().await })) - }; - let event = ready!(udp_mux_read_fut.as_mut().poll(cx)); - self.udp_mux_read_fut = None; - match event { + match ready!(self.udp_mux.as_ref().poll(cx)) { UDPMuxEvent::NewAddr(new_addr) => { let local_addr = socketaddr_to_multiaddr(&self.listen_addr); let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 2ab34e8f178..467992aeff9 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -21,12 +21,13 @@ // SOFTWARE. use async_trait::async_trait; +use futures::ready; use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, }; +use tokio::{io::ReadBuf, net::UdpSocket}; use tokio_crate as tokio; -use tokio_crate::sync::Mutex; use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc_util::{sync::RwLock, Conn, Error}; @@ -36,8 +37,9 @@ use std::{ net::SocketAddr, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Weak, + Arc, Mutex, Weak, }, + task::{Context, Poll}, }; const RECEIVE_MTU: usize = 8192; @@ -49,28 +51,11 @@ pub struct NewAddr { pub ufrag: String, } -/// Parameters for [`UDPMuxNewAddr`]. -pub struct UDPMuxParams { - conn: Box, -} - -impl UDPMuxParams { - /// Creates new params. - pub fn new(conn: C) -> Self - where - C: Conn + Send + Sync + 'static, - { - Self { - conn: Box::new(conn), - } - } -} - /// An event emitted by [`UDPMuxNewAddr`] when it's polled. #[derive(Debug)] pub enum UDPMuxEvent { /// Connection error. UDP mux should be stopped. - Error(Error), + Error(std::io::Error), /// Got a [`NewAddr`] from the socket. NewAddr(NewAddr), /// Non-important event. Can be ignored. @@ -80,9 +65,7 @@ pub enum UDPMuxEvent { /// A modified version of [`webrtc_ice::udp_mux::UDPMuxDefault`], which reports previously unseen /// addresses instead of ignoring them. pub struct UDPMuxNewAddr { - /// The params this instance is configured with. - /// Contains the underlying UDP socket in use - params: UDPMuxParams, + udp_sock: UdpSocket, /// Maps from ufrag to the underlying connection. conns: Mutex>, @@ -99,9 +82,9 @@ pub struct UDPMuxNewAddr { impl UDPMuxNewAddr { /// Creates a new UDP muxer. - pub fn new(params: UDPMuxParams) -> Arc { + pub fn new(udp_sock: UdpSocket) -> Arc { Arc::new(Self { - params, + udp_sock, conns: Mutex::default(), address_map: RwLock::default(), new_addrs: RwLock::default(), @@ -110,8 +93,8 @@ impl UDPMuxNewAddr { } /// Create a muxed connection for a given ufrag. - async fn create_muxed_conn(self: &Arc, ufrag: &str) -> Result { - let local_addr = self.params.conn.local_addr().await?; + fn create_muxed_conn(self: &Arc, ufrag: &str) -> Result { + let local_addr = self.udp_sock.local_addr()?; let params = UDPMuxConnParams { local_addr, @@ -124,10 +107,10 @@ impl UDPMuxNewAddr { /// Returns a muxed connection if the `ufrag` from the given STUN message matches an existing /// connection. - async fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { + fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { match ufrag_from_stun_message(buffer, true) { Ok(ufrag) => { - let conns = self.conns.lock().await; + let conns = self.conns.lock().unwrap(); conns.get(&ufrag).map(Clone::clone) } Err(e) => { @@ -144,26 +127,24 @@ impl UDPMuxNewAddr { /// Reads from the underlying UDP socket and either reports a new address or proxies data to the /// muxed connection. - pub async fn read_from_conn(&self) -> UDPMuxEvent { - // TODO: avoid reallocating the buffer - let mut buffer = [0u8; RECEIVE_MTU]; - let conn = &self.params.conn; - - let res = conn.recv_from(&mut buffer).await; - match res { - Ok((len, addr)) => { + pub fn poll(&self, cx: &mut Context) -> Poll { + // TODO: avoid allocating the buffer each time. + let mut recv_buf = [0u8; RECEIVE_MTU]; + let mut read = ReadBuf::new(&mut recv_buf); + + match ready!(self.udp_sock.poll_recv_from(cx, &mut read)) { + Ok(addr) => { // Find connection based on previously having seen this source address let conn = { let address_map = self.address_map.read(); - address_map.get(&addr).map(Clone::clone) }; let conn = match conn { // If we couldn't find the connection based on source address, see if // this is a STUN mesage and if so if we can find the connection based on ufrag. - None if is_stun_message(&buffer) => { - self.conn_from_stun_message(&buffer, &addr).await + None if is_stun_message(read.filled()) => { + self.conn_from_stun_message(&read.filled(), &addr) } s @ Some(_) => s, _ => None, @@ -172,7 +153,7 @@ impl UDPMuxNewAddr { match conn { None => { if !self.new_addrs.read().contains(&addr) { - match ufrag_from_stun_message(&buffer, false) { + match ufrag_from_stun_message(read.filled(), false) { Ok(ufrag) => { log::trace!( "Notifying about new address addr={} from ufrag={}", @@ -181,7 +162,10 @@ impl UDPMuxNewAddr { ); let mut new_addrs = self.new_addrs.write(); new_addrs.insert(addr); - return UDPMuxEvent::NewAddr(NewAddr { addr, ufrag }); + return Poll::Ready(UDPMuxEvent::NewAddr(NewAddr { + addr, + ufrag, + })); } Err(e) => { log::debug!( @@ -194,24 +178,31 @@ impl UDPMuxNewAddr { } } Some(conn) => { - tokio::spawn(async move { - if let Err(err) = conn.write_packet(&buffer[..len], addr).await { - log::error!("Failed to write packet: {} (addr={})", err, addr); - } - }); + let mut packet = Vec::with_capacity(read.filled().len()); + packet.copy_from_slice(read.filled()); + write_packet_to_conn_from_addr(conn, packet, addr); } } } - Err(Error::Io(err)) if err.0.kind() == ErrorKind::TimedOut => {} + Err(err) if err.kind() == ErrorKind::TimedOut => {} Err(err) => { log::error!("Could not read udp packet: {}", err); - return UDPMuxEvent::Error(err); + return Poll::Ready(UDPMuxEvent::Error(err)); } } - UDPMuxEvent::None + + Poll::Ready(UDPMuxEvent::None) } } +fn write_packet_to_conn_from_addr(conn: UDPMuxConn, packet: Vec, addr: SocketAddr) { + tokio::spawn(async move { + if let Err(err) = conn.write_packet(&packet, addr).await { + log::error!("Failed to write packet: {} (addr={})", err, addr); + } + }); +} + #[async_trait] impl UDPMux for UDPMuxNewAddr { async fn close(&self) -> Result<(), Error> { @@ -220,7 +211,7 @@ impl UDPMux for UDPMuxNewAddr { } let old_conns = { - let mut conns = self.conns.lock().await; + let mut conns = self.conns.lock().unwrap(); std::mem::take(&mut (*conns)) }; @@ -255,14 +246,14 @@ impl UDPMux for UDPMuxNewAddr { } { - let mut conns = self.conns.lock().await; + let mut conns = self.conns.lock().unwrap(); if let Some(conn) = conns.get(ufrag) { // UDPMuxConn uses `Arc` internally so it's cheap to clone, but because // we implement `Conn` we need to further wrap it in an `Arc` here. return Ok(Arc::new(conn.clone()) as Arc); } - let muxed_conn = self.create_muxed_conn(ufrag).await?; + let muxed_conn = self.create_muxed_conn(ufrag)?; let mut close_rx = muxed_conn.close_rx(); let cloned_self = Arc::clone(&self); let cloned_ufrag = ufrag.to_string(); @@ -284,7 +275,7 @@ impl UDPMux for UDPMuxNewAddr { // is keyed on `ufrag` their implementation is equivalent. let removed_conn = { - let mut conns = self.conns.lock().await; + let mut conns = self.conns.lock().unwrap(); conns.remove(ufrag) }; @@ -330,8 +321,7 @@ impl UDPMuxWriter for UDPMuxNewAddr { } async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result { - self.params - .conn + self.udp_sock .send_to(buf, *target) .await .map_err(Into::into) From da94baf52ea11e4640b5d61100db846cdd5b389e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 29 Jul 2022 13:01:00 +0400 Subject: [PATCH 043/244] add Fingerprint type --- transports/webrtc/src/sdp.rs | 7 ++- transports/webrtc/src/transport.rs | 31 ++++------ transports/webrtc/src/upgrade.rs | 20 +++--- transports/webrtc/src/webrtc_connection.rs | 72 +++++++++++++++++++--- 4 files changed, 90 insertions(+), 40 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index f0626a75b18..410a2dda107 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -105,7 +105,7 @@ a=mid:0 a=ice-options:ice2 a=ice-ufrag:{ufrag} a=ice-pwd:{pwd} -a=fingerprint:sha-256 {fingerprint} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} a=setup:actpass a=sctp-port:5000 a=max-message-size:100000 @@ -157,7 +157,7 @@ a=mid:0 a=ice-options:ice2 a=ice-ufrag:{ufrag} a=ice-pwd:{pwd} -a=fingerprint:sha-256 {fingerprint} +a=fingerprint:{fingerprint_algorithm} {fingerprint_value} a=setup:passive a=sctp-port:5000 @@ -179,7 +179,8 @@ pub struct DescriptionContext { pub ip_version: IpVersion, pub target_ip: IpAddr, pub target_port: u16, - pub fingerprint: String, + pub fingerprint_algorithm: String, + pub fingerprint_value: String, pub ufrag: String, pub pwd: String, } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 030a3137756..6decc293202 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -56,7 +56,7 @@ use crate::{ in_addr::InAddr, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, upgrade, - webrtc_connection::WebRTCConnection, + webrtc_connection::{Fingerprint, WebRTCConnection}, }; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -181,7 +181,7 @@ impl Transport for WebRTCTransport { // do the `set_remote_description` call within the [`Future`]. Ok(async move { let remote_fingerprint = fingerprint_from_addr(&addr) - .map(|f| fingerprint_to_string(f.iter())) + .map(|f| Fingerprint::from(f.iter())) .ok_or(Error::InvalidMultiaddr(addr.clone()))?; let conn = WebRTCConnection::connect( @@ -199,8 +199,8 @@ impl Transport for WebRTCTransport { let peer_id = perform_noise_handshake( id_keys, PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, + &our_fingerprint, + remote_fingerprint.as_ref(), ) .await?; @@ -449,7 +449,8 @@ impl WebRTCConfiguration { .get_fingerprints() .expect("get_fingerprints to succeed"); debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); - fingerprints.first().unwrap().value.clone() + // TODO: modify webrtc-rs to return value in upper-hex rather than lower-hex + fingerprints.first().unwrap().value.to_uppercase() } /// Consumes the `WebRTCConfiguration`, returning its inner configuration. @@ -489,16 +490,6 @@ fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { None } -/// Transforms a byte array fingerprint into a string. -pub(crate) fn fingerprint_to_string(f: T) -> String -where - T: IntoIterator, - ::Item: core::fmt::LowerHex, -{ - let values: Vec = f.into_iter().map(|x| format! {"{:02x}", x}).collect(); - values.join(":") -} - /// Tries to turn a WebRTC multiaddress into a [`SocketAddr`]. Returns None if the format of the /// multiaddr is wrong. fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { @@ -528,8 +519,8 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { async fn perform_noise_handshake( id_keys: identity::Keypair, poll_data_channel: T, - our_fingerprint: String, - remote_fingerprint: String, + our_fingerprint: &str, + remote_fingerprint: &str, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -552,7 +543,9 @@ where "exchanging TLS certificate fingerprints with peer_id={}", peer_id ); - let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + let n = noise_io + .write(&our_fingerprint.to_owned().into_bytes()) + .await?; noise_io.flush().await?; let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. noise_io.read_exact(buf.as_mut_slice()).await?; @@ -560,7 +553,7 @@ where String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { - expected: remote_fingerprint, + expected: remote_fingerprint.to_owned(), got: fingerprint_from_noise, }); } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 20ca6b231b7..ff04ed971bd 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -36,7 +36,7 @@ use crate::{ connection::{Connection, PollDataChannel}, error::Error, transport::WebRTCConfiguration, - webrtc_connection::WebRTCConnection, + webrtc_connection::{Fingerprint, WebRTCConnection}, }; pub(crate) async fn webrtc( @@ -48,7 +48,7 @@ pub(crate) async fn webrtc( ) -> Result<(PeerId, Connection), Error> { trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - let our_fingerprint = config.fingerprint_of_first_certificate(); + let our_fingerprint = Fingerprint::new_sha256(config.fingerprint_of_first_certificate()); let conn = WebRTCConnection::accept( socket_addr, @@ -70,13 +70,13 @@ pub(crate) async fn webrtc( ); let remote_fingerprint = { let f = conn.get_remote_fingerprint().await; - crate::transport::fingerprint_to_string(f.iter()) + Fingerprint::from(f.iter()) }; let peer_id = perform_noise_handshake( id_keys, PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, + our_fingerprint.as_ref(), + remote_fingerprint.as_ref(), ) .await?; @@ -97,8 +97,8 @@ pub(crate) async fn webrtc( async fn perform_noise_handshake( id_keys: identity::Keypair, poll_data_channel: T, - our_fingerprint: String, - remote_fingerprint: String, + our_fingerprint: &str, + remote_fingerprint: &str, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -123,7 +123,9 @@ where ); // 1. Submit SHA-256 fingerprint - let n = noise_io.write(&our_fingerprint.into_bytes()).await?; + let n = noise_io + .write(&our_fingerprint.to_owned().into_bytes()) + .await?; noise_io.flush().await?; // 2. Receive one too and compare it to the fingerprint of the remote DTLS certificate. @@ -133,7 +135,7 @@ where String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { - expected: remote_fingerprint, + expected: remote_fingerprint.to_owned(), got: fingerprint_from_noise, }); } diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 57a17be4adf..664b73e97da 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -27,6 +27,7 @@ use tinytemplate::TinyTemplate; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; use webrtc::dtls_transport::dtls_role::DTLSRole; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; @@ -55,7 +56,7 @@ impl WebRTCConnection { addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - remote_fingerprint: &str, + remote_fingerprint: &Fingerprint, ) -> Result { // TODO: at least 128 bit of entropy let ufrag: String = thread_rng() @@ -75,11 +76,11 @@ impl WebRTCConnection { // 2. ANSWER // Set the remote description to the predefined SDP. - let remote_ufrag = remote_fingerprint.to_owned().replace(":", ""); + let remote_ufrag = remote_fingerprint.to_ufrag(); let server_session_description = render_description( sdp::SERVER_SESSION_DESCRIPTION, addr, - &remote_fingerprint, + remote_fingerprint, &remote_ufrag, ); log::debug!("ANSWER: {:?}", server_session_description); @@ -94,12 +95,12 @@ impl WebRTCConnection { addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - our_fingerprint: &str, + our_fingerprint: &Fingerprint, remote_ufrag: &str, ) -> Result { // Set both ICE user and password to our fingerprint because that's what the client is // expecting (see [`Self::connect`] "2. ANSWER"). - let ufrag = our_fingerprint.to_owned().replace(':', ""); + let ufrag = our_fingerprint.to_ufrag(); let mut se = Self::setting_engine(udp_mux, &ufrag, addr.is_ipv4()); { se.set_lite(true); @@ -117,8 +118,8 @@ impl WebRTCConnection { let client_session_description = render_description( sdp::CLIENT_SESSION_DESCRIPTION, addr, - "NONE", // certificate verification is disabled, so any value is okay. - &remote_ufrag, + &Fingerprint::new_sha256("NONE".to_owned()), // certificate verification is disabled, so any value is okay. + remote_ufrag, ); log::debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); @@ -212,11 +213,63 @@ impl WebRTCConnection { } } +const SHA256: &str = "sha-256"; + +pub(crate) struct Fingerprint(RTCDtlsFingerprint); + +impl Fingerprint { + /// Creates new `Fingerprint` w/ "sha-256" hash function. + pub fn new_sha256(value: String) -> Self { + Self(RTCDtlsFingerprint { + algorithm: SHA256.to_owned(), + value, + }) + } + + /// Transforms this fingerprint into a ufrag. + pub fn to_ufrag(&self) -> String { + self.0.value.replace(':', "").to_lowercase() + } + + /// Returns the lower-hex value, each byte separated by ":". + /// E.g. "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC" + pub fn value(&self) -> String { + self.0.value.clone() + } + + /// Returns the algorithm used (e.g. "sha-256"). + /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 + pub fn algorithm(&self) -> String { + self.0.algorithm.clone() + } +} + +impl From for Fingerprint +where + T: IntoIterator, + ::Item: core::fmt::UpperHex, +{ + fn from(t: T) -> Self { + let values: Vec = t.into_iter().map(|x| format! {"{:02X}", x}).collect(); + Self(RTCDtlsFingerprint { + algorithm: SHA256.to_owned(), + value: values.join(":"), + }) + } +} + +impl AsRef for Fingerprint { + #[inline] + fn as_ref(&self) -> &str { + self.0.value.as_ref() + } +} + /// Renders a [`TinyTemplate`] description using the provided arguments. pub(crate) fn render_description( description: &str, addr: SocketAddr, - fingerprint: &str, + fingerprint: &Fingerprint, ufrag: &str, ) -> String { let mut tt = TinyTemplate::new(); @@ -232,7 +285,8 @@ pub(crate) fn render_description( }, target_ip: addr.ip(), target_port: addr.port(), - fingerprint: fingerprint.to_owned(), + fingerprint_algorithm: fingerprint.algorithm(), + fingerprint_value: fingerprint.value(), // NOTE: ufrag is equal to pwd. ufrag: ufrag.to_owned(), pwd: ufrag.to_owned(), From 385c51e2178db15b636522421eadf5f63fbb7d7d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 29 Jul 2022 16:52:23 +0400 Subject: [PATCH 044/244] updates after @thomaseizinger review --- transports/webrtc/src/fingerprint.rs | 67 +++++ transports/webrtc/src/lib.rs | 2 +- transports/webrtc/src/sdp.rs | 57 +++- transports/webrtc/src/transport.rs | 312 ++++++++++++++------- transports/webrtc/src/udp_mux.rs | 108 ++++--- transports/webrtc/src/upgrade.rs | 144 ---------- transports/webrtc/src/webrtc_connection.rs | 101 +------ transports/webrtc/tests/smoke.rs | 8 +- 8 files changed, 393 insertions(+), 406 deletions(-) create mode 100644 transports/webrtc/src/fingerprint.rs delete mode 100644 transports/webrtc/src/upgrade.rs diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs new file mode 100644 index 00000000000..4e0e5db255c --- /dev/null +++ b/transports/webrtc/src/fingerprint.rs @@ -0,0 +1,67 @@ +// Copyright 2022 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 webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; + +const SHA256: &str = "sha-256"; + +pub(crate) struct Fingerprint(RTCDtlsFingerprint); + +impl Fingerprint { + /// Creates new `Fingerprint` w/ "sha-256" hash function. + pub fn new_sha256(value: String) -> Self { + Self(RTCDtlsFingerprint { + algorithm: SHA256.to_owned(), + value, + }) + } + + /// Transforms this fingerprint into a ufrag. + pub fn to_ufrag(&self) -> String { + self.0.value.replace(':', "").to_lowercase() + } + + /// Returns the upper-hex value, each byte separated by ":". + /// E.g. "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC" + pub fn value(&self) -> String { + self.0.value.clone() + } + + /// Returns the algorithm used (e.g. "sha-256"). + /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 + pub fn algorithm(&self) -> String { + self.0.algorithm.clone() + } +} + +impl From<&[u8; 32]> for Fingerprint { + fn from(t: &[u8; 32]) -> Self { + let values: Vec = t.into_iter().map(|x| format! {"{:02X}", x}).collect(); + Self::new_sha256(values.join(":")) + } +} + +// TODO: derive when RTCDtlsFingerprint implements Eq. +impl PartialEq for Fingerprint { + fn eq(&self, other: &Self) -> bool { + self.0.algorithm == other.algorithm() && self.0.value == other.value() + } +} +impl Eq for Fingerprint {} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 0f29df2be42..a0a8c768d2f 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -84,8 +84,8 @@ pub mod connection; pub mod error; pub mod transport; +mod fingerprint; mod in_addr; mod sdp; mod udp_mux; -mod upgrade; mod webrtc_connection; diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 410a2dda107..5a41ddea961 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -19,9 +19,13 @@ // DEALINGS IN THE SOFTWARE. use serde::Serialize; +use std::net::SocketAddr; +use tinytemplate::TinyTemplate; use std::net::IpAddr; +use crate::fingerprint::Fingerprint; + // An SDP message that constitutes the offer. // // Main RFC: @@ -94,7 +98,7 @@ use std::net::IpAddr; // a=max-message-size: // // The maximum SCTP user message size (in bytes). (RFC8841) -pub const CLIENT_SESSION_DESCRIPTION: &'static str = "v=0 +const CLIENT_SESSION_DESCRIPTION: &'static str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- c=IN {ip_version} {target_ip} @@ -146,7 +150,7 @@ a=max-message-size:100000 // a=candidate: // // A transport address for a candidate that can be used for connectivity checks (RFC8839). -pub const SERVER_SESSION_DESCRIPTION: &'static str = "v=0 +const SERVER_SESSION_DESCRIPTION: &'static str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- t=0 0 @@ -167,7 +171,7 @@ a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host /// Indicates the IP version used in WebRTC: `IP4` or `IP6`. #[derive(Serialize)] -pub enum IpVersion { +enum IpVersion { IP4, IP6, } @@ -175,7 +179,7 @@ pub enum IpVersion { /// Context passed to the templating engine, which replaces the above placeholders (e.g. /// `{IP_VERSION}`) with real values. #[derive(Serialize)] -pub struct DescriptionContext { +struct DescriptionContext { pub ip_version: IpVersion, pub target_ip: IpAddr, pub target_port: u16, @@ -184,3 +188,48 @@ pub struct DescriptionContext { pub ufrag: String, pub pwd: String, } + +pub(crate) fn render_server_session_description( + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + render_description(SERVER_SESSION_DESCRIPTION, addr, fingerprint, ufrag) +} + +pub(crate) fn render_client_session_description( + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + render_description(CLIENT_SESSION_DESCRIPTION, addr, fingerprint, ufrag) +} + +/// Renders a [`TinyTemplate`] description using the provided arguments. +fn render_description( + description: &str, + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + let mut tt = TinyTemplate::new(); + tt.add_template("description", description).unwrap(); + + let context = DescriptionContext { + ip_version: { + if addr.is_ipv4() { + IpVersion::IP4 + } else { + IpVersion::IP6 + } + }, + target_ip: addr.ip(), + target_port: addr.port(), + fingerprint_algorithm: fingerprint.algorithm(), + fingerprint_value: fingerprint.value(), + // NOTE: ufrag is equal to pwd. + ufrag: ufrag.to_owned(), + pwd: ufrag.to_owned(), + }; + tt.render("description", &context).unwrap() +} diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 6decc293202..6e92eea58f7 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -33,13 +33,14 @@ use libp2p_core::{ identity, multiaddr::{Multiaddr, Protocol}, transport::{ListenerId, TransportError, TransportEvent}, - OutboundUpgrade, PeerId, Transport, UpgradeInfo, + InboundUpgrade, OutboundUpgrade, PeerId, Transport, UpgradeInfo, }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; use tokio_crate::net::UdpSocket; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc_ice::udp_mux::UDPMux; use std::{ borrow::Cow, @@ -53,10 +54,10 @@ use crate::{ connection::Connection, connection::PollDataChannel, error::Error, + fingerprint::Fingerprint, in_addr::InAddr, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, - upgrade, - webrtc_connection::{Fingerprint, WebRTCConnection}, + webrtc_connection::WebRTCConnection, }; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -79,12 +80,6 @@ impl WebRTCTransport { } } - /// Returns the SHA-256 fingerprint of the certificate in lowercase hex string as expressed - /// utilizing the syntax of 'fingerprint' in . - pub fn cert_fingerprint(&self) -> String { - self.config.fingerprint_of_first_certificate() - } - fn do_listen( &self, listener_id: ListenerId, @@ -167,7 +162,7 @@ impl Transport for WebRTCTransport { trace!("dialing addr={}", remote); let config = self.config.clone(); - let our_fingerprint = self.cert_fingerprint(); + let our_fingerprint = self.config.fingerprint_of_first_certificate(); let id_keys = self.id_keys.clone(); let first_listener = self @@ -180,9 +175,8 @@ impl Transport for WebRTCTransport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let remote_fingerprint = fingerprint_from_addr(&addr) - .map(|f| Fingerprint::from(f.iter())) - .ok_or(Error::InvalidMultiaddr(addr.clone()))?; + let remote_fingerprint = + fingerprint_from_addr(&addr).ok_or(Error::InvalidMultiaddr(addr.clone()))?; let conn = WebRTCConnection::connect( sock_addr, @@ -196,11 +190,11 @@ impl Transport for WebRTCTransport { let data_channel = conn.create_initial_upgrade_data_channel(None).await?; trace!("noise handshake with addr={}", remote); - let peer_id = perform_noise_handshake( + let peer_id = perform_noise_handshake_outbound( id_keys, PollDataChannel::new(data_channel.clone()), - &our_fingerprint, - remote_fingerprint.as_ref(), + our_fingerprint, + remote_fingerprint, ) .await?; @@ -318,59 +312,55 @@ impl WebRTCListenStream { /// Poll for a next If Event. fn poll_if_addr(&mut self, cx: &mut Context<'_>) -> Poll<::Item> { - match self.in_addr.poll_next_unpin(cx) { - Poll::Ready(mut item) => { - if let Some(item) = item.take() { - // Consume all events for up/down interface changes. - match item { - Ok(IfEvent::Up(inet)) => { - let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() - || self.listen_addr.is_ipv6() == ip.is_ipv6() - { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); - debug!("New listen address: {}", ma); - Poll::Ready(TransportEvent::NewAddress { - listener_id: self.listener_id, - listen_addr: ma, - }) - } else { - self.poll_if_addr(cx) - } - } - Ok(IfEvent::Down(inet)) => { - let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() - || self.listen_addr.is_ipv6() == ip.is_ipv6() - { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); - debug!("Expired listen address: {}", ma); - Poll::Ready(TransportEvent::AddressExpired { - listener_id: self.listener_id, - listen_addr: ma, - }) - } else { - self.poll_if_addr(cx) - } + loop { + let mut item = ready!(self.in_addr.poll_next_unpin(cx)); + if let Some(item) = item.take() { + // Consume all events for up/down interface changes. + match item { + Ok(IfEvent::Up(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + debug!("New listen address: {}", ma); + return Poll::Ready(TransportEvent::NewAddress { + listener_id: self.listener_id, + listen_addr: ma, + }); + } else { + continue; } - Err(err) => { - debug! { - "Failure polling interfaces: {:?}.", - err - }; - Poll::Ready(TransportEvent::ListenerError { + } + Ok(IfEvent::Down(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + debug!("Expired listen address: {}", ma); + return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, - error: err.into(), - }) + listen_addr: ma, + }); + } else { + continue; } } - } else { - self.poll_if_addr(cx) + Err(err) => { + debug! { + "Failure polling interfaces: {:?}.", + err + }; + return Poll::Ready(TransportEvent::ListenerError { + listener_id: self.listener_id, + error: err.into(), + }); + } } } - Poll::Pending => Poll::Pending, } } } @@ -379,40 +369,41 @@ impl Stream for WebRTCListenStream { type Item = TransportEvent<::ListenerUpgrade, Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - if let Some(closed) = self.report_closed.as_mut() { - // Listener was closed. - // Report the transport event if there is one. On the next iteration, return - // `Poll::Ready(None)` to terminate the stream. - return Poll::Ready(closed.take()); - } - if let Poll::Ready(event) = self.poll_if_addr(cx) { - return Poll::Ready(Some(event)); - } + loop { + if let Some(closed) = self.report_closed.as_mut() { + // Listener was closed. + // Report the transport event if there is one. On the next iteration, return + // `Poll::Ready(None)` to terminate the stream. + return Poll::Ready(closed.take()); + } - // Poll UDP muxer for new addresses or incoming data for streams. - match ready!(self.udp_mux.as_ref().poll(cx)) { - UDPMuxEvent::NewAddr(new_addr) => { - let local_addr = socketaddr_to_multiaddr(&self.listen_addr); - let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); - let event = TransportEvent::Incoming { - upgrade: Box::pin(upgrade::webrtc( - self.udp_mux.clone(), - self.config.clone(), - new_addr.addr, - new_addr.ufrag, - self.id_keys.clone(), - )) as BoxFuture<'static, _>, - local_addr, - send_back_addr, - listener_id: self.listener_id, - }; - Poll::Ready(Some(event)) + if let Poll::Ready(event) = self.poll_if_addr(cx) { + return Poll::Ready(Some(event)); } - UDPMuxEvent::Error(e) => { - self.close(Err(Error::UDPMuxError(e))); - return self.poll_next(cx); + + // Poll UDP muxer for new addresses or incoming data for streams. + match ready!(self.udp_mux.as_ref().poll(cx)) { + UDPMuxEvent::NewAddr(new_addr) => { + let local_addr = socketaddr_to_multiaddr(&self.listen_addr); + let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); + let event = TransportEvent::Incoming { + upgrade: Box::pin(upgrade( + self.udp_mux.clone(), + self.config.clone(), + new_addr.addr, + new_addr.ufrag, + self.id_keys.clone(), + )) as BoxFuture<'static, _>, + local_addr, + send_back_addr, + listener_id: self.listener_id, + }; + return Poll::Ready(Some(event)); + } + UDPMuxEvent::Error(e) => { + self.close(Err(Error::UDPMuxError(e))); + } } - _ => return self.poll_next(cx), } } } @@ -439,7 +430,7 @@ impl WebRTCConfiguration { /// # Panics /// /// Panics if the config does not contain any certificates. - pub fn fingerprint_of_first_certificate(&self) -> String { + pub fn fingerprint_of_first_certificate(&self) -> Fingerprint { // safe to unwrap here because we require a certificate during construction. let fingerprints = self .inner @@ -448,9 +439,11 @@ impl WebRTCConfiguration { .expect("at least one certificate") .get_fingerprints() .expect("get_fingerprints to succeed"); + // TODO: + // modify webrtc-rs to return value in upper-hex rather than lower-hex + // Fingerprint::from(fingerprints.first().unwrap()) debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); - // TODO: modify webrtc-rs to return value in upper-hex rather than lower-hex - fingerprints.first().unwrap().value.to_uppercase() + Fingerprint::new_sha256(fingerprints.first().unwrap().value.to_uppercase()) } /// Consumes the `WebRTCConfiguration`, returning its inner configuration. @@ -478,12 +471,12 @@ pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { /// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not /// contain one. -fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option> { +fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option { let iter = addr.iter(); for proto in iter { match proto { // TODO: check hash is one of https://datatracker.ietf.org/doc/html/rfc8122#section-5 - Protocol::XWebRTC(f) => return Some(f), + Protocol::XWebRTC(f) => return Some(Fingerprint::from(f.as_ref())), _ => continue, } } @@ -516,11 +509,11 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { } } -async fn perform_noise_handshake( +async fn perform_noise_handshake_outbound( id_keys: identity::Keypair, poll_data_channel: T, - our_fingerprint: &str, - remote_fingerprint: &str, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -543,18 +536,123 @@ where "exchanging TLS certificate fingerprints with peer_id={}", peer_id ); + debug_assert_eq!("sha-256", our_fingerprint.algorithm()); + let n = noise_io + .write(&our_fingerprint.value().into_bytes()) + .await?; + noise_io.flush().await?; + let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. + noise_io.read_exact(buf.as_mut_slice()).await?; + let fingerprint_from_noise = Fingerprint::new_sha256( + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?, + ); + if fingerprint_from_noise != remote_fingerprint { + return Err(Error::InvalidFingerprint { + expected: remote_fingerprint.value(), + got: fingerprint_from_noise.value(), + }); + } + + Ok(peer_id) +} + +async fn upgrade( + udp_mux: Arc, + config: WebRTCConfiguration, + socket_addr: SocketAddr, + ufrag: String, + id_keys: identity::Keypair, +) -> Result<(PeerId, Connection), Error> { + trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); + + let our_fingerprint = config.fingerprint_of_first_certificate(); + + let conn = WebRTCConnection::accept( + socket_addr, + config.into_inner(), + udp_mux, + &our_fingerprint, + &ufrag, + ) + .await?; + + // Open a data channel to do Noise on top and verify the remote. + // NOTE: channel is already negotiated by the client + let data_channel = conn.create_initial_upgrade_data_channel(Some(true)).await?; + + trace!( + "noise handshake with addr={} (ufrag={})", + socket_addr, + ufrag + ); + let remote_fingerprint = conn.get_remote_fingerprint().await; + let peer_id = perform_noise_handshake_inbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(conn.into_inner()).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) +} + +async fn perform_noise_handshake_inbound( + id_keys: identity::Keypair, + poll_data_channel: T, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = NoiseConfig::xx(dh_keys); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, mut noise_io) = noise + .upgrade_inbound(poll_data_channel, info) + .and_then(|(remote, io)| match remote { + RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), + _ => future::err(NoiseError::AuthenticationFailed), + }) + .await?; + + // Exchange TLS certificate fingerprints to prevent MiM attacks. + debug!( + "exchanging TLS certificate fingerprints with peer_id={}", + peer_id + ); + + // 1. Submit SHA-256 fingerprint + debug_assert_eq!("sha-256", our_fingerprint.algorithm()); let n = noise_io - .write(&our_fingerprint.to_owned().into_bytes()) + .write(&our_fingerprint.value().into_bytes()) .await?; noise_io.flush().await?; + + // 2. Receive one too and compare it to the fingerprint of the remote DTLS certificate. let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; + let fingerprint_from_noise = Fingerprint::new_sha256( + String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?, + ); if fingerprint_from_noise != remote_fingerprint { return Err(Error::InvalidFingerprint { - expected: remote_fingerprint.to_owned(), - got: fingerprint_from_noise, + expected: remote_fingerprint.value(), + got: fingerprint_from_noise.value(), }); } diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 467992aeff9..da73eec5854 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -58,8 +58,6 @@ pub enum UDPMuxEvent { Error(std::io::Error), /// Got a [`NewAddr`] from the socket. NewAddr(NewAddr), - /// Non-important event. Can be ignored. - None, } /// A modified version of [`webrtc_ice::udp_mux::UDPMuxDefault`], which reports previously unseen @@ -132,66 +130,66 @@ impl UDPMuxNewAddr { let mut recv_buf = [0u8; RECEIVE_MTU]; let mut read = ReadBuf::new(&mut recv_buf); - match ready!(self.udp_sock.poll_recv_from(cx, &mut read)) { - Ok(addr) => { - // Find connection based on previously having seen this source address - let conn = { - let address_map = self.address_map.read(); - address_map.get(&addr).map(Clone::clone) - }; - - let conn = match conn { - // If we couldn't find the connection based on source address, see if - // this is a STUN mesage and if so if we can find the connection based on ufrag. - None if is_stun_message(read.filled()) => { - self.conn_from_stun_message(&read.filled(), &addr) - } - s @ Some(_) => s, - _ => None, - }; - - match conn { - None => { - if !self.new_addrs.read().contains(&addr) { - match ufrag_from_stun_message(read.filled(), false) { - Ok(ufrag) => { - log::trace!( - "Notifying about new address addr={} from ufrag={}", - &addr, - ufrag - ); - let mut new_addrs = self.new_addrs.write(); - new_addrs.insert(addr); - return Poll::Ready(UDPMuxEvent::NewAddr(NewAddr { - addr, - ufrag, - })); - } - Err(e) => { - log::debug!( - "Unknown address addr={} (non STUN packet: {})", - &addr, - e - ); + loop { + match ready!(self.udp_sock.poll_recv_from(cx, &mut read)) { + Ok(addr) => { + // Find connection based on previously having seen this source address + let conn = { + let address_map = self.address_map.read(); + address_map.get(&addr).map(Clone::clone) + }; + + let conn = match conn { + // If we couldn't find the connection based on source address, see if + // this is a STUN mesage and if so if we can find the connection based on ufrag. + None if is_stun_message(read.filled()) => { + self.conn_from_stun_message(&read.filled(), &addr) + } + s @ Some(_) => s, + _ => None, + }; + + match conn { + None => { + if !self.new_addrs.read().contains(&addr) { + match ufrag_from_stun_message(read.filled(), false) { + Ok(ufrag) => { + log::trace!( + "Notifying about new address addr={} from ufrag={}", + &addr, + ufrag + ); + let mut new_addrs = self.new_addrs.write(); + new_addrs.insert(addr); + return Poll::Ready(UDPMuxEvent::NewAddr(NewAddr { + addr, + ufrag, + })); + } + Err(e) => { + log::debug!( + "Unknown address addr={} (non STUN packet: {})", + &addr, + e + ); + } } } } - } - Some(conn) => { - let mut packet = Vec::with_capacity(read.filled().len()); - packet.copy_from_slice(read.filled()); - write_packet_to_conn_from_addr(conn, packet, addr); + Some(conn) => { + let mut packet = Vec::with_capacity(read.filled().len()); + packet.copy_from_slice(read.filled()); + write_packet_to_conn_from_addr(conn, packet, addr); + } } } - } - Err(err) if err.kind() == ErrorKind::TimedOut => {} - Err(err) => { - log::error!("Could not read udp packet: {}", err); - return Poll::Ready(UDPMuxEvent::Error(err)); + Err(err) if err.kind() == ErrorKind::TimedOut => {} + Err(err) => { + log::error!("Could not read udp packet: {}", err); + return Poll::Ready(UDPMuxEvent::Error(err)); + } } } - - Poll::Ready(UDPMuxEvent::None) } } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs deleted file mode 100644 index ff04ed971bd..00000000000 --- a/transports/webrtc/src/upgrade.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2022 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 futures::{ - future, - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - TryFutureExt, -}; -use libp2p_core::{ - identity, PeerId, {InboundUpgrade, UpgradeInfo}, -}; -use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; -use log::{debug, trace}; -use webrtc_ice::udp_mux::UDPMux; - -use std::{net::SocketAddr, sync::Arc}; - -use crate::{ - connection::{Connection, PollDataChannel}, - error::Error, - transport::WebRTCConfiguration, - webrtc_connection::{Fingerprint, WebRTCConnection}, -}; - -pub(crate) async fn webrtc( - udp_mux: Arc, - config: WebRTCConfiguration, - socket_addr: SocketAddr, - ufrag: String, - id_keys: identity::Keypair, -) -> Result<(PeerId, Connection), Error> { - trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - - let our_fingerprint = Fingerprint::new_sha256(config.fingerprint_of_first_certificate()); - - let conn = WebRTCConnection::accept( - socket_addr, - config.into_inner(), - udp_mux, - &our_fingerprint, - &ufrag, - ) - .await?; - - // Open a data channel to do Noise on top and verify the remote. - // NOTE: channel is already negotiated by the client - let data_channel = conn.create_initial_upgrade_data_channel(Some(true)).await?; - - trace!( - "noise handshake with addr={} (ufrag={})", - socket_addr, - ufrag - ); - let remote_fingerprint = { - let f = conn.get_remote_fingerprint().await; - Fingerprint::from(f.iter()) - }; - let peer_id = perform_noise_handshake( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint.as_ref(), - remote_fingerprint.as_ref(), - ) - .await?; - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) -} - -async fn perform_noise_handshake( - id_keys: identity::Keypair, - poll_data_channel: T, - our_fingerprint: &str, - remote_fingerprint: &str, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let dh_keys = Keypair::::new() - .into_authentic(&id_keys) - .unwrap(); - let noise = NoiseConfig::xx(dh_keys); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise - .upgrade_inbound(poll_data_channel, info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) - .await?; - - // Exchange TLS certificate fingerprints to prevent MiM attacks. - debug!( - "exchanging TLS certificate fingerprints with peer_id={}", - peer_id - ); - - // 1. Submit SHA-256 fingerprint - let n = noise_io - .write(&our_fingerprint.to_owned().into_bytes()) - .await?; - noise_io.flush().await?; - - // 2. Receive one too and compare it to the fingerprint of the remote DTLS certificate. - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?; - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint.to_owned(), - got: fingerprint_from_noise, - }); - } - - Ok(peer_id) -} diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 664b73e97da..811057138a7 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -23,11 +23,9 @@ use futures_timer::Delay; use multihash::Hasher; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use tinytemplate::TinyTemplate; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; -use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; use webrtc::dtls_transport::dtls_role::DTLSRole; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; @@ -40,7 +38,7 @@ use webrtc_ice::udp_network::UDPNetwork; use std::{net::SocketAddr, sync::Arc, time::Duration}; use crate::error::Error; -use crate::sdp; +use crate::fingerprint::Fingerprint; pub(crate) struct WebRTCConnection { peer_connection: RTCPeerConnection, @@ -77,12 +75,8 @@ impl WebRTCConnection { // 2. ANSWER // Set the remote description to the predefined SDP. let remote_ufrag = remote_fingerprint.to_ufrag(); - let server_session_description = render_description( - sdp::SERVER_SESSION_DESCRIPTION, - addr, - remote_fingerprint, - &remote_ufrag, - ); + let server_session_description = + crate::sdp::render_server_session_description(addr, remote_fingerprint, &remote_ufrag); log::debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); // NOTE: this will start the gathering of ICE candidates @@ -115,8 +109,7 @@ impl WebRTCConnection { let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; - let client_session_description = render_description( - sdp::CLIENT_SESSION_DESCRIPTION, + let client_session_description = crate::sdp::render_client_session_description( addr, &Fingerprint::new_sha256("NONE".to_owned()), // certificate verification is disabled, so any value is okay. remote_ufrag, @@ -170,7 +163,8 @@ impl WebRTCConnection { self.peer_connection } - pub async fn get_remote_fingerprint(&self) -> [u8; 32] { + /// Returns the SHA-256 fingerprint of the remote. + pub async fn get_remote_fingerprint(&self) -> Fingerprint { let cert_bytes = self .peer_connection .sctp() @@ -181,7 +175,7 @@ impl WebRTCConnection { h.update(&cert_bytes); let mut bytes: [u8; 32] = [0; 32]; bytes.copy_from_slice(h.finalize()); - bytes + Fingerprint::from(&bytes) } fn setting_engine( @@ -212,84 +206,3 @@ impl WebRTCConnection { se } } - -const SHA256: &str = "sha-256"; - -pub(crate) struct Fingerprint(RTCDtlsFingerprint); - -impl Fingerprint { - /// Creates new `Fingerprint` w/ "sha-256" hash function. - pub fn new_sha256(value: String) -> Self { - Self(RTCDtlsFingerprint { - algorithm: SHA256.to_owned(), - value, - }) - } - - /// Transforms this fingerprint into a ufrag. - pub fn to_ufrag(&self) -> String { - self.0.value.replace(':', "").to_lowercase() - } - - /// Returns the lower-hex value, each byte separated by ":". - /// E.g. "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC" - pub fn value(&self) -> String { - self.0.value.clone() - } - - /// Returns the algorithm used (e.g. "sha-256"). - /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 - pub fn algorithm(&self) -> String { - self.0.algorithm.clone() - } -} - -impl From for Fingerprint -where - T: IntoIterator, - ::Item: core::fmt::UpperHex, -{ - fn from(t: T) -> Self { - let values: Vec = t.into_iter().map(|x| format! {"{:02X}", x}).collect(); - Self(RTCDtlsFingerprint { - algorithm: SHA256.to_owned(), - value: values.join(":"), - }) - } -} - -impl AsRef for Fingerprint { - #[inline] - fn as_ref(&self) -> &str { - self.0.value.as_ref() - } -} - -/// Renders a [`TinyTemplate`] description using the provided arguments. -pub(crate) fn render_description( - description: &str, - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - let mut tt = TinyTemplate::new(); - tt.add_template("description", description).unwrap(); - - let context = sdp::DescriptionContext { - ip_version: { - if addr.is_ipv4() { - sdp::IpVersion::IP4 - } else { - sdp::IpVersion::IP6 - } - }, - target_ip: addr.ip(), - target_port: addr.port(), - fingerprint_algorithm: fingerprint.algorithm(), - fingerprint_value: fingerprint.value(), - // NOTE: ufrag is equal to pwd. - ufrag: ufrag.to_owned(), - pwd: ufrag.to_owned(), - }; - tt.render("description", &context).unwrap() -} diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 36b92ca92ba..a810e7a636a 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -33,8 +33,14 @@ async fn create_swarm() -> Result<(Swarm>, String)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); + let fingerprint = cert + .get_fingerprints() + .unwrap() + .first() + .unwrap() + .value + .to_uppercase(); let transport = WebRTCTransport::new(cert, keypair); - let fingerprint = transport.cert_fingerprint(); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); From ecf7250a453fcf12ed42407f196b0d96d3c35e04 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 29 Jul 2022 17:20:18 +0400 Subject: [PATCH 045/244] simplify create_initial_upgrade_data_channel negotiated arg --- transports/webrtc/src/transport.rs | 4 ++-- transports/webrtc/src/webrtc_connection.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 6e92eea58f7..8e57997c894 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -187,7 +187,7 @@ impl Transport for WebRTCTransport { .await?; // Open a data channel to do Noise on top and verify the remote. - let data_channel = conn.create_initial_upgrade_data_channel(None).await?; + let data_channel = conn.create_initial_upgrade_data_channel(false).await?; trace!("noise handshake with addr={}", remote); let peer_id = perform_noise_handshake_outbound( @@ -578,7 +578,7 @@ async fn upgrade( // Open a data channel to do Noise on top and verify the remote. // NOTE: channel is already negotiated by the client - let data_channel = conn.create_initial_upgrade_data_channel(Some(true)).await?; + let data_channel = conn.create_initial_upgrade_data_channel(true).await?; trace!( "noise handshake with addr={} (ufrag={})", diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 811057138a7..29a43746f06 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -129,7 +129,7 @@ impl WebRTCConnection { pub async fn create_initial_upgrade_data_channel( &self, - negotiated: Option, + negotiated: bool, ) -> Result, Error> { // Open a data channel to do Noise on top and verify the remote. let data_channel = self @@ -138,7 +138,7 @@ impl WebRTCConnection { "data", Some(RTCDataChannelInit { id: Some(1), - negotiated, + negotiated: Some(negotiated), ..RTCDataChannelInit::default() }), ) From e781ef5a0592d4c72b93c71d56592a1f1229fcc7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 1 Aug 2022 17:38:40 +0400 Subject: [PATCH 046/244] closed detached channel if failed to send also fix the smoke test (partially) --- transports/webrtc/src/connection.rs | 15 ++++++++++----- transports/webrtc/src/udp_mux.rs | 2 +- transports/webrtc/tests/smoke.rs | 12 ++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 6701619232d..1d7f8c3d96c 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -128,11 +128,15 @@ impl Connection { let data_channel = data_channel.clone(); match data_channel.detach().await { Ok(detached) => { - if let Err(e) = tx.try_send(detached) { - // This can happen if the client is not reading - // events (using `poll_event`) fast enough, which - // generally shouldn't be the case. + if let Err(e) = tx.try_send(detached.clone()) { error!("Can't send data channel: {}", e); + // We're not accepting data channels fast enough => + // close this channel. + // + // Ideally we'd refuse to accept a data channel + // during the negotiation process, but it's not + // possible with the current API. + detached.close().await; } } Err(e) => { @@ -256,8 +260,9 @@ pub(crate) async fn register_data_channel_open_handler( let data_channel = data_channel.clone(); match data_channel.detach().await { Ok(detached) => { - if let Err(e) = data_channel_tx.send(detached) { + if let Err(e) = data_channel_tx.send(detached.clone()) { error!("Can't send data channel: {:?}", e); + detached.close().await; } } Err(e) => { diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index da73eec5854..43a6f1e2d9a 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -177,7 +177,7 @@ impl UDPMuxNewAddr { } } Some(conn) => { - let mut packet = Vec::with_capacity(read.filled().len()); + let mut packet = vec![0u8; read.filled().len()]; packet.copy_from_slice(read.filled()); write_packet_to_conn_from_addr(conn, packet, addr); } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index a810e7a636a..b9c15c38955 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -1,7 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use futures::{ - future::FutureExt, + future::{FutureExt, join}, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, stream::StreamExt, }; @@ -106,13 +106,9 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), }; - match b.next().await { - Some(SwarmEvent::ConnectionEstablished { .. }) => {} - e => panic!("{:?}", e), - }; - - match a.next().await { - Some(SwarmEvent::ConnectionEstablished { .. }) => {} + let pair = join(a.next(), b.next()); + match pair.await { + (Some(SwarmEvent::ConnectionEstablished { .. }), Some(SwarmEvent::ConnectionEstablished { .. })) => {} e => panic!("{:?}", e), }; From f5f930f790263f42ac6dce75a6b4b0abc37fe5eb Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 2 Aug 2022 10:30:04 +0400 Subject: [PATCH 047/244] fix smoke test --- transports/webrtc/src/connection.rs | 8 +- transports/webrtc/src/webrtc_connection.rs | 2 +- transports/webrtc/tests/smoke.rs | 111 +++++++++++++-------- 3 files changed, 74 insertions(+), 47 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 1d7f8c3d96c..f1d6875b3e8 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -136,7 +136,9 @@ impl Connection { // Ideally we'd refuse to accept a data channel // during the negotiation process, but it's not // possible with the current API. - detached.close().await; + if let Err(e) = detached.close().await { + error!("Failed to close data channel: {}", e); + } } } Err(e) => { @@ -262,7 +264,9 @@ pub(crate) async fn register_data_channel_open_handler( Ok(detached) => { if let Err(e) = data_channel_tx.send(detached.clone()) { error!("Can't send data channel: {:?}", e); - detached.close().await; + if let Err(e) = detached.close().await { + error!("Failed to close data channel: {}", e); + } } } Err(e) => { diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 29a43746f06..faea9567238 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -138,7 +138,7 @@ impl WebRTCConnection { "data", Some(RTCDataChannelInit { id: Some(1), - negotiated: Some(negotiated), + negotiated: if negotiated { Some(negotiated) } else { None }, ..RTCDataChannelInit::default() }), ) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index b9c15c38955..b0536ec3e40 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -1,7 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use futures::{ - future::{FutureExt, join}, + future::{join, select, Either, FutureExt}, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, stream::StreamExt, }; @@ -108,27 +108,35 @@ async fn smoke() -> Result<()> { let pair = join(a.next(), b.next()); match pair.await { - (Some(SwarmEvent::ConnectionEstablished { .. }), Some(SwarmEvent::ConnectionEstablished { .. })) => {} + ( + Some(SwarmEvent::ConnectionEstablished { .. }), + Some(SwarmEvent::ConnectionEstablished { .. }), + ) => {} e => panic!("{:?}", e), }; assert!(b.next().now_or_never().is_none()); - match a.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: - RequestResponseMessage::Request { - request: Ping(ping), - channel, - .. - }, - .. - })) => { + let pair = select(a.next(), b.next()); + match pair.await { + Either::Left(( + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request: Ping(ping), + channel, + .. + }, + .. + })), + _, + )) => { a.behaviour_mut() .send_response(channel, Pong(ping)) .unwrap(); } - e => panic!("{:?}", e), + Either::Left((e, _)) => panic!("{:?}", e), + Either::Right(_) => panic!("b completed first"), } match a.next().await { @@ -136,16 +144,21 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), } - match b.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: - RequestResponseMessage::Response { - response: Pong(pong), - .. - }, - .. - })) => assert_eq!(data, pong), - e => panic!("{:?}", e), + let pair = select(a.next(), b.next()); + match pair.await { + Either::Right(( + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(pong), + .. + }, + .. + })), + _, + )) => assert_eq!(data, pong), + Either::Right((e, _)) => panic!("{:?}", e), + Either::Left(_) => panic!("a completed first"), } a.behaviour_mut().send_request( @@ -155,21 +168,26 @@ async fn smoke() -> Result<()> { assert!(a.next().now_or_never().is_none()); - match b.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: - RequestResponseMessage::Request { - request: Ping(data), - channel, - .. - }, - .. - })) => { + let pair = select(a.next(), b.next()); + match pair.await { + Either::Right(( + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request: Ping(data), + channel, + .. + }, + .. + })), + _, + )) => { b.behaviour_mut() .send_response(channel, Pong(data)) .unwrap(); } - e => panic!("{:?}", e), + Either::Right((e, _)) => panic!("{:?}", e), + Either::Left(_) => panic!("a completed first"), } match b.next().await { @@ -177,16 +195,21 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), } - match a.next().await { - Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: - RequestResponseMessage::Response { - response: Pong(data), - .. - }, - .. - })) => assert_eq!(data, b"another substream".to_vec()), - e => panic!("{:?}", e), + let pair = select(a.next(), b.next()); + match pair.await { + Either::Left(( + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(data), + .. + }, + .. + })), + _, + )) => assert_eq!(data, b"another substream".to_vec()), + Either::Left((e, _)) => panic!("{:?}", e), + Either::Right(_) => panic!("b completed first"), } Ok(()) From 604fcc8365a293f4bd4f8df6801dbae830fcb3d1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 3 Aug 2022 16:09:39 +0400 Subject: [PATCH 048/244] reset outbound_fut and close_fut also, block_on writing to UDPMuxConn instead of spawning a thread. refs https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r936242038 --- transports/webrtc/src/connection.rs | 13 ++++++++++--- transports/webrtc/src/udp_mux.rs | 6 +++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index f1d6875b3e8..b55d69be918 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -217,10 +217,13 @@ impl<'a> StreamMuxer for Connection { if let Some(cap) = inner.read_buf_cap { ch.set_read_buf_capacity(cap); } - + inner.outbound_fut = None; Poll::Ready(Ok(ch)) } - Err(e) => Poll::Ready(Err(e)), + Err(e) => { + inner.outbound_fut = None; + Poll::Ready(Err(e)) + } } } @@ -237,9 +240,13 @@ impl<'a> StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(()) => { inner.incoming_data_channels_rx.close(); + inner.close_fut = None; Poll::Ready(Ok(())) } - Err(e) => Poll::Ready(Err(e)), + Err(e) => { + inner.close_fut = None; + Poll::Ready(Err(e)) + } } } } diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 43a6f1e2d9a..fd506b0dbd4 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -194,7 +194,11 @@ impl UDPMuxNewAddr { } fn write_packet_to_conn_from_addr(conn: UDPMuxConn, packet: Vec, addr: SocketAddr) { - tokio::spawn(async move { + // Writing the packet should be quick given it just buffers the data (no actual IO). + // + // Block until completion instead of spawning to provide backpressure to the clients. + // NOTE: `block_on` could be removed once/if `write_packet` becomes sync. + futures::executor::block_on(async move { if let Err(err) = conn.write_packet(&packet, addr).await { log::error!("Failed to write packet: {} (addr={})", err, addr); } From 8bd823359c6262ec9cdcac2b77b8b0d24647d81a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 3 Aug 2022 18:22:07 +0400 Subject: [PATCH 049/244] add concurrent_connections_and_streams test --- transports/webrtc/Cargo.toml | 1 + transports/webrtc/tests/smoke.rs | 160 +++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 25c92f5e8f5..866e480c4ad 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -41,3 +41,4 @@ libp2p-request-response = { version = "0.20.0", path = "../../protocols/request- libp2p-swarm = { version = "0.38.0", path = "../../swarm" } rand_core = "0.5" rcgen = "0.9" +quickcheck = "1" diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index b0536ec3e40..9ea4e4ac0a2 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -347,6 +347,166 @@ async fn dial_failure() -> Result<()> { Ok(()) } +#[tokio::test] +async fn concurrent_connections_and_streams() { + let _ = env_logger::builder().is_test(true).try_init(); + + use futures::executor::block_on; + use futures::task::Spawn; + use quickcheck::*; + use std::num::NonZeroU8; + + fn prop(number_listeners: NonZeroU8, number_streams: NonZeroU8) -> TestResult { + let (number_listeners, number_streams): (u8, u8) = + (number_listeners.into(), number_streams.into()); + if number_listeners > 10 || number_streams > 10 { + return TestResult::discard(); + } + + let mut pool = futures::executor::LocalPool::default(); + let mut data = vec![0; 4096 * 10]; + rand::thread_rng().fill_bytes(&mut data); + let mut listeners = vec![]; + + // Spawn the listener nodes. + for _ in 0..number_listeners { + let (mut listener, fingerprint) = block_on(create_swarm()).unwrap(); + Swarm::listen_on(&mut listener, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()).unwrap(); + + // Wait to listen on address. + let addr = match block_on(listener.next()) { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + + let addr = addr + .replace(2, |_| { + Some(Protocol::XWebRTC(hex_to_cow(&fingerprint.replace(":", "")))) + }) + .unwrap(); + + listeners.push((*listener.local_peer_id(), addr)); + + pool.spawner() + .spawn_obj( + async move { + loop { + match listener.next().await { + Some(SwarmEvent::IncomingConnection { .. }) => { + log::debug!("listener IncomingConnection"); + } + Some(SwarmEvent::ConnectionEstablished { .. }) => { + log::debug!("listener ConnectionEstablished"); + } + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request: Ping(ping), + channel, + .. + }, + .. + })) => { + log::debug!("listener got Message"); + listener + .behaviour_mut() + .send_response(channel, Pong(ping)) + .unwrap(); + } + Some(SwarmEvent::Behaviour( + RequestResponseEvent::ResponseSent { .. }, + )) => { + log::debug!("listener ResponseSent"); + } + Some(SwarmEvent::ConnectionClosed { .. }) => {} + Some(e) => { + panic!("unexpected event {:?}", e); + } + None => { + panic!("listener stopped"); + } + } + } + } + .boxed() + .into(), + ) + .unwrap(); + } + + let (mut dialer, _fingerprint) = block_on(create_swarm()).unwrap(); + Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()).unwrap(); + + // Wait to listen on address. + match block_on(dialer.next()) { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + + // For each listener node start `number_streams` requests. + for (listener_peer_id, listener_addr) in &listeners { + dialer + .behaviour_mut() + .add_address(&listener_peer_id, listener_addr.clone()); + + dialer.dial(listener_peer_id.clone()).unwrap(); + } + + // Wait for responses to each request. + pool.run_until(async { + let mut num_responses = 0; + loop { + match dialer.next().await { + Some(SwarmEvent::Dialing(_)) => { + log::debug!("dialer Dialing"); + } + Some(SwarmEvent::ConnectionEstablished { peer_id, .. }) => { + log::debug!("dialer Connection established"); + for _ in 0..number_streams { + dialer + .behaviour_mut() + .send_request(&peer_id, Ping(data.clone())); + } + } + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(pong), + .. + }, + .. + })) => { + log::debug!("dialer got Message"); + num_responses += 1; + assert_eq!(data, pong); + let should_be = number_listeners as usize * (number_streams) as usize; + log::debug!( + "num of responses: {}, num of listeners * num of streams: {}", + num_responses, + should_be + ); + if num_responses == should_be { + break; + } + } + Some(SwarmEvent::ConnectionClosed { .. }) => { + log::debug!("dialer ConnectionClosed"); + } + e => { + panic!("unexpected event {:?}", e); + } + } + } + }); + + TestResult::passed() + } + + prop(NonZeroU8::new(3).unwrap(), NonZeroU8::new(8).unwrap()); + + // QuickCheck::new().quickcheck(prop as fn(_, _) -> _); +} + fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { let mut buf = [0; 32]; hex::decode_to_slice(s, &mut buf).unwrap(); From 59894fbf5c4c9c3375ec6224a8f770386696d16d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 3 Aug 2022 18:40:30 +0400 Subject: [PATCH 050/244] get rid of ConnectionInner https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r936306416 --- transports/webrtc/Cargo.toml | 5 +- transports/webrtc/src/connection.rs | 76 +++++++++++++++-------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 866e480c4ad..fda43221d4a 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -21,6 +21,9 @@ if-watch = "0.2" libp2p-core = { version = "0.35.0", path = "../../core", default-features = false } libp2p-noise = { version = "0.38.0", path = "../../transports/noise" } log = "0.4" +multihash = { version = "0.16", default-features = false, features = ["sha2"] } +pin-project = "1.0.0" +rand = "0.8" serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" @@ -31,8 +34,6 @@ webrtc-data = "0.4.0" webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } -multihash = { version = "0.16", default-features = false, features = ["sha2"] } -rand = "0.8" [dev-dependencies] anyhow = "1.0" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index b55d69be918..8787b523e0d 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -31,12 +31,14 @@ use futures::{ use futures_lite::StreamExt; use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; +use pin_project::pin_project; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use std::{ - sync::{Arc, Mutex as StdMutex}, + pin::Pin, + sync::Arc, task::{Context, Poll}, }; @@ -46,16 +48,13 @@ pub(crate) use poll_data_channel::PollDataChannel; const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. +#[pin_project] pub struct Connection { /// `RTCPeerConnection` to the remote peer. /// /// Uses futures mutex because used in async code (see poll_outbound and poll_close). peer_conn: Arc>, - inner: StdMutex, -} - -struct ConnectionInner { /// Channel onto which incoming data channels are put. incoming_data_channels_rx: mpsc::Receiver>, @@ -64,9 +63,6 @@ struct ConnectionInner { read_buf_cap: Option, /// Future, which, once polled, will result in an outbound substream. - /// - /// NOTE: future might be waiting at one of the await points, and dropping the future will - /// abruptly interrupt the execution. outbound_fut: Option, Error>>>, /// Future, which, once polled, will result in closing the entire connection. @@ -82,19 +78,16 @@ impl Connection { Self { peer_conn: Arc::new(FutMutex::new(rtc_conn)), - inner: StdMutex::new(ConnectionInner { - incoming_data_channels_rx: data_channel_rx, - read_buf_cap: None, - outbound_fut: None, - close_fut: None, - }), + incoming_data_channels_rx: data_channel_rx, + read_buf_cap: None, + outbound_fut: None, + close_fut: None, } } /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). pub fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { - let mut inner = self.inner.lock().unwrap(); - inner.read_buf_cap = Some(cap); + self.read_buf_cap = Some(cap); } /// Registers a handler for incoming data channels. @@ -159,15 +152,18 @@ impl<'a> StreamMuxer for Connection { type Substream = PollDataChannel; type Error = Error; - fn poll_inbound(&self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.inner.lock().unwrap(); - match ready!(inner.incoming_data_channels_rx.poll_next(cx)) { + fn poll_inbound( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let this = self.project(); + match ready!(this.incoming_data_channels_rx.poll_next(cx)) { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); let mut ch = PollDataChannel::new(detached); - if let Some(cap) = inner.read_buf_cap { - ch.set_read_buf_capacity(cap); + if let Some(cap) = this.read_buf_cap { + ch.set_read_buf_capacity(*cap); } Poll::Ready(Ok(ch)) @@ -178,14 +174,20 @@ impl<'a> StreamMuxer for Connection { } } - fn poll_address_change(&self, _cx: &mut Context<'_>) -> Poll> { + fn poll_address_change( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { return Poll::Pending; } - fn poll_outbound(&self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.inner.lock().unwrap(); - let peer_conn = self.peer_conn.clone(); - let fut = inner.outbound_fut.get_or_insert(Box::pin(async move { + fn poll_outbound( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let this = self.project(); + let peer_conn = this.peer_conn.clone(); + let fut = this.outbound_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; // Create a datachannel with label 'data' @@ -214,37 +216,37 @@ impl<'a> StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { let mut ch = PollDataChannel::new(detached); - if let Some(cap) = inner.read_buf_cap { - ch.set_read_buf_capacity(cap); + if let Some(cap) = this.read_buf_cap { + ch.set_read_buf_capacity(*cap); } - inner.outbound_fut = None; + *this.outbound_fut = None; Poll::Ready(Ok(ch)) } Err(e) => { - inner.outbound_fut = None; + *this.outbound_fut = None; Poll::Ready(Err(e)) } } } - fn poll_close(&self, cx: &mut Context<'_>) -> Poll> { + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { debug!("Closing connection"); - let mut inner = self.inner.lock().unwrap(); - let peer_conn = self.peer_conn.clone(); - let fut = inner.close_fut.get_or_insert(Box::pin(async move { + let this = self.project(); + let peer_conn = this.peer_conn.clone(); + let fut = this.close_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; peer_conn.close().await.map_err(Error::WebRTC) })); match ready!(fut.as_mut().poll(cx)) { Ok(()) => { - inner.incoming_data_channels_rx.close(); - inner.close_fut = None; + this.incoming_data_channels_rx.close(); + *this.close_fut = None; Poll::Ready(Ok(())) } Err(e) => { - inner.close_fut = None; + *this.close_fut = None; Poll::Ready(Err(e)) } } From b770a04d7352a5674ae30436ade40fbbeecf4aa6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 4 Aug 2022 16:31:45 +0400 Subject: [PATCH 051/244] point webrtc and webrtc-data crates to master (temp) --- Cargo.toml | 1 + transports/webrtc/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da271177026..f048f8ab821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ libp2p-gossipsub = { version = "0.40.0", path = "protocols/gossipsub", optional # TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } +webrtc-data = { version = "0.4.0", git = "https://github.com/webrtc-rs/data.git" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index fda43221d4a..4103834f240 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -29,8 +29,8 @@ stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} -webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", branch = "master" } -webrtc-data = "0.4.0" +webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git" } +webrtc-data = { version = "0.4.0", git = "https://github.com/webrtc-rs/data.git" } webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } From 59617de20d12761c3d7fe882e4efc706b53102fa Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 Aug 2022 12:11:16 +0900 Subject: [PATCH 052/244] transports/webrtc/: Test message framing sizes --- transports/webrtc/Cargo.toml | 6 +++++ transports/webrtc/build.rs | 23 +++++++++++++++++ transports/webrtc/src/lib.rs | 39 +++++++++++++++++++++++++++++ transports/webrtc/src/message.proto | 19 ++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 transports/webrtc/build.rs create mode 100644 transports/webrtc/src/message.proto diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 4103834f240..9f412219bfd 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -23,6 +23,7 @@ libp2p-noise = { version = "0.38.0", path = "../../transports/noise" } log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } pin-project = "1.0.0" +prost = "0.10" rand = "0.8" serde = { version = "1.0", features = ["derive"] } stun = "0.4" @@ -35,6 +36,9 @@ webrtc-ice = "0.7.0" webrtc-sctp = "0.5.0" webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } +[build-dependencies] +prost-build = "0.10" + [dev-dependencies] anyhow = "1.0" env_logger = "0.9" @@ -43,3 +47,5 @@ libp2p-swarm = { version = "0.38.0", path = "../../swarm" } rand_core = "0.5" rcgen = "0.9" quickcheck = "1" +unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } +asynchronous-codec = { version = "0.6" } diff --git a/transports/webrtc/build.rs b/transports/webrtc/build.rs new file mode 100644 index 00000000000..3f582337a68 --- /dev/null +++ b/transports/webrtc/build.rs @@ -0,0 +1,23 @@ +// Copyright 2022 Protocol Labs. +// +// 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. + +fn main() { + prost_build::compile_protos(&["src/message.proto"], &["src"]).unwrap(); +} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index a0a8c768d2f..cc5e6a3225f 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -89,3 +89,42 @@ mod in_addr; mod sdp; mod udp_mux; mod webrtc_connection; + +mod message_proto { + include!(concat!(env!("OUT_DIR"), "/webrtc.pb.rs")); +} + +#[cfg(test)] +mod tests { + use super::*; + use asynchronous_codec::Encoder; + use bytes::BytesMut; + use prost::Message; + use unsigned_varint::codec::UviBytes; + + const MAX_MSG_LEN: usize = 16384; // 16kiB + const VARINT_LEN: usize = 2; + const PROTO_OVERHEAD: usize = 5; + + #[test] + fn proto_size() { + let message = [0; MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD]; + + let protobuf = message_proto::Message { + flag: Some(message_proto::message::Flag::CloseWrite.into()), + message: Some(message.to_vec()), + }; + + let mut encoded_msg = BytesMut::new(); + protobuf + .encode(&mut encoded_msg) + .expect("BytesMut to have sufficient capacity."); + assert_eq!(encoded_msg.len(), message.len() + PROTO_OVERHEAD); + + let mut uvi = UviBytes::default(); + let mut dst = BytesMut::new(); + uvi.encode(encoded_msg.clone().freeze(), &mut dst).unwrap(); + assert_eq!(dst.len(), MAX_MSG_LEN); + assert_eq!(dst.len() - encoded_msg.len(), VARINT_LEN); + } +} diff --git a/transports/webrtc/src/message.proto b/transports/webrtc/src/message.proto new file mode 100644 index 00000000000..988fa762de3 --- /dev/null +++ b/transports/webrtc/src/message.proto @@ -0,0 +1,19 @@ +syntax = "proto2"; + +package webrtc.pb; + +message Message { + enum Flag { + // The local endpoint will no longer send messages. + CLOSE_WRITE = 0; + // The local endpoint will no longer read messages. + CLOSE_READ = 1; + // The local endpoint abruptly terminates the stream. The remote endpoint + // may discard any in-flight data. + RESET = 2; + } + + optional Flag flag=1; + + optional bytes message = 2; +} From 7dc3ce1c916a5be2741930424095cedf838a1cd6 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 22 Aug 2022 17:34:12 +0900 Subject: [PATCH 053/244] transports/webrtc/: Implement message framing --- misc/prost-codec/src/lib.rs | 9 + transports/webrtc/Cargo.toml | 4 + .../src/connection/poll_data_channel.rs | 245 +++++++++++++++--- transports/webrtc/src/message.proto | 1 + 4 files changed, 224 insertions(+), 35 deletions(-) diff --git a/misc/prost-codec/src/lib.rs b/misc/prost-codec/src/lib.rs index 32b8c9b9577..8c797eeec15 100644 --- a/misc/prost-codec/src/lib.rs +++ b/misc/prost-codec/src/lib.rs @@ -79,3 +79,12 @@ pub enum Error { std::io::Error, ), } + +impl From for std::io::Error { + fn from(e: Error) -> Self { + match e { + Error::Decode(e) => e.into(), + Error::Io(e) => e, + } + } +} diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 9f412219bfd..cfab229cc44 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] +asynchronous-codec = "0.6" async-trait = "0.1" bytes = "1" fnv = "1.0" @@ -24,12 +25,15 @@ log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } pin-project = "1.0.0" prost = "0.10" +prost-codec = { version = "0.1", path = "../../misc/prost-codec" } rand = "0.8" serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} +# TODO: Needed? +tokio-util = { version = "0.7", features = ["compat"] } webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git" } webrtc-data = { version = "0.4.0", git = "https://github.com/webrtc-rs/data.git" } webrtc-ice = "0.7.0" diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 08d44a83657..cb0a719c049 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -18,7 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use asynchronous_codec::Framed; +use bytes::Bytes; use futures::prelude::*; +use futures::ready; +use tokio_util::compat::Compat; +use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc_data::data_channel::DataChannel; use webrtc_data::data_channel::PollDataChannel as RTCPollDataChannel; @@ -28,65 +33,133 @@ use std::sync::Arc; use std::task::{Context, Poll}; /// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. -#[derive(Debug)] -pub struct PollDataChannel(RTCPollDataChannel); +// TODO +// #[derive(Debug)] +pub struct PollDataChannel { + io: Framed, prost_codec::Codec>, + state: State, +} + +enum State { + Open { read_buffer: Bytes }, + WriteClosed { read_buffer: Bytes }, + ReadClosed { read_buffer: Bytes }, + ReadWriteClosed { read_buffer: Bytes }, + Reset, + Poisoned, +} + +impl State { + fn handle_flag(&mut self, flag: crate::message_proto::message::Flag) { + match (std::mem::replace(self, State::Poisoned), flag) { + ( + State::Open { read_buffer } | State::WriteClosed { read_buffer }, + crate::message_proto::message::Flag::CloseRead, + ) => { + *self = State::WriteClosed { read_buffer }; + } + ( + State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, + crate::message_proto::message::Flag::CloseRead, + ) => { + *self = State::ReadWriteClosed { read_buffer }; + } + ( + State::Open { read_buffer } | State::ReadClosed { read_buffer }, + crate::message_proto::message::Flag::CloseWrite, + ) => { + *self = State::ReadClosed { read_buffer }; + } + ( + State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, + crate::message_proto::message::Flag::CloseWrite, + ) => { + *self = State::ReadWriteClosed { read_buffer }; + } + // TODO: Or do we want to return an error? + (State::Reset, _) => *self = State::Reset, + (_, crate::message_proto::message::Flag::Reset) => *self = State::Reset, + (State::Poisoned, _) => unreachable!(), + } + } + + fn read_buffer_mut(&mut self) -> Option<&mut Bytes> { + match self { + State::Open { read_buffer } => Some(read_buffer), + State::WriteClosed { read_buffer } => Some(read_buffer), + State::ReadClosed { read_buffer } => Some(read_buffer), + State::ReadWriteClosed { read_buffer } => Some(read_buffer), + State::Reset => None, + State::Poisoned => todo!(), + } + } +} impl PollDataChannel { /// Constructs a new `PollDataChannel`. pub fn new(data_channel: Arc) -> Self { - Self(RTCPollDataChannel::new(data_channel)) + Self { + io: Framed::new( + RTCPollDataChannel::new(data_channel).compat(), + // TODO: Fix MAX + prost_codec::Codec::new(usize::MAX), + ), + state: State::Open { + read_buffer: Default::default(), + }, + } } /// Get back the inner data_channel. pub fn into_inner(self) -> RTCPollDataChannel { - self.0 + self.io.into_inner().into_inner() } /// Obtain a clone of the inner data_channel. pub fn clone_inner(&self) -> RTCPollDataChannel { - self.0.clone() + self.io.get_ref().clone() } /// MessagesSent returns the number of messages sent pub fn messages_sent(&self) -> usize { - self.0.messages_sent() + self.io.get_ref().messages_sent() } /// MessagesReceived returns the number of messages received pub fn messages_received(&self) -> usize { - self.0.messages_received() + self.io.get_ref().messages_received() } /// BytesSent returns the number of bytes sent pub fn bytes_sent(&self) -> usize { - self.0.bytes_sent() + self.io.get_ref().bytes_sent() } /// BytesReceived returns the number of bytes received pub fn bytes_received(&self) -> usize { - self.0.bytes_received() + self.io.get_ref().bytes_received() } /// StreamIdentifier returns the Stream identifier associated to the stream. pub fn stream_identifier(&self) -> u16 { - self.0.stream_identifier() + self.io.get_ref().stream_identifier() } /// BufferedAmount returns the number of bytes of data currently queued to be /// sent over this stream. pub fn buffered_amount(&self) -> usize { - self.0.buffered_amount() + self.io.get_ref().buffered_amount() } /// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing /// data that is considered "low." Defaults to 0. pub fn buffered_amount_low_threshold(&self) -> usize { - self.0.buffered_amount_low_threshold() + self.io.get_ref().buffered_amount_low_threshold() } /// Set the capacity of the temporary read buffer (default: 8192). pub fn set_read_buf_capacity(&mut self, capacity: usize) { - self.0.set_read_buf_capacity(capacity) + self.io.get_mut().set_read_buf_capacity(capacity) } } @@ -96,13 +169,84 @@ impl AsyncRead for PollDataChannel { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - let mut read_buf = tokio_crate::io::ReadBuf::new(buf); - futures::ready!(tokio_crate::io::AsyncRead::poll_read( - Pin::new(&mut self.0), - cx, - &mut read_buf - ))?; - Poll::Ready(Ok(read_buf.filled().len())) + loop { + if let Some(read_buffer) = self.state.read_buffer_mut() { + if !read_buffer.is_empty() { + let n = std::cmp::min(read_buffer.len(), buf.len()); + let data = read_buffer.split_to(n); + buf[0..n].copy_from_slice(&data[..]); + + return Poll::Ready(Ok(n)); + } + } + + match &mut *self { + PollDataChannel { + state: + State::Open { + ref mut read_buffer, + }, + io, + } + | PollDataChannel { + state: + State::WriteClosed { + ref mut read_buffer, + }, + io, + } => { + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(crate::message_proto::Message { flag, message }) => { + assert!(read_buffer.is_empty()); + if let Some(message) = message { + *read_buffer = message.into(); + } + + if let Some(flag) = flag + .map(|f| { + crate::message_proto::message::Flag::from_i32(f) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) + }) + .transpose()? + { + self.state.handle_flag(flag) + } + + continue; + } + None => { + self.state + .handle_flag(crate::message_proto::message::Flag::CloseWrite); + return Poll::Ready(Ok(0)); + } + } + } + PollDataChannel { + state: State::ReadClosed { .. }, + .. + } + | PollDataChannel { + state: State::ReadWriteClosed { .. }, + .. + } => return Poll::Ready(Ok(0)), + PollDataChannel { + state: State::Reset, + .. + } => { + // TODO: Is `""` valid? + return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); + } + PollDataChannel { + state: State::Poisoned, + .. + } => { + todo!() + } + } + } } } @@ -112,28 +256,59 @@ impl AsyncWrite for PollDataChannel { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - tokio_crate::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf) + match self.state { + State::WriteClosed { .. } | State::ReadWriteClosed { .. } => return Poll::Ready(Ok(0)), + State::Reset => { + return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); + } + State::Open { .. } => {} + State::ReadClosed { .. } => {} + State::Poisoned => todo!(), + } + + ready!(self.io.poll_ready_unpin(cx))?; + + Pin::new(&mut self.io).start_send(crate::message_proto::Message { + flag: None, + message: Some(buf.into()), + })?; + + Poll::Ready(Ok(buf.len())) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio_crate::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx) + // TODO: Double check that we don't have to depend on self.state here. + self.io.poll_flush_unpin(cx).map_err(Into::into) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio_crate::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx) - } + match &self.state { + State::WriteClosed { .. } | State::ReadWriteClosed { .. } => {} + State::Open { .. } | State::ReadClosed { .. } => { + ready!(self.io.poll_ready_unpin(cx))?; + Pin::new(&mut self.io).start_send(crate::message_proto::Message { + flag: Some(crate::message_proto::message::Flag::CloseWrite.into()), + message: None, + })?; - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - tokio_crate::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs) - } -} + match std::mem::replace(&mut self.state, State::Poisoned) { + State::Open { read_buffer } => self.state = State::WriteClosed { read_buffer }, + State::ReadClosed { read_buffer } => { + self.state = State::ReadWriteClosed { read_buffer } + } + State::WriteClosed { .. } + | State::ReadWriteClosed { .. } + | State::Reset + | State::Poisoned => unreachable!(), + } + } + State::Reset => { + return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); + } + State::Poisoned => todo!(), + } -impl Clone for PollDataChannel { - fn clone(&self) -> PollDataChannel { - PollDataChannel(self.clone_inner()) + // TODO: Is flush the correct thing here? We don't want the underlying layer to close both write and read. + self.io.poll_flush_unpin(cx).map_err(Into::into) } } diff --git a/transports/webrtc/src/message.proto b/transports/webrtc/src/message.proto index 988fa762de3..81890c14f17 100644 --- a/transports/webrtc/src/message.proto +++ b/transports/webrtc/src/message.proto @@ -4,6 +4,7 @@ package webrtc.pb; message Message { enum Flag { + // TODO: Change to sender // The local endpoint will no longer send messages. CLOSE_WRITE = 0; // The local endpoint will no longer read messages. From 503e32fb99098c3dc1b7e7f84e82749c8a4d87f2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 22 Aug 2022 17:56:03 +0900 Subject: [PATCH 054/244] transports/webrtc: Update protobuf --- transports/webrtc/src/message.proto | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/message.proto b/transports/webrtc/src/message.proto index 81890c14f17..27de2b7c45f 100644 --- a/transports/webrtc/src/message.proto +++ b/transports/webrtc/src/message.proto @@ -4,10 +4,9 @@ package webrtc.pb; message Message { enum Flag { - // TODO: Change to sender - // The local endpoint will no longer send messages. + // The sender will no longer send messages. CLOSE_WRITE = 0; - // The local endpoint will no longer read messages. + // The sender will no longer read messages. CLOSE_READ = 1; // The local endpoint abruptly terminates the stream. The remote endpoint // may discard any in-flight data. From 59e0deda239b014dfd13b39fb7cb45eddf1c5dbe Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 29 Aug 2022 08:04:33 +0200 Subject: [PATCH 055/244] transports/webrtc/: Remove pin_project dependency (#2) --- transports/webrtc/Cargo.toml | 1 - transports/webrtc/src/connection.rs | 41 +++++++++++++---------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 4103834f240..485bfdf87e4 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -22,7 +22,6 @@ libp2p-core = { version = "0.35.0", path = "../../core", default-features = fals libp2p-noise = { version = "0.38.0", path = "../../transports/noise" } log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } -pin-project = "1.0.0" rand = "0.8" serde = { version = "1.0", features = ["derive"] } stun = "0.4" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 8787b523e0d..5d51cffc05a 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -31,7 +31,6 @@ use futures::{ use futures_lite::StreamExt; use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; -use pin_project::pin_project; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; use webrtc_data::data_channel::DataChannel as DetachedDataChannel; @@ -48,7 +47,6 @@ pub(crate) use poll_data_channel::PollDataChannel; const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. -#[pin_project] pub struct Connection { /// `RTCPeerConnection` to the remote peer. /// @@ -69,6 +67,8 @@ pub struct Connection { close_fut: Option>>, } +impl Unpin for Connection {} + impl Connection { /// Creates a new connection. pub async fn new(rtc_conn: RTCPeerConnection) -> Self { @@ -153,17 +153,16 @@ impl<'a> StreamMuxer for Connection { type Error = Error; fn poll_inbound( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let this = self.project(); - match ready!(this.incoming_data_channels_rx.poll_next(cx)) { + match ready!(self.incoming_data_channels_rx.poll_next(cx)) { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); let mut ch = PollDataChannel::new(detached); - if let Some(cap) = this.read_buf_cap { - ch.set_read_buf_capacity(*cap); + if let Some(cap) = self.read_buf_cap { + ch.set_read_buf_capacity(cap); } Poll::Ready(Ok(ch)) @@ -182,12 +181,11 @@ impl<'a> StreamMuxer for Connection { } fn poll_outbound( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let this = self.project(); - let peer_conn = this.peer_conn.clone(); - let fut = this.outbound_fut.get_or_insert(Box::pin(async move { + let peer_conn = self.peer_conn.clone(); + let fut = self.outbound_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; // Create a datachannel with label 'data' @@ -216,37 +214,36 @@ impl<'a> StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { let mut ch = PollDataChannel::new(detached); - if let Some(cap) = this.read_buf_cap { - ch.set_read_buf_capacity(*cap); + if let Some(cap) = self.read_buf_cap { + ch.set_read_buf_capacity(cap); } - *this.outbound_fut = None; + self.outbound_fut = None; Poll::Ready(Ok(ch)) } Err(e) => { - *this.outbound_fut = None; + self.outbound_fut = None; Poll::Ready(Err(e)) } } } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { debug!("Closing connection"); - let this = self.project(); - let peer_conn = this.peer_conn.clone(); - let fut = this.close_fut.get_or_insert(Box::pin(async move { + let peer_conn = self.peer_conn.clone(); + let fut = self.close_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; peer_conn.close().await.map_err(Error::WebRTC) })); match ready!(fut.as_mut().poll(cx)) { Ok(()) => { - this.incoming_data_channels_rx.close(); - *this.close_fut = None; + self.incoming_data_channels_rx.close(); + self.close_fut = None; Poll::Ready(Ok(())) } Err(e) => { - *this.close_fut = None; + self.close_fut = None; Poll::Ready(Err(e)) } } From a2c74c68b41712057887133fcf12159f2669f15f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 29 Aug 2022 16:58:13 +0400 Subject: [PATCH 056/244] get rid of negotiated arg turns out we can pass negotiated: true and id=1 on both ends. --- transports/webrtc/src/transport.rs | 5 ++--- transports/webrtc/src/webrtc_connection.rs | 7 ++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 8e57997c894..9b4bbfbc6c4 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -187,7 +187,7 @@ impl Transport for WebRTCTransport { .await?; // Open a data channel to do Noise on top and verify the remote. - let data_channel = conn.create_initial_upgrade_data_channel(false).await?; + let data_channel = conn.create_initial_upgrade_data_channel().await?; trace!("noise handshake with addr={}", remote); let peer_id = perform_noise_handshake_outbound( @@ -577,8 +577,7 @@ async fn upgrade( .await?; // Open a data channel to do Noise on top and verify the remote. - // NOTE: channel is already negotiated by the client - let data_channel = conn.create_initial_upgrade_data_channel(true).await?; + let data_channel = conn.create_initial_upgrade_data_channel().await?; trace!( "noise handshake with addr={} (ufrag={})", diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index faea9567238..c706b6cc8ad 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -127,10 +127,7 @@ impl WebRTCConnection { return Ok(Self { peer_connection }); } - pub async fn create_initial_upgrade_data_channel( - &self, - negotiated: bool, - ) -> Result, Error> { + pub async fn create_initial_upgrade_data_channel(&self) -> Result, Error> { // Open a data channel to do Noise on top and verify the remote. let data_channel = self .peer_connection @@ -138,7 +135,7 @@ impl WebRTCConnection { "data", Some(RTCDataChannelInit { id: Some(1), - negotiated: if negotiated { Some(negotiated) } else { None }, + negotiated: Some(true), ..RTCDataChannelInit::default() }), ) From 3effb2e3c89a855d79b185abb73ae7d4cce28943 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 29 Aug 2022 15:12:51 +0200 Subject: [PATCH 057/244] Reduce webrtc dependency to a single one (#3) --- Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 6 +----- transports/webrtc/src/connection.rs | 2 +- transports/webrtc/src/connection/poll_data_channel.rs | 4 ++-- transports/webrtc/src/transport.rs | 2 +- transports/webrtc/src/udp_mux.rs | 4 ++-- transports/webrtc/src/webrtc_connection.rs | 8 ++++---- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f048f8ab821..2e714f86005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ libp2p-gossipsub = { version = "0.40.0", path = "protocols/gossipsub", optional # TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } -webrtc-data = { version = "0.4.0", git = "https://github.com/webrtc-rs/data.git" } +webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", rev = "8a570f2b650daac7e4455cc3d9ae094c56fbe037" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 485bfdf87e4..81ef871543f 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -28,11 +28,7 @@ stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} -webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git" } -webrtc-data = { version = "0.4.0", git = "https://github.com/webrtc-rs/data.git" } -webrtc-ice = "0.7.0" -webrtc-sctp = "0.5.0" -webrtc-util = { version = "0.5.4", default-features = false, features = ["conn", "vnet", "sync"] } +webrtc = "0.4.0" [dev-dependencies] anyhow = "1.0" diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 5d51cffc05a..a6b8c7ff1e6 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -31,9 +31,9 @@ use futures::{ use futures_lite::StreamExt; use libp2p_core::{muxing::StreamMuxer, Multiaddr}; use log::{debug, error, trace}; +use webrtc::data::data_channel::DataChannel as DetachedDataChannel; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; -use webrtc_data::data_channel::DataChannel as DetachedDataChannel; use std::{ pin::Pin, diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 08d44a83657..3e682219cb0 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -19,8 +19,8 @@ // DEALINGS IN THE SOFTWARE. use futures::prelude::*; -use webrtc_data::data_channel::DataChannel; -use webrtc_data::data_channel::PollDataChannel as RTCPollDataChannel; +use webrtc::data::data_channel::DataChannel; +use webrtc::data::data_channel::PollDataChannel as RTCPollDataChannel; use std::io; use std::pin::Pin; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 9b4bbfbc6c4..f1987a561d2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -38,9 +38,9 @@ use libp2p_core::{ use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; use tokio_crate::net::UdpSocket; +use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc_ice::udp_mux::UDPMux; use std::{ borrow::Cow, diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index fd506b0dbd4..28299ac5a1e 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -28,8 +28,8 @@ use stun::{ }; use tokio::{io::ReadBuf, net::UdpSocket}; use tokio_crate as tokio; -use webrtc_ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; -use webrtc_util::{sync::RwLock, Conn, Error}; +use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; +use webrtc::util::{sync::RwLock, Conn, Error}; use std::{ collections::{HashMap, HashSet}, diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index c706b6cc8ad..3408118ef31 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -25,15 +25,15 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; +use webrtc::data::data_channel::DataChannel; use webrtc::data_channel::data_channel_init::RTCDataChannelInit; use webrtc::dtls_transport::dtls_role::DTLSRole; +use webrtc::ice::network_type::NetworkType; +use webrtc::ice::udp_mux::UDPMux; +use webrtc::ice::udp_network::UDPNetwork; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc::peer_connection::RTCPeerConnection; -use webrtc_data::data_channel::DataChannel; -use webrtc_ice::network_type::NetworkType; -use webrtc_ice::udp_mux::UDPMux; -use webrtc_ice::udp_network::UDPNetwork; use std::{net::SocketAddr, sync::Arc, time::Duration}; From 71e56dd40194099b2358780c259d6f0d814d7135 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 31 Aug 2022 11:30:18 +0400 Subject: [PATCH 058/244] multiaddr 0.15 (master) --- Cargo.toml | 4 +-- core/Cargo.toml | 2 +- transports/webrtc/src/fingerprint.rs | 14 +++++++++ transports/webrtc/src/transport.rs | 42 ++++++++++--------------- transports/webrtc/tests/smoke.rs | 46 ++++++++++------------------ 5 files changed, 50 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e714f86005..c5eea3c78ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ libp2p-uds = { version = "0.34.0", path = "transports/uds", optional = true } libp2p-wasm-ext = { version = "0.35.0", path = "transports/wasm-ext", default-features = false, optional = true } libp2p-webrtc = { version = "0.1.0", path = "transports/webrtc", optional = true } libp2p-yamux = { version = "0.39.0", path = "muxers/yamux", optional = true } -multiaddr = { version = "0.14.0" } +multiaddr = { version = "0.15.0" } parking_lot = "0.12.0" pin-project = "1.0.0" rand = "0.7.3" # Explicit dependency to be used in `wasm-bindgen` feature @@ -117,7 +117,7 @@ libp2p-gossipsub = { version = "0.40.0", path = "protocols/gossipsub", optional # TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] -multiaddr = { version = "0.14.0", git = "https://github.com/melekes/rust-multiaddr.git", branch = "anton/x-webrtc" } +multiaddr = { version = "0.15.0", git = "https://github.com/multiformats/rust-multiaddr.git", branch = "master" } webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", rev = "8a570f2b650daac7e4455cc3d9ae094c56fbe037" } [dev-dependencies] diff --git a/core/Cargo.toml b/core/Cargo.toml index 386dff09669..7f72f257411 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,7 +22,7 @@ instant = "0.1.11" lazy_static = "1.2" libsecp256k1 = { version = "0.7.0", optional = true } log = "0.4" -multiaddr = { version = "0.14.0" } +multiaddr = { version = "0.15.0" } multihash = { version = "0.16", default-features = false, features = ["std", "multihash-impl", "identity", "sha2"] } multistream-select = { version = "0.11", path = "../misc/multistream-select" } p256 = { version = "0.10.0", default-features = false, features = ["ecdsa"], optional = true } diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 4e0e5db255c..790a8de01c1 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use multihash::Multihash; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; @@ -58,6 +59,19 @@ impl From<&[u8; 32]> for Fingerprint { } } +impl From for Fingerprint { + fn from(h: Multihash) -> Self { + // Only support SHA-256 (0x12) for now. + assert_eq!(h.code(), 0x12); + let values: Vec = h + .digest() + .into_iter() + .map(|x| format! {"{:02X}", x}) + .collect(); + Self::new_sha256(values.join(":")) + } +} + // TODO: derive when RTCDtlsFingerprint implements Eq. impl PartialEq for Fingerprint { fn eq(&self, other: &Self) -> bool { diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index f1987a561d2..bd0d34c1645 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -43,7 +43,6 @@ use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; use std::{ - borrow::Cow, net::SocketAddr, pin::Pin, sync::Arc, @@ -452,21 +451,12 @@ impl WebRTCConfiguration { } } -// TODO: remove -fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { - let mut buf = [0; 32]; - hex::decode_to_slice(s, &mut buf).unwrap(); - Cow::Owned(buf) -} - /// Turns an IP address and port into the corresponding WebRTC multiaddr. pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { - // TODO: remove - let f = "ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B"; Multiaddr::empty() .with(socket_addr.ip().into()) .with(Protocol::Udp(socket_addr.port())) - .with(Protocol::XWebRTC(hex_to_cow(&f))) + .with(Protocol::WebRTC) } /// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not @@ -475,8 +465,8 @@ fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option { let iter = addr.iter(); for proto in iter { match proto { - // TODO: check hash is one of https://datatracker.ietf.org/doc/html/rfc8122#section-5 - Protocol::XWebRTC(f) => return Some(Fingerprint::from(f.as_ref())), + // Only support SHA-256 (0x12) for now. + Protocol::Certhash(f) if f.code() == 0x12 => return Some(Fingerprint::from(f)), _ => continue, } } @@ -491,18 +481,20 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { let proto2 = iter.next()?; let proto3 = iter.next()?; + // Return `None` if protocols other than `p2p` or `certhash` are present. for proto in iter { match proto { - Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(_) => {} + Protocol::Certhash(_) => {} _ => return None, } } match (proto1, proto2, proto3) { - (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { + (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::WebRTC) => { Some(SocketAddr::new(ip.into(), port)) } - (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::XWebRTC(_)) => { + (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::WebRTC) => { Some(SocketAddr::new(ip.into(), port)) } _ => None, @@ -680,7 +672,7 @@ mod tests { assert_eq!( multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + &"/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" .parse::() .unwrap() ), @@ -692,14 +684,14 @@ mod tests { assert!( multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/tcp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + &"/ip4/127.0.0.1/tcp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" .parse::() .unwrap() ).is_none() ); assert!(multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B/tcp/12345" + &"/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/tcp/12345" .parse::() .unwrap() ) @@ -707,7 +699,7 @@ mod tests { assert_eq!( multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/udp/8080/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + &"/ip4/255.255.255.255/udp/8080/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" .parse::() .unwrap() ), @@ -718,7 +710,7 @@ mod tests { ); assert_eq!( multiaddr_to_socketaddr( - &"/ip6/::1/udp/12345/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + &"/ip6/::1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" .parse::() .unwrap() ), @@ -729,7 +721,7 @@ mod tests { ); assert_eq!( multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B" + &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" .parse::() .unwrap() ), @@ -759,7 +751,7 @@ mod tests { // is temporarily empty. for _ in 0..2 { let listener = transport - .listen_on("/ip4/0.0.0.0/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()) + .listen_on("/ip4/0.0.0.0/udp/0/webrtc".parse().unwrap()) .unwrap(); match poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)).await { TransportEvent::NewAddress { @@ -773,9 +765,7 @@ mod tests { assert!( matches!(listen_addr.iter().nth(1), Some(Protocol::Udp(port)) if port != 0) ); - assert!( - matches!(listen_addr.iter().nth(2), Some(Protocol::XWebRTC(f)) if !f.is_empty()) - ); + assert!(matches!(listen_addr.iter().nth(2), Some(Protocol::WebRTC))); } e => panic!("Unexpected event: {:?}", e), } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 9ea4e4ac0a2..8f3247ea04b 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -12,12 +12,12 @@ use libp2p_request_response::{ }; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p_webrtc::transport::WebRTCTransport; +use multihash::{Code, Multihash, MultihashDigest}; use rand::RngCore; use rcgen::KeyPair; use tokio_crate as tokio; use webrtc::peer_connection::certificate::RTCCertificate; -use std::borrow::Cow; use std::{io, iter}; fn generate_certificate() -> RTCCertificate { @@ -67,21 +67,15 @@ async fn smoke() -> Result<()> { let (mut a, a_fingerprint) = create_swarm().await?; let (mut b, _b_fingerprint) = create_swarm().await?; - Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; - Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; + Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; let addr = match a.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; - let addr = addr - .replace(2, |_| { - Some(Protocol::XWebRTC(hex_to_cow( - &a_fingerprint.replace(":", ""), - ))) - }) - .unwrap(); + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -301,21 +295,15 @@ async fn dial_failure() -> Result<()> { let (mut a, a_fingerprint) = create_swarm().await?; let (mut b, _b_fingerprint) = create_swarm().await?; - Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; - Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse()?)?; + Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; + Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; let addr = match a.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; - let addr = addr - .replace(2, |_| { - Some(Protocol::XWebRTC(hex_to_cow( - &a_fingerprint.replace(":", ""), - ))) - }) - .unwrap(); + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -371,7 +359,11 @@ async fn concurrent_connections_and_streams() { // Spawn the listener nodes. for _ in 0..number_listeners { let (mut listener, fingerprint) = block_on(create_swarm()).unwrap(); - Swarm::listen_on(&mut listener, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()).unwrap(); + Swarm::listen_on( + &mut listener, + "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap(), + ) + .unwrap(); // Wait to listen on address. let addr = match block_on(listener.next()) { @@ -379,11 +371,7 @@ async fn concurrent_connections_and_streams() { e => panic!("{:?}", e), }; - let addr = addr - .replace(2, |_| { - Some(Protocol::XWebRTC(hex_to_cow(&fingerprint.replace(":", "")))) - }) - .unwrap(); + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&fingerprint))); listeners.push((*listener.local_peer_id(), addr)); @@ -435,7 +423,7 @@ async fn concurrent_connections_and_streams() { } let (mut dialer, _fingerprint) = block_on(create_swarm()).unwrap(); - Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/x-webrtc/ACD1E533EC271FCDE0275947F4D62A2B2331FF10C9DDE0298EB7B399B4BFF60B".parse().unwrap()).unwrap(); + Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap()).unwrap(); // Wait to listen on address. match block_on(dialer.next()) { @@ -507,8 +495,8 @@ async fn concurrent_connections_and_streams() { // QuickCheck::new().quickcheck(prop as fn(_, _) -> _); } -fn hex_to_cow<'a>(s: &str) -> Cow<'a, [u8; 32]> { +fn fingerprint2multihash(s: &str) -> Multihash { let mut buf = [0; 32]; - hex::decode_to_slice(s, &mut buf).unwrap(); - Cow::Owned(buf) + hex::decode_to_slice(s.replace(":", ""), &mut buf).unwrap(); + Code::Sha2_256.wrap(&buf).unwrap() } From 38175fd87295650a7963c164402bfee7b339fd0f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 31 Aug 2022 09:41:26 +0200 Subject: [PATCH 059/244] Refactor `UdpMuxNewAddr` to be lock-less (#1) * Dirty PoC for lock-less UdpMuxNewAddr The general idea is this: We use `flume` channels between the actual `UdpMuxNewAddr` instance and a set of "handles", one per async-trait that we need to satisfy. There is only one instance of `UdpMuxNewAddr`, meaning we can access it via `&mut self` and drop all locks within it. In the poll function, we can then decide, which events to prioritise over others. Most importantly, all channels between handles and the actual instance are "rendezvous channels", meaning, sending will block until we actually read the item out of the channel. This allows us to enforce backpressure from the poll function all the way to all handles and into the task that is interacting with the handle. * Remove locks from handle * Replace `flume` with `futures::mpsc` channels * Introduce `req_res_chan` module * Move locking into `req_res_chan::Sender` * Fill in more implementations * Add TODO * Implement send command handling, buffering items if we can't send them * Remove tokio::spawn for handling waiting for closed sockets * Remove futures::block_on in favor of local task set * Fix clippy warnings * Don't loop inside a loop * Use match instead of `ready!` * Fix webrtc version to rev prior to monorepo merge * Never have more than one write future This is to ensure backpressure. To not loose drop any packets, we only read from the socket in case we are not currently writing a packet. * Resolve TODO's for error handling * Make `recv_buf` as short-lived as possible * Replace AtomicBool with regular bool * Remove unused import * Simplify send_buffer handling Instead of buffering multiple items, we only ever buffer one. To work off this queue as quickly as possible, we put it at the top of the loop which allows us to reduce some code duplication by writing directly to the buffer in case we popped an item off the queue and going back to the start of the loop which will trigger a different code branch. * Cargo fmt --- transports/webrtc/src/lib.rs | 1 + transports/webrtc/src/req_res_chan.rs | 49 +++ transports/webrtc/src/transport.rs | 10 +- transports/webrtc/src/udp_mux.rs | 518 ++++++++++++++++---------- 4 files changed, 386 insertions(+), 192 deletions(-) create mode 100644 transports/webrtc/src/req_res_chan.rs diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index a0a8c768d2f..1ae95f8a29e 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -86,6 +86,7 @@ pub mod transport; mod fingerprint; mod in_addr; +mod req_res_chan; mod sdp; mod udp_mux; mod webrtc_connection; diff --git a/transports/webrtc/src/req_res_chan.rs b/transports/webrtc/src/req_res_chan.rs new file mode 100644 index 00000000000..5e350996917 --- /dev/null +++ b/transports/webrtc/src/req_res_chan.rs @@ -0,0 +1,49 @@ +use futures::channel::mpsc; +use futures::channel::oneshot; +use futures::SinkExt; +use futures_lite::StreamExt; +use std::io; +use std::task::{Context, Poll}; + +pub fn new(capacity: usize) -> (Sender, Receiver) { + let (sender, receiver) = mpsc::channel(capacity); + + ( + Sender { + inner: futures::lock::Mutex::new(sender), + }, + Receiver { inner: receiver }, + ) +} + +pub struct Sender { + inner: futures::lock::Mutex)>>, +} + +impl Sender { + pub async fn send(&self, req: Req) -> io::Result { + let (sender, receiver) = oneshot::channel(); + + self.inner + .lock() + .await + .send((req, sender)) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let res = receiver + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + Ok(res) + } +} + +pub struct Receiver { + inner: mpsc::Receiver<(Req, oneshot::Sender)>, +} + +impl Receiver { + pub fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll)>> { + self.inner.poll_next(cx) + } +} diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index bd0d34c1645..00358b3bea4 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -169,7 +169,7 @@ impl Transport for WebRTCTransport { .iter() .next() .ok_or(TransportError::Other(Error::NoListeners))?; - let udp_mux = first_listener.udp_mux.clone(); + let udp_mux = first_listener.udp_mux.udp_mux_handle(); // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. @@ -258,7 +258,7 @@ pub struct WebRTCListenStream { config: WebRTCConfiguration, /// The UDP muxer that manages all ICE connections. - udp_mux: Arc, + udp_mux: UDPMuxNewAddr, /// `Keypair` identifying this peer id_keys: identity::Keypair, @@ -276,7 +276,7 @@ impl WebRTCListenStream { listener_id: ListenerId, listen_addr: SocketAddr, config: WebRTCConfiguration, - udp_mux: Arc, + udp_mux: UDPMuxNewAddr, id_keys: identity::Keypair, ) -> Self { let in_addr = InAddr::new(listen_addr.ip()); @@ -381,13 +381,13 @@ impl Stream for WebRTCListenStream { } // Poll UDP muxer for new addresses or incoming data for streams. - match ready!(self.udp_mux.as_ref().poll(cx)) { + match ready!(self.udp_mux.poll(cx)) { UDPMuxEvent::NewAddr(new_addr) => { let local_addr = socketaddr_to_multiaddr(&self.listen_addr); let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); let event = TransportEvent::Incoming { upgrade: Box::pin(upgrade( - self.udp_mux.clone(), + self.udp_mux.udp_mux_handle(), self.config.clone(), new_addr.addr, new_addr.ufrag, diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 28299ac5a1e..aa38cd1b478 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -21,7 +21,7 @@ // SOFTWARE. use async_trait::async_trait; -use futures::ready; +use futures::StreamExt; use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, @@ -29,16 +29,19 @@ use stun::{ use tokio::{io::ReadBuf, net::UdpSocket}; use tokio_crate as tokio; use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; -use webrtc::util::{sync::RwLock, Conn, Error}; - +use webrtc::util::{Conn, Error}; + +use crate::req_res_chan; +use futures::channel::oneshot; +use futures::channel::oneshot::Sender; +use futures::future::{BoxFuture, FutureExt, OptionFuture}; +use futures::stream::FuturesUnordered; +use std::collections::VecDeque; use std::{ collections::{HashMap, HashSet}, io::ErrorKind, net::SocketAddr, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, Weak, - }, + sync::Arc, task::{Context, Poll}, }; @@ -66,38 +69,72 @@ pub struct UDPMuxNewAddr { udp_sock: UdpSocket, /// Maps from ufrag to the underlying connection. - conns: Mutex>, + conns: HashMap, /// Maps from socket address to the underlying connection. - address_map: RwLock>, + address_map: HashMap, /// Set of the new addresses to avoid sending the same address multiple times. - new_addrs: RwLock>, + new_addrs: HashSet, /// `true` when UDP mux is closed. - is_closed: AtomicBool, + is_closed: bool, + + send_buffer: Option<(Vec, SocketAddr, oneshot::Sender>)>, + + close_futures: FuturesUnordered>, + write_future: OptionFuture>, + + close_command: req_res_chan::Receiver<(), Result<(), Error>>, + get_conn_command: req_res_chan::Receiver, Error>>, + remove_conn_command: req_res_chan::Receiver, + registration_command: req_res_chan::Receiver<(UDPMuxConn, SocketAddr), ()>, + send_command: req_res_chan::Receiver<(Vec, SocketAddr), Result>, + + udp_mux_handle: Arc, + udp_mux_writer_handle: Arc, } impl UDPMuxNewAddr { /// Creates a new UDP muxer. - pub fn new(udp_sock: UdpSocket) -> Arc { - Arc::new(Self { + pub fn new(udp_sock: UdpSocket) -> Self { + let (udp_mux_handle, close_command, get_conn_command, remove_conn_command) = + UdpMuxHandle::new(); + let (udp_mux_writer_handle, registration_command, send_command) = UdpMuxWriterHandle::new(); + + Self { udp_sock, - conns: Mutex::default(), - address_map: RwLock::default(), - new_addrs: RwLock::default(), - is_closed: AtomicBool::new(false), - }) + conns: HashMap::default(), + address_map: HashMap::default(), + new_addrs: HashSet::default(), + is_closed: false, + send_buffer: None, + close_futures: FuturesUnordered::default(), + write_future: OptionFuture::default(), + close_command, + get_conn_command, + remove_conn_command, + registration_command, + send_command, + udp_mux_handle: Arc::new(udp_mux_handle), + udp_mux_writer_handle: Arc::new(udp_mux_writer_handle), + } + } + + pub fn udp_mux_handle(&self) -> Arc { + self.udp_mux_handle.clone() } /// Create a muxed connection for a given ufrag. - fn create_muxed_conn(self: &Arc, ufrag: &str) -> Result { + fn create_muxed_conn(&self, ufrag: &str) -> Result { let local_addr = self.udp_sock.local_addr()?; let params = UDPMuxConnParams { local_addr, key: ufrag.into(), - udp_mux: Arc::downgrade(self) as Weak, + udp_mux: Arc::downgrade( + &(self.udp_mux_writer_handle.clone() as Arc), + ), }; Ok(UDPMuxConn::new(params)) @@ -107,10 +144,7 @@ impl UDPMuxNewAddr { /// connection. fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { match ufrag_from_stun_message(buffer, true) { - Ok(ufrag) => { - let conns = self.conns.lock().unwrap(); - conns.get(&ufrag).map(Clone::clone) - } + Ok(ufrag) => self.conns.get(&ufrag).map(Clone::clone), Err(e) => { log::debug!("{} (addr={})", e, addr); None @@ -118,215 +152,325 @@ impl UDPMuxNewAddr { } } - /// Returns true if the UDP muxer is closed. - pub fn is_closed(&self) -> bool { - return self.is_closed.load(Ordering::Relaxed); - } - /// Reads from the underlying UDP socket and either reports a new address or proxies data to the /// muxed connection. - pub fn poll(&self, cx: &mut Context) -> Poll { - // TODO: avoid allocating the buffer each time. - let mut recv_buf = [0u8; RECEIVE_MTU]; - let mut read = ReadBuf::new(&mut recv_buf); - + pub fn poll(&mut self, cx: &mut Context) -> Poll { loop { - match ready!(self.udp_sock.poll_recv_from(cx, &mut read)) { - Ok(addr) => { - // Find connection based on previously having seen this source address - let conn = { - let address_map = self.address_map.read(); - address_map.get(&addr).map(Clone::clone) - }; - - let conn = match conn { - // If we couldn't find the connection based on source address, see if - // this is a STUN mesage and if so if we can find the connection based on ufrag. - None if is_stun_message(read.filled()) => { - self.conn_from_stun_message(&read.filled(), &addr) + match self.send_buffer.take() { + None => { + if let Poll::Ready(Some(((buf, target), response))) = + self.send_command.poll_next(cx) + { + self.send_buffer = Some((buf, target, response)); + continue; + } + } + Some((buf, target, response)) => { + match self.udp_sock.poll_send_to(cx, &buf, target) { + Poll::Ready(result) => { + let _ = response.send(result.map_err(|e| Error::Io(e.into()))); + continue; } - s @ Some(_) => s, - _ => None, - }; - - match conn { - None => { - if !self.new_addrs.read().contains(&addr) { - match ufrag_from_stun_message(read.filled(), false) { - Ok(ufrag) => { - log::trace!( - "Notifying about new address addr={} from ufrag={}", - &addr, - ufrag - ); - let mut new_addrs = self.new_addrs.write(); - new_addrs.insert(addr); - return Poll::Ready(UDPMuxEvent::NewAddr(NewAddr { - addr, - ufrag, - })); - } - Err(e) => { - log::debug!( - "Unknown address addr={} (non STUN packet: {})", - &addr, - e - ); + Poll::Pending => { + self.send_buffer = Some((buf, target, response)); + } + } + } + } + + if let Poll::Ready(Some(((conn, addr), response))) = + self.registration_command.poll_next(cx) + { + let key = conn.key(); + + self.address_map + .entry(addr) + .and_modify(|e| { + if e.key() != key { + e.remove_address(&addr); + *e = conn.clone(); + } + }) + .or_insert_with(|| conn.clone()); + + // remove addr from new_addrs once conn is established + self.new_addrs.remove(&addr); + + let _ = response.send(()); + + continue; + } + + if let Poll::Ready(Some((ufrag, response))) = self.get_conn_command.poll_next(cx) { + if self.is_closed { + let _ = response.send(Err(Error::ErrUseClosedNetworkConn)); + continue; + } + + if let Some(conn) = self.conns.get(&ufrag).cloned() { + let _ = response.send(Ok(Arc::new(conn))); + continue; + } + + let muxed_conn = match self.create_muxed_conn(&ufrag) { + Ok(conn) => conn, + Err(e) => { + let _ = response.send(Err(e)); + continue; + } + }; + let mut close_rx = muxed_conn.close_rx(); + + self.close_futures.push({ + let ufrag = ufrag.clone(); + let udp_mux_handle = self.udp_mux_handle.clone(); + + Box::pin(async move { + let _ = close_rx.changed().await; + udp_mux_handle.remove_conn_by_ufrag(&ufrag).await; + }) + }); + + self.conns.insert(ufrag, muxed_conn.clone()); + + let _ = response.send(Ok(Arc::new(muxed_conn) as Arc)); + + continue; + } + + if let Poll::Ready(Some(((), response))) = self.close_command.poll_next(cx) { + if self.is_closed { + let _ = response.send(Err(Error::ErrAlreadyClosed)); + continue; + } + + for (_, conn) in self.conns.drain() { + conn.close(); + } + + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + self.address_map.clear(); + + // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to + // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. + self.new_addrs.clear(); + + let _ = response.send(Ok(())); + + self.is_closed = true; // TODO: Added by Thomas, don't we need this? + + continue; + } + + if let Poll::Ready(Some((ufrag, response))) = self.remove_conn_command.poll_next(cx) { + // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` + // is keyed on `ufrag` their implementation is equivalent. + + if let Some(removed_conn) = self.conns.remove(&ufrag) { + for address in removed_conn.get_addresses() { + self.address_map.remove(&address); + } + } + + let _ = response.send(()); + + continue; + } + + let _ = self.close_futures.poll_next_unpin(cx); + + match self.write_future.poll_unpin(cx) { + Poll::Ready(Some(())) => { + self.write_future = OptionFuture::default(); + continue; + } + Poll::Ready(None) => { + // TODO: avoid allocating the buffer each time. + let mut recv_buf = [0u8; RECEIVE_MTU]; + let mut read = ReadBuf::new(&mut recv_buf); + + match self.udp_sock.poll_recv_from(cx, &mut read) { + Poll::Ready(Ok(addr)) => { + // Find connection based on previously having seen this source address + let conn = self.address_map.get(&addr); + + let conn = match conn { + // If we couldn't find the connection based on source address, see if + // this is a STUN mesage and if so if we can find the connection based on ufrag. + None if is_stun_message(read.filled()) => { + self.conn_from_stun_message(read.filled(), &addr) + } + Some(s) => Some(s.to_owned()), + _ => None, + }; + + match conn { + None => { + if !self.new_addrs.contains(&addr) { + match ufrag_from_stun_message(read.filled(), false) { + Ok(ufrag) => { + log::trace!( + "Notifying about new address addr={} from ufrag={}", + &addr, + ufrag + ); + self.new_addrs.insert(addr); + return Poll::Ready(UDPMuxEvent::NewAddr( + NewAddr { addr, ufrag }, + )); + } + Err(e) => { + log::debug!( + "Unknown address addr={} (non STUN packet: {})", + &addr, + e + ); + } + } } } + Some(conn) => { + let mut packet = vec![0u8; read.filled().len()]; + packet.copy_from_slice(read.filled()); + self.write_future = OptionFuture::from(Some( + async move { + if let Err(err) = conn.write_packet(&packet, addr).await + { + log::error!( + "Failed to write packet: {} (addr={})", + err, + addr + ); + } + } + .boxed(), + )); + } } + + continue; } - Some(conn) => { - let mut packet = vec![0u8; read.filled().len()]; - packet.copy_from_slice(read.filled()); - write_packet_to_conn_from_addr(conn, packet, addr); + Poll::Ready(Err(err)) if err.kind() == ErrorKind::TimedOut => {} + Poll::Pending => {} + Poll::Ready(Err(err)) => { + log::error!("Could not read udp packet: {}", err); + return Poll::Ready(UDPMuxEvent::Error(err)); } } } - Err(err) if err.kind() == ErrorKind::TimedOut => {} - Err(err) => { - log::error!("Could not read udp packet: {}", err); - return Poll::Ready(UDPMuxEvent::Error(err)); - } + Poll::Pending => {} } + + return Poll::Pending; } } } -fn write_packet_to_conn_from_addr(conn: UDPMuxConn, packet: Vec, addr: SocketAddr) { - // Writing the packet should be quick given it just buffers the data (no actual IO). - // - // Block until completion instead of spawning to provide backpressure to the clients. - // NOTE: `block_on` could be removed once/if `write_packet` becomes sync. - futures::executor::block_on(async move { - if let Err(err) = conn.write_packet(&packet, addr).await { - log::error!("Failed to write packet: {} (addr={})", err, addr); - } - }); +pub struct UdpMuxHandle { + close_sender: req_res_chan::Sender<(), Result<(), Error>>, + get_conn_sender: req_res_chan::Sender, Error>>, + remove_sender: req_res_chan::Sender, } -#[async_trait] -impl UDPMux for UDPMuxNewAddr { - async fn close(&self) -> Result<(), Error> { - if self.is_closed() { - return Err(Error::ErrAlreadyClosed); - } - - let old_conns = { - let mut conns = self.conns.lock().unwrap(); - - std::mem::take(&mut (*conns)) +impl UdpMuxHandle { + pub fn new() -> ( + Self, + req_res_chan::Receiver<(), Result<(), Error>>, + req_res_chan::Receiver, Error>>, + req_res_chan::Receiver, + ) { + let (sender1, receiver1) = req_res_chan::new(1); + let (sender2, receiver2) = req_res_chan::new(1); + let (sender3, receiver3) = req_res_chan::new(1); + + let this = Self { + close_sender: sender1, + get_conn_sender: sender2, + remove_sender: sender3, }; - // NOTE: We don't wait for these closure to complete - for (_, conn) in old_conns { - conn.close(); - } - - { - let mut address_map = self.address_map.write(); - - // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to - // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. - let _ = std::mem::take(&mut (*address_map)); - } - - { - let mut new_addrs = self.new_addrs.write(); + (this, receiver1, receiver2, receiver3) + } +} - // NOTE: This is important, we need to drop all instances of `UDPMuxConn` to - // avoid a retain cycle due to the use of [`std::sync::Arc`] on both sides. - let _ = std::mem::take(&mut (*new_addrs)); - } +#[async_trait] +impl UDPMux for UdpMuxHandle { + async fn close(&self) -> Result<(), Error> { + self.close_sender + .send(()) + .await + .map_err(|e| Error::Io(e.into()))??; Ok(()) } async fn get_conn(self: Arc, ufrag: &str) -> Result, Error> { - if self.is_closed() { - return Err(Error::ErrUseClosedNetworkConn); - } - - { - let mut conns = self.conns.lock().unwrap(); - if let Some(conn) = conns.get(ufrag) { - // UDPMuxConn uses `Arc` internally so it's cheap to clone, but because - // we implement `Conn` we need to further wrap it in an `Arc` here. - return Ok(Arc::new(conn.clone()) as Arc); - } - - let muxed_conn = self.create_muxed_conn(ufrag)?; - let mut close_rx = muxed_conn.close_rx(); - let cloned_self = Arc::clone(&self); - let cloned_ufrag = ufrag.to_string(); - tokio::spawn(async move { - let _ = close_rx.changed().await; - - // Arc needed - cloned_self.remove_conn_by_ufrag(&cloned_ufrag).await; - }); + let conn = self + .get_conn_sender + .send(ufrag.to_owned()) + .await + .map_err(|e| Error::Io(e.into()))??; - conns.insert(ufrag.into(), muxed_conn.clone()); + Ok(conn) + } - Ok(Arc::new(muxed_conn) as Arc) + async fn remove_conn_by_ufrag(&self, ufrag: &str) { + if let Err(e) = self.remove_sender.send(ufrag.to_owned()).await { + log::debug!("Failed to send message through channel: {:?}", e); } } +} - async fn remove_conn_by_ufrag(&self, ufrag: &str) { - // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` - // is keyed on `ufrag` their implementation is equivalent. +pub struct UdpMuxWriterHandle { + registration_channel: req_res_chan::Sender<(UDPMuxConn, SocketAddr), ()>, + send_channel: req_res_chan::Sender<(Vec, SocketAddr), Result>, +} - let removed_conn = { - let mut conns = self.conns.lock().unwrap(); - conns.remove(ufrag) +impl UdpMuxWriterHandle { + fn new() -> ( + Self, + req_res_chan::Receiver<(UDPMuxConn, SocketAddr), ()>, + req_res_chan::Receiver<(Vec, SocketAddr), Result>, + ) { + let (sender1, receiver1) = req_res_chan::new(1); + let (sender2, receiver2) = req_res_chan::new(1); + + let this = Self { + registration_channel: sender1, + send_channel: sender2, }; - if let Some(conn) = removed_conn { - let mut address_map = self.address_map.write(); - - for address in conn.get_addresses() { - address_map.remove(&address); - } - } + (this, receiver1, receiver2) } } #[async_trait] -impl UDPMuxWriter for UDPMuxNewAddr { +impl UDPMuxWriter for UdpMuxWriterHandle { async fn register_conn_for_address(&self, conn: &UDPMuxConn, addr: SocketAddr) { - if self.is_closed() { - return; - } - - let key = conn.key(); - { - let mut addresses = self.address_map.write(); - - addresses - .entry(addr) - .and_modify(|e| { - if e.key() != key { - e.remove_address(&addr); - *e = conn.clone(); - } - }) - .or_insert_with(|| conn.clone()); - } - - // remove addr from new_addrs once conn is established + match self + .registration_channel + .send((conn.to_owned(), addr)) + .await { - let mut new_addrs = self.new_addrs.write(); - new_addrs.remove(&addr); + Ok(()) => {} + Err(e) => { + log::debug!("Failed to send message through channel: {:?}", e); + return; + } } - log::debug!("Registered {} for {}", addr, key); + log::debug!("Registered {} for {}", addr, conn.key()); } async fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result { - self.udp_sock - .send_to(buf, *target) + let bytes_written = self + .send_channel + .send((buf.to_owned(), target.to_owned())) .await - .map_err(Into::into) + .map_err(|e| Error::Io(e.into()))??; + + Ok(bytes_written) } } From 9ff67a3c678af490bc09f0fcf219c82ac21c7024 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 2 Sep 2022 10:07:35 +0400 Subject: [PATCH 060/244] update webrtc version --- Cargo.toml | 2 +- core/Cargo.toml | 2 +- transports/webrtc/src/udp_mux.rs | 34 ++++++++++++++------------------ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c5eea3c78ae..55414391866 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ libp2p-gossipsub = { version = "0.40.0", path = "protocols/gossipsub", optional # TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] multiaddr = { version = "0.15.0", git = "https://github.com/multiformats/rust-multiaddr.git", branch = "master" } -webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", rev = "8a570f2b650daac7e4455cc3d9ae094c56fbe037" } +webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", rev = "e549e67816a5278c0d208138de7e2125e17ab20c" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/core/Cargo.toml b/core/Cargo.toml index 7f72f257411..75c7971eedc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ log = "0.4" multiaddr = { version = "0.15.0" } multihash = { version = "0.16", default-features = false, features = ["std", "multihash-impl", "identity", "sha2"] } multistream-select = { version = "0.11", path = "../misc/multistream-select" } -p256 = { version = "0.10.0", default-features = false, features = ["ecdsa"], optional = true } +p256 = { version = "0.11.0", default-features = false, features = ["ecdsa"], optional = true } parking_lot = "0.12.0" pin-project = "1.0.0" prost = "0.10" diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index aa38cd1b478..efa89623df7 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -1,24 +1,22 @@ -// MIT License +// Copyright 2022 Parity Technologies (UK) Ltd. // -// Copyright (c) 2021 WebRTC.rs +// 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 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 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, +// 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. +// 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 async_trait::async_trait; use futures::StreamExt; @@ -33,10 +31,8 @@ use webrtc::util::{Conn, Error}; use crate::req_res_chan; use futures::channel::oneshot; -use futures::channel::oneshot::Sender; use futures::future::{BoxFuture, FutureExt, OptionFuture}; use futures::stream::FuturesUnordered; -use std::collections::VecDeque; use std::{ collections::{HashMap, HashSet}, io::ErrorKind, @@ -258,7 +254,7 @@ impl UDPMuxNewAddr { let _ = response.send(Ok(())); - self.is_closed = true; // TODO: Added by Thomas, don't we need this? + self.is_closed = true; continue; } From 9b1547d68a89c5839a2e9afaf5ac58338665766e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 2 Sep 2022 12:39:13 +0200 Subject: [PATCH 061/244] Don't use versions for dev-dependencies (#6) --- transports/webrtc/Cargo.toml | 3 +-- transports/webrtc/tests/smoke.rs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 81ef871543f..d635189670a 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -33,8 +33,7 @@ webrtc = "0.4.0" [dev-dependencies] anyhow = "1.0" env_logger = "0.9" -libp2p-request-response = { version = "0.20.0", path = "../../protocols/request-response" } -libp2p-swarm = { version = "0.38.0", path = "../../swarm" } +libp2p = { path = "../..", features = ["request-response"], default-features = false } rand_core = "0.5" rcgen = "0.9" quickcheck = "1" diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 8f3247ea04b..0cdf914c7c1 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -5,12 +5,12 @@ use futures::{ io::{AsyncRead, AsyncWrite, AsyncWriteExt}, stream::StreamExt, }; -use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; -use libp2p_request_response::{ +use libp2p::request_response::{ ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, }; -use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; +use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; use libp2p_webrtc::transport::WebRTCTransport; use multihash::{Code, Multihash, MultihashDigest}; use rand::RngCore; From 91df66c20aa0f55afa3e6cb1a3b2b00165466e7f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 2 Sep 2022 12:39:40 +0200 Subject: [PATCH 062/244] Remove unnecessary `async` from `create_swarm` (#5) --- transports/webrtc/tests/smoke.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 0cdf914c7c1..1d4975936e6 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -29,7 +29,7 @@ fn generate_tls_keypair() -> identity::Keypair { identity::Keypair::generate_ed25519() } -async fn create_swarm() -> Result<(Swarm>, String)> { +fn create_swarm() -> Result<(Swarm>, String)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); @@ -64,8 +64,8 @@ async fn smoke() -> Result<()> { let mut rng = rand::thread_rng(); - let (mut a, a_fingerprint) = create_swarm().await?; - let (mut b, _b_fingerprint) = create_swarm().await?; + let (mut a, a_fingerprint) = create_swarm()?; + let (mut b, _b_fingerprint) = create_swarm()?; Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; @@ -292,8 +292,8 @@ impl RequestResponseCodec for PingCodec { async fn dial_failure() -> Result<()> { let _ = env_logger::builder().is_test(true).try_init(); - let (mut a, a_fingerprint) = create_swarm().await?; - let (mut b, _b_fingerprint) = create_swarm().await?; + let (mut a, a_fingerprint) = create_swarm()?; + let (mut b, _b_fingerprint) = create_swarm()?; Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; @@ -358,7 +358,7 @@ async fn concurrent_connections_and_streams() { // Spawn the listener nodes. for _ in 0..number_listeners { - let (mut listener, fingerprint) = block_on(create_swarm()).unwrap(); + let (mut listener, fingerprint) = create_swarm().unwrap(); Swarm::listen_on( &mut listener, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap(), @@ -422,7 +422,7 @@ async fn concurrent_connections_and_streams() { .unwrap(); } - let (mut dialer, _fingerprint) = block_on(create_swarm()).unwrap(); + let (mut dialer, _fingerprint) = create_swarm().unwrap(); Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap()).unwrap(); // Wait to listen on address. From 8d0006ba1ca6194ff495ac94417dbaa973142a87 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 2 Sep 2022 16:24:06 +0400 Subject: [PATCH 063/244] fix smoke test --- transports/webrtc/tests/smoke.rs | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 1d4975936e6..413d6dff1d4 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -1,7 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use futures::{ - future::{join, select, Either, FutureExt}, + future::{select, Either, FutureExt}, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, stream::StreamExt, }; @@ -95,19 +95,28 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), } - match a.next().await { - Some(SwarmEvent::IncomingConnection { .. }) => {} - e => panic!("{:?}", e), - }; + let pair = select(a.next(), b.next()); + match pair.await { + Either::Left((Some(SwarmEvent::IncomingConnection { .. }), _)) => {} + Either::Left((e, _)) => panic!("{:?}", e), + Either::Right(_) => panic!("b completed first"), + } - let pair = join(a.next(), b.next()); + let pair = select(a.next(), b.next()); match pair.await { - ( - Some(SwarmEvent::ConnectionEstablished { .. }), - Some(SwarmEvent::ConnectionEstablished { .. }), - ) => {} - e => panic!("{:?}", e), - }; + Either::Left((Some(SwarmEvent::ConnectionEstablished { .. }), _)) => {} + Either::Left((e, _)) => panic!("{:?}", e), + Either::Right((Some(SwarmEvent::ConnectionEstablished { .. }), _)) => {} + Either::Right((e, _)) => panic!("{:?}", e), + } + + let pair = select(a.next(), b.next()); + match pair.await { + Either::Left((Some(SwarmEvent::ConnectionEstablished { .. }), _)) => {} + Either::Left((e, _)) => panic!("{:?}", e), + Either::Right((Some(SwarmEvent::ConnectionEstablished { .. }), _)) => {} + Either::Right((e, _)) => panic!("{:?}", e), + } assert!(b.next().now_or_never().is_none()); From 63996274bb800f377a6df6bb1eb97a494f947bd7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 2 Sep 2022 16:33:33 +0400 Subject: [PATCH 064/244] Connection: rename poll_address_change to poll --- transports/webrtc/src/connection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index a6b8c7ff1e6..c664ec033d6 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -29,7 +29,7 @@ use futures::{ {future::BoxFuture, prelude::*, ready}, }; use futures_lite::StreamExt; -use libp2p_core::{muxing::StreamMuxer, Multiaddr}; +use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use log::{debug, error, trace}; use webrtc::data::data_channel::DataChannel as DetachedDataChannel; use webrtc::data_channel::RTCDataChannel; @@ -173,10 +173,10 @@ impl<'a> StreamMuxer for Connection { } } - fn poll_address_change( + fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { return Poll::Pending; } From 55da918052a7c6ee864972c7166b665e29af5d90 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 5 Sep 2022 12:26:01 +0900 Subject: [PATCH 065/244] transports/webrtc/: Change semantic of RESET With https://github.com/mxinden/specs/pull/1/commits/865f4f2dea8872c8de301a16b59000ac4540f18d the RESET no longer resets both write and read part of a stream, but only the former. --- .../src/connection/poll_data_channel.rs | 84 +++++++++++++------ transports/webrtc/src/message.proto | 13 +-- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 19f6aec2061..c2df00a9ac8 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -45,40 +45,72 @@ enum State { WriteClosed { read_buffer: Bytes }, ReadClosed { read_buffer: Bytes }, ReadWriteClosed { read_buffer: Bytes }, - Reset, + ReadReset, + ReadResetWriteClosed, Poisoned, } impl State { fn handle_flag(&mut self, flag: crate::message_proto::message::Flag) { match (std::mem::replace(self, State::Poisoned), flag) { + // StopSending ( State::Open { read_buffer } | State::WriteClosed { read_buffer }, - crate::message_proto::message::Flag::CloseRead, + crate::message_proto::message::Flag::StopSending, ) => { *self = State::WriteClosed { read_buffer }; } + ( State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - crate::message_proto::message::Flag::CloseRead, + crate::message_proto::message::Flag::StopSending, ) => { *self = State::ReadWriteClosed { read_buffer }; } + + ( + State::ReadReset | State::ReadResetWriteClosed, + crate::message_proto::message::Flag::StopSending, + ) => { + *self = State::ReadResetWriteClosed; + } + + // Fin ( State::Open { read_buffer } | State::ReadClosed { read_buffer }, - crate::message_proto::message::Flag::CloseWrite, + crate::message_proto::message::Flag::Fin, ) => { *self = State::ReadClosed { read_buffer }; } + ( State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - crate::message_proto::message::Flag::CloseWrite, + crate::message_proto::message::Flag::Fin, ) => { *self = State::ReadWriteClosed { read_buffer }; } - // TODO: Or do we want to return an error? - (State::Reset, _) => *self = State::Reset, - (_, crate::message_proto::message::Flag::Reset) => *self = State::Reset, + + (State::ReadReset, crate::message_proto::message::Flag::Fin) => { + *self = State::ReadReset + } + + (State::ReadResetWriteClosed, crate::message_proto::message::Flag::Fin) => { + *self = State::ReadResetWriteClosed + } + + // Reset + ( + State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, + crate::message_proto::message::Flag::Reset, + ) => *self = State::ReadReset, + + ( + State::ReadWriteClosed { .. } + | State::WriteClosed { .. } + | State::ReadResetWriteClosed, + crate::message_proto::message::Flag::Reset, + ) => *self = State::ReadResetWriteClosed, + (State::Poisoned, _) => unreachable!(), } } @@ -89,7 +121,8 @@ impl State { State::WriteClosed { read_buffer } => Some(read_buffer), State::ReadClosed { read_buffer } => Some(read_buffer), State::ReadWriteClosed { read_buffer } => Some(read_buffer), - State::Reset => None, + State::ReadReset => None, + State::ReadResetWriteClosed => None, State::Poisoned => todo!(), } } @@ -219,7 +252,7 @@ impl AsyncRead for PollDataChannel { } None => { self.state - .handle_flag(crate::message_proto::message::Flag::CloseWrite); + .handle_flag(crate::message_proto::message::Flag::Fin); return Poll::Ready(Ok(0)); } } @@ -233,7 +266,7 @@ impl AsyncRead for PollDataChannel { .. } => return Poll::Ready(Ok(0)), PollDataChannel { - state: State::Reset, + state: State::ReadReset | State::ReadResetWriteClosed, .. } => { // TODO: Is `""` valid? @@ -257,11 +290,10 @@ impl AsyncWrite for PollDataChannel { buf: &[u8], ) -> Poll> { match self.state { - State::WriteClosed { .. } | State::ReadWriteClosed { .. } => return Poll::Ready(Ok(0)), - State::Reset => { - return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); - } - State::Open { .. } => {} + State::WriteClosed { .. } + | State::ReadWriteClosed { .. } + | State::ReadResetWriteClosed => return Poll::Ready(Ok(0)), + State::Open { .. } | State::ReadReset => {} State::ReadClosed { .. } => {} State::Poisoned => todo!(), } @@ -283,11 +315,14 @@ impl AsyncWrite for PollDataChannel { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &self.state { - State::WriteClosed { .. } | State::ReadWriteClosed { .. } => {} - State::Open { .. } | State::ReadClosed { .. } => { + State::WriteClosed { .. } + | State::ReadWriteClosed { .. } + | State::ReadResetWriteClosed { .. } => {} + + State::Open { .. } | State::ReadClosed { .. } | State::ReadReset => { ready!(self.io.poll_ready_unpin(cx))?; Pin::new(&mut self.io).start_send(crate::message_proto::Message { - flag: Some(crate::message_proto::message::Flag::CloseWrite.into()), + flag: Some(crate::message_proto::message::Flag::Fin.into()), message: None, })?; @@ -296,15 +331,16 @@ impl AsyncWrite for PollDataChannel { State::ReadClosed { read_buffer } => { self.state = State::ReadWriteClosed { read_buffer } } + State::ReadReset => self.state = State::ReadResetWriteClosed, State::WriteClosed { .. } | State::ReadWriteClosed { .. } - | State::Reset - | State::Poisoned => unreachable!(), + | State::ReadResetWriteClosed + | State::Poisoned => { + unreachable!() + } } } - State::Reset => { - return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); - } + State::Poisoned => todo!(), } diff --git a/transports/webrtc/src/message.proto b/transports/webrtc/src/message.proto index 27de2b7c45f..eab3ceb720b 100644 --- a/transports/webrtc/src/message.proto +++ b/transports/webrtc/src/message.proto @@ -4,12 +4,13 @@ package webrtc.pb; message Message { enum Flag { - // The sender will no longer send messages. - CLOSE_WRITE = 0; - // The sender will no longer read messages. - CLOSE_READ = 1; - // The local endpoint abruptly terminates the stream. The remote endpoint - // may discard any in-flight data. + // The sender will no longer send messages on the stream. + FIN = 0; + // The sender will no longer read messages on the stream. Incoming data is + // being discarded on receipt. + STOP_SENDING = 1; + // The sender abruptly terminates the sending part of the stream. The + // receiver can discard any data that it already received on that stream. RESET = 2; } From a2ae49f9536d6a7aa890c628f6fa29f32c322070 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 5 Sep 2022 11:59:42 +0400 Subject: [PATCH 066/244] sdp: move pub functions up --- transports/webrtc/src/sdp.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 5a41ddea961..ad69d04d166 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -26,6 +26,22 @@ use std::net::IpAddr; use crate::fingerprint::Fingerprint; +pub(crate) fn render_server_session_description( + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + render_description(SERVER_SESSION_DESCRIPTION, addr, fingerprint, ufrag) +} + +pub(crate) fn render_client_session_description( + addr: SocketAddr, + fingerprint: &Fingerprint, + ufrag: &str, +) -> String { + render_description(CLIENT_SESSION_DESCRIPTION, addr, fingerprint, ufrag) +} + // An SDP message that constitutes the offer. // // Main RFC: @@ -189,22 +205,6 @@ struct DescriptionContext { pub pwd: String, } -pub(crate) fn render_server_session_description( - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - render_description(SERVER_SESSION_DESCRIPTION, addr, fingerprint, ufrag) -} - -pub(crate) fn render_client_session_description( - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - render_description(CLIENT_SESSION_DESCRIPTION, addr, fingerprint, ufrag) -} - /// Renders a [`TinyTemplate`] description using the provided arguments. fn render_description( description: &str, From 31ae4221b709fdaaa3dcc40997cd1b93e086445e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 5 Sep 2022 16:03:29 +0400 Subject: [PATCH 067/244] simplify IfWatcher integration port of https://github.com/libp2p/rust-libp2p/pull/2813 --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/in_addr.rs | 120 ----------------------------- transports/webrtc/src/lib.rs | 1 - transports/webrtc/src/transport.rs | 111 ++++++++++++-------------- transports/webrtc/tests/smoke.rs | 20 ++++- 5 files changed, 71 insertions(+), 183 deletions(-) delete mode 100644 transports/webrtc/src/in_addr.rs diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index d635189670a..f5ef7cd3891 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -17,7 +17,7 @@ futures = "0.3" futures-lite = "1" futures-timer = "3" hex = "0.4" -if-watch = "0.2" +if-watch = "2.0" libp2p-core = { version = "0.35.0", path = "../../core", default-features = false } libp2p-noise = { version = "0.38.0", path = "../../transports/noise" } log = "0.4" diff --git a/transports/webrtc/src/in_addr.rs b/transports/webrtc/src/in_addr.rs deleted file mode 100644 index fd642d16dc5..00000000000 --- a/transports/webrtc/src/in_addr.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2022 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 if_watch::{IfEvent, IfWatcher}; - -use futures::{ - future::{BoxFuture, FutureExt}, - stream::Stream, -}; - -use std::{ - io::Result, - net::IpAddr, - ops::DerefMut, - pin::Pin, - task::{Context, Poll}, -}; - -/// Watches for interface changes. -#[derive(Debug)] -pub enum InAddr { - /// The socket accepts connections on a single interface. - One { ip: Option }, - /// The socket accepts connections on all interfaces. - Any { if_watch: Box }, -} - -impl InAddr { - /// If ip is specified then only one `IfEvent::Up` with IpNet(ip)/32 will be generated. - /// If ip is unspecified then `IfEvent::Up/Down` events will be generated for all interfaces. - pub fn new(ip: IpAddr) -> Self { - if ip.is_unspecified() { - let watcher = IfWatch::Pending(IfWatcher::new().boxed()); - InAddr::Any { - if_watch: Box::new(watcher), - } - } else { - InAddr::One { ip: Some(ip) } - } - } -} - -pub enum IfWatch { - Pending(BoxFuture<'static, std::io::Result>), - Ready(Box), -} - -impl std::fmt::Debug for IfWatch { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - IfWatch::Pending(_) => write!(f, "Pending"), - IfWatch::Ready(_) => write!(f, "Ready"), - } - } -} -impl Stream for InAddr { - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let me = Pin::into_inner(self); - loop { - match me { - // If the listener is bound to a single interface, make sure the - // address is reported once. - InAddr::One { ip } => { - if let Some(ip) = ip.take() { - return Poll::Ready(Some(Ok(IfEvent::Up(ip.into())))); - } - } - InAddr::Any { if_watch } => { - match if_watch.deref_mut() { - // If we listen on all interfaces, wait for `if-watch` to be ready. - IfWatch::Pending(f) => match futures::ready!(f.poll_unpin(cx)) { - Ok(watcher) => { - *if_watch = Box::new(IfWatch::Ready(Box::new(watcher))); - continue; - } - Err(err) => { - *if_watch = Box::new(IfWatch::Pending(IfWatcher::new().boxed())); - return Poll::Ready(Some(Err(err))); - } - }, - // Consume all events for up/down interface changes. - IfWatch::Ready(watcher) => { - if let Poll::Ready(ev) = watcher.poll_unpin(cx) { - match ev { - Ok(event) => { - return Poll::Ready(Some(Ok(event))); - } - Err(err) => { - return Poll::Ready(Some(Err(err))); - } - } - } - } - } - } - } - break; - } - Poll::Pending - } -} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 1ae95f8a29e..8a57dbac607 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -85,7 +85,6 @@ pub mod error; pub mod transport; mod fingerprint; -mod in_addr; mod req_res_chan; mod sdp; mod udp_mux; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 00358b3bea4..1624d69457d 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -28,7 +28,7 @@ use futures::{ stream::Stream, TryFutureExt, }; -use if_watch::IfEvent; +use if_watch::{IfEvent, IfWatcher}; use libp2p_core::{ identity, multiaddr::{Multiaddr, Protocol}, @@ -54,7 +54,6 @@ use crate::{ connection::PollDataChannel, error::Error, fingerprint::Fingerprint, - in_addr::InAddr, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, webrtc_connection::WebRTCConnection, }; @@ -107,13 +106,16 @@ impl WebRTCTransport { let udp_mux = UDPMuxNewAddr::new(socket); - Ok(WebRTCListenStream::new( + return Ok(WebRTCListenStream::new( listener_id, listen_addr, self.config.clone(), udp_mux, self.id_keys.clone(), - )) + IfWatcher::new() + .map_err(Error::IoError) + .map_err(TransportError::Other)?, + )); } } @@ -247,13 +249,6 @@ pub struct WebRTCListenStream { /// when listening on all interfaces for IPv4 respectively IPv6 connections. listen_addr: SocketAddr, - /// The IP addresses of network interfaces on which the listening socket - /// is accepting connections. - /// - /// If the listen socket listens on all interfaces, these may change over - /// time as interfaces become available or unavailable. - in_addr: InAddr, - /// The config which holds this peer's certificate(s). config: WebRTCConfiguration, @@ -268,6 +263,13 @@ pub struct WebRTCListenStream { /// Optionally contains a [`TransportEvent::ListenerClosed`] that should be /// reported before the listener's stream is terminated. report_closed: Option::Item>>, + + /// Watcher for network interface changes. + /// Reports [`IfEvent`]s for new / deleted ip-addresses when interfaces + /// become or stop being available. + /// + /// `None` if the socket is only listening on a single interface. + if_watcher: IfWatcher, } impl WebRTCListenStream { @@ -278,17 +280,16 @@ impl WebRTCListenStream { config: WebRTCConfiguration, udp_mux: UDPMuxNewAddr, id_keys: identity::Keypair, + if_watcher: IfWatcher, ) -> Self { - let in_addr = InAddr::new(listen_addr.ip()); - WebRTCListenStream { listener_id, listen_addr, - in_addr, config, udp_mux, id_keys, report_closed: None, + if_watcher, } } @@ -309,58 +310,48 @@ impl WebRTCListenStream { } } - /// Poll for a next If Event. - fn poll_if_addr(&mut self, cx: &mut Context<'_>) -> Poll<::Item> { - loop { - let mut item = ready!(self.in_addr.poll_next_unpin(cx)); - if let Some(item) = item.take() { - // Consume all events for up/down interface changes. - match item { - Ok(IfEvent::Up(inet)) => { - let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() - || self.listen_addr.is_ipv6() == ip.is_ipv6() - { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); - debug!("New listen address: {}", ma); - return Poll::Ready(TransportEvent::NewAddress { - listener_id: self.listener_id, - listen_addr: ma, - }); - } else { - continue; - } - } - Ok(IfEvent::Down(inet)) => { - let ip = inet.addr(); - if self.listen_addr.is_ipv4() == ip.is_ipv4() - || self.listen_addr.is_ipv6() == ip.is_ipv6() - { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); - debug!("Expired listen address: {}", ma); - return Poll::Ready(TransportEvent::AddressExpired { - listener_id: self.listener_id, - listen_addr: ma, - }); - } else { - continue; - } + fn poll_if_watcher(&mut self, cx: &mut Context<'_>) -> Poll<::Item> { + while let Poll::Ready(event) = self.if_watcher.poll_if_event(cx) { + match event { + Ok(IfEvent::Up(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + log::debug!("New listen address: {}", ma); + return Poll::Ready(TransportEvent::NewAddress { + listener_id: self.listener_id, + listen_addr: ma, + }); } - Err(err) => { - debug! { - "Failure polling interfaces: {:?}.", - err - }; - return Poll::Ready(TransportEvent::ListenerError { + } + Ok(IfEvent::Down(inet)) => { + let ip = inet.addr(); + if self.listen_addr.is_ipv4() == ip.is_ipv4() + || self.listen_addr.is_ipv6() == ip.is_ipv6() + { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + let ma = socketaddr_to_multiaddr(&socket_addr); + log::debug!("Expired listen address: {}", ma); + return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, - error: err.into(), + listen_addr: ma, }); } } + Err(err) => { + log::debug!("Error when polling network interfaces {}", err); + return Poll::Ready(TransportEvent::ListenerError { + listener_id: self.listener_id, + error: err.into(), + }); + } } } + + Poll::Pending } } @@ -376,7 +367,7 @@ impl Stream for WebRTCListenStream { return Poll::Ready(closed.take()); } - if let Poll::Ready(event) = self.poll_if_addr(cx) { + if let Poll::Ready(event) = self.poll_if_watcher(cx) { return Poll::Ready(Some(event)); } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 413d6dff1d4..eae884867cf 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -75,6 +75,9 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), }; + // skip other interface addresses + while let Some(_) = a.next().now_or_never() {} + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); let _ = match b.next().await { @@ -82,6 +85,9 @@ async fn smoke() -> Result<()> { e => panic!("{:?}", e), }; + // skip other interface addresses + while let Some(_) = b.next().now_or_never() {} + let mut data = vec![0; 4096]; rng.fill_bytes(&mut data); @@ -312,6 +318,9 @@ async fn dial_failure() -> Result<()> { e => panic!("{:?}", e), }; + // skip other interface addresses + while let Some(_) = a.next().now_or_never() {} + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); let _ = match b.next().await { @@ -319,6 +328,9 @@ async fn dial_failure() -> Result<()> { e => panic!("{:?}", e), }; + // skip other interface addresses + while let Some(_) = b.next().now_or_never() {} + let a_peer_id = &Swarm::local_peer_id(&a).clone(); drop(a); // stop a swarm so b can never reach it @@ -361,7 +373,7 @@ async fn concurrent_connections_and_streams() { } let mut pool = futures::executor::LocalPool::default(); - let mut data = vec![0; 4096 * 10]; + let mut data = vec![0; 4096]; rand::thread_rng().fill_bytes(&mut data); let mut listeners = vec![]; @@ -416,6 +428,9 @@ async fn concurrent_connections_and_streams() { log::debug!("listener ResponseSent"); } Some(SwarmEvent::ConnectionClosed { .. }) => {} + Some(SwarmEvent::NewListenAddr { .. }) => { + log::debug!("listener NewListenAddr"); + } Some(e) => { panic!("unexpected event {:?}", e); } @@ -489,6 +504,9 @@ async fn concurrent_connections_and_streams() { Some(SwarmEvent::ConnectionClosed { .. }) => { log::debug!("dialer ConnectionClosed"); } + Some(SwarmEvent::NewListenAddr { .. }) => { + log::debug!("dialer NewListenAddr"); + } e => { panic!("unexpected event {:?}", e); } From e0fe0deba4be07b05270918c089002a5eff4b311 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 18:07:26 +0800 Subject: [PATCH 068/244] Fix `dial_failure` test --- transports/webrtc/tests/smoke.rs | 36 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index eae884867cf..396d8f64805 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -314,32 +314,36 @@ async fn dial_failure() -> Result<()> { Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; let addr = match a.next().await { - Some(SwarmEvent::NewListenAddr { address, .. }) => address, + Some(SwarmEvent::NewListenAddr { address, .. }) => { + address.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))) + } e => panic!("{:?}", e), }; - // skip other interface addresses - while let Some(_) = a.next().now_or_never() {} + let a_peer_id = *a.local_peer_id(); + drop(a); // stop a swarm so b can never reach it - let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); + b.behaviour_mut().add_address(&a_peer_id, addr); + b.behaviour_mut() + .send_request(&a_peer_id, Ping(b"hello world".to_vec())); - let _ = match b.next().await { - Some(SwarmEvent::NewListenAddr { address, .. }) => address, + match b.next().await { + Some(SwarmEvent::Dialing(_)) => {} e => panic!("{:?}", e), - }; - - // skip other interface addresses - while let Some(_) = b.next().now_or_never() {} + } - let a_peer_id = &Swarm::local_peer_id(&a).clone(); - drop(a); // stop a swarm so b can never reach it + match b.next().await { + Some(SwarmEvent::NewListenAddr { .. }) => {} + e => panic!("{:?}", e), + } - b.behaviour_mut().add_address(a_peer_id, addr); - b.behaviour_mut() - .send_request(a_peer_id, Ping(b"hello world".to_vec())); + match b.next().await { + Some(SwarmEvent::NewListenAddr { .. }) => {} + e => panic!("{:?}", e), + } match b.next().await { - Some(SwarmEvent::Dialing(_)) => {} + Some(SwarmEvent::NewListenAddr { .. }) => {} e => panic!("{:?}", e), } From 2163b4bc490c70462dc38bd93066d5e4d2cb58df Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 18:27:11 +0800 Subject: [PATCH 069/244] Fix clippy warnings --- transports/webrtc/src/connection.rs | 5 ++--- transports/webrtc/src/fingerprint.rs | 8 ++------ transports/webrtc/src/sdp.rs | 4 ++-- transports/webrtc/src/transport.rs | 14 +++++++------- transports/webrtc/src/webrtc_connection.rs | 8 ++++---- transports/webrtc/tests/smoke.rs | 16 ++++++++-------- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index c664ec033d6..df82951ca7b 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -103,7 +103,6 @@ impl Connection { data_channel.id() ); - let data_channel = data_channel.clone(); let mut tx = tx.clone(); Box::pin(async move { @@ -148,7 +147,7 @@ impl Connection { } } -impl<'a> StreamMuxer for Connection { +impl StreamMuxer for Connection { type Substream = PollDataChannel; type Error = Error; @@ -177,7 +176,7 @@ impl<'a> StreamMuxer for Connection { self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { - return Poll::Pending; + Poll::Pending } fn poll_outbound( diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 790a8de01c1..c7a1ec8d715 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -54,7 +54,7 @@ impl Fingerprint { impl From<&[u8; 32]> for Fingerprint { fn from(t: &[u8; 32]) -> Self { - let values: Vec = t.into_iter().map(|x| format! {"{:02X}", x}).collect(); + let values: Vec = t.iter().map(|x| format! {"{:02X}", x}).collect(); Self::new_sha256(values.join(":")) } } @@ -63,11 +63,7 @@ impl From for Fingerprint { fn from(h: Multihash) -> Self { // Only support SHA-256 (0x12) for now. assert_eq!(h.code(), 0x12); - let values: Vec = h - .digest() - .into_iter() - .map(|x| format! {"{:02X}", x}) - .collect(); + let values: Vec = h.digest().iter().map(|x| format! {"{:02X}", x}).collect(); Self::new_sha256(values.join(":")) } } diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index ad69d04d166..665e0b34098 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -114,7 +114,7 @@ pub(crate) fn render_client_session_description( // a=max-message-size: // // The maximum SCTP user message size (in bytes). (RFC8841) -const CLIENT_SESSION_DESCRIPTION: &'static str = "v=0 +const CLIENT_SESSION_DESCRIPTION: &str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- c=IN {ip_version} {target_ip} @@ -166,7 +166,7 @@ a=max-message-size:100000 // a=candidate: // // A transport address for a candidate that can be used for connectivity checks (RFC8839). -const SERVER_SESSION_DESCRIPTION: &'static str = "v=0 +const SERVER_SESSION_DESCRIPTION: &str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- t=0 0 diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 1624d69457d..442b97e721a 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -83,8 +83,8 @@ impl WebRTCTransport { listener_id: ListenerId, addr: Multiaddr, ) -> Result> { - let sock_addr = multiaddr_to_socketaddr(&addr) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?; + let sock_addr = + multiaddr_to_socketaddr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; // XXX: `UdpSocket::bind` is async, so use a std socket and convert let std_sock = std::net::UdpSocket::bind(sock_addr) @@ -106,7 +106,7 @@ impl WebRTCTransport { let udp_mux = UDPMuxNewAddr::new(socket); - return Ok(WebRTCListenStream::new( + Ok(WebRTCListenStream::new( listener_id, listen_addr, self.config.clone(), @@ -115,7 +115,7 @@ impl WebRTCTransport { IfWatcher::new() .map_err(Error::IoError) .map_err(TransportError::Other)?, - )); + )) } } @@ -176,8 +176,8 @@ impl Transport for WebRTCTransport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let remote_fingerprint = - fingerprint_from_addr(&addr).ok_or(Error::InvalidMultiaddr(addr.clone()))?; + let remote_fingerprint = fingerprint_from_addr(&addr) + .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; let conn = WebRTCConnection::connect( sock_addr, @@ -452,7 +452,7 @@ pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { /// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not /// contain one. -fn fingerprint_from_addr<'a>(addr: &'a Multiaddr) -> Option { +fn fingerprint_from_addr(addr: &Multiaddr) -> Option { let iter = addr.iter(); for proto in iter { match proto { diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 3408118ef31..2a133b0fb06 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -82,7 +82,7 @@ impl WebRTCConnection { // NOTE: this will start the gathering of ICE candidates peer_connection.set_remote_description(sdp).await?; - return Ok(Self { peer_connection }); + Ok(Self { peer_connection }) } pub async fn accept( @@ -124,7 +124,7 @@ impl WebRTCConnection { log::debug!("ANSWER: {:?}", answer.sdp); peer_connection.set_local_description(answer).await?; - return Ok(Self { peer_connection }); + Ok(Self { peer_connection }) } pub async fn create_initial_upgrade_data_channel(&self) -> Result, Error> { @@ -148,9 +148,9 @@ impl WebRTCConnection { select! { res = rx => match res { Ok(detached) => Ok(detached), - Err(e) => return Err(Error::InternalError(e.to_string())), + Err(e) => Err(Error::InternalError(e.to_string())), }, - _ = Delay::new(Duration::from_secs(10)).fuse() => return Err(Error::InternalError( + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( "data channel opening took longer than 10 seconds (see logs)".into(), )) } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 396d8f64805..c1bbaa84c8f 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -76,7 +76,7 @@ async fn smoke() -> Result<()> { }; // skip other interface addresses - while let Some(_) = a.next().now_or_never() {} + while a.next().now_or_never().is_some() {} let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); @@ -86,15 +86,15 @@ async fn smoke() -> Result<()> { }; // skip other interface addresses - while let Some(_) = b.next().now_or_never() {} + while b.next().now_or_never().is_some() {} let mut data = vec![0; 4096]; rng.fill_bytes(&mut data); b.behaviour_mut() - .add_address(&Swarm::local_peer_id(&a), addr); + .add_address(Swarm::local_peer_id(&a), addr); b.behaviour_mut() - .send_request(&Swarm::local_peer_id(&a), Ping(data.clone())); + .send_request(Swarm::local_peer_id(&a), Ping(data.clone())); match b.next().await { Some(SwarmEvent::Dialing(_)) => {} @@ -171,7 +171,7 @@ async fn smoke() -> Result<()> { } a.behaviour_mut().send_request( - &Swarm::local_peer_id(&b), + Swarm::local_peer_id(&b), Ping(b"another substream".to_vec()), ); @@ -463,9 +463,9 @@ async fn concurrent_connections_and_streams() { for (listener_peer_id, listener_addr) in &listeners { dialer .behaviour_mut() - .add_address(&listener_peer_id, listener_addr.clone()); + .add_address(listener_peer_id, listener_addr.clone()); - dialer.dial(listener_peer_id.clone()).unwrap(); + dialer.dial(*listener_peer_id).unwrap(); } // Wait for responses to each request. @@ -528,6 +528,6 @@ async fn concurrent_connections_and_streams() { fn fingerprint2multihash(s: &str) -> Multihash { let mut buf = [0; 32]; - hex::decode_to_slice(s.replace(":", ""), &mut buf).unwrap(); + hex::decode_to_slice(s.replace(':', ""), &mut buf).unwrap(); Code::Sha2_256.wrap(&buf).unwrap() } From 4b2325613c19fa0f979647c4ad792a1e63109d9c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 18:28:28 +0800 Subject: [PATCH 070/244] Revert "Fix `dial_failure` test" This reverts commit e0fe0deba4be07b05270918c089002a5eff4b311. --- transports/webrtc/tests/smoke.rs | 36 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index c1bbaa84c8f..2a133b0ada8 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -314,36 +314,32 @@ async fn dial_failure() -> Result<()> { Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; let addr = match a.next().await { - Some(SwarmEvent::NewListenAddr { address, .. }) => { - address.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))) - } + Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; - let a_peer_id = *a.local_peer_id(); - drop(a); // stop a swarm so b can never reach it + // skip other interface addresses + while let Some(_) = a.next().now_or_never() {} - b.behaviour_mut().add_address(&a_peer_id, addr); - b.behaviour_mut() - .send_request(&a_peer_id, Ping(b"hello world".to_vec())); + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); - match b.next().await { - Some(SwarmEvent::Dialing(_)) => {} + let _ = match b.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), - } + }; - match b.next().await { - Some(SwarmEvent::NewListenAddr { .. }) => {} - e => panic!("{:?}", e), - } + // skip other interface addresses + while let Some(_) = b.next().now_or_never() {} - match b.next().await { - Some(SwarmEvent::NewListenAddr { .. }) => {} - e => panic!("{:?}", e), - } + let a_peer_id = &Swarm::local_peer_id(&a).clone(); + drop(a); // stop a swarm so b can never reach it + + b.behaviour_mut().add_address(a_peer_id, addr); + b.behaviour_mut() + .send_request(a_peer_id, Ping(b"hello world".to_vec())); match b.next().await { - Some(SwarmEvent::NewListenAddr { .. }) => {} + Some(SwarmEvent::Dialing(_)) => {} e => panic!("{:?}", e), } From 23e779b44a2183b1c4f628b2577f5fc42143ccca Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 18:29:46 +0800 Subject: [PATCH 071/244] Fix more clippy lints --- transports/webrtc/tests/smoke.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 2a133b0ada8..f850a69324a 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -319,7 +319,7 @@ async fn dial_failure() -> Result<()> { }; // skip other interface addresses - while let Some(_) = a.next().now_or_never() {} + while a.next().now_or_never().is_some() {} let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); @@ -329,7 +329,7 @@ async fn dial_failure() -> Result<()> { }; // skip other interface addresses - while let Some(_) = b.next().now_or_never() {} + while b.next().now_or_never().is_some() {} let a_peer_id = &Swarm::local_peer_id(&a).clone(); drop(a); // stop a swarm so b can never reach it From 44506ee124f7b653097330b6e2a63fbe9f2ffb09 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 18:48:54 +0800 Subject: [PATCH 072/244] Make `concurrent_connections_and_streams` pass by not using quickcheck --- transports/webrtc/tests/smoke.rs | 233 ++++++++++++++----------------- 1 file changed, 103 insertions(+), 130 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index f850a69324a..41edc4bbfc9 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -360,166 +360,139 @@ async fn dial_failure() -> Result<()> { async fn concurrent_connections_and_streams() { let _ = env_logger::builder().is_test(true).try_init(); - use futures::executor::block_on; - use futures::task::Spawn; - use quickcheck::*; - use std::num::NonZeroU8; - - fn prop(number_listeners: NonZeroU8, number_streams: NonZeroU8) -> TestResult { - let (number_listeners, number_streams): (u8, u8) = - (number_listeners.into(), number_streams.into()); - if number_listeners > 10 || number_streams > 10 { - return TestResult::discard(); - } - - let mut pool = futures::executor::LocalPool::default(); - let mut data = vec![0; 4096]; - rand::thread_rng().fill_bytes(&mut data); - let mut listeners = vec![]; - - // Spawn the listener nodes. - for _ in 0..number_listeners { - let (mut listener, fingerprint) = create_swarm().unwrap(); - Swarm::listen_on( - &mut listener, - "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap(), - ) - .unwrap(); - - // Wait to listen on address. - let addr = match block_on(listener.next()) { - Some(SwarmEvent::NewListenAddr { address, .. }) => address, - e => panic!("{:?}", e), - }; - - let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&fingerprint))); - - listeners.push((*listener.local_peer_id(), addr)); - - pool.spawner() - .spawn_obj( - async move { - loop { - match listener.next().await { - Some(SwarmEvent::IncomingConnection { .. }) => { - log::debug!("listener IncomingConnection"); - } - Some(SwarmEvent::ConnectionEstablished { .. }) => { - log::debug!("listener ConnectionEstablished"); - } - Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: - RequestResponseMessage::Request { - request: Ping(ping), - channel, - .. - }, - .. - })) => { - log::debug!("listener got Message"); - listener - .behaviour_mut() - .send_response(channel, Pong(ping)) - .unwrap(); - } - Some(SwarmEvent::Behaviour( - RequestResponseEvent::ResponseSent { .. }, - )) => { - log::debug!("listener ResponseSent"); - } - Some(SwarmEvent::ConnectionClosed { .. }) => {} - Some(SwarmEvent::NewListenAddr { .. }) => { - log::debug!("listener NewListenAddr"); - } - Some(e) => { - panic!("unexpected event {:?}", e); - } - None => { - panic!("listener stopped"); - } - } - } - } - .boxed() - .into(), - ) - .unwrap(); - } + let num_listeners = 3usize; + let num_streams = 8usize; - let (mut dialer, _fingerprint) = create_swarm().unwrap(); - Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap()).unwrap(); + let mut data = vec![0; 4096]; + rand::thread_rng().fill_bytes(&mut data); + let mut listeners = vec![]; + + // Spawn the listener nodes. + for _ in 0..num_listeners { + let (mut listener, fingerprint) = create_swarm().unwrap(); + Swarm::listen_on( + &mut listener, + "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap(), + ) + .unwrap(); // Wait to listen on address. - match block_on(dialer.next()) { + let addr = match listener.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), }; - // For each listener node start `number_streams` requests. - for (listener_peer_id, listener_addr) in &listeners { - dialer - .behaviour_mut() - .add_address(listener_peer_id, listener_addr.clone()); + let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&fingerprint))); - dialer.dial(*listener_peer_id).unwrap(); - } + listeners.push((*listener.local_peer_id(), addr)); - // Wait for responses to each request. - pool.run_until(async { - let mut num_responses = 0; + tokio::spawn(async move { loop { - match dialer.next().await { - Some(SwarmEvent::Dialing(_)) => { - log::debug!("dialer Dialing"); + match listener.next().await { + Some(SwarmEvent::IncomingConnection { .. }) => { + log::debug!("listener IncomingConnection"); } - Some(SwarmEvent::ConnectionEstablished { peer_id, .. }) => { - log::debug!("dialer Connection established"); - for _ in 0..number_streams { - dialer - .behaviour_mut() - .send_request(&peer_id, Ping(data.clone())); - } + Some(SwarmEvent::ConnectionEstablished { .. }) => { + log::debug!("listener ConnectionEstablished"); } Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { message: - RequestResponseMessage::Response { - response: Pong(pong), + RequestResponseMessage::Request { + request: Ping(ping), + channel, .. }, .. })) => { - log::debug!("dialer got Message"); - num_responses += 1; - assert_eq!(data, pong); - let should_be = number_listeners as usize * (number_streams) as usize; - log::debug!( - "num of responses: {}, num of listeners * num of streams: {}", - num_responses, - should_be - ); - if num_responses == should_be { - break; - } + log::debug!("listener got Message"); + listener + .behaviour_mut() + .send_response(channel, Pong(ping)) + .unwrap(); } - Some(SwarmEvent::ConnectionClosed { .. }) => { - log::debug!("dialer ConnectionClosed"); + Some(SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { .. })) => { + log::debug!("listener ResponseSent"); } + Some(SwarmEvent::ConnectionClosed { .. }) => {} Some(SwarmEvent::NewListenAddr { .. }) => { - log::debug!("dialer NewListenAddr"); + log::debug!("listener NewListenAddr"); } - e => { + Some(e) => { panic!("unexpected event {:?}", e); } + None => { + panic!("listener stopped"); + } } } }); - - TestResult::passed() } - prop(NonZeroU8::new(3).unwrap(), NonZeroU8::new(8).unwrap()); + let (mut dialer, _fingerprint) = create_swarm().unwrap(); + Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap()).unwrap(); - // QuickCheck::new().quickcheck(prop as fn(_, _) -> _); + // Wait to listen on address. + match dialer.next().await { + Some(SwarmEvent::NewListenAddr { address, .. }) => address, + e => panic!("{:?}", e), + }; + + // For each listener node start `number_streams` requests. + for (listener_peer_id, listener_addr) in &listeners { + dialer + .behaviour_mut() + .add_address(listener_peer_id, listener_addr.clone()); + + dialer.dial(*listener_peer_id).unwrap(); + } + + // Wait for responses to each request. + let mut num_responses = 0; + loop { + match dialer.next().await { + Some(SwarmEvent::Dialing(_)) => { + log::debug!("dialer Dialing"); + } + Some(SwarmEvent::ConnectionEstablished { peer_id, .. }) => { + log::debug!("dialer Connection established"); + for _ in 0..num_streams { + dialer + .behaviour_mut() + .send_request(&peer_id, Ping(data.clone())); + } + } + Some(SwarmEvent::Behaviour(RequestResponseEvent::Message { + message: + RequestResponseMessage::Response { + response: Pong(pong), + .. + }, + .. + })) => { + log::debug!("dialer got Message"); + num_responses += 1; + assert_eq!(data, pong); + let should_be = num_listeners * num_streams; + log::debug!( + "num of responses: {}, num of listeners * num of streams: {}", + num_responses, + should_be + ); + if num_responses == should_be { + break; + } + } + Some(SwarmEvent::ConnectionClosed { .. }) => { + log::debug!("dialer ConnectionClosed"); + } + Some(SwarmEvent::NewListenAddr { .. }) => { + log::debug!("dialer NewListenAddr"); + } + e => { + panic!("unexpected event {:?}", e); + } + } + } } fn fingerprint2multihash(s: &str) -> Multihash { From bb29c7a0376d308f7b0d71808e008f58db0d3c17 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 6 Sep 2022 19:12:20 +0800 Subject: [PATCH 073/244] Fix intra doc link and adjust docs --- transports/webrtc/src/udp_mux.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index efa89623df7..906e63c1e41 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -59,8 +59,10 @@ pub enum UDPMuxEvent { NewAddr(NewAddr), } -/// A modified version of [`webrtc_ice::udp_mux::UDPMuxDefault`], which reports previously unseen -/// addresses instead of ignoring them. +/// A modified version of [`webrtc::ice::udp_mux::UDPMuxDefault`]. +/// +/// - It has been rewritten to work without locks and channels instead. +/// - It reports previously unseen addresses instead of ignoring them. pub struct UDPMuxNewAddr { udp_sock: UdpSocket, From 11c016f27181fc0cd172ff97a10996c418db6196 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 10 Sep 2022 20:19:05 +0900 Subject: [PATCH 074/244] transports/webrtc/: Import message_proto types --- .../src/connection/poll_data_channel.rs | 53 ++++++++----------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index c2df00a9ac8..d5694ff7cba 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -32,11 +32,14 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +use crate::message_proto::message::Flag; +use crate::message_proto::Message; + /// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. // TODO // #[derive(Debug)] pub struct PollDataChannel { - io: Framed, prost_codec::Codec>, + io: Framed, prost_codec::Codec>, state: State, } @@ -51,64 +54,53 @@ enum State { } impl State { - fn handle_flag(&mut self, flag: crate::message_proto::message::Flag) { + fn handle_flag(&mut self, flag: Flag) { match (std::mem::replace(self, State::Poisoned), flag) { // StopSending ( State::Open { read_buffer } | State::WriteClosed { read_buffer }, - crate::message_proto::message::Flag::StopSending, + Flag::StopSending, ) => { *self = State::WriteClosed { read_buffer }; } ( State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - crate::message_proto::message::Flag::StopSending, + Flag::StopSending, ) => { *self = State::ReadWriteClosed { read_buffer }; } - ( - State::ReadReset | State::ReadResetWriteClosed, - crate::message_proto::message::Flag::StopSending, - ) => { + (State::ReadReset | State::ReadResetWriteClosed, Flag::StopSending) => { *self = State::ReadResetWriteClosed; } // Fin - ( - State::Open { read_buffer } | State::ReadClosed { read_buffer }, - crate::message_proto::message::Flag::Fin, - ) => { + (State::Open { read_buffer } | State::ReadClosed { read_buffer }, Flag::Fin) => { *self = State::ReadClosed { read_buffer }; } ( State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - crate::message_proto::message::Flag::Fin, + Flag::Fin, ) => { *self = State::ReadWriteClosed { read_buffer }; } - (State::ReadReset, crate::message_proto::message::Flag::Fin) => { - *self = State::ReadReset - } + (State::ReadReset, Flag::Fin) => *self = State::ReadReset, - (State::ReadResetWriteClosed, crate::message_proto::message::Flag::Fin) => { - *self = State::ReadResetWriteClosed - } + (State::ReadResetWriteClosed, Flag::Fin) => *self = State::ReadResetWriteClosed, // Reset - ( - State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, - crate::message_proto::message::Flag::Reset, - ) => *self = State::ReadReset, + (State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, Flag::Reset) => { + *self = State::ReadReset + } ( State::ReadWriteClosed { .. } | State::WriteClosed { .. } | State::ReadResetWriteClosed, - crate::message_proto::message::Flag::Reset, + Flag::Reset, ) => *self = State::ReadResetWriteClosed, (State::Poisoned, _) => unreachable!(), @@ -232,7 +224,7 @@ impl AsyncRead for PollDataChannel { .transpose() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? { - Some(crate::message_proto::Message { flag, message }) => { + Some(Message { flag, message }) => { assert!(read_buffer.is_empty()); if let Some(message) = message { *read_buffer = message.into(); @@ -240,7 +232,7 @@ impl AsyncRead for PollDataChannel { if let Some(flag) = flag .map(|f| { - crate::message_proto::message::Flag::from_i32(f) + Flag::from_i32(f) .ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) }) .transpose()? @@ -251,8 +243,7 @@ impl AsyncRead for PollDataChannel { continue; } None => { - self.state - .handle_flag(crate::message_proto::message::Flag::Fin); + self.state.handle_flag(Flag::Fin); return Poll::Ready(Ok(0)); } } @@ -300,7 +291,7 @@ impl AsyncWrite for PollDataChannel { ready!(self.io.poll_ready_unpin(cx))?; - Pin::new(&mut self.io).start_send(crate::message_proto::Message { + Pin::new(&mut self.io).start_send(Message { flag: None, message: Some(buf.into()), })?; @@ -321,8 +312,8 @@ impl AsyncWrite for PollDataChannel { State::Open { .. } | State::ReadClosed { .. } | State::ReadReset => { ready!(self.io.poll_ready_unpin(cx))?; - Pin::new(&mut self.io).start_send(crate::message_proto::Message { - flag: Some(crate::message_proto::message::Flag::Fin.into()), + Pin::new(&mut self.io).start_send(Message { + flag: Some(Flag::Fin.into()), message: None, })?; From 1a6e4bd81efe7aed4dd861520cc7c92756288ba2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 10 Sep 2022 20:25:56 +0900 Subject: [PATCH 075/244] transports/webrtc/: Refactor AsyncRead match arm --- .../src/connection/poll_data_channel.rs | 96 ++++++++----------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index d5694ff7cba..cb34e0e99f3 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -205,69 +205,49 @@ impl AsyncRead for PollDataChannel { } } - match &mut *self { - PollDataChannel { - state: - State::Open { - ref mut read_buffer, - }, - io, - } - | PollDataChannel { - state: - State::WriteClosed { - ref mut read_buffer, - }, - io, - } => { - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => { - assert!(read_buffer.is_empty()); - if let Some(message) = message { - *read_buffer = message.into(); - } - - if let Some(flag) = flag - .map(|f| { - Flag::from_i32(f) - .ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) - }) - .transpose()? - { - self.state.handle_flag(flag) - } - - continue; - } - None => { - self.state.handle_flag(Flag::Fin); - return Poll::Ready(Ok(0)); - } - } + let PollDataChannel { state, io } = &mut *self; + + let read_buffer = match state { + State::Open { + ref mut read_buffer, } - PollDataChannel { - state: State::ReadClosed { .. }, - .. + | State::WriteClosed { + ref mut read_buffer, + } => read_buffer, + State::ReadClosed { .. } | State::ReadWriteClosed { .. } => { + return Poll::Ready(Ok(0)) } - | PollDataChannel { - state: State::ReadWriteClosed { .. }, - .. - } => return Poll::Ready(Ok(0)), - PollDataChannel { - state: State::ReadReset | State::ReadResetWriteClosed, - .. - } => { + State::ReadReset | State::ReadResetWriteClosed => { // TODO: Is `""` valid? return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); } - PollDataChannel { - state: State::Poisoned, - .. - } => { - todo!() + State::Poisoned => todo!(), + }; + + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => { + assert!(read_buffer.is_empty()); + if let Some(message) = message { + *read_buffer = message.into(); + } + + if let Some(flag) = flag + .map(|f| { + Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) + }) + .transpose()? + { + self.state.handle_flag(flag) + } + + continue; + } + None => { + self.state.handle_flag(Flag::Fin); + return Poll::Ready(Ok(0)); } } } From d46a171ae11d6984480289deded633bde2bb57e2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 10 Sep 2022 22:38:45 +0900 Subject: [PATCH 076/244] transports/webrtc/: Handle flags when read side closed --- .../src/connection/poll_data_channel.rs | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index cb34e0e99f3..75153d50091 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -186,6 +186,27 @@ impl PollDataChannel { pub fn set_read_buf_capacity(&mut self, capacity: usize) { self.io.get_mut().set_read_buf_capacity(capacity) } + + fn io_poll_next( + io: &mut Framed, prost_codec::Codec>, + cx: &mut Context<'_>, + ) -> Poll, Option>)>>> { + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => { + let flag = flag + .map(|f| { + Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) + }) + .transpose()?; + + Poll::Ready(Ok(Some((flag, message)))) + } + None => Poll::Ready(Ok(None)), + } + } } impl AsyncRead for PollDataChannel { @@ -214,8 +235,10 @@ impl AsyncRead for PollDataChannel { | State::WriteClosed { ref mut read_buffer, } => read_buffer, - State::ReadClosed { .. } | State::ReadWriteClosed { .. } => { - return Poll::Ready(Ok(0)) + State::ReadClosed { read_buffer, .. } + | State::ReadWriteClosed { read_buffer, .. } => { + assert!(read_buffer.is_empty()); + return Poll::Ready(Ok(0)); } State::ReadReset | State::ReadResetWriteClosed => { // TODO: Is `""` valid? @@ -224,26 +247,16 @@ impl AsyncRead for PollDataChannel { State::Poisoned => todo!(), }; - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => { + match ready!(Self::io_poll_next(io, cx))? { + Some((flag, message)) => { assert!(read_buffer.is_empty()); if let Some(message) = message { *read_buffer = message.into(); } - if let Some(flag) = flag - .map(|f| { - Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) - }) - .transpose()? - { + if let Some(flag) = flag { self.state.handle_flag(flag) - } - - continue; + }; } None => { self.state.handle_flag(Flag::Fin); @@ -260,12 +273,33 @@ impl AsyncWrite for PollDataChannel { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { + // Handle flags iff read side closed. + loop { + match self.state { + State::ReadClosed { .. } | State::ReadReset => + // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the + // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + { + match Self::io_poll_next(&mut self.io, cx)? { + Poll::Ready(Some((Some(flag), message))) => { + // Read side is closed. Discard any incoming messages. + drop(message); + // But still handle flags, e.g. a `Flag::StopSending`. + self.state.handle_flag(flag) + } + Poll::Ready(Some((None, message))) => drop(message), + Poll::Ready(None) | Poll::Pending => break, + } + } + _ => break, + } + } + match self.state { State::WriteClosed { .. } | State::ReadWriteClosed { .. } | State::ReadResetWriteClosed => return Poll::Ready(Ok(0)), - State::Open { .. } | State::ReadReset => {} - State::ReadClosed { .. } => {} + State::Open { .. } | State::ReadClosed { .. } | State::ReadReset => {} State::Poisoned => todo!(), } From 9cd4ef7584c2e83e768b6dc728d911a503f1f827 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 11 Sep 2022 18:51:23 +0900 Subject: [PATCH 077/244] transports/webrtc: Enforce maximum message length --- .../src/connection/poll_data_channel.rs | 48 ++++++++++++++++++- transports/webrtc/src/lib.rs | 35 -------------- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 75153d50091..e805875dd98 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -35,6 +35,12 @@ use std::task::{Context, Poll}; use crate::message_proto::message::Flag; use crate::message_proto::Message; +// TODO: Document +const MAX_MSG_LEN: usize = 16384; // 16kiB +const VARINT_LEN: usize = 2; +const PROTO_OVERHEAD: usize = 5; +const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; + /// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. // TODO // #[derive(Debug)] @@ -305,12 +311,14 @@ impl AsyncWrite for PollDataChannel { ready!(self.io.poll_ready_unpin(cx))?; + let n = usize::min(buf.len(), MAX_DATA_LEN); + Pin::new(&mut self.io).start_send(Message { flag: None, - message: Some(buf.into()), + message: Some(buf[0..n].into()), })?; - Poll::Ready(Ok(buf.len())) + Poll::Ready(Ok(n)) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -353,3 +361,39 @@ impl AsyncWrite for PollDataChannel { self.io.poll_flush_unpin(cx).map_err(Into::into) } } + +#[cfg(test)] +mod tests { + use super::*; + use asynchronous_codec::Encoder; + use bytes::BytesMut; + use prost::Message; + use unsigned_varint::codec::UviBytes; + + #[test] + fn max_data_len() { + // Largest possible message. + let message = [0; MAX_DATA_LEN]; + + let protobuf = crate::message_proto::Message { + flag: Some(crate::message_proto::message::Flag::Fin.into()), + message: Some(message.to_vec()), + }; + + let mut encoded_msg = BytesMut::new(); + protobuf + .encode(&mut encoded_msg) + .expect("BytesMut to have sufficient capacity."); + assert_eq!(encoded_msg.len(), message.len() + PROTO_OVERHEAD); + + let mut uvi = UviBytes::default(); + let mut dst = BytesMut::new(); + uvi.encode(encoded_msg.clone().freeze(), &mut dst).unwrap(); + + // Ensure the varint prefixed and protobuf encoded largest message is no longer than the + // maximum limit specified in the libp2p WebRTC specification. + assert_eq!(dst.len(), MAX_MSG_LEN); + + assert_eq!(dst.len() - encoded_msg.len(), VARINT_LEN); + } +} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index cffabe3c6fd..4d5615353b4 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -94,38 +94,3 @@ mod webrtc_connection; mod message_proto { include!(concat!(env!("OUT_DIR"), "/webrtc.pb.rs")); } - -#[cfg(test)] -mod tests { - use super::*; - use asynchronous_codec::Encoder; - use bytes::BytesMut; - use prost::Message; - use unsigned_varint::codec::UviBytes; - - const MAX_MSG_LEN: usize = 16384; // 16kiB - const VARINT_LEN: usize = 2; - const PROTO_OVERHEAD: usize = 5; - - #[test] - fn proto_size() { - let message = [0; MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD]; - - let protobuf = message_proto::Message { - flag: Some(message_proto::message::Flag::CloseWrite.into()), - message: Some(message.to_vec()), - }; - - let mut encoded_msg = BytesMut::new(); - protobuf - .encode(&mut encoded_msg) - .expect("BytesMut to have sufficient capacity."); - assert_eq!(encoded_msg.len(), message.len() + PROTO_OVERHEAD); - - let mut uvi = UviBytes::default(); - let mut dst = BytesMut::new(); - uvi.encode(encoded_msg.clone().freeze(), &mut dst).unwrap(); - assert_eq!(dst.len(), MAX_MSG_LEN); - assert_eq!(dst.len() - encoded_msg.len(), VARINT_LEN); - } -} From 3a46fd0c82b01a1fa5a253113472a31fc060b12e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 12 Sep 2022 17:00:30 +0400 Subject: [PATCH 078/244] upgrade webrtc-rs to 0.5.0 --- Cargo.toml | 3 +-- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/webrtc_connection.rs | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e8846bd630..981f18800ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,10 +116,9 @@ libp2p-websocket = { version = "0.37.0", path = "transports/websocket", optional [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.41.0", path = "protocols/gossipsub", optional = true } -# TODO: use upstream once Protocol for WebRTC is there. [patch.crates-io] +# TODO: remove once a new version of rust-multiaddr is released multiaddr = { version = "0.15.0", git = "https://github.com/multiformats/rust-multiaddr.git", branch = "master" } -webrtc = { version = "0.4.0", git = "https://github.com/webrtc-rs/webrtc.git", rev = "e549e67816a5278c0d208138de7e2125e17ab20c" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index f5ef7cd3891..549a78ca233 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -28,7 +28,7 @@ stun = "0.4" thiserror = "1" tinytemplate = "1.2" tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} -webrtc = "0.4.0" +webrtc = "0.5.0" [dev-dependencies] anyhow = "1.0" diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 2a133b0fb06..11cbd2d554d 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -134,8 +134,7 @@ impl WebRTCConnection { .create_data_channel( "data", Some(RTCDataChannelInit { - id: Some(1), - negotiated: Some(true), + negotiated: Some(1), ..RTCDataChannelInit::default() }), ) From 018854d31bac2d3dfe11a9c6b7a5e1c0e366423d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 14 Sep 2022 11:04:42 +0400 Subject: [PATCH 079/244] fix Fingerprint::to_ufrag ufrag is now multibase multihash --- transports/webrtc/Cargo.toml | 1 + transports/webrtc/src/fingerprint.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index cf71cca9420..2b57461ff12 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -22,6 +22,7 @@ libp2p-core = { version = "0.36.0", path = "../../core", default-features = fals libp2p-noise = { version = "0.39.0", path = "../../transports/noise" } log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } +multibase = "0.9" rand = "0.8" serde = { version = "1.0", features = ["derive"] } stun = "0.4" diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index c7a1ec8d715..102523c381f 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -18,7 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multihash::Multihash; +use multibase::Base; +use multihash::{Code, Multihash, MultihashDigest}; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; @@ -36,7 +37,14 @@ impl Fingerprint { /// Transforms this fingerprint into a ufrag. pub fn to_ufrag(&self) -> String { - self.0.value.replace(':', "").to_lowercase() + // Only support SHA-256 for now. + assert_eq!(self.algorithm(), SHA256.to_owned()); + let mut buf = [0; 32]; + hex::decode_to_slice(self.0.value.replace(':', ""), &mut buf).unwrap(); + multibase::encode( + Base::Base64Url, + Code::Sha2_256.wrap(&buf).unwrap().to_bytes(), + ) } /// Returns the upper-hex value, each byte separated by ":". From 31e019acadc44e9a68b7d12e04cdcc629f93f2ef Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Sep 2022 16:17:27 +0400 Subject: [PATCH 080/244] minor refactoring --- .../src/connection/poll_data_channel.rs | 64 ++++++++----------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index e805875dd98..23e7d9a9ee7 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -192,26 +192,24 @@ impl PollDataChannel { pub fn set_read_buf_capacity(&mut self, capacity: usize) { self.io.get_mut().set_read_buf_capacity(capacity) } +} - fn io_poll_next( - io: &mut Framed, prost_codec::Codec>, - cx: &mut Context<'_>, - ) -> Poll, Option>)>>> { - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => { - let flag = flag - .map(|f| { - Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, "")) - }) - .transpose()?; - - Poll::Ready(Ok(Some((flag, message)))) - } - None => Poll::Ready(Ok(None)), +fn io_poll_next( + io: &mut Framed, prost_codec::Codec>, + cx: &mut Context<'_>, +) -> Poll, Option>)>>> { + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => { + let flag = flag + .map(|f| Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, ""))) + .transpose()?; + + Poll::Ready(Ok(Some((flag, message)))) } + None => Poll::Ready(Ok(None)), } } @@ -222,38 +220,30 @@ impl AsyncRead for PollDataChannel { buf: &mut [u8], ) -> Poll> { loop { - if let Some(read_buffer) = self.state.read_buffer_mut() { - if !read_buffer.is_empty() { - let n = std::cmp::min(read_buffer.len(), buf.len()); - let data = read_buffer.split_to(n); - buf[0..n].copy_from_slice(&data[..]); + if let Some(read_buffer) = self.state.read_buffer_mut() && !read_buffer.is_empty() { + let n = std::cmp::min(read_buffer.len(), buf.len()); + let data = read_buffer.split_to(n); + buf[0..n].copy_from_slice(&data[..]); - return Poll::Ready(Ok(n)); - } + return Poll::Ready(Ok(n)); } - let PollDataChannel { state, io } = &mut *self; + let Self { state, io } = &mut *self; let read_buffer = match state { - State::Open { - ref mut read_buffer, - } - | State::WriteClosed { - ref mut read_buffer, - } => read_buffer, + State::Open { read_buffer } | State::WriteClosed { read_buffer } => read_buffer, State::ReadClosed { read_buffer, .. } | State::ReadWriteClosed { read_buffer, .. } => { assert!(read_buffer.is_empty()); return Poll::Ready(Ok(0)); } State::ReadReset | State::ReadResetWriteClosed => { - // TODO: Is `""` valid? - return Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, ""))); + return Poll::Ready(Err(io::Error::from(io::ErrorKind::ConnectionReset))); } - State::Poisoned => todo!(), + State::Poisoned => unreachable!(), }; - match ready!(Self::io_poll_next(io, cx))? { + match ready!(io_poll_next(io, cx))? { Some((flag, message)) => { assert!(read_buffer.is_empty()); if let Some(message) = message { @@ -286,7 +276,7 @@ impl AsyncWrite for PollDataChannel { // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? { - match Self::io_poll_next(&mut self.io, cx)? { + match io_poll_next(&mut self.io, cx)? { Poll::Ready(Some((Some(flag), message))) => { // Read side is closed. Discard any incoming messages. drop(message); From 6f57ed6363c8b1db63738895a98663573a96a7cc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Sep 2022 16:24:10 +0400 Subject: [PATCH 081/244] rename PollDataChannel to Substream --- transports/webrtc/src/connection.rs | 12 +- .../{poll_data_channel.rs => substream.rs} | 164 +++++++++--------- transports/webrtc/src/transport.rs | 6 +- 3 files changed, 91 insertions(+), 91 deletions(-) rename transports/webrtc/src/connection/{poll_data_channel.rs => substream.rs} (98%) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index c664ec033d6..a807d0c09da 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/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. -mod poll_data_channel; +mod substream; use futures::{ channel::{ @@ -42,7 +42,7 @@ use std::{ }; use crate::error::Error; -pub(crate) use poll_data_channel::PollDataChannel; +pub(crate) use substream::Substream; const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; @@ -57,7 +57,7 @@ pub struct Connection { incoming_data_channels_rx: mpsc::Receiver>, /// Temporary read buffer's capacity (equal for all data channels). - /// See [`PollDataChannel`] `read_buf_cap`. + /// See [`Substream`] `read_buf_cap`. read_buf_cap: Option, /// Future, which, once polled, will result in an outbound substream. @@ -149,7 +149,7 @@ impl Connection { } impl<'a> StreamMuxer for Connection { - type Substream = PollDataChannel; + type Substream = Substream; type Error = Error; fn poll_inbound( @@ -160,7 +160,7 @@ impl<'a> StreamMuxer for Connection { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); - let mut ch = PollDataChannel::new(detached); + let mut ch = Substream::new(detached); if let Some(cap) = self.read_buf_cap { ch.set_read_buf_capacity(cap); } @@ -213,7 +213,7 @@ impl<'a> StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { - let mut ch = PollDataChannel::new(detached); + let mut ch = Substream::new(detached); if let Some(cap) = self.read_buf_cap { ch.set_read_buf_capacity(cap); } diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/substream.rs similarity index 98% rename from transports/webrtc/src/connection/poll_data_channel.rs rename to transports/webrtc/src/connection/substream.rs index 23e7d9a9ee7..b065149e3bd 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/substream.rs @@ -44,90 +44,13 @@ const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; /// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. // TODO // #[derive(Debug)] -pub struct PollDataChannel { +pub struct Substream { io: Framed, prost_codec::Codec>, state: State, } -enum State { - Open { read_buffer: Bytes }, - WriteClosed { read_buffer: Bytes }, - ReadClosed { read_buffer: Bytes }, - ReadWriteClosed { read_buffer: Bytes }, - ReadReset, - ReadResetWriteClosed, - Poisoned, -} - -impl State { - fn handle_flag(&mut self, flag: Flag) { - match (std::mem::replace(self, State::Poisoned), flag) { - // StopSending - ( - State::Open { read_buffer } | State::WriteClosed { read_buffer }, - Flag::StopSending, - ) => { - *self = State::WriteClosed { read_buffer }; - } - - ( - State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - Flag::StopSending, - ) => { - *self = State::ReadWriteClosed { read_buffer }; - } - - (State::ReadReset | State::ReadResetWriteClosed, Flag::StopSending) => { - *self = State::ReadResetWriteClosed; - } - - // Fin - (State::Open { read_buffer } | State::ReadClosed { read_buffer }, Flag::Fin) => { - *self = State::ReadClosed { read_buffer }; - } - - ( - State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - Flag::Fin, - ) => { - *self = State::ReadWriteClosed { read_buffer }; - } - - (State::ReadReset, Flag::Fin) => *self = State::ReadReset, - - (State::ReadResetWriteClosed, Flag::Fin) => *self = State::ReadResetWriteClosed, - - // Reset - (State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, Flag::Reset) => { - *self = State::ReadReset - } - - ( - State::ReadWriteClosed { .. } - | State::WriteClosed { .. } - | State::ReadResetWriteClosed, - Flag::Reset, - ) => *self = State::ReadResetWriteClosed, - - (State::Poisoned, _) => unreachable!(), - } - } - - fn read_buffer_mut(&mut self) -> Option<&mut Bytes> { - match self { - State::Open { read_buffer } => Some(read_buffer), - State::WriteClosed { read_buffer } => Some(read_buffer), - State::ReadClosed { read_buffer } => Some(read_buffer), - State::ReadWriteClosed { read_buffer } => Some(read_buffer), - State::ReadReset => None, - State::ReadResetWriteClosed => None, - State::Poisoned => todo!(), - } - } -} - -impl PollDataChannel { - /// Constructs a new `PollDataChannel`. +impl Substream { + /// Constructs a new `Substream`. pub fn new(data_channel: Arc) -> Self { Self { io: Framed::new( @@ -213,7 +136,7 @@ fn io_poll_next( } } -impl AsyncRead for PollDataChannel { +impl AsyncRead for Substream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -263,7 +186,7 @@ impl AsyncRead for PollDataChannel { } } -impl AsyncWrite for PollDataChannel { +impl AsyncWrite for Substream { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -352,6 +275,83 @@ impl AsyncWrite for PollDataChannel { } } +enum State { + Open { read_buffer: Bytes }, + WriteClosed { read_buffer: Bytes }, + ReadClosed { read_buffer: Bytes }, + ReadWriteClosed { read_buffer: Bytes }, + ReadReset, + ReadResetWriteClosed, + Poisoned, +} + +impl State { + fn handle_flag(&mut self, flag: Flag) { + match (std::mem::replace(self, State::Poisoned), flag) { + // StopSending + ( + State::Open { read_buffer } | State::WriteClosed { read_buffer }, + Flag::StopSending, + ) => { + *self = State::WriteClosed { read_buffer }; + } + + ( + State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, + Flag::StopSending, + ) => { + *self = State::ReadWriteClosed { read_buffer }; + } + + (State::ReadReset | State::ReadResetWriteClosed, Flag::StopSending) => { + *self = State::ReadResetWriteClosed; + } + + // Fin + (State::Open { read_buffer } | State::ReadClosed { read_buffer }, Flag::Fin) => { + *self = State::ReadClosed { read_buffer }; + } + + ( + State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, + Flag::Fin, + ) => { + *self = State::ReadWriteClosed { read_buffer }; + } + + (State::ReadReset, Flag::Fin) => *self = State::ReadReset, + + (State::ReadResetWriteClosed, Flag::Fin) => *self = State::ReadResetWriteClosed, + + // Reset + (State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, Flag::Reset) => { + *self = State::ReadReset + } + + ( + State::ReadWriteClosed { .. } + | State::WriteClosed { .. } + | State::ReadResetWriteClosed, + Flag::Reset, + ) => *self = State::ReadResetWriteClosed, + + (State::Poisoned, _) => unreachable!(), + } + } + + fn read_buffer_mut(&mut self) -> Option<&mut Bytes> { + match self { + State::Open { read_buffer } => Some(read_buffer), + State::WriteClosed { read_buffer } => Some(read_buffer), + State::ReadClosed { read_buffer } => Some(read_buffer), + State::ReadWriteClosed { read_buffer } => Some(read_buffer), + State::ReadReset => None, + State::ReadResetWriteClosed => None, + State::Poisoned => todo!(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 00358b3bea4..f79726d796c 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -51,7 +51,7 @@ use std::{ use crate::{ connection::Connection, - connection::PollDataChannel, + connection::Substream, error::Error, fingerprint::Fingerprint, in_addr::InAddr, @@ -191,7 +191,7 @@ impl Transport for WebRTCTransport { trace!("noise handshake with addr={}", remote); let peer_id = perform_noise_handshake_outbound( id_keys, - PollDataChannel::new(data_channel.clone()), + Substream::new(data_channel.clone()), our_fingerprint, remote_fingerprint, ) @@ -579,7 +579,7 @@ async fn upgrade( let remote_fingerprint = conn.get_remote_fingerprint().await; let peer_id = perform_noise_handshake_inbound( id_keys, - PollDataChannel::new(data_channel.clone()), + Substream::new(data_channel.clone()), our_fingerprint, remote_fingerprint, ) From 4a1d4d672095af8aa9764946a7e649bc6f1c4d87 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Sep 2022 17:32:25 +0400 Subject: [PATCH 082/244] add comments --- transports/webrtc/src/connection/substream.rs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/transports/webrtc/src/connection/substream.rs b/transports/webrtc/src/connection/substream.rs index b065149e3bd..09f1533788c 100644 --- a/transports/webrtc/src/connection/substream.rs +++ b/transports/webrtc/src/connection/substream.rs @@ -27,23 +27,29 @@ use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc::data::data_channel::DataChannel; use webrtc::data::data_channel::PollDataChannel as RTCPollDataChannel; -use std::io; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; +use std::{ + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; use crate::message_proto::message::Flag; use crate::message_proto::Message; -// TODO: Document +/// Maximum length of a message, in bytes. const MAX_MSG_LEN: usize = 16384; // 16kiB +/// Length of varint, in bytes. const VARINT_LEN: usize = 2; +/// Overhead of the protobuf encoding, in bytes. const PROTO_OVERHEAD: usize = 5; +/// Maximum length of data, in bytes. const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; -/// A wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / [`AsyncWrite`]. -// TODO -// #[derive(Debug)] +/// Substream is a wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / +/// [`AsyncWrite`] and message framing (as per specification). +/// +/// #[derive(Debug)] pub struct Substream { io: Framed, prost_codec::Codec>, state: State, @@ -55,8 +61,7 @@ impl Substream { Self { io: Framed::new( RTCPollDataChannel::new(data_channel).compat(), - // TODO: Fix MAX - prost_codec::Codec::new(usize::MAX), + prost_codec::Codec::new(MAX_MSG_LEN), ), state: State::Open { read_buffer: Default::default(), From c6c5a963fe6dedcf515c85f385bdf2bc2f418d45 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 20 Sep 2022 09:32:08 +0400 Subject: [PATCH 083/244] add debug to handle_flag --- transports/webrtc/src/connection/substream.rs | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/connection/substream.rs b/transports/webrtc/src/connection/substream.rs index 09f1533788c..ca35dc48dab 100644 --- a/transports/webrtc/src/connection/substream.rs +++ b/transports/webrtc/src/connection/substream.rs @@ -28,7 +28,7 @@ use webrtc::data::data_channel::DataChannel; use webrtc::data::data_channel::PollDataChannel as RTCPollDataChannel; use std::{ - io, + fmt, io, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -156,6 +156,7 @@ impl AsyncRead for Substream { return Poll::Ready(Ok(n)); } + let substream_id = self.stream_identifier(); let Self { state, io } = &mut *self; let read_buffer = match state { @@ -179,11 +180,11 @@ impl AsyncRead for Substream { } if let Some(flag) = flag { - self.state.handle_flag(flag) + self.state.handle_flag(flag, substream_id) }; } None => { - self.state.handle_flag(Flag::Fin); + self.state.handle_flag(Flag::Fin, substream_id); return Poll::Ready(Ok(0)); } } @@ -197,6 +198,7 @@ impl AsyncWrite for Substream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { + let substream_id = self.stream_identifier(); // Handle flags iff read side closed. loop { match self.state { @@ -209,7 +211,7 @@ impl AsyncWrite for Substream { // Read side is closed. Discard any incoming messages. drop(message); // But still handle flags, e.g. a `Flag::StopSending`. - self.state.handle_flag(flag) + self.state.handle_flag(flag, substream_id) } Poll::Ready(Some((None, message))) => drop(message), Poll::Ready(None) | Poll::Pending => break, @@ -291,7 +293,8 @@ enum State { } impl State { - fn handle_flag(&mut self, flag: Flag) { + fn handle_flag(&mut self, flag: Flag, substream_id: u16) { + let old_state = format!("{}", self); match (std::mem::replace(self, State::Poisoned), flag) { // StopSending ( @@ -342,6 +345,14 @@ impl State { (State::Poisoned, _) => unreachable!(), } + + log::debug!( + "substream={}: got flag {:?}, moved from {} to {}", + substream_id, + flag, + old_state, + *self + ); } fn read_buffer_mut(&mut self) -> Option<&mut Bytes> { @@ -357,6 +368,20 @@ impl State { } } +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + State::Open { .. } => write!(f, "Open"), + State::WriteClosed { .. } => write!(f, "WriteClosed"), + State::ReadClosed { .. } => write!(f, "ReadClosed"), + State::ReadWriteClosed { .. } => write!(f, "ReadWriteClosed"), + State::ReadReset => write!(f, "ReadReset"), + State::ReadResetWriteClosed => write!(f, "ReadResetWriteClosed"), + State::Poisoned => write!(f, "Poisoned"), + } + } +} + #[cfg(test)] mod tests { use super::*; From 7e630a634c3cd82a2286e19f208e2203e86173a6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 22 Sep 2022 12:27:57 +0400 Subject: [PATCH 084/244] use newly added NoiseConfig::with_prologue Refs https://github.com/paritytech/smoldot/pull/2759/files --- transports/webrtc/src/fingerprint.rs | 12 ++++- transports/webrtc/src/lib.rs | 2 +- transports/webrtc/src/transport.rs | 74 ++++++++-------------------- transports/webrtc/tests/smoke.rs | 18 +++---- 4 files changed, 38 insertions(+), 68 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 102523c381f..76d0e511e5f 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -24,7 +24,7 @@ use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; -pub(crate) struct Fingerprint(RTCDtlsFingerprint); +pub struct Fingerprint(RTCDtlsFingerprint); impl Fingerprint { /// Creates new `Fingerprint` w/ "sha-256" hash function. @@ -76,6 +76,16 @@ impl From for Fingerprint { } } +impl Into for Fingerprint { + fn into(self) -> Multihash { + // Only support SHA-256 for now. + assert_eq!(self.algorithm(), SHA256.to_owned()); + let mut buf = [0; 32]; + hex::decode_to_slice(self.0.value.replace(':', ""), &mut buf).unwrap(); + Code::Sha2_256.wrap(&buf).unwrap() + } +} + // TODO: derive when RTCDtlsFingerprint implements Eq. impl PartialEq for Fingerprint { fn eq(&self, other: &Self) -> bool { diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 8a57dbac607..660ae739282 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -82,9 +82,9 @@ pub mod connection; pub mod error; +pub mod fingerprint; pub mod transport; -mod fingerprint; mod req_res_chan; mod sdp; mod udp_mux; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 442b97e721a..443a60cf54d 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -21,7 +21,7 @@ use futures::{ future, future::BoxFuture, - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + io::{AsyncRead, AsyncWrite}, prelude::*, ready, stream::SelectAll, @@ -37,6 +37,7 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; use log::{debug, trace}; +use multihash::Multihash; use tokio_crate::net::UdpSocket; use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; @@ -504,9 +505,10 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = NoiseConfig::xx(dh_keys); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise + let (peer_id, _noise_io) = noise .upgrade_outbound(poll_data_channel, info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), @@ -514,28 +516,6 @@ where }) .await?; - // Exchange TLS certificate fingerprints to prevent MiM attacks. - debug!( - "exchanging TLS certificate fingerprints with peer_id={}", - peer_id - ); - debug_assert_eq!("sha-256", our_fingerprint.algorithm()); - let n = noise_io - .write(&our_fingerprint.value().into_bytes()) - .await?; - noise_io.flush().await?; - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = Fingerprint::new_sha256( - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?, - ); - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint.value(), - got: fingerprint_from_noise.value(), - }); - } - Ok(peer_id) } @@ -602,45 +582,31 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = NoiseConfig::xx(dh_keys); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); - let (peer_id, mut noise_io) = noise + let (peer_id, _noise_io) = noise .upgrade_inbound(poll_data_channel, info) .and_then(|(remote, io)| match remote { RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), _ => future::err(NoiseError::AuthenticationFailed), }) .await?; - - // Exchange TLS certificate fingerprints to prevent MiM attacks. - debug!( - "exchanging TLS certificate fingerprints with peer_id={}", - peer_id - ); - - // 1. Submit SHA-256 fingerprint - debug_assert_eq!("sha-256", our_fingerprint.algorithm()); - let n = noise_io - .write(&our_fingerprint.value().into_bytes()) - .await?; - noise_io.flush().await?; - - // 2. Receive one too and compare it to the fingerprint of the remote DTLS certificate. - let mut buf = vec![0; n]; // ASSERT: fingerprint's format is the same. - noise_io.read_exact(buf.as_mut_slice()).await?; - let fingerprint_from_noise = Fingerprint::new_sha256( - String::from_utf8(buf).map_err(|_| Error::Noise(NoiseError::AuthenticationFailed))?, - ); - if fingerprint_from_noise != remote_fingerprint { - return Err(Error::InvalidFingerprint { - expected: remote_fingerprint.value(), - got: fingerprint_from_noise.value(), - }); - } - Ok(peer_id) } +fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { + let (a, b): (Multihash, Multihash) = (our_fingerprint.into(), remote_fingerprint.into()); + let (a, b) = (a.to_bytes(), b.to_bytes()); + let (first, second) = if a < b { (a, b) } else { (b, a) }; + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; + let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); + out.extend_from_slice(PREFIX); + out.extend_from_slice(&first); + out.extend_from_slice(&second); + out +} + // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 41edc4bbfc9..3251db9e532 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -11,8 +11,8 @@ use libp2p::request_response::{ }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; +use libp2p_webrtc::fingerprint::Fingerprint; use libp2p_webrtc::transport::WebRTCTransport; -use multihash::{Code, Multihash, MultihashDigest}; use rand::RngCore; use rcgen::KeyPair; use tokio_crate as tokio; @@ -29,7 +29,7 @@ fn generate_tls_keypair() -> identity::Keypair { identity::Keypair::generate_ed25519() } -fn create_swarm() -> Result<(Swarm>, String)> { +fn create_swarm() -> Result<(Swarm>, Fingerprint)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); @@ -54,7 +54,7 @@ fn create_swarm() -> Result<(Swarm>, String)> { tokio::spawn(fut); })) .build(), - fingerprint, + Fingerprint::new_sha256(fingerprint), )) } @@ -78,7 +78,7 @@ async fn smoke() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); + let addr = addr.with(Protocol::Certhash(a_fingerprint.into())); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -321,7 +321,7 @@ async fn dial_failure() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&a_fingerprint))); + let addr = addr.with(Protocol::Certhash(a_fingerprint.into())); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -382,7 +382,7 @@ async fn concurrent_connections_and_streams() { e => panic!("{:?}", e), }; - let addr = addr.with(Protocol::Certhash(fingerprint2multihash(&fingerprint))); + let addr = addr.with(Protocol::Certhash(fingerprint.into())); listeners.push((*listener.local_peer_id(), addr)); @@ -494,9 +494,3 @@ async fn concurrent_connections_and_streams() { } } } - -fn fingerprint2multihash(s: &str) -> Multihash { - let mut buf = [0; 32]; - hex::decode_to_slice(s.replace(':', ""), &mut buf).unwrap(); - Code::Sha2_256.wrap(&buf).unwrap() -} From 2868d4835d306e488b398b42b39742d069c92944 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 22 Sep 2022 13:30:34 +0400 Subject: [PATCH 085/244] use into_authenticated fn credits to @thomaseizinger --- transports/webrtc/src/transport.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 443a60cf54d..76358138905 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -19,14 +19,12 @@ // DEALINGS IN THE SOFTWARE. use futures::{ - future, future::BoxFuture, io::{AsyncRead, AsyncWrite}, prelude::*, ready, stream::SelectAll, stream::Stream, - TryFutureExt, }; use if_watch::{IfEvent, IfWatcher}; use libp2p_core::{ @@ -35,7 +33,7 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, InboundUpgrade, OutboundUpgrade, PeerId, Transport, UpgradeInfo, }; -use libp2p_noise::{Keypair, NoiseConfig, NoiseError, RemoteIdentity, X25519Spec}; +use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use log::{debug, trace}; use multihash::Multihash; use tokio_crate::net::UdpSocket; @@ -509,11 +507,8 @@ where NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise + .into_authenticated() .upgrade_outbound(poll_data_channel, info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) .await?; Ok(peer_id) @@ -586,11 +581,8 @@ where NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise + .into_authenticated() .upgrade_inbound(poll_data_channel, info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(NoiseError::AuthenticationFailed), - }) .await?; Ok(peer_id) } From 0c45291418e76c2ce4e1e42f4af2ef2fab036d9a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 18:06:02 +1000 Subject: [PATCH 086/244] Slim down public interface of `libp2p-webrtc` --- transports/webrtc/src/lib.rs | 14 +++++++++----- transports/webrtc/tests/smoke.rs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 660ae739282..f7366d28967 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -80,12 +80,16 @@ //! is to make the hash a part of the remote's multiaddr. On the server side, we turn //! certificate verification off. -pub mod connection; -pub mod error; -pub mod fingerprint; -pub mod transport; - +mod connection; +mod error; +mod fingerprint; mod req_res_chan; mod sdp; +mod transport; mod udp_mux; mod webrtc_connection; + +pub use connection::Connection; +pub use error::Error; +pub use fingerprint::Fingerprint; +pub use transport::{WebRTCListenStream, WebRTCTransport}; diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 3251db9e532..bfb8c0c8a08 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -11,8 +11,8 @@ use libp2p::request_response::{ }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; -use libp2p_webrtc::fingerprint::Fingerprint; -use libp2p_webrtc::transport::WebRTCTransport; +use libp2p_webrtc::Fingerprint; +use libp2p_webrtc::WebRTCTransport; use rand::RngCore; use rcgen::KeyPair; use tokio_crate as tokio; From bbd7e75f9f867bd7a693d6ed109a61ee0cd4694c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 18:13:10 +1000 Subject: [PATCH 087/244] Remove `Fingerprint` argument from `render_client_session_description` --- transports/webrtc/src/sdp.rs | 16 ++++++++++------ transports/webrtc/src/webrtc_connection.rs | 7 ++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 665e0b34098..8a008bf7d64 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -34,12 +34,16 @@ pub(crate) fn render_server_session_description( render_description(SERVER_SESSION_DESCRIPTION, addr, fingerprint, ufrag) } -pub(crate) fn render_client_session_description( - addr: SocketAddr, - fingerprint: &Fingerprint, - ufrag: &str, -) -> String { - render_description(CLIENT_SESSION_DESCRIPTION, addr, fingerprint, ufrag) +/// Renders the SDP client session description. +/// +/// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. +pub(crate) fn render_client_session_description(addr: SocketAddr, ufrag: &str) -> String { + render_description( + CLIENT_SESSION_DESCRIPTION, + addr, + &Fingerprint::new_sha256("FF".to_owned()), + ufrag, + ) } // An SDP message that constitutes the offer. diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 11cbd2d554d..bf712e97b3d 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -109,11 +109,8 @@ impl WebRTCConnection { let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; - let client_session_description = crate::sdp::render_client_session_description( - addr, - &Fingerprint::new_sha256("NONE".to_owned()), // certificate verification is disabled, so any value is okay. - remote_ufrag, - ); + let client_session_description = + crate::sdp::render_client_session_description(addr, remote_ufrag); log::debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); peer_connection.set_remote_description(sdp).await?; From e84eb7b8f4f2e96ec41b2376634d611fad952a1d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:05:03 +1000 Subject: [PATCH 088/244] Refactor `Fingerprint` type --- transports/webrtc/Cargo.toml | 1 + transports/webrtc/src/fingerprint.rs | 115 +++++++++++---------- transports/webrtc/src/sdp.rs | 9 +- transports/webrtc/src/transport.rs | 27 +++-- transports/webrtc/src/webrtc_connection.rs | 8 +- transports/webrtc/tests/smoke.rs | 16 +-- 6 files changed, 91 insertions(+), 85 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 2b57461ff12..d2c140e7ed9 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -38,3 +38,4 @@ libp2p = { path = "../..", features = ["request-response"], default-features = f rand_core = "0.5" rcgen = "0.9" quickcheck = "1" +hex-literal = "0.3" diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 76d0e511e5f..fdf62f0f0b1 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -19,77 +19,88 @@ // DEALINGS IN THE SOFTWARE. use multibase::Base; -use multihash::{Code, Multihash, MultihashDigest}; +use multihash::{Code, Hasher, Multihash, MultihashDigest}; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; -pub struct Fingerprint(RTCDtlsFingerprint); +/// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. +#[derive(Eq, PartialEq)] +pub struct Fingerprint([u8; 32]); impl Fingerprint { - /// Creates new `Fingerprint` w/ "sha-256" hash function. - pub fn new_sha256(value: String) -> Self { - Self(RTCDtlsFingerprint { - algorithm: SHA256.to_owned(), - value, - }) + pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); + + pub fn raw(bytes: [u8; 32]) -> Self { + Self(bytes) } - /// Transforms this fingerprint into a ufrag. - pub fn to_ufrag(&self) -> String { - // Only support SHA-256 for now. - assert_eq!(self.algorithm(), SHA256.to_owned()); - let mut buf = [0; 32]; - hex::decode_to_slice(self.0.value.replace(':', ""), &mut buf).unwrap(); - multibase::encode( - Base::Base64Url, - Code::Sha2_256.wrap(&buf).unwrap().to_bytes(), - ) + pub fn from_certificate(bytes: &[u8]) -> Self { + let mut h = multihash::Sha2_256::default(); + h.update(&bytes); + + let mut bytes: [u8; 32] = [0; 32]; + bytes.copy_from_slice(h.finalize()); + + Fingerprint(bytes) } - /// Returns the upper-hex value, each byte separated by ":". - /// E.g. "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC" - pub fn value(&self) -> String { - self.0.value.clone() + pub fn try_from_rtc_dtls(fp: RTCDtlsFingerprint) -> Option { + if fp.algorithm != SHA256 { + return None; + } + + let mut buf = [0; 32]; + hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; + + Some(Self(buf)) } - /// Returns the algorithm used (e.g. "sha-256"). - /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 - pub fn algorithm(&self) -> String { - self.0.algorithm.clone() + pub fn try_from_multihash(hash: Multihash) -> Option { + if hash.code() != 0x12 { + // Only support SHA256 for now. + return None; + } + + let bytes = hash.digest().try_into().ok()?; + + Some(Self(bytes)) } -} -impl From<&[u8; 32]> for Fingerprint { - fn from(t: &[u8; 32]) -> Self { - let values: Vec = t.iter().map(|x| format! {"{:02X}", x}).collect(); - Self::new_sha256(values.join(":")) + pub fn to_multi_hash(&self) -> Multihash { + Code::Sha2_256.wrap(&self.0).unwrap() } -} -impl From for Fingerprint { - fn from(h: Multihash) -> Self { - // Only support SHA-256 (0x12) for now. - assert_eq!(h.code(), 0x12); - let values: Vec = h.digest().iter().map(|x| format! {"{:02X}", x}).collect(); - Self::new_sha256(values.join(":")) + /// Transforms this fingerprint into a ufrag. + pub fn to_ufrag(&self) -> String { + multibase::encode( + Base::Base64Url, + Code::Sha2_256.wrap(&self.0).unwrap().to_bytes(), + ) } -} -impl Into for Fingerprint { - fn into(self) -> Multihash { - // Only support SHA-256 for now. - assert_eq!(self.algorithm(), SHA256.to_owned()); - let mut buf = [0; 32]; - hex::decode_to_slice(self.0.value.replace(':', ""), &mut buf).unwrap(); - Code::Sha2_256.wrap(&buf).unwrap() + /// Formats this fingerprint as uppercase hex, separated by colons (`:`). + /// + /// This is the format described in . + /// + /// # Example + /// + /// ```rust + /// # use hex_literal::hex; + /// # use libp2p_webrtc::Fingerprint; + /// let fp = Fingerprint::raw(hex!("7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC")); + /// + /// let sdp_format = fp.to_sdp_format(); + /// + /// assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") + /// ``` + pub fn to_sdp_format(&self) -> String { + self.0.map(|byte| format!("{:02X}", byte)).join(":") } -} -// TODO: derive when RTCDtlsFingerprint implements Eq. -impl PartialEq for Fingerprint { - fn eq(&self, other: &Self) -> bool { - self.0.algorithm == other.algorithm() && self.0.value == other.value() + /// Returns the algorithm used (e.g. "sha-256"). + /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 + pub fn algorithm(&self) -> String { + SHA256.to_owned() } } -impl Eq for Fingerprint {} diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 8a008bf7d64..0e8425c4094 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -38,12 +38,7 @@ pub(crate) fn render_server_session_description( /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. pub(crate) fn render_client_session_description(addr: SocketAddr, ufrag: &str) -> String { - render_description( - CLIENT_SESSION_DESCRIPTION, - addr, - &Fingerprint::new_sha256("FF".to_owned()), - ufrag, - ) + render_description(CLIENT_SESSION_DESCRIPTION, addr, &Fingerprint::FF, ufrag) } // An SDP message that constitutes the offer. @@ -230,7 +225,7 @@ fn render_description( target_ip: addr.ip(), target_port: addr.port(), fingerprint_algorithm: fingerprint.algorithm(), - fingerprint_value: fingerprint.value(), + fingerprint_value: fingerprint.to_sdp_format(), // NOTE: ufrag is equal to pwd. ufrag: ufrag.to_owned(), pwd: ufrag.to_owned(), diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 76358138905..926fbd5e482 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -421,18 +421,18 @@ impl WebRTCConfiguration { /// Panics if the config does not contain any certificates. pub fn fingerprint_of_first_certificate(&self) -> Fingerprint { // safe to unwrap here because we require a certificate during construction. - let fingerprints = self + let fingerprint = self .inner .certificates .first() .expect("at least one certificate") .get_fingerprints() - .expect("get_fingerprints to succeed"); - // TODO: - // modify webrtc-rs to return value in upper-hex rather than lower-hex - // Fingerprint::from(fingerprints.first().unwrap()) - debug_assert_eq!("sha-256", fingerprints.first().unwrap().algorithm); - Fingerprint::new_sha256(fingerprints.first().unwrap().value.to_uppercase()) + .expect("get_fingerprints to succeed") + .first() + .expect("at least one certificate") + .clone(); + + Fingerprint::try_from_rtc_dtls(fingerprint).expect("a sha256 fingerprint") } /// Consumes the `WebRTCConfiguration`, returning its inner configuration. @@ -456,7 +456,13 @@ fn fingerprint_from_addr(addr: &Multiaddr) -> Option { for proto in iter { match proto { // Only support SHA-256 (0x12) for now. - Protocol::Certhash(f) if f.code() == 0x12 => return Some(Fingerprint::from(f)), + Protocol::Certhash(hash) => { + if let Some(fp) = Fingerprint::try_from_multihash(hash) { + return Some(fp); + } else { + continue; + } + } _ => continue, } } @@ -588,7 +594,10 @@ where } fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { - let (a, b): (Multihash, Multihash) = (our_fingerprint.into(), remote_fingerprint.into()); + let (a, b): (Multihash, Multihash) = ( + our_fingerprint.to_multi_hash(), + remote_fingerprint.to_multi_hash(), + ); let (a, b) = (a.to_bytes(), b.to_bytes()); let (first, second) = if a < b { (a, b) } else { (b, a) }; const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index bf712e97b3d..19a7250b8e0 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -20,7 +20,6 @@ use futures::{channel::oneshot, prelude::*, select}; use futures_timer::Delay; -use multihash::Hasher; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use webrtc::api::setting_engine::SettingEngine; @@ -164,11 +163,8 @@ impl WebRTCConnection { .transport() .get_remote_certificate() .await; - let mut h = multihash::Sha2_256::default(); - h.update(&cert_bytes); - let mut bytes: [u8; 32] = [0; 32]; - bytes.copy_from_slice(h.finalize()); - Fingerprint::from(&bytes) + + Fingerprint::from_certificate(&cert_bytes) } fn setting_engine( diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index bfb8c0c8a08..e5b97a4569d 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -33,13 +33,7 @@ fn create_swarm() -> Result<(Swarm>, Fingerprint)> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let fingerprint = cert - .get_fingerprints() - .unwrap() - .first() - .unwrap() - .value - .to_uppercase(); + let fingerprint = cert.get_fingerprints().unwrap().first().unwrap().clone(); let transport = WebRTCTransport::new(cert, keypair); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -54,7 +48,7 @@ fn create_swarm() -> Result<(Swarm>, Fingerprint)> { tokio::spawn(fut); })) .build(), - Fingerprint::new_sha256(fingerprint), + Fingerprint::try_from_rtc_dtls(fingerprint).unwrap(), )) } @@ -78,7 +72,7 @@ async fn smoke() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(a_fingerprint.into())); + let addr = addr.with(Protocol::Certhash(a_fingerprint.to_multi_hash())); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -321,7 +315,7 @@ async fn dial_failure() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(a_fingerprint.into())); + let addr = addr.with(Protocol::Certhash(a_fingerprint.to_multi_hash())); let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, @@ -382,7 +376,7 @@ async fn concurrent_connections_and_streams() { e => panic!("{:?}", e), }; - let addr = addr.with(Protocol::Certhash(fingerprint.into())); + let addr = addr.with(Protocol::Certhash(fingerprint.to_multi_hash())); listeners.push((*listener.local_peer_id(), addr)); From c709da95df7b222c6ea79f3d1404b478f3fcbf9c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:12:42 +1000 Subject: [PATCH 089/244] Add test for noise_prologue --- transports/webrtc/src/fingerprint.rs | 4 ++-- transports/webrtc/src/transport.rs | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index fdf62f0f0b1..ec3c669bfb7 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -25,7 +25,7 @@ use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; /// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Copy, Clone)] pub struct Fingerprint([u8; 32]); impl Fingerprint { @@ -51,7 +51,7 @@ impl Fingerprint { } let mut buf = [0; 32]; - hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; + hex::decode_to_slice(dbg!(fp.value.replace(':', "")), &mut buf).ok()?; Some(Self(buf)) } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 926fbd5e482..042230edf2c 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -612,14 +612,32 @@ fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) #[cfg(test)] mod tests { + use super::*; use futures::future::poll_fn; + use hex_literal::hex; use libp2p_core::{multiaddr::Protocol, Multiaddr}; use rcgen::KeyPair; - use tokio_crate as tokio; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use tokio_crate as tokio; - use super::*; + #[test] + fn noise_prologue_tests() { + let a = Fingerprint::raw(hex!( + "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" + )); + let b = Fingerprint::raw(hex!( + "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" + )); + + let prologue1 = noise_prologue(a, b); + let prologue2 = noise_prologue(b, a); + + assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + assert_eq!( + prologue1, prologue2, + "order of fingerprints does not matter" + ); + } #[test] fn multiaddr_to_socketaddr_conversion() { From 6ddf754875e0704510df7c92d6239dc0aa503892 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:14:52 +1000 Subject: [PATCH 090/244] Remove `dbg!` --- transports/webrtc/src/fingerprint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index ec3c669bfb7..ad7a23a5c72 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -51,7 +51,7 @@ impl Fingerprint { } let mut buf = [0; 32]; - hex::decode_to_slice(dbg!(fp.value.replace(':', "")), &mut buf).ok()?; + hex::decode_to_slice(fp.value.replace(':', ""), &mut buf).ok()?; Some(Self(buf)) } From 299575f3222507219d77bb8fdef61756a3fc0601 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:17:38 +1000 Subject: [PATCH 091/244] Fix tests not compiling --- transports/webrtc/src/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 042230edf2c..775a703090b 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -632,7 +632,7 @@ mod tests { let prologue1 = noise_prologue(a, b); let prologue2 = noise_prologue(b, a); - assert_eq!(hex::encode(prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); assert_eq!( prologue1, prologue2, "order of fingerprints does not matter" From d3da8f7d99df14fb963b975d69bc7783871ee079 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:17:48 +1000 Subject: [PATCH 092/244] Don't use magic constants --- transports/webrtc/src/fingerprint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index ad7a23a5c72..de6e3d2cc0a 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -57,7 +57,7 @@ impl Fingerprint { } pub fn try_from_multihash(hash: Multihash) -> Option { - if hash.code() != 0x12 { + if hash.code() != u64::from(Code::Sha2_256) { // Only support SHA256 for now. return None; } From 5b44acafed89c766f6976f8c182e180b7d7405e0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:19:18 +1000 Subject: [PATCH 093/244] Only pass fingerprint to `render_server_session_description` --- transports/webrtc/src/sdp.rs | 8 ++++++-- transports/webrtc/src/webrtc_connection.rs | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 0e8425c4094..94e0153b74f 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -29,9 +29,13 @@ use crate::fingerprint::Fingerprint; pub(crate) fn render_server_session_description( addr: SocketAddr, fingerprint: &Fingerprint, - ufrag: &str, ) -> String { - render_description(SERVER_SESSION_DESCRIPTION, addr, fingerprint, ufrag) + render_description( + SERVER_SESSION_DESCRIPTION, + addr, + fingerprint, + &fingerprint.to_ufrag(), + ) } /// Renders the SDP client session description. diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 19a7250b8e0..8e496111464 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -73,9 +73,8 @@ impl WebRTCConnection { // 2. ANSWER // Set the remote description to the predefined SDP. - let remote_ufrag = remote_fingerprint.to_ufrag(); let server_session_description = - crate::sdp::render_server_session_description(addr, remote_fingerprint, &remote_ufrag); + crate::sdp::render_server_session_description(addr, remote_fingerprint); log::debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); // NOTE: this will start the gathering of ICE candidates From b05219022ec719e48f12cc38c5ff951672ddc0de Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 23 Sep 2022 19:29:22 +1000 Subject: [PATCH 094/244] Fix clippy --- transports/webrtc/src/fingerprint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index de6e3d2cc0a..73111991678 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -37,7 +37,7 @@ impl Fingerprint { pub fn from_certificate(bytes: &[u8]) -> Self { let mut h = multihash::Sha2_256::default(); - h.update(&bytes); + h.update(bytes); let mut bytes: [u8; 32] = [0; 32]; bytes.copy_from_slice(h.finalize()); From 1b0b671ab086950d1e7b00de9ee8200da0b7be57 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 26 Sep 2022 18:51:16 +1000 Subject: [PATCH 095/244] Create noise-prologue from server + client FP in fixed order --- transports/webrtc/src/transport.rs | 62 ++++++++++++++---------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 775a703090b..bf5f25c57f3 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -35,7 +35,6 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use log::{debug, trace}; -use multihash::Multihash; use tokio_crate::net::UdpSocket; use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; @@ -162,7 +161,7 @@ impl Transport for WebRTCTransport { trace!("dialing addr={}", remote); let config = self.config.clone(); - let our_fingerprint = self.config.fingerprint_of_first_certificate(); + let client_fingerprint = self.config.fingerprint_of_first_certificate(); let id_keys = self.id_keys.clone(); let first_listener = self @@ -175,14 +174,14 @@ impl Transport for WebRTCTransport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let remote_fingerprint = fingerprint_from_addr(&addr) + let server_fingerprint = fingerprint_from_addr(&addr) .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; let conn = WebRTCConnection::connect( sock_addr, config.into_inner(), udp_mux, - &remote_fingerprint, + &server_fingerprint, ) .await?; @@ -193,8 +192,8 @@ impl Transport for WebRTCTransport { let peer_id = perform_noise_handshake_outbound( id_keys, PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, + client_fingerprint, + server_fingerprint, ) .await?; @@ -500,8 +499,8 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { async fn perform_noise_handshake_outbound( id_keys: identity::Keypair, poll_data_channel: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -509,8 +508,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + let noise = NoiseConfig::xx(dh_keys) + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() @@ -529,13 +528,13 @@ async fn upgrade( ) -> Result<(PeerId, Connection), Error> { trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - let our_fingerprint = config.fingerprint_of_first_certificate(); + let server_fingerprint = config.fingerprint_of_first_certificate(); let conn = WebRTCConnection::accept( socket_addr, config.into_inner(), udp_mux, - &our_fingerprint, + &server_fingerprint, &ufrag, ) .await?; @@ -548,12 +547,13 @@ async fn upgrade( socket_addr, ufrag ); - let remote_fingerprint = conn.get_remote_fingerprint().await; + let client_fingerprint = conn.get_remote_fingerprint().await; + let peer_id = perform_noise_handshake_inbound( id_keys, PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, + client_fingerprint, + server_fingerprint, ) .await?; @@ -574,8 +574,8 @@ async fn upgrade( async fn perform_noise_handshake_inbound( id_keys: identity::Keypair, poll_data_channel: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -583,8 +583,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + let noise = NoiseConfig::xx(dh_keys) + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() @@ -593,18 +593,17 @@ where Ok(peer_id) } -fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { - let (a, b): (Multihash, Multihash) = ( - our_fingerprint.to_multi_hash(), - remote_fingerprint.to_multi_hash(), - ); - let (a, b) = (a.to_bytes(), b.to_bytes()); - let (first, second) = if a < b { (a, b) } else { (b, a) }; +fn noise_prologue(client: Fingerprint, server: Fingerprint) -> Vec { + let server = server.to_multi_hash().to_bytes(); + let client = client.to_multi_hash().to_bytes(); + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); + + let mut out = Vec::with_capacity(PREFIX.len() + server.len() + client.len()); out.extend_from_slice(PREFIX); - out.extend_from_slice(&first); - out.extend_from_slice(&second); + out.extend_from_slice(&server); + out.extend_from_slice(&client); + out } @@ -633,10 +632,7 @@ mod tests { let prologue2 = noise_prologue(b, a); assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - assert_eq!( - prologue1, prologue2, - "order of fingerprints does not matter" - ); + assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); } #[test] From aa38c81cffecd1069fc30a4138466d9553ad5fdf Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 12:48:01 +1000 Subject: [PATCH 096/244] Make `substream` a top-level module --- transports/webrtc/src/connection.rs | 4 +--- transports/webrtc/src/lib.rs | 1 + transports/webrtc/src/{connection => }/substream.rs | 0 transports/webrtc/src/transport.rs | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) rename transports/webrtc/src/{connection => }/substream.rs (100%) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index a807d0c09da..b3e351c79d2 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -18,8 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod substream; - use futures::{ channel::{ mpsc, @@ -42,7 +40,7 @@ use std::{ }; use crate::error::Error; -pub(crate) use substream::Substream; +use crate::substream::Substream; const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 4d5615353b4..463e3ba7a6a 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -88,6 +88,7 @@ mod fingerprint; mod in_addr; mod req_res_chan; mod sdp; +mod substream; mod udp_mux; mod webrtc_connection; diff --git a/transports/webrtc/src/connection/substream.rs b/transports/webrtc/src/substream.rs similarity index 100% rename from transports/webrtc/src/connection/substream.rs rename to transports/webrtc/src/substream.rs diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index f79726d796c..e7f564002ca 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -51,10 +51,10 @@ use std::{ use crate::{ connection::Connection, - connection::Substream, error::Error, fingerprint::Fingerprint, in_addr::InAddr, + substream::Substream, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, webrtc_connection::WebRTCConnection, }; From d0e918bc60617da939d59edaa765a974dd3fc891 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 12:52:58 +1000 Subject: [PATCH 097/244] Replace nightly feature with refactoring --- transports/webrtc/src/substream.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index ca35dc48dab..0648a169fc8 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -148,7 +148,7 @@ impl AsyncRead for Substream { buf: &mut [u8], ) -> Poll> { loop { - if let Some(read_buffer) = self.state.read_buffer_mut() && !read_buffer.is_empty() { + if let Some(read_buffer) = self.state.non_empty_read_buffer_mut() { let n = std::cmp::min(read_buffer.len(), buf.len()); let data = read_buffer.split_to(n); buf[0..n].copy_from_slice(&data[..]); @@ -355,15 +355,19 @@ impl State { ); } - fn read_buffer_mut(&mut self) -> Option<&mut Bytes> { + /// Returns a reference to the underlying buffer if possible and the buffer is not empty. + fn non_empty_read_buffer_mut(&mut self) -> Option<&mut Bytes> { match self { - State::Open { read_buffer } => Some(read_buffer), - State::WriteClosed { read_buffer } => Some(read_buffer), - State::ReadClosed { read_buffer } => Some(read_buffer), - State::ReadWriteClosed { read_buffer } => Some(read_buffer), - State::ReadReset => None, - State::ReadResetWriteClosed => None, - State::Poisoned => todo!(), + State::Open { read_buffer } + | State::WriteClosed { read_buffer } + | State::ReadClosed { read_buffer } + | State::ReadWriteClosed { read_buffer } + if !read_buffer.is_empty() => + { + Some(read_buffer) + } + State::Poisoned => unreachable!(), + _ => None, } } } From 171c613309a4f8379516e251575163a6642db53a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 12:54:21 +1000 Subject: [PATCH 098/244] Remove use of import rename --- transports/webrtc/src/substream.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 0648a169fc8..5017c2a4701 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -25,7 +25,7 @@ use futures::ready; use tokio_util::compat::Compat; use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc::data::data_channel::DataChannel; -use webrtc::data::data_channel::PollDataChannel as RTCPollDataChannel; +use webrtc::data::data_channel::PollDataChannel; use std::{ fmt, io, @@ -51,7 +51,7 @@ const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; /// /// #[derive(Debug)] pub struct Substream { - io: Framed, prost_codec::Codec>, + io: Framed, prost_codec::Codec>, state: State, } @@ -60,7 +60,7 @@ impl Substream { pub fn new(data_channel: Arc) -> Self { Self { io: Framed::new( - RTCPollDataChannel::new(data_channel).compat(), + PollDataChannel::new(data_channel).compat(), prost_codec::Codec::new(MAX_MSG_LEN), ), state: State::Open { @@ -70,12 +70,12 @@ impl Substream { } /// Get back the inner data_channel. - pub fn into_inner(self) -> RTCPollDataChannel { + pub fn into_inner(self) -> PollDataChannel { self.io.into_inner().into_inner() } /// Obtain a clone of the inner data_channel. - pub fn clone_inner(&self) -> RTCPollDataChannel { + pub fn clone_inner(&self) -> PollDataChannel { self.io.get_ref().clone() } @@ -123,7 +123,7 @@ impl Substream { } fn io_poll_next( - io: &mut Framed, prost_codec::Codec>, + io: &mut Framed, prost_codec::Codec>, cx: &mut Context<'_>, ) -> Poll, Option>)>>> { match ready!(io.poll_next_unpin(cx)) From 64026650c984cef2c8046fef3f64ed2405c23be4 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:09:36 +1000 Subject: [PATCH 099/244] Remove unused public API --- transports/webrtc/src/substream.rs | 42 ------------------------------ 1 file changed, 42 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 5017c2a4701..20368155b98 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -69,53 +69,11 @@ impl Substream { } } - /// Get back the inner data_channel. - pub fn into_inner(self) -> PollDataChannel { - self.io.into_inner().into_inner() - } - - /// Obtain a clone of the inner data_channel. - pub fn clone_inner(&self) -> PollDataChannel { - self.io.get_ref().clone() - } - - /// MessagesSent returns the number of messages sent - pub fn messages_sent(&self) -> usize { - self.io.get_ref().messages_sent() - } - - /// MessagesReceived returns the number of messages received - pub fn messages_received(&self) -> usize { - self.io.get_ref().messages_received() - } - - /// BytesSent returns the number of bytes sent - pub fn bytes_sent(&self) -> usize { - self.io.get_ref().bytes_sent() - } - - /// BytesReceived returns the number of bytes received - pub fn bytes_received(&self) -> usize { - self.io.get_ref().bytes_received() - } - /// StreamIdentifier returns the Stream identifier associated to the stream. pub fn stream_identifier(&self) -> u16 { self.io.get_ref().stream_identifier() } - /// BufferedAmount returns the number of bytes of data currently queued to be - /// sent over this stream. - pub fn buffered_amount(&self) -> usize { - self.io.get_ref().buffered_amount() - } - - /// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing - /// data that is considered "low." Defaults to 0. - pub fn buffered_amount_low_threshold(&self) -> usize { - self.io.get_ref().buffered_amount_low_threshold() - } - /// Set the capacity of the temporary read buffer (default: 8192). pub fn set_read_buf_capacity(&mut self, capacity: usize) { self.io.get_mut().set_read_buf_capacity(capacity) From d53769671239a135c4a98b883be9bb6c8301220f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:13:10 +1000 Subject: [PATCH 100/244] Make sure we don't construct substreams outside of this crate --- transports/webrtc/src/substream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 20368155b98..b3c5cf70282 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -57,7 +57,7 @@ pub struct Substream { impl Substream { /// Constructs a new `Substream`. - pub fn new(data_channel: Arc) -> Self { + pub(crate) fn new(data_channel: Arc) -> Self { Self { io: Framed::new( PollDataChannel::new(data_channel).compat(), From c8c244659a4ef8b8bcfc49fe91107947ea66e239 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:14:34 +1000 Subject: [PATCH 101/244] Don't expose public APIs for temporary workarounds --- transports/webrtc/src/connection.rs | 23 ++--------------------- transports/webrtc/src/substream.rs | 16 +++++++--------- transports/webrtc/src/transport.rs | 13 ++----------- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 93f7f9d3b15..c0468654600 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -54,10 +54,6 @@ pub struct Connection { /// Channel onto which incoming data channels are put. incoming_data_channels_rx: mpsc::Receiver>, - /// Temporary read buffer's capacity (equal for all data channels). - /// See [`Substream`] `read_buf_cap`. - read_buf_cap: Option, - /// Future, which, once polled, will result in an outbound substream. outbound_fut: Option, Error>>>, @@ -77,17 +73,11 @@ impl Connection { Self { peer_conn: Arc::new(FutMutex::new(rtc_conn)), incoming_data_channels_rx: data_channel_rx, - read_buf_cap: None, outbound_fut: None, close_fut: None, } } - /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). - pub fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { - self.read_buf_cap = Some(cap); - } - /// Registers a handler for incoming data channels. async fn register_incoming_data_channels_handler( rtc_conn: &RTCPeerConnection, @@ -157,12 +147,7 @@ impl StreamMuxer for Connection { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); - let mut ch = Substream::new(detached); - if let Some(cap) = self.read_buf_cap { - ch.set_read_buf_capacity(cap); - } - - Poll::Ready(Ok(ch)) + Poll::Ready(Ok(Substream::new(detached))) } None => Poll::Ready(Err(Error::InternalError( "incoming_data_channels_rx is closed (no messages left)".to_string(), @@ -210,12 +195,8 @@ impl StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { - let mut ch = Substream::new(detached); - if let Some(cap) = self.read_buf_cap { - ch.set_read_buf_capacity(cap); - } self.outbound_fut = None; - Poll::Ready(Ok(ch)) + Poll::Ready(Ok(Substream::new(detached))) } Err(e) => { self.outbound_fut = None; diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index b3c5cf70282..d52e54dc317 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -58,11 +58,14 @@ pub struct Substream { impl Substream { /// Constructs a new `Substream`. pub(crate) fn new(data_channel: Arc) -> Self { + let mut inner = PollDataChannel::new(data_channel); + + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. + inner.set_read_buf_capacity(8192 * 10); + Self { - io: Framed::new( - PollDataChannel::new(data_channel).compat(), - prost_codec::Codec::new(MAX_MSG_LEN), - ), + io: Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)), state: State::Open { read_buffer: Default::default(), }, @@ -73,11 +76,6 @@ impl Substream { pub fn stream_identifier(&self) -> u16 { self.io.get_ref().stream_identifier() } - - /// Set the capacity of the temporary read buffer (default: 8192). - pub fn set_read_buf_capacity(&mut self, capacity: usize) { - self.io.get_mut().set_read_buf_capacity(capacity) - } } fn io_poll_next( diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 575944694f5..507c200b308 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -212,11 +212,7 @@ impl Transport for WebRTCTransport { .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - Ok((peer_id, c)) + Ok((peer_id, Connection::new(conn.into_inner()).await)) } .boxed()) } @@ -563,12 +559,7 @@ async fn upgrade( .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) + Ok((peer_id, Connection::new(conn.into_inner()).await)) } async fn perform_noise_handshake_inbound( From d58f219c1090c836c6f5e4b455a1474cc2851d23 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:17:23 +1000 Subject: [PATCH 102/244] Remove pub where not necessary --- transports/webrtc/src/substream.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index d52e54dc317..b37e5f216be 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -72,8 +72,7 @@ impl Substream { } } - /// StreamIdentifier returns the Stream identifier associated to the stream. - pub fn stream_identifier(&self) -> u16 { + fn stream_identifier(&self) -> u16 { self.io.get_ref().stream_identifier() } } From eb09d3651c88fa7f1cf7c15b1a86327ae6e98df1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:18:27 +1000 Subject: [PATCH 103/244] Remove utilities below usage --- transports/webrtc/src/substream.rs | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index b37e5f216be..b93fda5d02d 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -77,25 +77,6 @@ impl Substream { } } -fn io_poll_next( - io: &mut Framed, prost_codec::Codec>, - cx: &mut Context<'_>, -) -> Poll, Option>)>>> { - match ready!(io.poll_next_unpin(cx)) - .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - { - Some(Message { flag, message }) => { - let flag = flag - .map(|f| Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, ""))) - .transpose()?; - - Poll::Ready(Ok(Some((flag, message)))) - } - None => Poll::Ready(Ok(None)), - } -} - impl AsyncRead for Substream { fn poll_read( mut self: Pin<&mut Self>, @@ -237,6 +218,25 @@ impl AsyncWrite for Substream { } } +fn io_poll_next( + io: &mut Framed, prost_codec::Codec>, + cx: &mut Context<'_>, +) -> Poll, Option>)>>> { + match ready!(io.poll_next_unpin(cx)) + .transpose() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + { + Some(Message { flag, message }) => { + let flag = flag + .map(|f| Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, ""))) + .transpose()?; + + Poll::Ready(Ok(Some((flag, message)))) + } + None => Poll::Ready(Ok(None)), + } +} + enum State { Open { read_buffer: Bytes }, WriteClosed { read_buffer: Bytes }, From 99af2a1e6ad5822befe99e049daa0d17de433a49 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:19:50 +1000 Subject: [PATCH 104/244] Remove stale derive --- transports/webrtc/src/substream.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index b93fda5d02d..8059048b1d3 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -48,8 +48,6 @@ const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; /// Substream is a wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / /// [`AsyncWrite`] and message framing (as per specification). -/// -/// #[derive(Debug)] pub struct Substream { io: Framed, prost_codec::Codec>, state: State, From 2d85ab451ad301b23febeac9284c9e762068c7a4 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 13:21:43 +1000 Subject: [PATCH 105/244] Update docs --- transports/webrtc/src/substream.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 8059048b1d3..329fde7b9d9 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -46,8 +46,10 @@ const PROTO_OVERHEAD: usize = 5; /// Maximum length of data, in bytes. const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; -/// Substream is a wrapper around [`RTCPollDataChannel`] implementing futures [`AsyncRead`] / -/// [`AsyncWrite`] and message framing (as per specification). +/// A substream on top of a WebRTC data channel. +/// +/// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well +/// as support a half-closed state which we do by framing messages in a protobuf envelope. pub struct Substream { io: Framed, prost_codec::Codec>, state: State, From 6e2aeb17ff1e3e6c107d0145978c229568c6682e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 27 Sep 2022 14:10:12 +1000 Subject: [PATCH 106/244] Fix clippy warnings --- transports/webrtc/src/lib.rs | 2 ++ transports/webrtc/src/substream.rs | 32 +++++++++++++----------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index a070015fdb7..a63d0fa624f 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -90,6 +90,8 @@ mod transport; mod udp_mux; mod webrtc_connection; mod message_proto { + #![allow(clippy::derive_partial_eq_without_eq)] + include!(concat!(env!("OUT_DIR"), "/webrtc.pb.rs")); } diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 329fde7b9d9..c78196e2fa9 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -136,24 +136,18 @@ impl AsyncWrite for Substream { ) -> Poll> { let substream_id = self.stream_identifier(); // Handle flags iff read side closed. - loop { - match self.state { - State::ReadClosed { .. } | State::ReadReset => - // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the - // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? - { - match io_poll_next(&mut self.io, cx)? { - Poll::Ready(Some((Some(flag), message))) => { - // Read side is closed. Discard any incoming messages. - drop(message); - // But still handle flags, e.g. a `Flag::StopSending`. - self.state.handle_flag(flag, substream_id) - } - Poll::Ready(Some((None, message))) => drop(message), - Poll::Ready(None) | Poll::Pending => break, - } + while let State::ReadClosed { .. } | State::ReadReset = self.state { + // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the + // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + match io_poll_next(&mut self.io, cx)? { + Poll::Ready(Some((Some(flag), message))) => { + // Read side is closed. Discard any incoming messages. + drop(message); + // But still handle flags, e.g. a `Flag::StopSending`. + self.state.handle_flag(flag, substream_id) } - _ => break, + Poll::Ready(Some((None, message))) => drop(message), + Poll::Ready(None) | Poll::Pending => break, } } @@ -228,7 +222,9 @@ fn io_poll_next( { Some(Message { flag, message }) => { let flag = flag - .map(|f| Flag::from_i32(f).ok_or(io::Error::new(io::ErrorKind::InvalidData, ""))) + .map(|f| { + Flag::from_i32(f).ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "")) + }) .transpose()?; Poll::Ready(Ok(Some((flag, message)))) From 2c900b507dd2832e03d14278cd38dc85230bf9db Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 28 Sep 2022 15:13:46 +0400 Subject: [PATCH 107/244] change noise channel ID to 0 Co-authored-by: Max Inden --- transports/webrtc/src/webrtc_connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 8e496111464..b8af653bd64 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -129,7 +129,7 @@ impl WebRTCConnection { .create_data_channel( "data", Some(RTCDataChannelInit { - negotiated: Some(1), + negotiated: Some(0), ..RTCDataChannelInit::default() }), ) From 226f2930daf9a2614c216546c6084de646a9f26d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 28 Sep 2022 21:25:38 +0400 Subject: [PATCH 108/244] only allow one addr per ufrag reflects https://github.com/libp2p/specs/commit/7ce6213ee0998660032b853d7c64f3a3dae312b7 --- transports/webrtc/src/udp_mux.rs | 35 +++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 906e63c1e41..79c5e02a56d 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -24,6 +24,7 @@ use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, }; +use thiserror::Error; use tokio::{io::ReadBuf, net::UdpSocket}; use tokio_crate as tokio; use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; @@ -140,9 +141,24 @@ impl UDPMuxNewAddr { /// Returns a muxed connection if the `ufrag` from the given STUN message matches an existing /// connection. - fn conn_from_stun_message(&self, buffer: &[u8], addr: &SocketAddr) -> Option { + fn conn_from_stun_message( + &self, + buffer: &[u8], + addr: &SocketAddr, + ) -> Option> { match ufrag_from_stun_message(buffer, true) { - Ok(ufrag) => self.conns.get(&ufrag).map(Clone::clone), + Ok(ufrag) => { + if let Some(conn) = self.conns.get(&ufrag) { + let associated_addrs = conn.get_addresses(); + // This basically ensures only one address is registered per ufrag. + if associated_addrs.is_empty() || associated_addrs.contains(addr) { + return Some(Ok(conn.clone())); + } else { + return Some(Err(ConnQueryError::UfragAlreadyTaken { associated_addrs })); + } + } + None + } Err(e) => { log::debug!("{} (addr={})", e, addr); None @@ -297,7 +313,14 @@ impl UDPMuxNewAddr { // If we couldn't find the connection based on source address, see if // this is a STUN mesage and if so if we can find the connection based on ufrag. None if is_stun_message(read.filled()) => { - self.conn_from_stun_message(read.filled(), &addr) + match self.conn_from_stun_message(read.filled(), &addr) { + Some(Ok(s)) => Some(s), + Some(Err(e)) => { + log::debug!("addr={}: Error when querying existing connections: {}", &addr, e); + continue; + } + None => None, + } } Some(s) => Some(s.to_owned()), _ => None, @@ -514,3 +537,9 @@ fn ufrag_from_stun_message(buffer: &[u8], local_ufrag: bool) -> Result }, +} From a6b2aacac87f284434560ad9015be16cc278d112 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 10:58:04 +1100 Subject: [PATCH 109/244] Revert "Create noise-prologue from server + client FP in fixed order" This reverts commit 1b0b671ab086950d1e7b00de9ee8200da0b7be57. --- transports/webrtc/src/transport.rs | 62 ++++++++++++++++-------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 507c200b308..29078271b05 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -35,6 +35,7 @@ use libp2p_core::{ }; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use log::{debug, trace}; +use multihash::Multihash; use tokio_crate::net::UdpSocket; use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; @@ -161,7 +162,7 @@ impl Transport for WebRTCTransport { trace!("dialing addr={}", remote); let config = self.config.clone(); - let client_fingerprint = self.config.fingerprint_of_first_certificate(); + let our_fingerprint = self.config.fingerprint_of_first_certificate(); let id_keys = self.id_keys.clone(); let first_listener = self @@ -174,14 +175,14 @@ impl Transport for WebRTCTransport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let server_fingerprint = fingerprint_from_addr(&addr) + let remote_fingerprint = fingerprint_from_addr(&addr) .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; let conn = WebRTCConnection::connect( sock_addr, config.into_inner(), udp_mux, - &server_fingerprint, + &remote_fingerprint, ) .await?; @@ -192,8 +193,8 @@ impl Transport for WebRTCTransport { let peer_id = perform_noise_handshake_outbound( id_keys, Substream::new(data_channel.clone()), - client_fingerprint, - server_fingerprint, + our_fingerprint, + remote_fingerprint, ) .await?; @@ -495,8 +496,8 @@ fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { async fn perform_noise_handshake_outbound( id_keys: identity::Keypair, poll_data_channel: T, - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -504,8 +505,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = NoiseConfig::xx(dh_keys) - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() @@ -524,13 +525,13 @@ async fn upgrade( ) -> Result<(PeerId, Connection), Error> { trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - let server_fingerprint = config.fingerprint_of_first_certificate(); + let our_fingerprint = config.fingerprint_of_first_certificate(); let conn = WebRTCConnection::accept( socket_addr, config.into_inner(), udp_mux, - &server_fingerprint, + &our_fingerprint, &ufrag, ) .await?; @@ -543,13 +544,12 @@ async fn upgrade( socket_addr, ufrag ); - let client_fingerprint = conn.get_remote_fingerprint().await; - + let remote_fingerprint = conn.get_remote_fingerprint().await; let peer_id = perform_noise_handshake_inbound( id_keys, Substream::new(data_channel.clone()), - client_fingerprint, - server_fingerprint, + our_fingerprint, + remote_fingerprint, ) .await?; @@ -565,8 +565,8 @@ async fn upgrade( async fn perform_noise_handshake_inbound( id_keys: identity::Keypair, poll_data_channel: T, - client_fingerprint: Fingerprint, - server_fingerprint: Fingerprint, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -574,8 +574,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = NoiseConfig::xx(dh_keys) - .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() @@ -584,17 +584,18 @@ where Ok(peer_id) } -fn noise_prologue(client: Fingerprint, server: Fingerprint) -> Vec { - let server = server.to_multi_hash().to_bytes(); - let client = client.to_multi_hash().to_bytes(); - +fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { + let (a, b): (Multihash, Multihash) = ( + our_fingerprint.to_multi_hash(), + remote_fingerprint.to_multi_hash(), + ); + let (a, b) = (a.to_bytes(), b.to_bytes()); + let (first, second) = if a < b { (a, b) } else { (b, a) }; const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - - let mut out = Vec::with_capacity(PREFIX.len() + server.len() + client.len()); + let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); out.extend_from_slice(PREFIX); - out.extend_from_slice(&server); - out.extend_from_slice(&client); - + out.extend_from_slice(&first); + out.extend_from_slice(&second); out } @@ -623,7 +624,10 @@ mod tests { let prologue2 = noise_prologue(b, a); assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!( + prologue1, prologue2, + "order of fingerprints does not matter" + ); } #[test] From d5dcdd137262edf3affec9fe9de6fb517247b26f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:03:39 +1100 Subject: [PATCH 110/244] Update to latest core and noise version --- transports/webrtc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index d2c140e7ed9..26c46c8ac53 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -18,8 +18,8 @@ futures-lite = "1" futures-timer = "3" hex = "0.4" if-watch = "2.0" -libp2p-core = { version = "0.36.0", path = "../../core", default-features = false } -libp2p-noise = { version = "0.39.0", path = "../../transports/noise" } +libp2p-core = { version = "0.37.0", path = "../../core", default-features = false } +libp2p-noise = { version = "0.40.0", path = "../../transports/noise" } log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } multibase = "0.9" From b43f86102828ae89ff73aa4cc59904be85886918 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:05:52 +1100 Subject: [PATCH 111/244] Don't call `Transport::map` via trait --- transports/webrtc/tests/smoke.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index e5b97a4569d..f6ef5e84cef 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -38,10 +38,10 @@ fn create_swarm() -> Result<(Swarm>, Fingerprint)> { let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); - let transport = Transport::map(transport, |(peer_id, conn), _| { - (peer_id, StreamMuxerBox::new(conn)) - }) - .boxed(); + let transport = transport + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); + Ok(( SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(|fut| { From fcf55b25cde1d1b2b12eee9bd4681b12b9785d60 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:32:37 +1100 Subject: [PATCH 112/244] Append certhash to emitted listen address --- transports/webrtc/src/transport.rs | 12 ++++++++-- transports/webrtc/tests/smoke.rs | 37 +++++++++++------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 775a703090b..1a2eafe6808 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -318,7 +318,11 @@ impl WebRTCListenStream { || self.listen_addr.is_ipv6() == ip.is_ipv6() { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); + let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( + self.config + .fingerprint_of_first_certificate() + .to_multi_hash(), + )); log::debug!("New listen address: {}", ma); return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, @@ -332,7 +336,11 @@ impl WebRTCListenStream { || self.listen_addr.is_ipv6() == ip.is_ipv6() { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr); + let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( + self.config + .fingerprint_of_first_certificate() + .to_multi_hash(), + )); log::debug!("Expired listen address: {}", ma); return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index f6ef5e84cef..a296111abf8 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -10,8 +10,7 @@ use libp2p::request_response::{ RequestResponseEvent, RequestResponseMessage, }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use libp2p_core::{identity, multiaddr::Protocol, muxing::StreamMuxerBox, upgrade, Transport}; -use libp2p_webrtc::Fingerprint; +use libp2p_core::{identity, muxing::StreamMuxerBox, upgrade, Transport}; use libp2p_webrtc::WebRTCTransport; use rand::RngCore; use rcgen::KeyPair; @@ -29,11 +28,10 @@ fn generate_tls_keypair() -> identity::Keypair { identity::Keypair::generate_ed25519() } -fn create_swarm() -> Result<(Swarm>, Fingerprint)> { +fn create_swarm() -> Result>> { let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let fingerprint = cert.get_fingerprints().unwrap().first().unwrap().clone(); let transport = WebRTCTransport::new(cert, keypair); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -42,14 +40,11 @@ fn create_swarm() -> Result<(Swarm>, Fingerprint)> { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(); - Ok(( - SwarmBuilder::new(transport, behaviour, peer_id) - .executor(Box::new(|fut| { - tokio::spawn(fut); - })) - .build(), - Fingerprint::try_from_rtc_dtls(fingerprint).unwrap(), - )) + Ok(SwarmBuilder::new(transport, behaviour, peer_id) + .executor(Box::new(|fut| { + tokio::spawn(fut); + })) + .build()) } #[tokio::test] @@ -58,8 +53,8 @@ async fn smoke() -> Result<()> { let mut rng = rand::thread_rng(); - let (mut a, a_fingerprint) = create_swarm()?; - let (mut b, _b_fingerprint) = create_swarm()?; + let mut a = create_swarm()?; + let mut b = create_swarm()?; Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; @@ -72,8 +67,6 @@ async fn smoke() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(a_fingerprint.to_multi_hash())); - let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), @@ -301,8 +294,8 @@ impl RequestResponseCodec for PingCodec { async fn dial_failure() -> Result<()> { let _ = env_logger::builder().is_test(true).try_init(); - let (mut a, a_fingerprint) = create_swarm()?; - let (mut b, _b_fingerprint) = create_swarm()?; + let mut a = create_swarm()?; + let mut b = create_swarm()?; Swarm::listen_on(&mut a, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; Swarm::listen_on(&mut b, "/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; @@ -315,8 +308,6 @@ async fn dial_failure() -> Result<()> { // skip other interface addresses while a.next().now_or_never().is_some() {} - let addr = addr.with(Protocol::Certhash(a_fingerprint.to_multi_hash())); - let _ = match b.next().await { Some(SwarmEvent::NewListenAddr { address, .. }) => address, e => panic!("{:?}", e), @@ -363,7 +354,7 @@ async fn concurrent_connections_and_streams() { // Spawn the listener nodes. for _ in 0..num_listeners { - let (mut listener, fingerprint) = create_swarm().unwrap(); + let mut listener = create_swarm().unwrap(); Swarm::listen_on( &mut listener, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap(), @@ -376,8 +367,6 @@ async fn concurrent_connections_and_streams() { e => panic!("{:?}", e), }; - let addr = addr.with(Protocol::Certhash(fingerprint.to_multi_hash())); - listeners.push((*listener.local_peer_id(), addr)); tokio::spawn(async move { @@ -422,7 +411,7 @@ async fn concurrent_connections_and_streams() { }); } - let (mut dialer, _fingerprint) = create_swarm().unwrap(); + let mut dialer = create_swarm().unwrap(); Swarm::listen_on(&mut dialer, "/ip4/127.0.0.1/udp/0/webrtc".parse().unwrap()).unwrap(); // Wait to listen on address. From 979b179a0d62413b5846cd251958897bd5a9ac25 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:34:33 +1100 Subject: [PATCH 113/244] Remove unnecessary `pub` --- transports/webrtc/src/connection.rs | 4 ++-- transports/webrtc/src/lib.rs | 2 +- transports/webrtc/src/transport.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index df82951ca7b..af9a94966ad 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -71,7 +71,7 @@ impl Unpin for Connection {} impl Connection { /// Creates a new connection. - pub async fn new(rtc_conn: RTCPeerConnection) -> Self { + pub(crate) async fn new(rtc_conn: RTCPeerConnection) -> Self { let (data_channel_tx, data_channel_rx) = mpsc::channel(MAX_DATA_CHANNELS_IN_FLIGHT); Connection::register_incoming_data_channels_handler(&rtc_conn, data_channel_tx).await; @@ -86,7 +86,7 @@ impl Connection { } /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). - pub fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { + pub(crate) fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { self.read_buf_cap = Some(cap); } diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index f7366d28967..8ee49c03f26 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -92,4 +92,4 @@ mod webrtc_connection; pub use connection::Connection; pub use error::Error; pub use fingerprint::Fingerprint; -pub use transport::{WebRTCListenStream, WebRTCTransport}; +pub use transport::WebRTCTransport; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 1a2eafe6808..ff555c8ccdc 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -239,7 +239,7 @@ impl Transport for WebRTCTransport { } /// A stream of incoming connections on one or more interfaces. -pub struct WebRTCListenStream { +struct WebRTCListenStream { /// The ID of this listener. listener_id: ListenerId, @@ -407,13 +407,13 @@ impl Stream for WebRTCListenStream { /// A wrapper around [`RTCConfiguration`]. #[derive(Clone)] -pub(crate) struct WebRTCConfiguration { +struct WebRTCConfiguration { inner: RTCConfiguration, } impl WebRTCConfiguration { /// Creates a new config. - pub fn new(certificate: RTCCertificate) -> Self { + fn new(certificate: RTCCertificate) -> Self { Self { inner: RTCConfiguration { certificates: vec![certificate], @@ -427,7 +427,7 @@ impl WebRTCConfiguration { /// # Panics /// /// Panics if the config does not contain any certificates. - pub fn fingerprint_of_first_certificate(&self) -> Fingerprint { + fn fingerprint_of_first_certificate(&self) -> Fingerprint { // safe to unwrap here because we require a certificate during construction. let fingerprint = self .inner @@ -444,13 +444,13 @@ impl WebRTCConfiguration { } /// Consumes the `WebRTCConfiguration`, returning its inner configuration. - pub fn into_inner(self) -> RTCConfiguration { + fn into_inner(self) -> RTCConfiguration { self.inner } } /// Turns an IP address and port into the corresponding WebRTC multiaddr. -pub(crate) fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { +fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { Multiaddr::empty() .with(socket_addr.ip().into()) .with(Protocol::Udp(socket_addr.port())) From 1055efcbf7eed94c3a7f24bafbd0f5ff8a8865ca Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:37:01 +1100 Subject: [PATCH 114/244] Follow naming convention of other transports --- transports/webrtc/src/transport.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index ff555c8ccdc..d71e590d6b2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -60,7 +60,7 @@ use crate::{ /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct WebRTCTransport { /// The config which holds this peer's certificate(s). - config: WebRTCConfiguration, + config: Config, /// `Keypair` identifying this peer id_keys: identity::Keypair, /// All the active listeners. @@ -71,7 +71,7 @@ impl WebRTCTransport { /// Creates a new WebRTC transport. pub fn new(certificate: RTCCertificate, id_keys: identity::Keypair) -> Self { Self { - config: WebRTCConfiguration::new(certificate), + config: Config::new(certificate), id_keys, listeners: SelectAll::new(), } @@ -249,7 +249,7 @@ struct WebRTCListenStream { listen_addr: SocketAddr, /// The config which holds this peer's certificate(s). - config: WebRTCConfiguration, + config: Config, /// The UDP muxer that manages all ICE connections. udp_mux: UDPMuxNewAddr, @@ -276,7 +276,7 @@ impl WebRTCListenStream { fn new( listener_id: ListenerId, listen_addr: SocketAddr, - config: WebRTCConfiguration, + config: Config, udp_mux: UDPMuxNewAddr, id_keys: identity::Keypair, if_watcher: IfWatcher, @@ -405,13 +405,12 @@ impl Stream for WebRTCListenStream { } } -/// A wrapper around [`RTCConfiguration`]. #[derive(Clone)] -struct WebRTCConfiguration { +struct Config { inner: RTCConfiguration, } -impl WebRTCConfiguration { +impl Config { /// Creates a new config. fn new(certificate: RTCCertificate) -> Self { Self { @@ -530,7 +529,7 @@ where async fn upgrade( udp_mux: Arc, - config: WebRTCConfiguration, + config: Config, socket_addr: SocketAddr, ufrag: String, id_keys: identity::Keypair, From c5ca42d721e735b68a193f53c9230c904ed37f85 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:47:45 +1100 Subject: [PATCH 115/244] Don't require users to generate a certificate themselves --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/transport.rs | 21 +++++++++++---------- transports/webrtc/tests/smoke.rs | 10 +--------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 26c46c8ac53..bdfa02c6af7 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -24,6 +24,7 @@ log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } multibase = "0.9" rand = "0.8" +rcgen = "0.9.3" serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" @@ -36,6 +37,5 @@ anyhow = "1.0" env_logger = "0.9" libp2p = { path = "../..", features = ["request-response"], default-features = false } rand_core = "0.5" -rcgen = "0.9" quickcheck = "1" hex-literal = "0.3" diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index d71e590d6b2..7143e1028f1 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -41,6 +41,7 @@ use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; +use rand::distributions::DistString; use std::{ net::SocketAddr, pin::Pin, @@ -69,9 +70,9 @@ pub struct WebRTCTransport { impl WebRTCTransport { /// Creates a new WebRTC transport. - pub fn new(certificate: RTCCertificate, id_keys: identity::Keypair) -> Self { + pub fn new(id_keys: identity::Keypair) -> Self { Self { - config: Config::new(certificate), + config: Config::new(), id_keys, listeners: SelectAll::new(), } @@ -411,8 +412,13 @@ struct Config { } impl Config { - /// Creates a new config. - fn new(certificate: RTCCertificate) -> Self { + fn new() -> Self { + let mut params = rcgen::CertificateParams::new(vec![ + rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + ]); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + let certificate = RTCCertificate::from_params(params).expect("default params to work"); + Self { inner: RTCConfiguration { certificates: vec![certificate], @@ -623,7 +629,6 @@ mod tests { use futures::future::poll_fn; use hex_literal::hex; use libp2p_core::{multiaddr::Protocol, Multiaddr}; - use rcgen::KeyPair; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tokio_crate as tokio; @@ -720,11 +725,7 @@ mod tests { #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); - let mut transport = { - let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); - let cert = RTCCertificate::from_key_pair(kp).expect("certificate"); - WebRTCTransport::new(cert, id_keys) - }; + let mut transport = WebRTCTransport::new(id_keys); assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) .now_or_never() diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index a296111abf8..944a5304f66 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -13,26 +13,18 @@ use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p_core::{identity, muxing::StreamMuxerBox, upgrade, Transport}; use libp2p_webrtc::WebRTCTransport; use rand::RngCore; -use rcgen::KeyPair; use tokio_crate as tokio; -use webrtc::peer_connection::certificate::RTCCertificate; use std::{io, iter}; -fn generate_certificate() -> RTCCertificate { - let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("key pair"); - RTCCertificate::from_key_pair(kp).expect("certificate") -} - fn generate_tls_keypair() -> identity::Keypair { identity::Keypair::generate_ed25519() } fn create_swarm() -> Result>> { - let cert = generate_certificate(); let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let transport = WebRTCTransport::new(cert, keypair); + let transport = WebRTCTransport::new(keypair); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); From 09424244e6473a1ffaf983ad008ef04963fee66d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:53:03 +1100 Subject: [PATCH 116/244] Only calculate fingerprint once --- transports/webrtc/src/fingerprint.rs | 2 +- transports/webrtc/src/transport.rs | 41 ++++++++++------------------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 73111991678..5c7b7849065 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -45,7 +45,7 @@ impl Fingerprint { Fingerprint(bytes) } - pub fn try_from_rtc_dtls(fp: RTCDtlsFingerprint) -> Option { + pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { if fp.algorithm != SHA256 { return None; } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 7143e1028f1..5a24ba1dac2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -163,7 +163,7 @@ impl Transport for WebRTCTransport { trace!("dialing addr={}", remote); let config = self.config.clone(); - let our_fingerprint = self.config.fingerprint_of_first_certificate(); + let our_fingerprint = self.config.fingerprint(); let id_keys = self.id_keys.clone(); let first_listener = self @@ -320,9 +320,7 @@ impl WebRTCListenStream { { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( - self.config - .fingerprint_of_first_certificate() - .to_multi_hash(), + self.config.fingerprint().to_multi_hash(), )); log::debug!("New listen address: {}", ma); return Poll::Ready(TransportEvent::NewAddress { @@ -338,9 +336,7 @@ impl WebRTCListenStream { { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( - self.config - .fingerprint_of_first_certificate() - .to_multi_hash(), + self.config.fingerprint().to_multi_hash(), )); log::debug!("Expired listen address: {}", ma); return Poll::Ready(TransportEvent::AddressExpired { @@ -409,6 +405,7 @@ impl Stream for WebRTCListenStream { #[derive(Clone)] struct Config { inner: RTCConfiguration, + fingerprint: Fingerprint, } impl Config { @@ -419,33 +416,23 @@ impl Config { params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; let certificate = RTCCertificate::from_params(params).expect("default params to work"); + let fingerprints = certificate.get_fingerprints().expect("to never fail"); // TODO: Remove `Result` upstream? + Self { inner: RTCConfiguration { certificates: vec![certificate], ..RTCConfiguration::default() }, + fingerprint: Fingerprint::try_from_rtc_dtls( + fingerprints.first().expect("at least one certificate"), + ) + .expect("we specified SHA-256"), } } - /// Returns a SHA-256 fingerprint of the first certificate. - /// - /// # Panics - /// - /// Panics if the config does not contain any certificates. - fn fingerprint_of_first_certificate(&self) -> Fingerprint { - // safe to unwrap here because we require a certificate during construction. - let fingerprint = self - .inner - .certificates - .first() - .expect("at least one certificate") - .get_fingerprints() - .expect("get_fingerprints to succeed") - .first() - .expect("at least one certificate") - .clone(); - - Fingerprint::try_from_rtc_dtls(fingerprint).expect("a sha256 fingerprint") + /// Returns the fingerprint of our certificate. + fn fingerprint(&self) -> Fingerprint { + self.fingerprint } /// Consumes the `WebRTCConfiguration`, returning its inner configuration. @@ -542,7 +529,7 @@ async fn upgrade( ) -> Result<(PeerId, Connection), Error> { trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - let our_fingerprint = config.fingerprint_of_first_certificate(); + let our_fingerprint = config.fingerprint(); let conn = WebRTCConnection::accept( socket_addr, From e59b93bce7bc0f91b312a9a9762f19c3215139b3 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:54:58 +1100 Subject: [PATCH 117/244] Use naming convention of other transports --- transports/webrtc/src/lib.rs | 2 +- transports/webrtc/src/transport.rs | 14 +++++++------- transports/webrtc/tests/smoke.rs | 5 ++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 8ee49c03f26..c3da0c6b2bd 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -92,4 +92,4 @@ mod webrtc_connection; pub use connection::Connection; pub use error::Error; pub use fingerprint::Fingerprint; -pub use transport::WebRTCTransport; +pub use transport::Transport; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 5a24ba1dac2..df58710141f 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -31,7 +31,7 @@ use libp2p_core::{ identity, multiaddr::{Multiaddr, Protocol}, transport::{ListenerId, TransportError, TransportEvent}, - InboundUpgrade, OutboundUpgrade, PeerId, Transport, UpgradeInfo, + InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo, }; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use log::{debug, trace}; @@ -59,7 +59,7 @@ use crate::{ }; /// A WebRTC transport with direct p2p communication (without a STUN server). -pub struct WebRTCTransport { +pub struct Transport { /// The config which holds this peer's certificate(s). config: Config, /// `Keypair` identifying this peer @@ -68,7 +68,7 @@ pub struct WebRTCTransport { listeners: SelectAll, } -impl WebRTCTransport { +impl Transport { /// Creates a new WebRTC transport. pub fn new(id_keys: identity::Keypair) -> Self { Self { @@ -119,7 +119,7 @@ impl WebRTCTransport { } } -impl Transport for WebRTCTransport { +impl libp2p_core::Transport for Transport { type Output = (PeerId, Connection); type Error = Error; type ListenerUpgrade = BoxFuture<'static, Result>; @@ -360,7 +360,7 @@ impl WebRTCListenStream { } impl Stream for WebRTCListenStream { - type Item = TransportEvent<::ListenerUpgrade, Error>; + type Item = TransportEvent<::ListenerUpgrade, Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { @@ -615,7 +615,7 @@ mod tests { use super::*; use futures::future::poll_fn; use hex_literal::hex; - use libp2p_core::{multiaddr::Protocol, Multiaddr}; + use libp2p_core::{multiaddr::Protocol, Multiaddr, Transport as _}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tokio_crate as tokio; @@ -712,7 +712,7 @@ mod tests { #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); - let mut transport = WebRTCTransport::new(id_keys); + let mut transport = Transport::new(id_keys); assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) .now_or_never() diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 944a5304f66..8feb260338f 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -10,8 +10,7 @@ use libp2p::request_response::{ RequestResponseEvent, RequestResponseMessage, }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use libp2p_core::{identity, muxing::StreamMuxerBox, upgrade, Transport}; -use libp2p_webrtc::WebRTCTransport; +use libp2p_core::{identity, muxing::StreamMuxerBox, upgrade, Transport as _}; use rand::RngCore; use tokio_crate as tokio; @@ -24,7 +23,7 @@ fn generate_tls_keypair() -> identity::Keypair { fn create_swarm() -> Result>> { let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let transport = WebRTCTransport::new(keypair); + let transport = libp2p_webrtc::Transport::new(keypair); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); From 2133789e53c30e20002b4051957d57146d020069 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:55:23 +1100 Subject: [PATCH 118/244] Remove `Fingerprint` from public API --- transports/webrtc/src/fingerprint.rs | 29 ++++++++++++++++------------ transports/webrtc/src/lib.rs | 1 - 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 5c7b7849065..dfb1061d4eb 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -31,6 +31,7 @@ pub struct Fingerprint([u8; 32]); impl Fingerprint { pub(crate) const FF: Fingerprint = Fingerprint([0xFF; 32]); + #[cfg(test)] pub fn raw(bytes: [u8; 32]) -> Self { Self(bytes) } @@ -82,18 +83,6 @@ impl Fingerprint { /// Formats this fingerprint as uppercase hex, separated by colons (`:`). /// /// This is the format described in . - /// - /// # Example - /// - /// ```rust - /// # use hex_literal::hex; - /// # use libp2p_webrtc::Fingerprint; - /// let fp = Fingerprint::raw(hex!("7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC")); - /// - /// let sdp_format = fp.to_sdp_format(); - /// - /// assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") - /// ``` pub fn to_sdp_format(&self) -> String { self.0.map(|byte| format!("{:02X}", byte)).join(":") } @@ -104,3 +93,19 @@ impl Fingerprint { SHA256.to_owned() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sdp_format() { + let fp = Fingerprint::raw(hex_literal::hex!( + "7DE3D83F81A680592A471E6B6ABB0747ABD35385A8093FDFE112C1EEBB6CC6AC" + )); + + let sdp_format = fp.to_sdp_format(); + + assert_eq!(sdp_format, "7D:E3:D8:3F:81:A6:80:59:2A:47:1E:6B:6A:BB:07:47:AB:D3:53:85:A8:09:3F:DF:E1:12:C1:EE:BB:6C:C6:AC") + } +} diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index c3da0c6b2bd..dd0b465c576 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -91,5 +91,4 @@ mod webrtc_connection; pub use connection::Connection; pub use error::Error; -pub use fingerprint::Fingerprint; pub use transport::Transport; From 87f138b4e144a4ecaea941f84af8c9f7b8112ef6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:57:43 +1100 Subject: [PATCH 119/244] Move utilities to the bottom of the file --- transports/webrtc/tests/smoke.rs | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 8feb260338f..a95b245539c 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -16,28 +16,6 @@ use tokio_crate as tokio; use std::{io, iter}; -fn generate_tls_keypair() -> identity::Keypair { - identity::Keypair::generate_ed25519() -} - -fn create_swarm() -> Result>> { - let keypair = generate_tls_keypair(); - let peer_id = keypair.public().to_peer_id(); - let transport = libp2p_webrtc::Transport::new(keypair); - let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); - let cfg = RequestResponseConfig::default(); - let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); - let transport = transport - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(); - - Ok(SwarmBuilder::new(transport, behaviour, peer_id) - .executor(Box::new(|fut| { - tokio::spawn(fut); - })) - .build()) -} - #[tokio::test] async fn smoke() -> Result<()> { let _ = env_logger::builder().is_test(true).try_init(); @@ -468,3 +446,25 @@ async fn concurrent_connections_and_streams() { } } } + +fn create_swarm() -> Result>> { + let keypair = generate_tls_keypair(); + let peer_id = keypair.public().to_peer_id(); + let transport = libp2p_webrtc::Transport::new(keypair); + let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); + let cfg = RequestResponseConfig::default(); + let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); + let transport = transport + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); + + Ok(SwarmBuilder::new(transport, behaviour, peer_id) + .executor(Box::new(|fut| { + tokio::spawn(fut); + })) + .build()) +} + +fn generate_tls_keypair() -> identity::Keypair { + identity::Keypair::generate_ed25519() +} From 7fc56ed4b793845f53bcc7124f1fa7e797ccc51b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 11:59:07 +1100 Subject: [PATCH 120/244] Be consistent in how we import symbols --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/tests/smoke.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index bdfa02c6af7..5ba5e3d61cb 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -35,7 +35,7 @@ webrtc = "0.5.0" [dev-dependencies] anyhow = "1.0" env_logger = "0.9" -libp2p = { path = "../..", features = ["request-response"], default-features = false } +libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } rand_core = "0.5" quickcheck = "1" hex-literal = "0.3" diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index a95b245539c..f5d780b8e64 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -5,12 +5,13 @@ use futures::{ io::{AsyncRead, AsyncWrite, AsyncWriteExt}, stream::StreamExt, }; +use libp2p::core::{identity, muxing::StreamMuxerBox, upgrade, Transport as _}; use libp2p::request_response::{ ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use libp2p_core::{identity, muxing::StreamMuxerBox, upgrade, Transport as _}; +use libp2p::webrtc; use rand::RngCore; use tokio_crate as tokio; @@ -450,7 +451,7 @@ async fn concurrent_connections_and_streams() { fn create_swarm() -> Result>> { let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); - let transport = libp2p_webrtc::Transport::new(keypair); + let transport = webrtc::Transport::new(keypair); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); From 969d03734f564e7a8c83c53aa7d867d1ae9124ea Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 12:00:47 +1100 Subject: [PATCH 121/244] Parse certhash before starting dial --- transports/webrtc/src/error.rs | 2 -- transports/webrtc/src/transport.rs | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 7dd29c1ab9d..7a12b2efa74 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -24,8 +24,6 @@ use thiserror::Error; /// Error in WebRTC. #[derive(Error, Debug)] pub enum Error { - #[error("multi-address {0} is not supported")] - InvalidMultiaddr(libp2p_core::Multiaddr), #[error("webrtc error: {0}")] WebRTC(#[from] webrtc::Error), #[error("io error: {0}")] diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index df58710141f..608c503ebda 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -158,6 +158,8 @@ impl libp2p_core::Transport for Transport { if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } + let remote_fingerprint = fingerprint_from_addr(&addr) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; let remote = addr.clone(); // used for logging trace!("dialing addr={}", remote); @@ -176,9 +178,6 @@ impl libp2p_core::Transport for Transport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. Ok(async move { - let remote_fingerprint = fingerprint_from_addr(&addr) - .ok_or_else(|| Error::InvalidMultiaddr(addr.clone()))?; - let conn = WebRTCConnection::connect( sock_addr, config.into_inner(), From 5e7b9643f8073f8ba8dc13d4e3b4f4d4efeca07d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:21:12 +1100 Subject: [PATCH 122/244] Refactor multiaddr parsing --- transports/webrtc/Cargo.toml | 1 + transports/webrtc/src/fingerprint.rs | 7 + transports/webrtc/src/transport.rs | 252 ++++++++++++++++----------- 3 files changed, 161 insertions(+), 99 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 5ba5e3d61cb..c76eb04c86e 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -39,3 +39,4 @@ libp2p = { path = "../..", features = ["request-response", "webrtc"], default-fe rand_core = "0.5" quickcheck = "1" hex-literal = "0.3" +multihash = { version = "0.16", default-features = false, features = ["sha3"] } diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index dfb1061d4eb..2488c5aa954 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -20,6 +20,7 @@ use multibase::Base; use multihash::{Code, Hasher, Multihash, MultihashDigest}; +use std::fmt; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; const SHA256: &str = "sha-256"; @@ -94,6 +95,12 @@ impl Fingerprint { } } +impl fmt::Debug for Fingerprint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 608c503ebda..6d40e6d7372 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -42,6 +42,7 @@ use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; use rand::distributions::DistString; +use std::net::IpAddr; use std::{ net::SocketAddr, pin::Pin, @@ -84,7 +85,7 @@ impl Transport { addr: Multiaddr, ) -> Result> { let sock_addr = - multiaddr_to_socketaddr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; + parse_webrtc_listen_addr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; // XXX: `UdpSocket::bind` is async, so use a std socket and convert let std_sock = std::net::UdpSocket::bind(sock_addr) @@ -153,13 +154,11 @@ impl libp2p_core::Transport for Transport { } fn dial(&mut self, addr: Multiaddr) -> Result> { - let sock_addr = multiaddr_to_socketaddr(&addr) + let (sock_addr, remote_fingerprint) = parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } - let remote_fingerprint = fingerprint_from_addr(&addr) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; let remote = addr.clone(); // used for logging trace!("dialing addr={}", remote); @@ -448,52 +447,64 @@ fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { .with(Protocol::WebRTC) } -/// Extracts a SHA-256 fingerprint from the given address. Returns `None` if the address does not -/// contain one. -fn fingerprint_from_addr(addr: &Multiaddr) -> Option { - let iter = addr.iter(); - for proto in iter { - match proto { - // Only support SHA-256 (0x12) for now. - Protocol::Certhash(hash) => { - if let Some(fp) = Fingerprint::try_from_multihash(hash) { - return Some(fp); - } else { - continue; - } - } - _ => continue, - } +/// Parse the given [`Multiaddr`] into a [`SocketAddr`] for listening. +fn parse_webrtc_listen_addr(addr: &Multiaddr) -> Option { + let mut iter = addr.iter(); + + let ip = match iter.next()? { + Protocol::Ip4(ip) => IpAddr::from(ip), + Protocol::Ip6(ip) => IpAddr::from(ip), + _ => return None, + }; + + let port = iter.next()?; + let webrtc = iter.next()?; + + let port = match (port, webrtc) { + (Protocol::Udp(port), Protocol::WebRTC) => port, + _ => return None, + }; + + if iter.next().is_some() { + return None; } - None + + Some(SocketAddr::new(ip, port)) } -/// Tries to turn a WebRTC multiaddress into a [`SocketAddr`]. Returns None if the format of the -/// multiaddr is wrong. -fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option { +/// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. +fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { let mut iter = addr.iter(); - let proto1 = iter.next()?; - let proto2 = iter.next()?; - let proto3 = iter.next()?; - - // Return `None` if protocols other than `p2p` or `certhash` are present. - for proto in iter { - match proto { - Protocol::P2p(_) => {} - Protocol::Certhash(_) => {} - _ => return None, - } - } - match (proto1, proto2, proto3) { - (Protocol::Ip4(ip), Protocol::Udp(port), Protocol::WebRTC) => { - Some(SocketAddr::new(ip.into(), port)) - } - (Protocol::Ip6(ip), Protocol::Udp(port), Protocol::WebRTC) => { - Some(SocketAddr::new(ip.into(), port)) + let ip = match iter.next()? { + Protocol::Ip4(ip) => IpAddr::from(ip), + Protocol::Ip6(ip) => IpAddr::from(ip), + _ => return None, + }; + + let port = iter.next()?; + let webrtc = iter.next()?; + let certhash = iter.next()?; + + let (port, fingerprint) = match (port, webrtc, certhash) { + (Protocol::Udp(port), Protocol::WebRTC, Protocol::Certhash(hash)) => { + let fingerprint = Fingerprint::try_from_multihash(hash)?; + + (port, fingerprint) } - _ => None, + _ => return None, + }; + + let maybe_p2p = iter.next(); + let end = iter.next(); + + match (maybe_p2p, end) { + (Some(Protocol::P2p(_)), None) => {} // `/p2p` postfix is allowed, + (None, _) => {} // No postfix is allowed too + (Some(_), _) => return None, // other protocols are not } + + Some((SocketAddr::new(ip, port), fingerprint)) } async fn perform_noise_handshake_outbound( @@ -614,7 +625,7 @@ mod tests { use super::*; use futures::future::poll_fn; use hex_literal::hex; - use libp2p_core::{multiaddr::Protocol, Multiaddr, Transport as _}; + use libp2p_core::{multiaddr::Protocol, Transport as _}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tokio_crate as tokio; @@ -638,76 +649,119 @@ mod tests { } #[test] - fn multiaddr_to_socketaddr_conversion() { - assert!( - multiaddr_to_socketaddr(&"/ip4/127.0.0.1/udp/1234".parse::().unwrap()) - .is_none() - ); + fn missing_webrtc_protocol() { + let addr = "/ip4/127.0.0.1/udp/1234".parse().unwrap(); + + let maybe_parsed = parse_webrtc_listen_addr(&addr); + + assert!(maybe_parsed.is_none()); + } + + #[test] + fn parse_valid_address_with_certhash() { + let addr = "/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse::() - .unwrap() - ), - Some( SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 12345, - ) ) + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345,), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) ); + } - assert!( - multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/tcp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse::() - .unwrap() - ).is_none() - ); + #[test] + fn parse_valid_address_with_certhash_and_p2p() { + let addr = "/ip4/127.0.0.1/udp/39901/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" + .parse() + .unwrap(); - assert!(multiaddr_to_socketaddr( - &"/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/tcp/12345" - .parse::() - .unwrap() - ) - .is_none()); + let maybe_parsed = parse_webrtc_dial_addr(&addr); assert_eq!( - multiaddr_to_socketaddr( - &"/ip4/255.255.255.255/udp/8080/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse::() - .unwrap() - ), - Some(SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), - 8080, + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) )) ); + } + + #[test] + fn tcp_is_invalid_protocol() { + let addr = "/ip4/127.0.0.1/tcp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_listen_addr(&addr); + + assert!(maybe_parsed.is_none()); + } + + #[test] + fn cannot_follow_other_protocols_after_certhash() { + let addr = "/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/tcp/12345" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_listen_addr(&addr); + + assert!(maybe_parsed.is_none()); + } + + #[test] + fn parse_ipv6() { + let addr = + "/ip6/::1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); + assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/::1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse::() - .unwrap() - ), - Some( SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - ) ) + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) ); + } + + #[test] + fn can_parse_valid_addr_without_certhash() { + let addr = "/ip6/::1/udp/12345/webrtc".parse().unwrap(); + + let maybe_parsed = parse_webrtc_listen_addr(&addr); + assert_eq!( - multiaddr_to_socketaddr( - &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse::() - .unwrap() - ), - Some( SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, - )), - 8080, - ) ) + maybe_parsed, + Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345)) ); } + #[test] + fn fails_to_parse_if_certhash_present_but_wrong_hash_function() { + // We only support SHA2-256 for now but this certhash has been encoded with SHA3-256. + let addr = + "/ip6/::1/udp/12345/webrtc/certhash/uFiCH_tkkzpAwkoIDbE4I7QtQksFMYs5nQ4MyYrkgCJYi4A" + .parse() + .unwrap(); + + let maybe_addr = parse_webrtc_listen_addr(&addr); + + assert!(maybe_addr.is_none()) + } + #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); From 268d0fc77a62bf070f5b125429f870be25c4b755 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:24:36 +1100 Subject: [PATCH 123/244] Remove unused error variant --- transports/webrtc/src/error.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 7a12b2efa74..3c73e69f9c7 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -32,8 +32,6 @@ pub enum Error { Noise(#[from] libp2p_noise::NoiseError), // Authentication errors. - #[error("invalid fingerprint (expected {expected:?}, got {got:?})")] - InvalidFingerprint { expected: String, got: String }, #[error("invalid peer ID (expected {expected:?}, got {got:?})")] InvalidPeerID { expected: Option, From ba71d9b3541ea9ad472917655c2cf610e8c6f613 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:28:02 +1100 Subject: [PATCH 124/244] Don't use `#[from]` if we never use it --- transports/webrtc/src/error.rs | 2 +- transports/webrtc/src/transport.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 3c73e69f9c7..da1e99c9b40 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error("webrtc error: {0}")] WebRTC(#[from] webrtc::Error), #[error("io error: {0}")] - IoError(#[from] std::io::Error), + IoError(#[source] std::io::Error), #[error("noise error: {0}")] Noise(#[from] libp2p_noise::NoiseError), diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 6d40e6d7372..5d2d33cec82 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -347,7 +347,7 @@ impl WebRTCListenStream { log::debug!("Error when polling network interfaces {}", err); return Poll::Ready(TransportEvent::ListenerError { listener_id: self.listener_id, - error: err.into(), + error: Error::IoError(err), }); } } From 7452c4b55255fc7fe89bb5698b21f6537592174f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:28:35 +1100 Subject: [PATCH 125/244] Don't repeat `Error` name in variant --- transports/webrtc/src/connection.rs | 4 ++-- transports/webrtc/src/error.rs | 6 +++--- transports/webrtc/src/transport.rs | 14 +++++++------- transports/webrtc/src/webrtc_connection.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index af9a94966ad..af1a37529c6 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -166,7 +166,7 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(ch)) } - None => Poll::Ready(Err(Error::InternalError( + None => Poll::Ready(Err(Error::Internal( "incoming_data_channels_rx is closed (no messages left)".to_string(), ))), } @@ -206,7 +206,7 @@ impl StreamMuxer for Connection { // Wait until data channel is opened and ready to use match rx.await { Ok(detached) => Ok(detached), - Err(e) => Err(Error::InternalError(e.to_string())), + Err(e) => Err(Error::Internal(e.to_string())), } })); diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index da1e99c9b40..ad50ff2a203 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error("webrtc error: {0}")] WebRTC(#[from] webrtc::Error), #[error("io error: {0}")] - IoError(#[source] std::io::Error), + Io(#[source] std::io::Error), #[error("noise error: {0}")] Noise(#[from] libp2p_noise::NoiseError), @@ -42,8 +42,8 @@ pub enum Error { NoListeners, #[error("UDP mux error: {0}")] - UDPMuxError(std::io::Error), + UDPMux(std::io::Error), #[error("internal error: {0} (see debug logs)")] - InternalError(String), + Internal(String), } diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 5d2d33cec82..02e6cc55b25 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -89,19 +89,19 @@ impl Transport { // XXX: `UdpSocket::bind` is async, so use a std socket and convert let std_sock = std::net::UdpSocket::bind(sock_addr) - .map_err(Error::IoError) + .map_err(Error::Io) .map_err(TransportError::Other)?; std_sock .set_nonblocking(true) - .map_err(Error::IoError) + .map_err(Error::Io) .map_err(TransportError::Other)?; let socket = UdpSocket::from_std(std_sock) - .map_err(Error::IoError) + .map_err(Error::Io) .map_err(TransportError::Other)?; let listen_addr = socket .local_addr() - .map_err(Error::IoError) + .map_err(Error::Io) .map_err(TransportError::Other)?; debug!("listening on {}", listen_addr); @@ -114,7 +114,7 @@ impl Transport { udp_mux, self.id_keys.clone(), IfWatcher::new() - .map_err(Error::IoError) + .map_err(Error::Io) .map_err(TransportError::Other)?, )) } @@ -347,7 +347,7 @@ impl WebRTCListenStream { log::debug!("Error when polling network interfaces {}", err); return Poll::Ready(TransportEvent::ListenerError { listener_id: self.listener_id, - error: Error::IoError(err), + error: Error::Io(err), }); } } @@ -393,7 +393,7 @@ impl Stream for WebRTCListenStream { return Poll::Ready(Some(event)); } UDPMuxEvent::Error(e) => { - self.close(Err(Error::UDPMuxError(e))); + self.close(Err(Error::UDPMux(e))); } } } diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index b8af653bd64..1a39e478dce 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -142,9 +142,9 @@ impl WebRTCConnection { select! { res = rx => match res { Ok(detached) => Ok(detached), - Err(e) => Err(Error::InternalError(e.to_string())), + Err(e) => Err(Error::Internal(e.to_string())), }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::InternalError( + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( "data channel opening took longer than 10 seconds (see logs)".into(), )) } From 15a09ad83fe9d7af11bdcfd6fa480f53f52d7bb2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:30:39 +1100 Subject: [PATCH 126/244] Rename noise error variant and improve printed log Errors should never print their inner source. --- transports/webrtc/src/error.rs | 4 ++-- transports/webrtc/src/transport.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index ad50ff2a203..b29f758607b 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -28,8 +28,8 @@ pub enum Error { WebRTC(#[from] webrtc::Error), #[error("io error: {0}")] Io(#[source] std::io::Error), - #[error("noise error: {0}")] - Noise(#[from] libp2p_noise::NoiseError), + #[error("failed to authenticate peer")] + Authentication(#[from] libp2p_noise::NoiseError), // Authentication errors. #[error("invalid peer ID (expected {expected:?}, got {got:?})")] diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 02e6cc55b25..4d7152b5d22 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -600,6 +600,7 @@ where .into_authenticated() .upgrade_inbound(poll_data_channel, info) .await?; + Ok(peer_id) } From 69e2f5904c0288bf22cb332440755aca2603a6ce Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 14:32:03 +1100 Subject: [PATCH 127/244] Don't have errors print their inner source This leads to double printing of messages. --- transports/webrtc/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index b29f758607b..296e8eb1b6f 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -24,9 +24,9 @@ use thiserror::Error; /// Error in WebRTC. #[derive(Error, Debug)] pub enum Error { - #[error("webrtc error: {0}")] + #[error(transparent)] WebRTC(#[from] webrtc::Error), - #[error("io error: {0}")] + #[error("IO error")] Io(#[source] std::io::Error), #[error("failed to authenticate peer")] Authentication(#[from] libp2p_noise::NoiseError), From d0a0bb64ea0de8bbc0190d191becd9e7b1f7e95a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 15:17:52 +1100 Subject: [PATCH 128/244] Straight up require addresses to specify a peer ID The current implementation already enforces this by checking `peer_id_from_addr.is_none`. --- transports/webrtc/src/error.rs | 7 +-- transports/webrtc/src/transport.rs | 70 +++++++++++++----------------- 2 files changed, 31 insertions(+), 46 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 296e8eb1b6f..3a96d716201 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -32,11 +32,8 @@ pub enum Error { Authentication(#[from] libp2p_noise::NoiseError), // Authentication errors. - #[error("invalid peer ID (expected {expected:?}, got {got:?})")] - InvalidPeerID { - expected: Option, - got: PeerId, - }, + #[error("invalid peer ID (expected {expected}, got {got})")] + InvalidPeerID { expected: PeerId, got: PeerId }, #[error("no active listeners")] NoListeners, diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 4d7152b5d22..0877ad966a0 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -154,7 +154,7 @@ impl libp2p_core::Transport for Transport { } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (sock_addr, remote_fingerprint) = parse_webrtc_dial_addr(&addr) + let (sock_addr, remote_fingerprint, expected_peer_id) = parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); @@ -198,10 +198,9 @@ impl libp2p_core::Transport for Transport { .await?; trace!("verifying peer's identity addr={}", remote); - let peer_id_from_addr = PeerId::try_from_multiaddr(&addr); - if peer_id_from_addr.is_none() || peer_id_from_addr.unwrap() != peer_id { + if expected_peer_id != peer_id { return Err(Error::InvalidPeerID { - expected: peer_id_from_addr, + expected: expected_peer_id, got: peer_id, }); } @@ -473,7 +472,7 @@ fn parse_webrtc_listen_addr(addr: &Multiaddr) -> Option { } /// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. -fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { +fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, PeerId)> { let mut iter = addr.iter(); let ip = match iter.next()? { @@ -485,26 +484,28 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> let port = iter.next()?; let webrtc = iter.next()?; let certhash = iter.next()?; - - let (port, fingerprint) = match (port, webrtc, certhash) { - (Protocol::Udp(port), Protocol::WebRTC, Protocol::Certhash(hash)) => { - let fingerprint = Fingerprint::try_from_multihash(hash)?; - - (port, fingerprint) + let p2p = iter.next()?; + + let (port, fingerprint, peer_id) = match (port, webrtc, certhash, p2p) { + ( + Protocol::Udp(port), + Protocol::WebRTC, + Protocol::Certhash(cert_hash), + Protocol::P2p(peer_hash), + ) => { + let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; + let peer_id = PeerId::from_multihash(peer_hash).ok()?; + + (port, fingerprint, peer_id) } _ => return None, }; - let maybe_p2p = iter.next(); - let end = iter.next(); - - match (maybe_p2p, end) { - (Some(Protocol::P2p(_)), None) => {} // `/p2p` postfix is allowed, - (None, _) => {} // No postfix is allowed too - (Some(_), _) => return None, // other protocols are not + if iter.next().is_some() { + return None; } - Some((SocketAddr::new(ip, port), fingerprint)) + Some((SocketAddr::new(ip, port), fingerprint, peer_id)) } async fn perform_noise_handshake_outbound( @@ -658,25 +659,6 @@ mod tests { assert!(maybe_parsed.is_none()); } - #[test] - fn parse_valid_address_with_certhash() { - let addr = "/ip4/127.0.0.1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" - .parse() - .unwrap(); - - let maybe_parsed = parse_webrtc_dial_addr(&addr); - - assert_eq!( - maybe_parsed, - Some(( - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345,), - Fingerprint::raw(hex_literal::hex!( - "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) - )) - ); - } - #[test] fn parse_valid_address_with_certhash_and_p2p() { let addr = "/ip4/127.0.0.1/udp/39901/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" @@ -691,7 +673,10 @@ mod tests { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), Fingerprint::raw(hex_literal::hex!( "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) + )), + "12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" + .parse::() + .unwrap() )) ); } @@ -721,7 +706,7 @@ mod tests { #[test] fn parse_ipv6() { let addr = - "/ip6/::1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + "/ip6/::1/udp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" .parse() .unwrap(); @@ -733,7 +718,10 @@ mod tests { SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345), Fingerprint::raw(hex_literal::hex!( "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )) + )), + "12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" + .parse::() + .unwrap() )) ); } From 3d38e935eb1378069f87d5329e0d331b3e789055 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 15:50:01 +1100 Subject: [PATCH 129/244] Move noise upgrades to dedicated module --- transports/webrtc/src/lib.rs | 1 + transports/webrtc/src/transport.rs | 98 ++------------------------ transports/webrtc/src/upgrade.rs | 1 + transports/webrtc/src/upgrade/noise.rs | 92 ++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 94 deletions(-) create mode 100644 transports/webrtc/src/upgrade.rs create mode 100644 transports/webrtc/src/upgrade/noise.rs diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index dd0b465c576..8c7ca33b35d 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -87,6 +87,7 @@ mod req_res_chan; mod sdp; mod transport; mod udp_mux; +mod upgrade; mod webrtc_connection; pub use connection::Connection; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 0877ad966a0..025e6f36b79 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -18,24 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{ - future::BoxFuture, - io::{AsyncRead, AsyncWrite}, - prelude::*, - ready, - stream::SelectAll, - stream::Stream, -}; +use futures::{future::BoxFuture, prelude::*, ready, stream::SelectAll, stream::Stream}; use if_watch::{IfEvent, IfWatcher}; use libp2p_core::{ identity, multiaddr::{Multiaddr, Protocol}, transport::{ListenerId, TransportError, TransportEvent}, - InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo, + PeerId, }; -use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use log::{debug, trace}; -use multihash::Multihash; use tokio_crate::net::UdpSocket; use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; @@ -189,7 +180,7 @@ impl libp2p_core::Transport for Transport { let data_channel = conn.create_initial_upgrade_data_channel().await?; trace!("noise handshake with addr={}", remote); - let peer_id = perform_noise_handshake_outbound( + let peer_id = crate::upgrade::noise::outbound( id_keys, PollDataChannel::new(data_channel.clone()), our_fingerprint, @@ -508,29 +499,6 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, Some((SocketAddr::new(ip, port), fingerprint, peer_id)) } -async fn perform_noise_handshake_outbound( - id_keys: identity::Keypair, - poll_data_channel: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let dh_keys = Keypair::::new() - .into_authentic(&id_keys) - .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, _noise_io) = noise - .into_authenticated() - .upgrade_outbound(poll_data_channel, info) - .await?; - - Ok(peer_id) -} - async fn upgrade( udp_mux: Arc, config: Config, @@ -560,7 +528,7 @@ async fn upgrade( ufrag ); let remote_fingerprint = conn.get_remote_fingerprint().await; - let peer_id = perform_noise_handshake_inbound( + let peer_id = crate::upgrade::noise::inbound( id_keys, PollDataChannel::new(data_channel.clone()), our_fingerprint, @@ -582,74 +550,16 @@ async fn upgrade( Ok((peer_id, c)) } -async fn perform_noise_handshake_inbound( - id_keys: identity::Keypair, - poll_data_channel: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, -) -> Result -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - let dh_keys = Keypair::::new() - .into_authentic(&id_keys) - .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); - let info = noise.protocol_info().next().unwrap(); - let (peer_id, _noise_io) = noise - .into_authenticated() - .upgrade_inbound(poll_data_channel, info) - .await?; - - Ok(peer_id) -} - -fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { - let (a, b): (Multihash, Multihash) = ( - our_fingerprint.to_multi_hash(), - remote_fingerprint.to_multi_hash(), - ); - let (a, b) = (a.to_bytes(), b.to_bytes()); - let (first, second) = if a < b { (a, b) } else { (b, a) }; - const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); - out.extend_from_slice(PREFIX); - out.extend_from_slice(&first); - out.extend_from_slice(&second); - out -} - // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { use super::*; use futures::future::poll_fn; - use hex_literal::hex; use libp2p_core::{multiaddr::Protocol, Transport as _}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tokio_crate as tokio; - #[test] - fn noise_prologue_tests() { - let a = Fingerprint::raw(hex!( - "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" - )); - let b = Fingerprint::raw(hex!( - "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" - )); - - let prologue1 = noise_prologue(a, b); - let prologue2 = noise_prologue(b, a); - - assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - assert_eq!( - prologue1, prologue2, - "order of fingerprints does not matter" - ); - } - #[test] fn missing_webrtc_protocol() { let addr = "/ip4/127.0.0.1/udp/1234".parse().unwrap(); diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs new file mode 100644 index 00000000000..9b29c7b5d1a --- /dev/null +++ b/transports/webrtc/src/upgrade.rs @@ -0,0 +1 @@ +pub mod noise; diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs new file mode 100644 index 00000000000..85350862add --- /dev/null +++ b/transports/webrtc/src/upgrade/noise.rs @@ -0,0 +1,92 @@ +use crate::fingerprint::Fingerprint; +use crate::Error; +use futures::{AsyncRead, AsyncWrite}; +use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; +use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; +use multihash::Multihash; + +pub async fn outbound( + id_keys: identity::Keypair, + poll_data_channel: T, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, _noise_io) = noise + .into_authenticated() + .upgrade_outbound(poll_data_channel, info) + .await?; + + Ok(peer_id) +} + +pub async fn inbound( + id_keys: identity::Keypair, + poll_data_channel: T, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, +) -> Result +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let dh_keys = Keypair::::new() + .into_authentic(&id_keys) + .unwrap(); + let noise = + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + let info = noise.protocol_info().next().unwrap(); + let (peer_id, _noise_io) = noise + .into_authenticated() + .upgrade_inbound(poll_data_channel, info) + .await?; + + Ok(peer_id) +} + +pub fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { + let (a, b): (Multihash, Multihash) = ( + our_fingerprint.to_multi_hash(), + remote_fingerprint.to_multi_hash(), + ); + let (a, b) = (a.to_bytes(), b.to_bytes()); + let (first, second) = if a < b { (a, b) } else { (b, a) }; + const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; + let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); + out.extend_from_slice(PREFIX); + out.extend_from_slice(&first); + out.extend_from_slice(&second); + out +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn noise_prologue_tests() { + let a = Fingerprint::raw(hex!( + "3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870" + )); + let b = Fingerprint::raw(hex!( + "30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99" + )); + + let prologue1 = noise_prologue(a, b); + let prologue2 = noise_prologue(b, a); + + assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + assert_eq!( + prologue1, prologue2, + "order of fingerprints does not matter" + ); + } +} From 43eb42ada57a938da7929e48cdb7bdce87c931c6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 15:58:18 +1100 Subject: [PATCH 130/244] Have `WebRTCConnection::{accept,connect}` to the entire upgrade --- transports/webrtc/src/transport.rs | 65 +-------- transports/webrtc/src/webrtc_connection.rs | 161 ++++++++++++++------- 2 files changed, 111 insertions(+), 115 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 025e6f36b79..fd3f3243935 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -43,7 +43,6 @@ use std::{ use crate::{ connection::Connection, - connection::PollDataChannel, error::Error, fingerprint::Fingerprint, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, @@ -172,41 +171,14 @@ impl libp2p_core::Transport for Transport { sock_addr, config.into_inner(), udp_mux, - &remote_fingerprint, - ) - .await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = conn.create_initial_upgrade_data_channel().await?; - - trace!("noise handshake with addr={}", remote); - let peer_id = crate::upgrade::noise::outbound( - id_keys, - PollDataChannel::new(data_channel.clone()), our_fingerprint, remote_fingerprint, + id_keys, + expected_peer_id, ) .await?; - trace!("verifying peer's identity addr={}", remote); - if expected_peer_id != peer_id { - return Err(Error::InvalidPeerID { - expected: expected_peer_id, - got: peer_id, - }); - } - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - Ok((peer_id, c)) + Ok(conn) } .boxed()) } @@ -514,40 +486,13 @@ async fn upgrade( socket_addr, config.into_inner(), udp_mux, - &our_fingerprint, + our_fingerprint, &ufrag, - ) - .await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = conn.create_initial_upgrade_data_channel().await?; - - trace!( - "noise handshake with addr={} (ufrag={})", - socket_addr, - ufrag - ); - let remote_fingerprint = conn.get_remote_fingerprint().await; - let peer_id = crate::upgrade::noise::inbound( id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, ) .await?; - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(conn.into_inner()).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) + Ok(conn) } // Tests ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index 1a39e478dce..de0048e5569 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -18,10 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::connection::PollDataChannel; +use crate::Connection; use futures::{channel::oneshot, prelude::*, select}; use futures_timer::Delay; +use libp2p_core::{identity, PeerId}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data::data_channel::DataChannel; @@ -34,14 +38,10 @@ use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc::peer_connection::RTCPeerConnection; -use std::{net::SocketAddr, sync::Arc, time::Duration}; - use crate::error::Error; use crate::fingerprint::Fingerprint; -pub(crate) struct WebRTCConnection { - peer_connection: RTCPeerConnection, -} +pub(crate) struct WebRTCConnection; impl WebRTCConnection { /// Creates a new WebRTC peer connection to the remote. @@ -53,8 +53,11 @@ impl WebRTCConnection { addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - remote_fingerprint: &Fingerprint, - ) -> Result { + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, + id_keys: identity::Keypair, + expected_peer_id: PeerId, + ) -> Result<(PeerId, Connection), Error> { // TODO: at least 128 bit of entropy let ufrag: String = thread_rng() .sample_iter(&Alphanumeric) @@ -74,22 +77,54 @@ impl WebRTCConnection { // 2. ANSWER // Set the remote description to the predefined SDP. let server_session_description = - crate::sdp::render_server_session_description(addr, remote_fingerprint); + crate::sdp::render_server_session_description(addr, &remote_fingerprint); log::debug!("ANSWER: {:?}", server_session_description); let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); // NOTE: this will start the gathering of ICE candidates peer_connection.set_remote_description(sdp).await?; - Ok(Self { peer_connection }) + // Open a data channel to do Noise on top and verify the remote. + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={}", addr); + let peer_id = crate::upgrade::noise::outbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + log::trace!("verifying peer's identity addr={}", addr); + if expected_peer_id != peer_id { + return Err(Error::InvalidPeerID { + expected: expected_peer_id, + got: peer_id, + }); + } + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) } pub async fn accept( addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - our_fingerprint: &Fingerprint, + our_fingerprint: Fingerprint, remote_ufrag: &str, - ) -> Result { + id_keys: identity::Keypair, + ) -> Result<(PeerId, Connection), Error> { // Set both ICE user and password to our fingerprint because that's what the client is // expecting (see [`Self::connect`] "2. ANSWER"). let ufrag = our_fingerprint.to_ufrag(); @@ -119,51 +154,31 @@ impl WebRTCConnection { log::debug!("ANSWER: {:?}", answer.sdp); peer_connection.set_local_description(answer).await?; - Ok(Self { peer_connection }) - } - - pub async fn create_initial_upgrade_data_channel(&self) -> Result, Error> { // Open a data channel to do Noise on top and verify the remote. - let data_channel = self - .peer_connection - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: Some(0), - ..RTCDataChannelInit::default() - }), - ) - .await?; - - let (tx, mut rx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; - select! { - res = rx => match res { - Ok(detached) => Ok(detached), - Err(e) => Err(Error::Internal(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - } - } - - pub fn into_inner(self) -> RTCPeerConnection { - self.peer_connection - } - - /// Returns the SHA-256 fingerprint of the remote. - pub async fn get_remote_fingerprint(&self) -> Fingerprint { - let cert_bytes = self - .peer_connection - .sctp() - .transport() - .get_remote_certificate() - .await; - - Fingerprint::from_certificate(&cert_bytes) + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); + let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; + let peer_id = crate::upgrade::noise::inbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) } fn setting_engine( @@ -194,3 +209,39 @@ impl WebRTCConnection { se } } + +/// Returns the SHA-256 fingerprint of the remote. +async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { + let cert_bytes = conn.sctp().transport().get_remote_certificate().await; + + Fingerprint::from_certificate(&cert_bytes) +} + +async fn create_initial_upgrade_data_channel( + conn: &RTCPeerConnection, +) -> Result, Error> { + // Open a data channel to do Noise on top and verify the remote. + let data_channel = conn + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: Some(0), + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + select! { + res = rx => match res { + Ok(detached) => Ok(detached), + Err(e) => Err(Error::Internal(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + } +} From a9895778f013e145a4682513eafe63ede8133063 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 15:59:04 +1100 Subject: [PATCH 131/244] Make `setting_engine` a free function --- transports/webrtc/src/webrtc_connection.rs | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs index de0048e5569..de5ec8b682c 100644 --- a/transports/webrtc/src/webrtc_connection.rs +++ b/transports/webrtc/src/webrtc_connection.rs @@ -64,7 +64,7 @@ impl WebRTCConnection { .take(64) .map(char::from) .collect(); - let se = Self::setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); let api = APIBuilder::new().with_setting_engine(se).build(); let peer_connection = api.new_peer_connection(config).await?; @@ -128,7 +128,7 @@ impl WebRTCConnection { // Set both ICE user and password to our fingerprint because that's what the client is // expecting (see [`Self::connect`] "2. ANSWER"). let ufrag = our_fingerprint.to_ufrag(); - let mut se = Self::setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); { se.set_lite(true); se.disable_certificate_fingerprint_verification(true); @@ -180,34 +180,34 @@ impl WebRTCConnection { Ok((peer_id, c)) } +} - fn setting_engine( - udp_mux: Arc, - ufrag: &str, - is_ipv4: bool, - ) -> SettingEngine { - let mut se = SettingEngine::default(); - - se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); - - se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); - - // Allow detaching data channels. - se.detach_data_channels(); - - // Set the desired network type. - // - // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate - // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). - let network_type = if is_ipv4 { - NetworkType::Udp4 - } else { - NetworkType::Udp6 - }; - se.set_network_types(vec![network_type]); - - se - } +fn setting_engine( + udp_mux: Arc, + ufrag: &str, + is_ipv4: bool, +) -> SettingEngine { + let mut se = SettingEngine::default(); + + se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); + + se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); + + // Allow detaching data channels. + se.detach_data_channels(); + + // Set the desired network type. + // + // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate + // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). + let network_type = if is_ipv4 { + NetworkType::Udp4 + } else { + NetworkType::Udp6 + }; + se.set_network_types(vec![network_type]); + + se } /// Returns the SHA-256 fingerprint of the remote. From d5e67c25cda51f51e1f744e53c51be82c71589aa Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:00:01 +1100 Subject: [PATCH 132/244] Move `WebRTCConnection` to `upgrade` module --- transports/webrtc/src/lib.rs | 1 - transports/webrtc/src/transport.rs | 2 +- transports/webrtc/src/upgrade.rs | 248 +++++++++++++++++++++ transports/webrtc/src/webrtc_connection.rs | 247 -------------------- 4 files changed, 249 insertions(+), 249 deletions(-) delete mode 100644 transports/webrtc/src/webrtc_connection.rs diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index 8c7ca33b35d..5003bb3dfd0 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -88,7 +88,6 @@ mod sdp; mod transport; mod udp_mux; mod upgrade; -mod webrtc_connection; pub use connection::Connection; pub use error::Error; diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index fd3f3243935..6d692e92713 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -41,12 +41,12 @@ use std::{ task::{Context, Poll}, }; +use crate::upgrade::WebRTCConnection; use crate::{ connection::Connection, error::Error, fingerprint::Fingerprint, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, - webrtc_connection::WebRTCConnection, }; /// A WebRTC transport with direct p2p communication (without a STUN server). diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 9b29c7b5d1a..c8fda3d7114 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -1 +1,249 @@ pub mod noise; + +// Copyright 2022 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::PollDataChannel; +use crate::Connection; +use futures::{channel::oneshot, prelude::*, select}; +use futures_timer::Delay; +use libp2p_core::{identity, PeerId}; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data::data_channel::DataChannel; +use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::dtls_transport::dtls_role::DTLSRole; +use webrtc::ice::network_type::NetworkType; +use webrtc::ice::udp_mux::UDPMux; +use webrtc::ice::udp_network::UDPNetwork; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc::peer_connection::RTCPeerConnection; + +use crate::error::Error; +use crate::fingerprint::Fingerprint; + +pub(crate) struct WebRTCConnection; + +impl WebRTCConnection { + /// Creates a new WebRTC peer connection to the remote. + /// + /// # Panics + /// + /// Panics if the given address is not valid WebRTC dialing address. + pub async fn connect( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, + id_keys: identity::Keypair, + expected_peer_id: PeerId, + ) -> Result<(PeerId, Connection), Error> { + // TODO: at least 128 bit of entropy + let ufrag: String = thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect(); + let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api.new_peer_connection(config).await?; + + // 1. OFFER + let offer = peer_connection.create_offer(None).await?; + log::debug!("OFFER: {:?}", offer.sdp); + peer_connection.set_local_description(offer).await?; + + // 2. ANSWER + // Set the remote description to the predefined SDP. + let server_session_description = + crate::sdp::render_server_session_description(addr, &remote_fingerprint); + log::debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // NOTE: this will start the gathering of ICE candidates + peer_connection.set_remote_description(sdp).await?; + + // Open a data channel to do Noise on top and verify the remote. + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={}", addr); + let peer_id = crate::upgrade::noise::outbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + log::trace!("verifying peer's identity addr={}", addr); + if expected_peer_id != peer_id { + return Err(Error::InvalidPeerID { + expected: expected_peer_id, + got: peer_id, + }); + } + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) + } + + pub async fn accept( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: Fingerprint, + remote_ufrag: &str, + id_keys: identity::Keypair, + ) -> Result<(PeerId, Connection), Error> { + // Set both ICE user and password to our fingerprint because that's what the client is + // expecting (see [`Self::connect`] "2. ANSWER"). + let ufrag = our_fingerprint.to_ufrag(); + let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + { + se.set_lite(true); + se.disable_certificate_fingerprint_verification(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server)?; + } + + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; + + let client_session_description = + crate::sdp::render_client_session_description(addr, remote_ufrag); + log::debug!("OFFER: {:?}", client_session_description); + let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + peer_connection.set_remote_description(sdp).await?; + + let answer = peer_connection.create_answer(None).await?; + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + log::debug!("ANSWER: {:?}", answer.sdp); + peer_connection.set_local_description(answer).await?; + + // Open a data channel to do Noise on top and verify the remote. + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); + let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; + let peer_id = crate::upgrade::noise::inbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) + } +} + +fn setting_engine( + udp_mux: Arc, + ufrag: &str, + is_ipv4: bool, +) -> SettingEngine { + let mut se = SettingEngine::default(); + + se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); + + se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); + + // Allow detaching data channels. + se.detach_data_channels(); + + // Set the desired network type. + // + // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate + // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). + let network_type = if is_ipv4 { + NetworkType::Udp4 + } else { + NetworkType::Udp6 + }; + se.set_network_types(vec![network_type]); + + se +} + +/// Returns the SHA-256 fingerprint of the remote. +async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { + let cert_bytes = conn.sctp().transport().get_remote_certificate().await; + + Fingerprint::from_certificate(&cert_bytes) +} + +async fn create_initial_upgrade_data_channel( + conn: &RTCPeerConnection, +) -> Result, Error> { + // Open a data channel to do Noise on top and verify the remote. + let data_channel = conn + .create_data_channel( + "data", + Some(RTCDataChannelInit { + negotiated: Some(0), + ..RTCDataChannelInit::default() + }), + ) + .await?; + + let (tx, mut rx) = oneshot::channel::>(); + + // Wait until the data channel is opened and detach it. + crate::connection::register_data_channel_open_handler(data_channel, tx).await; + select! { + res = rx => match res { + Ok(detached) => Ok(detached), + Err(e) => Err(Error::Internal(e.to_string())), + }, + _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + } +} diff --git a/transports/webrtc/src/webrtc_connection.rs b/transports/webrtc/src/webrtc_connection.rs deleted file mode 100644 index de5ec8b682c..00000000000 --- a/transports/webrtc/src/webrtc_connection.rs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2022 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::PollDataChannel; -use crate::Connection; -use futures::{channel::oneshot, prelude::*, select}; -use futures_timer::Delay; -use libp2p_core::{identity, PeerId}; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; -use std::{net::SocketAddr, sync::Arc, time::Duration}; -use webrtc::api::setting_engine::SettingEngine; -use webrtc::api::APIBuilder; -use webrtc::data::data_channel::DataChannel; -use webrtc::data_channel::data_channel_init::RTCDataChannelInit; -use webrtc::dtls_transport::dtls_role::DTLSRole; -use webrtc::ice::network_type::NetworkType; -use webrtc::ice::udp_mux::UDPMux; -use webrtc::ice::udp_network::UDPNetwork; -use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; -use webrtc::peer_connection::RTCPeerConnection; - -use crate::error::Error; -use crate::fingerprint::Fingerprint; - -pub(crate) struct WebRTCConnection; - -impl WebRTCConnection { - /// Creates a new WebRTC peer connection to the remote. - /// - /// # Panics - /// - /// Panics if the given address is not valid WebRTC dialing address. - pub async fn connect( - addr: SocketAddr, - config: RTCConfiguration, - udp_mux: Arc, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, - id_keys: identity::Keypair, - expected_peer_id: PeerId, - ) -> Result<(PeerId, Connection), Error> { - // TODO: at least 128 bit of entropy - let ufrag: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(64) - .map(char::from) - .collect(); - let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - let api = APIBuilder::new().with_setting_engine(se).build(); - - let peer_connection = api.new_peer_connection(config).await?; - - // 1. OFFER - let offer = peer_connection.create_offer(None).await?; - log::debug!("OFFER: {:?}", offer.sdp); - peer_connection.set_local_description(offer).await?; - - // 2. ANSWER - // Set the remote description to the predefined SDP. - let server_session_description = - crate::sdp::render_server_session_description(addr, &remote_fingerprint); - log::debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); - // NOTE: this will start the gathering of ICE candidates - peer_connection.set_remote_description(sdp).await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - - log::trace!("noise handshake with addr={}", addr); - let peer_id = crate::upgrade::noise::outbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; - - log::trace!("verifying peer's identity addr={}", addr); - if expected_peer_id != peer_id { - return Err(Error::InvalidPeerID { - expected: expected_peer_id, - got: peer_id, - }); - } - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) - } - - pub async fn accept( - addr: SocketAddr, - config: RTCConfiguration, - udp_mux: Arc, - our_fingerprint: Fingerprint, - remote_ufrag: &str, - id_keys: identity::Keypair, - ) -> Result<(PeerId, Connection), Error> { - // Set both ICE user and password to our fingerprint because that's what the client is - // expecting (see [`Self::connect`] "2. ANSWER"). - let ufrag = our_fingerprint.to_ufrag(); - let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - { - se.set_lite(true); - se.disable_certificate_fingerprint_verification(true); - // Act as a DTLS server (one which waits for a connection). - // - // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, - // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server)?; - } - - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; - - let client_session_description = - crate::sdp::render_client_session_description(addr, remote_ufrag); - log::debug!("OFFER: {:?}", client_session_description); - let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); - peer_connection.set_remote_description(sdp).await?; - - let answer = peer_connection.create_answer(None).await?; - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - log::debug!("ANSWER: {:?}", answer.sdp); - peer_connection.set_local_description(answer).await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - - log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); - let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; - let peer_id = crate::upgrade::noise::inbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) - } -} - -fn setting_engine( - udp_mux: Arc, - ufrag: &str, - is_ipv4: bool, -) -> SettingEngine { - let mut se = SettingEngine::default(); - - se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); - - se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); - - // Allow detaching data channels. - se.detach_data_channels(); - - // Set the desired network type. - // - // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate - // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). - let network_type = if is_ipv4 { - NetworkType::Udp4 - } else { - NetworkType::Udp6 - }; - se.set_network_types(vec![network_type]); - - se -} - -/// Returns the SHA-256 fingerprint of the remote. -async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { - let cert_bytes = conn.sctp().transport().get_remote_certificate().await; - - Fingerprint::from_certificate(&cert_bytes) -} - -async fn create_initial_upgrade_data_channel( - conn: &RTCPeerConnection, -) -> Result, Error> { - // Open a data channel to do Noise on top and verify the remote. - let data_channel = conn - .create_data_channel( - "data", - Some(RTCDataChannelInit { - negotiated: Some(0), - ..RTCDataChannelInit::default() - }), - ) - .await?; - - let (tx, mut rx) = oneshot::channel::>(); - - // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; - select! { - res = rx => match res { - Ok(detached) => Ok(detached), - Err(e) => Err(Error::Internal(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - } -} From 410fc7750a475cad0ecec5d66e89a02940a07e7b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:01:03 +1100 Subject: [PATCH 133/244] Simplify call to `WebRTCConnection::connect` --- transports/webrtc/src/transport.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 6d692e92713..2cc5fd6c3e5 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -166,20 +166,15 @@ impl libp2p_core::Transport for Transport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. - Ok(async move { - let conn = WebRTCConnection::connect( - sock_addr, - config.into_inner(), - udp_mux, - our_fingerprint, - remote_fingerprint, - id_keys, - expected_peer_id, - ) - .await?; - - Ok(conn) - } + Ok(WebRTCConnection::connect( + sock_addr, + config.into_inner(), + udp_mux, + our_fingerprint, + remote_fingerprint, + id_keys, + expected_peer_id, + ) .boxed()) } From d363369cb6d7e41dd3283d156f366a40b7639b87 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:03:51 +1100 Subject: [PATCH 134/244] Inline `upgrade` function --- transports/webrtc/src/transport.rs | 36 +++++------------------------- transports/webrtc/src/upgrade.rs | 6 +++-- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 2cc5fd6c3e5..a10bc8431e3 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -28,7 +28,6 @@ use libp2p_core::{ }; use log::{debug, trace}; use tokio_crate::net::UdpSocket; -use webrtc::ice::udp_mux::UDPMux; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; @@ -37,7 +36,6 @@ use std::net::IpAddr; use std::{ net::SocketAddr, pin::Pin, - sync::Arc, task::{Context, Poll}, }; @@ -336,13 +334,15 @@ impl Stream for WebRTCListenStream { let local_addr = socketaddr_to_multiaddr(&self.listen_addr); let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); let event = TransportEvent::Incoming { - upgrade: Box::pin(upgrade( - self.udp_mux.udp_mux_handle(), - self.config.clone(), + upgrade: WebRTCConnection::accept( new_addr.addr, + self.config.clone().into_inner(), + self.udp_mux.udp_mux_handle(), + self.config.fingerprint(), new_addr.ufrag, self.id_keys.clone(), - )) as BoxFuture<'static, _>, + ) + .boxed(), local_addr, send_back_addr, listener_id: self.listener_id, @@ -466,30 +466,6 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, Some((SocketAddr::new(ip, port), fingerprint, peer_id)) } -async fn upgrade( - udp_mux: Arc, - config: Config, - socket_addr: SocketAddr, - ufrag: String, - id_keys: identity::Keypair, -) -> Result<(PeerId, Connection), Error> { - trace!("upgrading addr={} (ufrag={})", socket_addr, ufrag); - - let our_fingerprint = config.fingerprint(); - - let conn = WebRTCConnection::accept( - socket_addr, - config.into_inner(), - udp_mux, - our_fingerprint, - &ufrag, - id_keys, - ) - .await?; - - Ok(conn) -} - // Tests ////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index c8fda3d7114..4b2539025e8 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -124,9 +124,11 @@ impl WebRTCConnection { config: RTCConfiguration, udp_mux: Arc, our_fingerprint: Fingerprint, - remote_ufrag: &str, + remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { + log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); + // Set both ICE user and password to our fingerprint because that's what the client is // expecting (see [`Self::connect`] "2. ANSWER"). let ufrag = our_fingerprint.to_ufrag(); @@ -145,7 +147,7 @@ impl WebRTCConnection { let peer_connection = api.new_peer_connection(config).await?; let client_session_description = - crate::sdp::render_client_session_description(addr, remote_ufrag); + crate::sdp::render_client_session_description(addr, &remote_ufrag); log::debug!("OFFER: {:?}", client_session_description); let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); peer_connection.set_remote_description(sdp).await?; From 7108323528e6c508159d8070f4718794cc54d894 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:06:13 +1100 Subject: [PATCH 135/244] Fix imports and modules --- transports/webrtc/src/upgrade.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 4b2539025e8..afe1144cd4b 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -1,5 +1,3 @@ -pub mod noise; - // Copyright 2022 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,7 +18,11 @@ pub mod noise; // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +pub mod noise; + use crate::connection::PollDataChannel; +use crate::error::Error; +use crate::fingerprint::Fingerprint; use crate::Connection; use futures::{channel::oneshot, prelude::*, select}; use futures_timer::Delay; @@ -40,9 +42,6 @@ use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc::peer_connection::RTCPeerConnection; -use crate::error::Error; -use crate::fingerprint::Fingerprint; - pub(crate) struct WebRTCConnection; impl WebRTCConnection { From 41f7ab13520e5ef5194ddc9ee7bdcd9b7af77085 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:07:25 +1100 Subject: [PATCH 136/244] Remove unnecessary path prefix --- transports/webrtc/src/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index afe1144cd4b..d7ec3149e1a 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -88,7 +88,7 @@ impl WebRTCConnection { let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; log::trace!("noise handshake with addr={}", addr); - let peer_id = crate::upgrade::noise::outbound( + let peer_id = noise::outbound( id_keys, PollDataChannel::new(data_channel.clone()), our_fingerprint, @@ -162,7 +162,7 @@ impl WebRTCConnection { log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; - let peer_id = crate::upgrade::noise::inbound( + let peer_id = noise::inbound( id_keys, PollDataChannel::new(data_channel.clone()), our_fingerprint, From 0ba6954bdfe0fec0fa616e376053dedb119b8114 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:10:23 +1100 Subject: [PATCH 137/244] Get rid of `WebRTCConnection` object --- transports/webrtc/src/transport.rs | 6 +- transports/webrtc/src/upgrade.rs | 261 ++++++++++++++--------------- 2 files changed, 130 insertions(+), 137 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index a10bc8431e3..0b23be8b871 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -39,12 +39,12 @@ use std::{ task::{Context, Poll}, }; -use crate::upgrade::WebRTCConnection; use crate::{ connection::Connection, error::Error, fingerprint::Fingerprint, udp_mux::{UDPMuxEvent, UDPMuxNewAddr}, + upgrade, }; /// A WebRTC transport with direct p2p communication (without a STUN server). @@ -164,7 +164,7 @@ impl libp2p_core::Transport for Transport { // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `set_remote_description` call within the [`Future`]. - Ok(WebRTCConnection::connect( + Ok(upgrade::outbound( sock_addr, config.into_inner(), udp_mux, @@ -334,7 +334,7 @@ impl Stream for WebRTCListenStream { let local_addr = socketaddr_to_multiaddr(&self.listen_addr); let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); let event = TransportEvent::Incoming { - upgrade: WebRTCConnection::accept( + upgrade: upgrade::inbound( new_addr.addr, self.config.clone().into_inner(), self.udp_mux.udp_mux_handle(), diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index d7ec3149e1a..3b2f3397a4c 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -42,147 +42,140 @@ use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc::peer_connection::RTCPeerConnection; -pub(crate) struct WebRTCConnection; - -impl WebRTCConnection { - /// Creates a new WebRTC peer connection to the remote. - /// - /// # Panics - /// - /// Panics if the given address is not valid WebRTC dialing address. - pub async fn connect( - addr: SocketAddr, - config: RTCConfiguration, - udp_mux: Arc, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, - id_keys: identity::Keypair, - expected_peer_id: PeerId, - ) -> Result<(PeerId, Connection), Error> { - // TODO: at least 128 bit of entropy - let ufrag: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(64) - .map(char::from) - .collect(); - let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - let api = APIBuilder::new().with_setting_engine(se).build(); - - let peer_connection = api.new_peer_connection(config).await?; - - // 1. OFFER - let offer = peer_connection.create_offer(None).await?; - log::debug!("OFFER: {:?}", offer.sdp); - peer_connection.set_local_description(offer).await?; - - // 2. ANSWER - // Set the remote description to the predefined SDP. - let server_session_description = - crate::sdp::render_server_session_description(addr, &remote_fingerprint); - log::debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); - // NOTE: this will start the gathering of ICE candidates - peer_connection.set_remote_description(sdp).await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - - log::trace!("noise handshake with addr={}", addr); - let peer_id = noise::outbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; +/// Creates a new outbound WebRTC connection. +pub async fn outbound( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: Fingerprint, + remote_fingerprint: Fingerprint, + id_keys: identity::Keypair, + expected_peer_id: PeerId, +) -> Result<(PeerId, Connection), Error> { + // TODO: at least 128 bit of entropy + let ufrag: String = thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect(); + let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api.new_peer_connection(config).await?; + + // 1. OFFER + let offer = peer_connection.create_offer(None).await?; + log::debug!("OFFER: {:?}", offer.sdp); + peer_connection.set_local_description(offer).await?; + + // 2. ANSWER + // Set the remote description to the predefined SDP. + let server_session_description = + crate::sdp::render_server_session_description(addr, &remote_fingerprint); + log::debug!("ANSWER: {:?}", server_session_description); + let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + // NOTE: this will start the gathering of ICE candidates + peer_connection.set_remote_description(sdp).await?; - log::trace!("verifying peer's identity addr={}", addr); - if expected_peer_id != peer_id { - return Err(Error::InvalidPeerID { - expected: expected_peer_id, - got: peer_id, - }); - } - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) + // Open a data channel to do Noise on top and verify the remote. + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={}", addr); + let peer_id = noise::outbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + log::trace!("verifying peer's identity addr={}", addr); + if expected_peer_id != peer_id { + return Err(Error::InvalidPeerID { + expected: expected_peer_id, + got: peer_id, + }); } - pub async fn accept( - addr: SocketAddr, - config: RTCConfiguration, - udp_mux: Arc, - our_fingerprint: Fingerprint, - remote_ufrag: String, - id_keys: identity::Keypair, - ) -> Result<(PeerId, Connection), Error> { - log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); - - // Set both ICE user and password to our fingerprint because that's what the client is - // expecting (see [`Self::connect`] "2. ANSWER"). - let ufrag = our_fingerprint.to_ufrag(); - let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - { - se.set_lite(true); - se.disable_certificate_fingerprint_verification(true); - // Act as a DTLS server (one which waits for a connection). - // - // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, - // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server)?; - } - - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; - - let client_session_description = - crate::sdp::render_client_session_description(addr, &remote_ufrag); - log::debug!("OFFER: {:?}", client_session_description); - let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); - peer_connection.set_remote_description(sdp).await?; - - let answer = peer_connection.create_answer(None).await?; - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates - log::debug!("ANSWER: {:?}", answer.sdp); - peer_connection.set_local_description(answer).await?; - - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - - log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); - let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; - let peer_id = noise::inbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); + Ok((peer_id, c)) +} - Ok((peer_id, c)) +/// Creates a new inbound WebRTC connection. +pub async fn inbound( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + our_fingerprint: Fingerprint, + remote_ufrag: String, + id_keys: identity::Keypair, +) -> Result<(PeerId, Connection), Error> { + log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); + + // Set both ICE user and password to our fingerprint because that's what the client is + // expecting (see [`Self::connect`] "2. ANSWER"). + let ufrag = our_fingerprint.to_ufrag(); + let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + { + se.set_lite(true); + se.disable_certificate_fingerprint_verification(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server)?; } + + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; + + let client_session_description = + crate::sdp::render_client_session_description(addr, &remote_ufrag); + log::debug!("OFFER: {:?}", client_session_description); + let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + peer_connection.set_remote_description(sdp).await?; + + let answer = peer_connection.create_answer(None).await?; + // Set the local description and start UDP listeners + // Note: this will start the gathering of ICE candidates + log::debug!("ANSWER: {:?}", answer.sdp); + peer_connection.set_local_description(answer).await?; + + // Open a data channel to do Noise on top and verify the remote. + let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + + log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); + let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; + let peer_id = noise::inbound( + id_keys, + PollDataChannel::new(data_channel.clone()), + our_fingerprint, + remote_fingerprint, + ) + .await?; + + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + + let mut c = Connection::new(peer_connection).await; + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + c.set_data_channels_read_buf_capacity(8192 * 10); + + Ok((peer_id, c)) } fn setting_engine( From 9e3c1db4f3a2e8c045e47d5b87c1b1b8280cc5b2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:12:00 +1100 Subject: [PATCH 138/244] Extract helper for creating random ufrag --- transports/webrtc/src/upgrade.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 3b2f3397a4c..02c22579de3 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -52,12 +52,7 @@ pub async fn outbound( id_keys: identity::Keypair, expected_peer_id: PeerId, ) -> Result<(PeerId, Connection), Error> { - // TODO: at least 128 bit of entropy - let ufrag: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(64) - .map(char::from) - .collect(); + let ufrag = random_ufrag(); let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); let api = APIBuilder::new().with_setting_engine(se).build(); @@ -123,7 +118,7 @@ pub async fn inbound( log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); // Set both ICE user and password to our fingerprint because that's what the client is - // expecting (see [`Self::connect`] "2. ANSWER"). + // expecting (see [`outbound`] "2. ANSWER"). let ufrag = our_fingerprint.to_ufrag(); let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); { @@ -178,6 +173,16 @@ pub async fn inbound( Ok((peer_id, c)) } +fn random_ufrag() -> String { + // TODO: at least 128 bit of entropy + + thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect() +} + fn setting_engine( udp_mux: Arc, ufrag: &str, From 88c29fbcd1755e1f43eaf074a266a293c7b80e52 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:15:35 +1100 Subject: [PATCH 139/244] Extract helpers for new connections --- transports/webrtc/src/upgrade.rs | 60 ++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 02c22579de3..e9be7e2b489 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -52,11 +52,7 @@ pub async fn outbound( id_keys: identity::Keypair, expected_peer_id: PeerId, ) -> Result<(PeerId, Connection), Error> { - let ufrag = random_ufrag(); - let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - let api = APIBuilder::new().with_setting_engine(se).build(); - - let peer_connection = api.new_peer_connection(config).await?; + let peer_connection = new_outbound_connection(addr, config, udp_mux).await?; // 1. OFFER let offer = peer_connection.create_offer(None).await?; @@ -117,22 +113,9 @@ pub async fn inbound( ) -> Result<(PeerId, Connection), Error> { log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); - // Set both ICE user and password to our fingerprint because that's what the client is - // expecting (see [`outbound`] "2. ANSWER"). let ufrag = our_fingerprint.to_ufrag(); - let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - { - se.set_lite(true); - se.disable_certificate_fingerprint_verification(true); - // Act as a DTLS server (one which waits for a connection). - // - // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, - // but none end up responding). - se.set_answering_dtls_role(DTLSRole::Server)?; - } - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; + let peer_connection = new_inbound_connection(addr, config, udp_mux, &ufrag).await?; let client_session_description = crate::sdp::render_client_session_description(addr, &remote_ufrag); @@ -173,6 +156,43 @@ pub async fn inbound( Ok((peer_id, c)) } +async fn new_outbound_connection( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, +) -> Result { + let ufrag = random_ufrag(); + let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let api = APIBuilder::new().with_setting_engine(se).build(); + + let peer_connection = api.new_peer_connection(config).await?; + + Ok(peer_connection) +} + +async fn new_inbound_connection( + addr: SocketAddr, + config: RTCConfiguration, + udp_mux: Arc, + ufrag: &str, +) -> Result { + let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + { + se.set_lite(true); + se.disable_certificate_fingerprint_verification(true); + // Act as a DTLS server (one which waits for a connection). + // + // NOTE: removing this seems to break DTLS setup (both sides send `ClientHello` messages, + // but none end up responding). + se.set_answering_dtls_role(DTLSRole::Server)?; + } + + let api = APIBuilder::new().with_setting_engine(se).build(); + let peer_connection = api.new_peer_connection(config).await?; + + Ok(peer_connection) +} + fn random_ufrag() -> String { // TODO: at least 128 bit of entropy @@ -190,6 +210,8 @@ fn setting_engine( ) -> SettingEngine { let mut se = SettingEngine::default(); + // Set both ICE user and password to our fingerprint because that's what the client is + // expecting.. se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned()); se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone())); From a2136c9fa435e644a571be2b01675ed797ff74ac Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:19:10 +1100 Subject: [PATCH 140/244] Inline `api` variable --- transports/webrtc/src/upgrade.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index e9be7e2b489..e61dcd35005 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -163,11 +163,14 @@ async fn new_outbound_connection( ) -> Result { let ufrag = random_ufrag(); let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; + let connection = APIBuilder::new() + .with_setting_engine(se) + .build() + .new_peer_connection(config) + .await?; - Ok(peer_connection) + Ok(connection) } async fn new_inbound_connection( @@ -187,10 +190,13 @@ async fn new_inbound_connection( se.set_answering_dtls_role(DTLSRole::Server)?; } - let api = APIBuilder::new().with_setting_engine(se).build(); - let peer_connection = api.new_peer_connection(config).await?; + let connection = APIBuilder::new() + .with_setting_engine(se) + .build() + .new_peer_connection(config) + .await?; - Ok(peer_connection) + Ok(connection) } fn random_ufrag() -> String { From 116e498b285f7e0c100521d96484485aea9a678a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:20:20 +1100 Subject: [PATCH 141/244] Don't use boolean arguments when we can match on an enum --- transports/webrtc/src/upgrade.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index e61dcd35005..004727018e1 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -162,7 +162,7 @@ async fn new_outbound_connection( udp_mux: Arc, ) -> Result { let ufrag = random_ufrag(); - let se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let se = setting_engine(udp_mux, &ufrag, addr); let connection = APIBuilder::new() .with_setting_engine(se) @@ -179,7 +179,7 @@ async fn new_inbound_connection( udp_mux: Arc, ufrag: &str, ) -> Result { - let mut se = setting_engine(udp_mux, &ufrag, addr.is_ipv4()); + let mut se = setting_engine(udp_mux, &ufrag, addr); { se.set_lite(true); se.disable_certificate_fingerprint_verification(true); @@ -212,7 +212,7 @@ fn random_ufrag() -> String { fn setting_engine( udp_mux: Arc, ufrag: &str, - is_ipv4: bool, + addr: SocketAddr, ) -> SettingEngine { let mut se = SettingEngine::default(); @@ -229,10 +229,9 @@ fn setting_engine( // // NOTE: if not set, a [`webrtc_ice::agent::Agent`] might pick a wrong local candidate // (e.g. IPv6 `[::1]` while dialing an IPv4 `10.11.12.13`). - let network_type = if is_ipv4 { - NetworkType::Udp4 - } else { - NetworkType::Udp6 + let network_type = match addr { + SocketAddr::V4(_) => NetworkType::Udp4, + SocketAddr::V6(_) => NetworkType::Udp6, }; se.set_network_types(vec![network_type]); From 2935ca755ab269295181844360789d70a793071f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:23:49 +1100 Subject: [PATCH 142/244] Only set read_buf_capacity once --- transports/webrtc/src/connection.rs | 23 ++----------------- .../src/connection/poll_data_channel.rs | 13 ++++++----- transports/webrtc/src/upgrade.rs | 14 ++--------- 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index af1a37529c6..2445ff418be 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -56,10 +56,6 @@ pub struct Connection { /// Channel onto which incoming data channels are put. incoming_data_channels_rx: mpsc::Receiver>, - /// Temporary read buffer's capacity (equal for all data channels). - /// See [`PollDataChannel`] `read_buf_cap`. - read_buf_cap: Option, - /// Future, which, once polled, will result in an outbound substream. outbound_fut: Option, Error>>>, @@ -79,17 +75,11 @@ impl Connection { Self { peer_conn: Arc::new(FutMutex::new(rtc_conn)), incoming_data_channels_rx: data_channel_rx, - read_buf_cap: None, outbound_fut: None, close_fut: None, } } - /// Set the capacity of a data channel's temporary read buffer (equal for all data channels; default: 8192). - pub(crate) fn set_data_channels_read_buf_capacity(&mut self, cap: usize) { - self.read_buf_cap = Some(cap); - } - /// Registers a handler for incoming data channels. async fn register_incoming_data_channels_handler( rtc_conn: &RTCPeerConnection, @@ -159,12 +149,7 @@ impl StreamMuxer for Connection { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); - let mut ch = PollDataChannel::new(detached); - if let Some(cap) = self.read_buf_cap { - ch.set_read_buf_capacity(cap); - } - - Poll::Ready(Ok(ch)) + Poll::Ready(Ok(PollDataChannel::new(detached))) } None => Poll::Ready(Err(Error::Internal( "incoming_data_channels_rx is closed (no messages left)".to_string(), @@ -212,12 +197,8 @@ impl StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { - let mut ch = PollDataChannel::new(detached); - if let Some(cap) = self.read_buf_cap { - ch.set_read_buf_capacity(cap); - } self.outbound_fut = None; - Poll::Ready(Ok(ch)) + Poll::Ready(Ok(PollDataChannel::new(detached))) } Err(e) => { self.outbound_fut = None; diff --git a/transports/webrtc/src/connection/poll_data_channel.rs b/transports/webrtc/src/connection/poll_data_channel.rs index 3e682219cb0..095d9173e84 100644 --- a/transports/webrtc/src/connection/poll_data_channel.rs +++ b/transports/webrtc/src/connection/poll_data_channel.rs @@ -34,7 +34,13 @@ pub struct PollDataChannel(RTCPollDataChannel); impl PollDataChannel { /// Constructs a new `PollDataChannel`. pub fn new(data_channel: Arc) -> Self { - Self(RTCPollDataChannel::new(data_channel)) + let mut data_channel = RTCPollDataChannel::new(data_channel); + + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/sctp/issues/28 is fixed. + data_channel.set_read_buf_capacity(8192 * 10); + + Self(data_channel) } /// Get back the inner data_channel. @@ -83,11 +89,6 @@ impl PollDataChannel { pub fn buffered_amount_low_threshold(&self) -> usize { self.0.buffered_amount_low_threshold() } - - /// Set the capacity of the temporary read buffer (default: 8192). - pub fn set_read_buf_capacity(&mut self, capacity: usize) { - self.0.set_read_buf_capacity(capacity) - } } impl AsyncRead for PollDataChannel { diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 004727018e1..dc081e17c62 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -94,12 +94,7 @@ pub async fn outbound( .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) + Ok((peer_id, Connection::new(peer_connection).await)) } /// Creates a new inbound WebRTC connection. @@ -148,12 +143,7 @@ pub async fn inbound( .await .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - let mut c = Connection::new(peer_connection).await; - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/sctp/issues/28 is fixed. - c.set_data_channels_read_buf_capacity(8192 * 10); - - Ok((peer_id, c)) + Ok((peer_id, Connection::new(peer_connection).await)) } async fn new_outbound_connection( From 033e4853a2ff7f7e4f26452d8d6a409dd1dcbe04 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:25:25 +1100 Subject: [PATCH 143/244] Close data channel before verifying peer ID This is slightly nicer in case we would bail out of the function. --- transports/webrtc/src/upgrade.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index dc081e17c62..86833af3c47 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -80,6 +80,12 @@ pub async fn outbound( ) .await?; + // Close the initial data channel after noise handshake is done. + data_channel + .close() + .await + .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + log::trace!("verifying peer's identity addr={}", addr); if expected_peer_id != peer_id { return Err(Error::InvalidPeerID { @@ -88,12 +94,6 @@ pub async fn outbound( }); } - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; - Ok((peer_id, Connection::new(peer_connection).await)) } From c5c5b8da9e5b017d04494bc3dbcabb56b9d3143d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:27:37 +1100 Subject: [PATCH 144/244] Improve variable naming --- transports/webrtc/src/upgrade.rs | 1 - transports/webrtc/src/upgrade/noise.rs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 86833af3c47..c8cf5f333dd 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -71,7 +71,6 @@ pub async fn outbound( // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - log::trace!("noise handshake with addr={}", addr); let peer_id = noise::outbound( id_keys, PollDataChannel::new(data_channel.clone()), diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index 85350862add..bd1f910a7eb 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -7,7 +7,7 @@ use multihash::Multihash; pub async fn outbound( id_keys: identity::Keypair, - poll_data_channel: T, + stream: T, our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint, ) -> Result @@ -22,7 +22,7 @@ where let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() - .upgrade_outbound(poll_data_channel, info) + .upgrade_outbound(stream, info) .await?; Ok(peer_id) @@ -30,7 +30,7 @@ where pub async fn inbound( id_keys: identity::Keypair, - poll_data_channel: T, + stream: T, our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint, ) -> Result @@ -45,7 +45,7 @@ where let info = noise.protocol_info().next().unwrap(); let (peer_id, _noise_io) = noise .into_authenticated() - .upgrade_inbound(poll_data_channel, info) + .upgrade_inbound(stream, info) .await?; Ok(peer_id) From ef45b58c623c32516f84306a97b169a159b8d1e4 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:31:37 +1100 Subject: [PATCH 145/244] Directly return RTCSessionDescription from `sdp` module --- transports/webrtc/src/sdp.rs | 21 ++++++++++++++++----- transports/webrtc/src/upgrade.rs | 15 +++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index 94e0153b74f..b277ac8b0a6 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -23,26 +23,37 @@ use std::net::SocketAddr; use tinytemplate::TinyTemplate; use std::net::IpAddr; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use crate::fingerprint::Fingerprint; pub(crate) fn render_server_session_description( addr: SocketAddr, fingerprint: &Fingerprint, -) -> String { - render_description( +) -> RTCSessionDescription { + RTCSessionDescription::answer(render_description( SERVER_SESSION_DESCRIPTION, addr, fingerprint, &fingerprint.to_ufrag(), - ) + )) + .unwrap() } /// Renders the SDP client session description. /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn render_client_session_description(addr: SocketAddr, ufrag: &str) -> String { - render_description(CLIENT_SESSION_DESCRIPTION, addr, &Fingerprint::FF, ufrag) +pub(crate) fn render_client_session_description( + addr: SocketAddr, + ufrag: &str, +) -> RTCSessionDescription { + RTCSessionDescription::offer(render_description( + CLIENT_SESSION_DESCRIPTION, + addr, + &Fingerprint::FF, + ufrag, + )) + .unwrap() } // An SDP message that constitutes the offer. diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index c8cf5f333dd..d04ff6e6e4e 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -23,7 +23,7 @@ pub mod noise; use crate::connection::PollDataChannel; use crate::error::Error; use crate::fingerprint::Fingerprint; -use crate::Connection; +use crate::{sdp, Connection}; use futures::{channel::oneshot, prelude::*, select}; use futures_timer::Delay; use libp2p_core::{identity, PeerId}; @@ -39,7 +39,6 @@ use webrtc::ice::network_type::NetworkType; use webrtc::ice::udp_mux::UDPMux; use webrtc::ice::udp_network::UDPNetwork; use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use webrtc::peer_connection::RTCPeerConnection; /// Creates a new outbound WebRTC connection. @@ -61,10 +60,8 @@ pub async fn outbound( // 2. ANSWER // Set the remote description to the predefined SDP. - let server_session_description = - crate::sdp::render_server_session_description(addr, &remote_fingerprint); - log::debug!("ANSWER: {:?}", server_session_description); - let sdp = RTCSessionDescription::answer(server_session_description).unwrap(); + let sdp = sdp::render_server_session_description(addr, &remote_fingerprint); + log::debug!("ANSWER: {:?}", sdp); // NOTE: this will start the gathering of ICE candidates peer_connection.set_remote_description(sdp).await?; @@ -111,10 +108,8 @@ pub async fn inbound( let peer_connection = new_inbound_connection(addr, config, udp_mux, &ufrag).await?; - let client_session_description = - crate::sdp::render_client_session_description(addr, &remote_ufrag); - log::debug!("OFFER: {:?}", client_session_description); - let sdp = RTCSessionDescription::offer(client_session_description).unwrap(); + let sdp = sdp::render_client_session_description(addr, &remote_ufrag); + log::debug!("OFFER: {:?}", sdp); peer_connection.set_remote_description(sdp).await?; let answer = peer_connection.create_answer(None).await?; From d4c89bfc15228fb70fcb4d5a60d96cf456e0764a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:32:04 +1100 Subject: [PATCH 146/244] Remove unnecessary `pub(crate)` The entire module is crate private. --- transports/webrtc/src/sdp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index b277ac8b0a6..ed299e3c1a1 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -27,7 +27,7 @@ use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use crate::fingerprint::Fingerprint; -pub(crate) fn render_server_session_description( +pub fn render_server_session_description( addr: SocketAddr, fingerprint: &Fingerprint, ) -> RTCSessionDescription { @@ -43,7 +43,7 @@ pub(crate) fn render_server_session_description( /// Renders the SDP client session description. /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub(crate) fn render_client_session_description( +pub fn render_client_session_description( addr: SocketAddr, ufrag: &str, ) -> RTCSessionDescription { From 4795f81b2f1890a5c466b3397c45ae8681575a25 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:33:32 +1100 Subject: [PATCH 147/244] Consistent structure between inbound and outbound fns --- transports/webrtc/src/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index d04ff6e6e4e..39732a9c210 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -60,9 +60,9 @@ pub async fn outbound( // 2. ANSWER // Set the remote description to the predefined SDP. + // NOTE: this will start the gathering of ICE candidates let sdp = sdp::render_server_session_description(addr, &remote_fingerprint); log::debug!("ANSWER: {:?}", sdp); - // NOTE: this will start the gathering of ICE candidates peer_connection.set_remote_description(sdp).await?; // Open a data channel to do Noise on top and verify the remote. @@ -112,9 +112,9 @@ pub async fn inbound( log::debug!("OFFER: {:?}", sdp); peer_connection.set_remote_description(sdp).await?; - let answer = peer_connection.create_answer(None).await?; // Set the local description and start UDP listeners // Note: this will start the gathering of ICE candidates + let answer = peer_connection.create_answer(None).await?; log::debug!("ANSWER: {:?}", answer.sdp); peer_connection.set_local_description(answer).await?; From e0bec13d12d94415a1ddd3a1e25d51fc879cae70 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:35:31 +1100 Subject: [PATCH 148/244] Consistent logging --- transports/webrtc/src/upgrade.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 39732a9c210..8669b10b995 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -51,6 +51,8 @@ pub async fn outbound( id_keys: identity::Keypair, expected_peer_id: PeerId, ) -> Result<(PeerId, Connection), Error> { + log::trace!("new outbound connection to {addr})"); + let peer_connection = new_outbound_connection(addr, config, udp_mux).await?; // 1. OFFER @@ -102,7 +104,7 @@ pub async fn inbound( remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - log::trace!("upgrading addr={} (ufrag={})", addr, remote_ufrag); + log::trace!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); let ufrag = our_fingerprint.to_ufrag(); From ba961c38813f1b0179d540863bc8338fa72ffc4c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:39:39 +1100 Subject: [PATCH 149/244] Remove log before noise handshake We don't log it for outbound either. --- transports/webrtc/src/upgrade.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 8669b10b995..0fed8314fca 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -123,7 +123,6 @@ pub async fn inbound( // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - log::trace!("noise handshake with addr={} (ufrag={})", addr, ufrag); let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; let peer_id = noise::inbound( id_keys, From f5f447685299afd539462f09dd230fd697dca5fd Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:40:10 +1100 Subject: [PATCH 150/244] Inline `ufrag` variable --- transports/webrtc/src/upgrade.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 0fed8314fca..264093de088 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -106,9 +106,8 @@ pub async fn inbound( ) -> Result<(PeerId, Connection), Error> { log::trace!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); - let ufrag = our_fingerprint.to_ufrag(); - - let peer_connection = new_inbound_connection(addr, config, udp_mux, &ufrag).await?; + let peer_connection = + new_inbound_connection(addr, config, udp_mux, &our_fingerprint.to_ufrag()).await?; let sdp = sdp::render_client_session_description(addr, &remote_ufrag); log::debug!("OFFER: {:?}", sdp); From f643e0960f8a3b8df9ba24ec9e6d7b92ce579aaa Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:40:53 +1100 Subject: [PATCH 151/244] Improve naming of `sdp` module functions --- transports/webrtc/src/sdp.rs | 19 +++++++------------ transports/webrtc/src/upgrade.rs | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/sdp.rs index ed299e3c1a1..db4306d72f8 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/sdp.rs @@ -27,31 +27,26 @@ use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; use crate::fingerprint::Fingerprint; -pub fn render_server_session_description( - addr: SocketAddr, - fingerprint: &Fingerprint, -) -> RTCSessionDescription { +/// Creates the SDP answer used by the client. +pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint) -> RTCSessionDescription { RTCSessionDescription::answer(render_description( SERVER_SESSION_DESCRIPTION, addr, - fingerprint, - &fingerprint.to_ufrag(), + server_fingerprint, + &server_fingerprint.to_ufrag(), )) .unwrap() } -/// Renders the SDP client session description. +/// Creates the SDP offer used by the server. /// /// Certificate verification is disabled which is why we hardcode a dummy fingerprint here. -pub fn render_client_session_description( - addr: SocketAddr, - ufrag: &str, -) -> RTCSessionDescription { +pub fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescription { RTCSessionDescription::offer(render_description( CLIENT_SESSION_DESCRIPTION, addr, &Fingerprint::FF, - ufrag, + client_ufrag, )) .unwrap() } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 264093de088..c212676fb60 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -57,15 +57,18 @@ pub async fn outbound( // 1. OFFER let offer = peer_connection.create_offer(None).await?; - log::debug!("OFFER: {:?}", offer.sdp); + log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; // 2. ANSWER // Set the remote description to the predefined SDP. // NOTE: this will start the gathering of ICE candidates - let sdp = sdp::render_server_session_description(addr, &remote_fingerprint); - log::debug!("ANSWER: {:?}", sdp); - peer_connection.set_remote_description(sdp).await?; + let answer = sdp::answer(addr, &remote_fingerprint); + log::debug!( + "calculated SDP answer for outbound connection: {:?}", + answer + ); + peer_connection.set_remote_description(answer).await?; // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; @@ -109,14 +112,14 @@ pub async fn inbound( let peer_connection = new_inbound_connection(addr, config, udp_mux, &our_fingerprint.to_ufrag()).await?; - let sdp = sdp::render_client_session_description(addr, &remote_ufrag); - log::debug!("OFFER: {:?}", sdp); - peer_connection.set_remote_description(sdp).await?; + let offer = sdp::offer(addr, &remote_ufrag); + log::debug!("calculated SDP offer for inbound connection: {:?}", offer); + peer_connection.set_remote_description(offer).await?; // Set the local description and start UDP listeners // Note: this will start the gathering of ICE candidates let answer = peer_connection.create_answer(None).await?; - log::debug!("ANSWER: {:?}", answer.sdp); + log::debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // Open a data channel to do Noise on top and verify the remote. From 8a74009f9298e92b5b2819157a8889063e940e2c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:46:45 +1100 Subject: [PATCH 152/244] Remove superfluous comments --- transports/webrtc/src/upgrade.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index c212676fb60..f6a89aff67c 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -55,20 +55,16 @@ pub async fn outbound( let peer_connection = new_outbound_connection(addr, config, udp_mux).await?; - // 1. OFFER let offer = peer_connection.create_offer(None).await?; log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; - // 2. ANSWER - // Set the remote description to the predefined SDP. - // NOTE: this will start the gathering of ICE candidates let answer = sdp::answer(addr, &remote_fingerprint); log::debug!( "calculated SDP answer for outbound connection: {:?}", answer ); - peer_connection.set_remote_description(answer).await?; + peer_connection.set_remote_description(answer).await?; // This will start the gathering of ICE candidates. // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; @@ -116,11 +112,9 @@ pub async fn inbound( log::debug!("calculated SDP offer for inbound connection: {:?}", offer); peer_connection.set_remote_description(offer).await?; - // Set the local description and start UDP listeners - // Note: this will start the gathering of ICE candidates let answer = peer_connection.create_answer(None).await?; log::debug!("created SDP answer for inbound connection: {:?}", answer); - peer_connection.set_local_description(answer).await?; + peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; From b9917d5b115aaf329b8f715770fd6dcd030cf899 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:50:43 +1100 Subject: [PATCH 153/244] Replace `select` macro with `select` fn This is easier to maintain. --- transports/webrtc/src/upgrade.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index f6a89aff67c..0a4079ca2af 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -24,7 +24,8 @@ use crate::connection::PollDataChannel; use crate::error::Error; use crate::fingerprint::Fingerprint; use crate::{sdp, Connection}; -use futures::{channel::oneshot, prelude::*, select}; +use futures::channel::oneshot; +use futures::future::Either; use futures_timer::Delay; use libp2p_core::{identity, PeerId}; use rand::distributions::Alphanumeric; @@ -240,17 +241,22 @@ async fn create_initial_upgrade_data_channel( ) .await?; - let (tx, mut rx) = oneshot::channel::>(); + let (tx, rx) = oneshot::channel::>(); // Wait until the data channel is opened and detach it. crate::connection::register_data_channel_open_handler(data_channel, tx).await; - select! { - res = rx => match res { - Ok(detached) => Ok(detached), - Err(e) => Err(Error::Internal(e.to_string())), - }, - _ = Delay::new(Duration::from_secs(10)).fuse() => Err(Error::Internal( - "data channel opening took longer than 10 seconds (see logs)".into(), - )) - } + + let channel = match futures::future::select(rx, Delay::new(Duration::from_secs(10))).await { + Either::Left((Ok(channel), _)) => channel, + Either::Left((Err(_), _)) => { + return Err(Error::Internal("failed to open data channel".to_owned())) + } + Either::Right(((), _)) => { + return Err(Error::Internal( + "data channel opening took longer than 10 seconds (see logs)".into(), + )) + } + }; + + Ok(channel) } From f5d105e3b99a0ffe7187fe0821a75ff79f62060f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:56:14 +1100 Subject: [PATCH 154/244] Close noise data channel as part of upgrade --- transports/webrtc/src/error.rs | 2 +- transports/webrtc/src/upgrade.rs | 34 +++++--------------------- transports/webrtc/src/upgrade/noise.rs | 10 +++++--- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/error.rs index 3a96d716201..7dc620ee1f5 100644 --- a/transports/webrtc/src/error.rs +++ b/transports/webrtc/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] WebRTC(#[from] webrtc::Error), #[error("IO error")] - Io(#[source] std::io::Error), + Io(#[from] std::io::Error), #[error("failed to authenticate peer")] Authentication(#[from] libp2p_noise::NoiseError), diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 0a4079ca2af..915bc6c8724 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -70,19 +70,8 @@ pub async fn outbound( // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - let peer_id = noise::outbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + let peer_id = + noise::outbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; log::trace!("verifying peer's identity addr={}", addr); if expected_peer_id != peer_id { @@ -121,19 +110,8 @@ pub async fn inbound( let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; - let peer_id = noise::inbound( - id_keys, - PollDataChannel::new(data_channel.clone()), - our_fingerprint, - remote_fingerprint, - ) - .await?; - - // Close the initial data channel after noise handshake is done. - data_channel - .close() - .await - .map_err(|e| Error::WebRTC(webrtc::Error::Data(e)))?; + let peer_id = + noise::inbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; Ok((peer_id, Connection::new(peer_connection).await)) } @@ -229,7 +207,7 @@ async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { async fn create_initial_upgrade_data_channel( conn: &RTCPeerConnection, -) -> Result, Error> { +) -> Result { // Open a data channel to do Noise on top and verify the remote. let data_channel = conn .create_data_channel( @@ -258,5 +236,5 @@ async fn create_initial_upgrade_data_channel( } }; - Ok(channel) + Ok(PollDataChannel::new(channel)) } diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index bd1f910a7eb..9265b3e2bcf 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -1,6 +1,6 @@ use crate::fingerprint::Fingerprint; use crate::Error; -use futures::{AsyncRead, AsyncWrite}; +use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; use multihash::Multihash; @@ -20,11 +20,13 @@ where let noise = NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); - let (peer_id, _noise_io) = noise + let (peer_id, mut channel) = noise .into_authenticated() .upgrade_outbound(stream, info) .await?; + channel.close().await?; + Ok(peer_id) } @@ -43,11 +45,13 @@ where let noise = NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); let info = noise.protocol_info().next().unwrap(); - let (peer_id, _noise_io) = noise + let (peer_id, mut channel) = noise .into_authenticated() .upgrade_inbound(stream, info) .await?; + channel.close().await?; + Ok(peer_id) } From d4448ade20dac9fff384276d50834b8a4572f360 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:56:56 +1100 Subject: [PATCH 155/244] Group creation of arguments to next fn together --- transports/webrtc/src/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 915bc6c8724..0347f0ba663 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -108,8 +108,8 @@ pub async fn inbound( // Open a data channel to do Noise on top and verify the remote. let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; - let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; + let peer_id = noise::inbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; From afa35bbb93d7d0ba0cf1b165444ca3f89b39eb01 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 16:57:41 +1100 Subject: [PATCH 156/244] Remove need for comment by renaming function --- transports/webrtc/src/upgrade.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 0347f0ba663..ea129b5d3d2 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -67,8 +67,7 @@ pub async fn outbound( ); peer_connection.set_remote_description(answer).await?; // This will start the gathering of ICE candidates. - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + let data_channel = create_data_channel_for_noise_handshake(&peer_connection).await?; let peer_id = noise::outbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; @@ -106,8 +105,7 @@ pub async fn inbound( log::debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. - // Open a data channel to do Noise on top and verify the remote. - let data_channel = create_initial_upgrade_data_channel(&peer_connection).await?; + let data_channel = create_data_channel_for_noise_handshake(&peer_connection).await?; let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; let peer_id = @@ -205,7 +203,7 @@ async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { Fingerprint::from_certificate(&cert_bytes) } -async fn create_initial_upgrade_data_channel( +async fn create_data_channel_for_noise_handshake( conn: &RTCPeerConnection, ) -> Result { // Open a data channel to do Noise on top and verify the remote. From 030d210c674f76dc54970f6c1a031f1be053d937 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:00:49 +1100 Subject: [PATCH 157/244] Remove outdated comment --- transports/webrtc/src/transport.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 0b23be8b871..268cf0b66a9 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -162,8 +162,6 @@ impl libp2p_core::Transport for Transport { .ok_or(TransportError::Other(Error::NoListeners))?; let udp_mux = first_listener.udp_mux.udp_mux_handle(); - // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus - // do the `set_remote_description` call within the [`Future`]. Ok(upgrade::outbound( sock_addr, config.into_inner(), From ceba78efe8d812ef2cd7b2a02497131e46abe980 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:02:13 +1100 Subject: [PATCH 158/244] Perform peer ID check outside of ugprade functions This makes them more symmetric. --- transports/webrtc/src/transport.rs | 29 ++++++++++++++++++++--------- transports/webrtc/src/upgrade.rs | 9 --------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 268cf0b66a9..8c400ca1cbe 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -162,15 +162,26 @@ impl libp2p_core::Transport for Transport { .ok_or(TransportError::Other(Error::NoListeners))?; let udp_mux = first_listener.udp_mux.udp_mux_handle(); - Ok(upgrade::outbound( - sock_addr, - config.into_inner(), - udp_mux, - our_fingerprint, - remote_fingerprint, - id_keys, - expected_peer_id, - ) + Ok(async move { + let (actual_peer_id, connection) = upgrade::outbound( + sock_addr, + config.into_inner(), + udp_mux, + our_fingerprint, + remote_fingerprint, + id_keys, + ) + .await?; + + if actual_peer_id != expected_peer_id { + return Err(Error::InvalidPeerID { + expected: expected_peer_id, + got: actual_peer_id, + }); + } + + Ok((actual_peer_id, connection)) + } .boxed()) } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index ea129b5d3d2..6b18036267e 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -50,7 +50,6 @@ pub async fn outbound( our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint, id_keys: identity::Keypair, - expected_peer_id: PeerId, ) -> Result<(PeerId, Connection), Error> { log::trace!("new outbound connection to {addr})"); @@ -72,14 +71,6 @@ pub async fn outbound( let peer_id = noise::outbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; - log::trace!("verifying peer's identity addr={}", addr); - if expected_peer_id != peer_id { - return Err(Error::InvalidPeerID { - expected: expected_peer_id, - got: peer_id, - }); - } - Ok((peer_id, Connection::new(peer_connection).await)) } From ed9fad1454de7ccd50bdcdf40a1bb44aaabd054a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:02:55 +1100 Subject: [PATCH 159/244] Remove WebRTC prefix --- transports/webrtc/src/transport.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 8c400ca1cbe..9e1847e97f7 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -54,7 +54,7 @@ pub struct Transport { /// `Keypair` identifying this peer id_keys: identity::Keypair, /// All the active listeners. - listeners: SelectAll, + listeners: SelectAll, } impl Transport { @@ -71,7 +71,7 @@ impl Transport { &self, listener_id: ListenerId, addr: Multiaddr, - ) -> Result> { + ) -> Result> { let sock_addr = parse_webrtc_listen_addr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; @@ -95,7 +95,7 @@ impl Transport { let udp_mux = UDPMuxNewAddr::new(socket); - Ok(WebRTCListenStream::new( + Ok(ListenStream::new( listener_id, listen_addr, self.config.clone(), @@ -202,7 +202,7 @@ impl libp2p_core::Transport for Transport { } /// A stream of incoming connections on one or more interfaces. -struct WebRTCListenStream { +struct ListenStream { /// The ID of this listener. listener_id: ListenerId, @@ -234,7 +234,7 @@ struct WebRTCListenStream { if_watcher: IfWatcher, } -impl WebRTCListenStream { +impl ListenStream { /// Constructs a `WebRTCListenStream` for incoming connections. fn new( listener_id: ListenerId, @@ -244,7 +244,7 @@ impl WebRTCListenStream { id_keys: identity::Keypair, if_watcher: IfWatcher, ) -> Self { - WebRTCListenStream { + ListenStream { listener_id, listen_addr, config, @@ -321,7 +321,7 @@ impl WebRTCListenStream { } } -impl Stream for WebRTCListenStream { +impl Stream for ListenStream { type Item = TransportEvent<::ListenerUpgrade, Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { From 06be47607fd287f34fa90c4d949b289ce1bb090b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:04:21 +1100 Subject: [PATCH 160/244] Remove wrong log At this point we are not yet listening. --- transports/webrtc/src/transport.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 9e1847e97f7..b12e763f7bb 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -91,7 +91,6 @@ impl Transport { .local_addr() .map_err(Error::Io) .map_err(TransportError::Other)?; - debug!("listening on {}", listen_addr); let udp_mux = UDPMuxNewAddr::new(socket); From 5d20a00cd8b5f1c099e325f93cddb2261c8c44ff Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:04:47 +1100 Subject: [PATCH 161/244] Don't import log macros --- transports/webrtc/src/transport.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index b12e763f7bb..3faf2065f9b 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -26,7 +26,6 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, PeerId, }; -use log::{debug, trace}; use tokio_crate::net::UdpSocket; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; @@ -148,7 +147,7 @@ impl libp2p_core::Transport for Transport { } let remote = addr.clone(); // used for logging - trace!("dialing addr={}", remote); + log::trace!("dialing addr={}", remote); let config = self.config.clone(); let our_fingerprint = self.config.fingerprint(); @@ -258,7 +257,7 @@ impl ListenStream { /// terminate the stream. fn close(&mut self, reason: Result<(), Error>) { match self.report_closed { - Some(_) => debug!("Listener was already closed."), + Some(_) => log::debug!("Listener was already closed."), None => { // Report the listener event as closed. let _ = self From 52c9705fff14e4ca71ab91642d8bce0858103303 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:05:22 +1100 Subject: [PATCH 162/244] Inline variable --- transports/webrtc/src/transport.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 3faf2065f9b..812ff38d453 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -152,13 +152,13 @@ impl libp2p_core::Transport for Transport { let config = self.config.clone(); let our_fingerprint = self.config.fingerprint(); let id_keys = self.id_keys.clone(); - - let first_listener = self + let udp_mux = self .listeners .iter() .next() - .ok_or(TransportError::Other(Error::NoListeners))?; - let udp_mux = first_listener.udp_mux.udp_mux_handle(); + .ok_or(TransportError::Other(Error::NoListeners))? + .udp_mux + .udp_mux_handle(); Ok(async move { let (actual_peer_id, connection) = upgrade::outbound( From afaaea90c35d47cec748e0503d40ee5aa1aac68a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:05:39 +1100 Subject: [PATCH 163/244] Remove duplicate logging We now have a log in `upgrade::outbound`. --- transports/webrtc/src/transport.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 812ff38d453..277a4070c5c 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -146,9 +146,6 @@ impl libp2p_core::Transport for Transport { return Err(TransportError::MultiaddrNotSupported(addr)); } - let remote = addr.clone(); // used for logging - log::trace!("dialing addr={}", remote); - let config = self.config.clone(); let our_fingerprint = self.config.fingerprint(); let id_keys = self.id_keys.clone(); From c0323433fca573a905057c913712d90fb97cf877 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:08:48 +1100 Subject: [PATCH 164/244] Remove duplication in constructing local addr --- transports/webrtc/src/transport.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 277a4070c5c..b1ba83b3b27 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -276,9 +276,8 @@ impl ListenStream { || self.listen_addr.is_ipv6() == ip.is_ipv6() { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( - self.config.fingerprint().to_multi_hash(), - )); + let ma = + socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); log::debug!("New listen address: {}", ma); return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, @@ -292,9 +291,8 @@ impl ListenStream { || self.listen_addr.is_ipv6() == ip.is_ipv6() { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = socketaddr_to_multiaddr(&socket_addr).with(Protocol::Certhash( - self.config.fingerprint().to_multi_hash(), - )); + let ma = + socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); log::debug!("Expired listen address: {}", ma); return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, @@ -335,8 +333,9 @@ impl Stream for ListenStream { // Poll UDP muxer for new addresses or incoming data for streams. match ready!(self.udp_mux.poll(cx)) { UDPMuxEvent::NewAddr(new_addr) => { - let local_addr = socketaddr_to_multiaddr(&self.listen_addr); - let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr); + let local_addr = + socketaddr_to_multiaddr(&self.listen_addr, Some(self.config.fingerprint)); + let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr, None); let event = TransportEvent::Incoming { upgrade: upgrade::inbound( new_addr.addr, @@ -401,11 +400,17 @@ impl Config { } /// Turns an IP address and port into the corresponding WebRTC multiaddr. -fn socketaddr_to_multiaddr(socket_addr: &SocketAddr) -> Multiaddr { - Multiaddr::empty() +fn socketaddr_to_multiaddr(socket_addr: &SocketAddr, certhash: Option) -> Multiaddr { + let addr = Multiaddr::empty() .with(socket_addr.ip().into()) .with(Protocol::Udp(socket_addr.port())) - .with(Protocol::WebRTC) + .with(Protocol::WebRTC); + + if let Some(fp) = certhash { + return addr.with(Protocol::Certhash(fp.to_multi_hash())); + } + + addr } /// Parse the given [`Multiaddr`] into a [`SocketAddr`] for listening. From 3d44ac421219e8cab872734fedec196846326ca0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:10:13 +1100 Subject: [PATCH 165/244] Better use of whitespace --- transports/webrtc/src/transport.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index b1ba83b3b27..eec2be6cac4 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -336,21 +336,23 @@ impl Stream for ListenStream { let local_addr = socketaddr_to_multiaddr(&self.listen_addr, Some(self.config.fingerprint)); let send_back_addr = socketaddr_to_multiaddr(&new_addr.addr, None); - let event = TransportEvent::Incoming { - upgrade: upgrade::inbound( - new_addr.addr, - self.config.clone().into_inner(), - self.udp_mux.udp_mux_handle(), - self.config.fingerprint(), - new_addr.ufrag, - self.id_keys.clone(), - ) - .boxed(), + + let upgrade = upgrade::inbound( + new_addr.addr, + self.config.clone().into_inner(), + self.udp_mux.udp_mux_handle(), + self.config.fingerprint(), + new_addr.ufrag, + self.id_keys.clone(), + ) + .boxed(); + + return Poll::Ready(Some(TransportEvent::Incoming { + upgrade, local_addr, send_back_addr, listener_id: self.listener_id, - }; - return Poll::Ready(Some(event)); + })); } UDPMuxEvent::Error(e) => { self.close(Err(Error::UDPMux(e))); From e98a98167e9f75307429b31b03c59726ae07eed7 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:11:47 +1100 Subject: [PATCH 166/244] Remove superfluous logs This is logged by `libp2p_swarm` already. --- transports/webrtc/src/transport.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index eec2be6cac4..ad83b1f69a7 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -278,7 +278,7 @@ impl ListenStream { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); - log::debug!("New listen address: {}", ma); + return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, listen_addr: ma, @@ -293,7 +293,7 @@ impl ListenStream { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); let ma = socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); - log::debug!("Expired listen address: {}", ma); + return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, listen_addr: ma, @@ -301,7 +301,6 @@ impl ListenStream { } } Err(err) => { - log::debug!("Error when polling network interfaces {}", err); return Poll::Ready(TransportEvent::ListenerError { listener_id: self.listener_id, error: Error::Io(err), From f5095a02a18c414b7748be1a2162534fa6f074c6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:12:47 +1100 Subject: [PATCH 167/244] Remove duplication in `poll_if_watcher` --- transports/webrtc/src/transport.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index ad83b1f69a7..347d26d6121 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -275,13 +275,9 @@ impl ListenStream { if self.listen_addr.is_ipv4() == ip.is_ipv4() || self.listen_addr.is_ipv6() == ip.is_ipv6() { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = - socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); - return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, - listen_addr: ma, + listen_addr: self.listen_multi_address(ip), }); } } @@ -290,13 +286,9 @@ impl ListenStream { if self.listen_addr.is_ipv4() == ip.is_ipv4() || self.listen_addr.is_ipv6() == ip.is_ipv6() { - let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); - let ma = - socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)); - return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, - listen_addr: ma, + listen_addr: self.listen_multi_address(ip), }); } } @@ -311,6 +303,13 @@ impl ListenStream { Poll::Pending } + + /// Constructs a [`Multiaddr`] for the given IP address that represents our listen address. + fn listen_multi_address(&self, ip: IpAddr) -> Multiaddr { + let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); + + socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)) + } } impl Stream for ListenStream { From 09235a34b65bf9393fd3590078d7b82c8615bc2e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:17:38 +1100 Subject: [PATCH 168/244] Remove unnecessary functions from `Config` --- transports/webrtc/src/transport.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 347d26d6121..03d442fdea5 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -147,7 +147,7 @@ impl libp2p_core::Transport for Transport { } let config = self.config.clone(); - let our_fingerprint = self.config.fingerprint(); + let our_fingerprint = self.config.fingerprint; let id_keys = self.id_keys.clone(); let udp_mux = self .listeners @@ -160,7 +160,7 @@ impl libp2p_core::Transport for Transport { Ok(async move { let (actual_peer_id, connection) = upgrade::outbound( sock_addr, - config.into_inner(), + config.inner, udp_mux, our_fingerprint, remote_fingerprint, @@ -337,9 +337,9 @@ impl Stream for ListenStream { let upgrade = upgrade::inbound( new_addr.addr, - self.config.clone().into_inner(), + self.config.inner.clone(), self.udp_mux.udp_mux_handle(), - self.config.fingerprint(), + self.config.fingerprint, new_addr.ufrag, self.id_keys.clone(), ) @@ -387,16 +387,6 @@ impl Config { .expect("we specified SHA-256"), } } - - /// Returns the fingerprint of our certificate. - fn fingerprint(&self) -> Fingerprint { - self.fingerprint - } - - /// Consumes the `WebRTCConfiguration`, returning its inner configuration. - fn into_inner(self) -> RTCConfiguration { - self.inner - } } /// Turns an IP address and port into the corresponding WebRTC multiaddr. From b2cee2389ba40c0b53f29d9dcf1a263d225e0e60 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:25:48 +1100 Subject: [PATCH 169/244] Reduce boilerplate in `do_listen` --- transports/webrtc/src/transport.rs | 27 ++++----------------------- transports/webrtc/src/udp_mux.rs | 24 +++++++++++++++++++----- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 03d442fdea5..8e284139a9e 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -26,7 +26,6 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, PeerId, }; -use tokio_crate::net::UdpSocket; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; @@ -71,31 +70,14 @@ impl Transport { listener_id: ListenerId, addr: Multiaddr, ) -> Result> { - let sock_addr = + let socket_addr = parse_webrtc_listen_addr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; - // XXX: `UdpSocket::bind` is async, so use a std socket and convert - let std_sock = std::net::UdpSocket::bind(sock_addr) - .map_err(Error::Io) - .map_err(TransportError::Other)?; - std_sock - .set_nonblocking(true) - .map_err(Error::Io) - .map_err(TransportError::Other)?; - let socket = UdpSocket::from_std(std_sock) - .map_err(Error::Io) - .map_err(TransportError::Other)?; - - let listen_addr = socket - .local_addr() - .map_err(Error::Io) - .map_err(TransportError::Other)?; - - let udp_mux = UDPMuxNewAddr::new(socket); + let udp_mux = UDPMuxNewAddr::listen_on(socket_addr) + .map_err(|io| TransportError::Other(Error::Io(io)))?; Ok(ListenStream::new( listener_id, - listen_addr, self.config.clone(), udp_mux, self.id_keys.clone(), @@ -233,7 +215,6 @@ impl ListenStream { /// Constructs a `WebRTCListenStream` for incoming connections. fn new( listener_id: ListenerId, - listen_addr: SocketAddr, config: Config, udp_mux: UDPMuxNewAddr, id_keys: identity::Keypair, @@ -241,7 +222,7 @@ impl ListenStream { ) -> Self { ListenStream { listener_id, - listen_addr, + listen_addr: udp_mux.listen_addr(), config, udp_mux, id_keys, diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 79c5e02a56d..b52adcc5ee5 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -36,6 +36,7 @@ use futures::future::{BoxFuture, FutureExt, OptionFuture}; use futures::stream::FuturesUnordered; use std::{ collections::{HashMap, HashSet}, + io, io::ErrorKind, net::SocketAddr, sync::Arc, @@ -67,6 +68,8 @@ pub enum UDPMuxEvent { pub struct UDPMuxNewAddr { udp_sock: UdpSocket, + listen_addr: SocketAddr, + /// Maps from ufrag to the underlying connection. conns: HashMap, @@ -95,14 +98,21 @@ pub struct UDPMuxNewAddr { } impl UDPMuxNewAddr { - /// Creates a new UDP muxer. - pub fn new(udp_sock: UdpSocket) -> Self { + pub fn listen_on(addr: SocketAddr) -> Result { + // XXX: `UdpSocket::bind` is async, so use a std socket and convert + let std_sock = std::net::UdpSocket::bind(addr)?; + std_sock.set_nonblocking(true)?; + + let tokio_socket = UdpSocket::from_std(std_sock)?; + let listen_addr = tokio_socket.local_addr()?; + let (udp_mux_handle, close_command, get_conn_command, remove_conn_command) = UdpMuxHandle::new(); let (udp_mux_writer_handle, registration_command, send_command) = UdpMuxWriterHandle::new(); - Self { - udp_sock, + Ok(Self { + udp_sock: tokio_socket, + listen_addr, conns: HashMap::default(), address_map: HashMap::default(), new_addrs: HashSet::default(), @@ -117,7 +127,11 @@ impl UDPMuxNewAddr { send_command, udp_mux_handle: Arc::new(udp_mux_handle), udp_mux_writer_handle: Arc::new(udp_mux_writer_handle), - } + }) + } + + pub fn listen_addr(&self) -> SocketAddr { + self.listen_addr } pub fn udp_mux_handle(&self) -> Arc { From dce23716ed15a9ebb1ab11a78876c182063d7455 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:26:51 +1100 Subject: [PATCH 170/244] Inline `do_listen` --- transports/webrtc/src/transport.rs | 34 ++++++++++++------------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 8e284139a9e..b380e148703 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -64,40 +64,32 @@ impl Transport { listeners: SelectAll::new(), } } +} + +impl libp2p_core::Transport for Transport { + type Output = (PeerId, Connection); + type Error = Error; + type ListenerUpgrade = BoxFuture<'static, Result>; + type Dial = BoxFuture<'static, Result>; + + fn listen_on(&mut self, addr: Multiaddr) -> Result> { + let id = ListenerId::new(); - fn do_listen( - &self, - listener_id: ListenerId, - addr: Multiaddr, - ) -> Result> { let socket_addr = parse_webrtc_listen_addr(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; - let udp_mux = UDPMuxNewAddr::listen_on(socket_addr) .map_err(|io| TransportError::Other(Error::Io(io)))?; - Ok(ListenStream::new( - listener_id, + self.listeners.push(ListenStream::new( + id, self.config.clone(), udp_mux, self.id_keys.clone(), IfWatcher::new() .map_err(Error::Io) .map_err(TransportError::Other)?, - )) - } -} - -impl libp2p_core::Transport for Transport { - type Output = (PeerId, Connection); - type Error = Error; - type ListenerUpgrade = BoxFuture<'static, Result>; - type Dial = BoxFuture<'static, Result>; + )); - fn listen_on(&mut self, addr: Multiaddr) -> Result> { - let id = ListenerId::new(); - let listener = self.do_listen(id, addr)?; - self.listeners.push(listener); Ok(id) } From 577a24e0f67c9c7c75bcc60b55d621a22568d8c6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:27:07 +1100 Subject: [PATCH 171/244] Less verbose error mapping --- transports/webrtc/src/transport.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index b380e148703..4540e6088b4 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -85,9 +85,7 @@ impl libp2p_core::Transport for Transport { self.config.clone(), udp_mux, self.id_keys.clone(), - IfWatcher::new() - .map_err(Error::Io) - .map_err(TransportError::Other)?, + IfWatcher::new().map_err(|io| TransportError::Other(Error::Io(io)))?, )); Ok(id) From fab5a32728fe5644f51db2f9f3ce38b9ded19696 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:31:23 +1100 Subject: [PATCH 172/244] Inline `webrtc` in `full` feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ae5e90aefe5..efed92b7afd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ full = [ "wasm-bindgen", "wasm-ext", "wasm-ext-websocket", + "webrtc", "websocket", "yamux", ] From 316052cab5b6dd46dbc7c68a2ead56689a575dac Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:34:33 +1100 Subject: [PATCH 173/244] Log new connections on debug --- transports/webrtc/src/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 6b18036267e..1afb14d2870 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -51,7 +51,7 @@ pub async fn outbound( remote_fingerprint: Fingerprint, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - log::trace!("new outbound connection to {addr})"); + log::debug!("new outbound connection to {addr})"); let peer_connection = new_outbound_connection(addr, config, udp_mux).await?; @@ -83,7 +83,7 @@ pub async fn inbound( remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { - log::trace!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); + log::debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); let peer_connection = new_inbound_connection(addr, config, udp_mux, &our_fingerprint.to_ufrag()).await?; From ee8fe4e071d5eaea1e95b1eafaacf8e6e3c1cf74 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:36:49 +1100 Subject: [PATCH 174/244] Make noise module private No longer needs to be pub. --- transports/webrtc/src/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 1afb14d2870..969f06cfe14 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub mod noise; +mod noise; use crate::connection::PollDataChannel; use crate::error::Error; From c5b86d7204b2695d92abff122736b45cac5b7217 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 7 Oct 2022 17:37:21 +1100 Subject: [PATCH 175/244] Move test utils to bottom --- transports/webrtc/tests/smoke.rs | 159 ++++++++++++++++--------------- 1 file changed, 80 insertions(+), 79 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index f5d780b8e64..46fd6639d0f 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -181,85 +181,6 @@ async fn smoke() -> Result<()> { Ok(()) } -#[derive(Debug, Clone)] -struct PingProtocol(); - -#[derive(Clone)] -struct PingCodec(); - -#[derive(Debug, Clone, PartialEq, Eq)] -struct Ping(Vec); - -#[derive(Debug, Clone, PartialEq, Eq)] -struct Pong(Vec); - -impl ProtocolName for PingProtocol { - fn protocol_name(&self) -> &[u8] { - "/ping/1".as_bytes() - } -} - -#[async_trait] -impl RequestResponseCodec for PingCodec { - type Protocol = PingProtocol; - type Request = Ping; - type Response = Pong; - - async fn read_request(&mut self, _: &PingProtocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - upgrade::read_length_prefixed(io, 4096) - .map(|res| match res { - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), - Ok(vec) => Ok(Ping(vec)), - }) - .await - } - - async fn read_response(&mut self, _: &PingProtocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - upgrade::read_length_prefixed(io, 4096) - .map(|res| match res { - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), - Ok(vec) => Ok(Pong(vec)), - }) - .await - } - - async fn write_request( - &mut self, - _: &PingProtocol, - io: &mut T, - Ping(data): Ping, - ) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - upgrade::write_length_prefixed(io, data).await?; - io.close().await?; - Ok(()) - } - - async fn write_response( - &mut self, - _: &PingProtocol, - io: &mut T, - Pong(data): Pong, - ) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - upgrade::write_length_prefixed(io, data).await?; - io.close().await?; - Ok(()) - } -} - #[tokio::test] async fn dial_failure() -> Result<()> { let _ = env_logger::builder().is_test(true).try_init(); @@ -448,6 +369,86 @@ async fn concurrent_connections_and_streams() { } } + +#[derive(Debug, Clone)] +struct PingProtocol(); + +#[derive(Clone)] +struct PingCodec(); + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Ping(Vec); + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Pong(Vec); + +impl ProtocolName for PingProtocol { + fn protocol_name(&self) -> &[u8] { + "/ping/1".as_bytes() + } +} + +#[async_trait] +impl RequestResponseCodec for PingCodec { + type Protocol = PingProtocol; + type Request = Ping; + type Response = Pong; + + async fn read_request(&mut self, _: &PingProtocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + upgrade::read_length_prefixed(io, 4096) + .map(|res| match res { + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), + Ok(vec) => Ok(Ping(vec)), + }) + .await + } + + async fn read_response(&mut self, _: &PingProtocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + upgrade::read_length_prefixed(io, 4096) + .map(|res| match res { + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + Ok(vec) if vec.is_empty() => Err(io::ErrorKind::UnexpectedEof.into()), + Ok(vec) => Ok(Pong(vec)), + }) + .await + } + + async fn write_request( + &mut self, + _: &PingProtocol, + io: &mut T, + Ping(data): Ping, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + upgrade::write_length_prefixed(io, data).await?; + io.close().await?; + Ok(()) + } + + async fn write_response( + &mut self, + _: &PingProtocol, + io: &mut T, + Pong(data): Pong, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + upgrade::write_length_prefixed(io, data).await?; + io.close().await?; + Ok(()) + } +} + fn create_swarm() -> Result>> { let keypair = generate_tls_keypair(); let peer_id = keypair.public().to_peer_id(); From db456029a42dbfae04b544020d7569645ec97d6a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 8 Oct 2022 01:29:35 +1100 Subject: [PATCH 176/244] Fix formatting issues --- transports/webrtc/tests/smoke.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 46fd6639d0f..ed18e94c307 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -369,7 +369,6 @@ async fn concurrent_connections_and_streams() { } } - #[derive(Debug, Clone)] struct PingProtocol(); @@ -395,8 +394,8 @@ impl RequestResponseCodec for PingCodec { type Response = Pong; async fn read_request(&mut self, _: &PingProtocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, + where + T: AsyncRead + Unpin + Send, { upgrade::read_length_prefixed(io, 4096) .map(|res| match res { @@ -408,8 +407,8 @@ impl RequestResponseCodec for PingCodec { } async fn read_response(&mut self, _: &PingProtocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, + where + T: AsyncRead + Unpin + Send, { upgrade::read_length_prefixed(io, 4096) .map(|res| match res { @@ -426,8 +425,8 @@ impl RequestResponseCodec for PingCodec { io: &mut T, Ping(data): Ping, ) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, + where + T: AsyncWrite + Unpin + Send, { upgrade::write_length_prefixed(io, data).await?; io.close().await?; @@ -440,8 +439,8 @@ impl RequestResponseCodec for PingCodec { io: &mut T, Pong(data): Pong, ) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, + where + T: AsyncWrite + Unpin + Send, { upgrade::write_length_prefixed(io, data).await?; io.close().await?; From 85aa4efdf316f6fa71227e917960221daf13e375 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 8 Oct 2022 01:30:36 +1100 Subject: [PATCH 177/244] Fix clippy lints --- transports/webrtc/src/fingerprint.rs | 6 +++--- transports/webrtc/src/upgrade.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 2488c5aa954..0a307edeb36 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -69,12 +69,12 @@ impl Fingerprint { Some(Self(bytes)) } - pub fn to_multi_hash(&self) -> Multihash { + pub fn to_multi_hash(self) -> Multihash { Code::Sha2_256.wrap(&self.0).unwrap() } /// Transforms this fingerprint into a ufrag. - pub fn to_ufrag(&self) -> String { + pub fn to_ufrag(self) -> String { multibase::encode( Base::Base64Url, Code::Sha2_256.wrap(&self.0).unwrap().to_bytes(), @@ -84,7 +84,7 @@ impl Fingerprint { /// Formats this fingerprint as uppercase hex, separated by colons (`:`). /// /// This is the format described in . - pub fn to_sdp_format(&self) -> String { + pub fn to_sdp_format(self) -> String { self.0.map(|byte| format!("{:02X}", byte)).join(":") } diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 969f06cfe14..03ef616e13c 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -128,7 +128,7 @@ async fn new_inbound_connection( udp_mux: Arc, ufrag: &str, ) -> Result { - let mut se = setting_engine(udp_mux, &ufrag, addr); + let mut se = setting_engine(udp_mux, ufrag, addr); { se.set_lite(true); se.disable_certificate_fingerprint_verification(true); From f6431fb4f52c650906a32b7ea8026a009780a0c4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 10 Oct 2022 13:45:00 +0400 Subject: [PATCH 178/244] remove TODO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ThreadRng, provided by the thread_rng function, is a handle to a thread-local CSPRNG with periodic seeding from OsRng. Because this is local, it is typically much faster than OsRng. It should be secure, though the paranoid may prefer OsRng. -> OsRng is an interface to the operating system’s random number source. Typically the operating system uses a CSPRNG with entropy provided by a TRNG and some type of on-going re-seeding. -> "Before Linux 5.2 (2019), the blocking pool was ready to dispense N bits of randomness as soon as it accumulated N bits of entropy. In its information theoretical design this would make sense, if we could trust the entropy estimates, but nobody does! That means using /dev/random could be strictly less secure than using getrandom(2), because at least the latter always waits for 128 bits of entropy, which should provide some margin for error." Source: https://words.filippo.io/dispatches/linux-csprng/ Therefore, at least 128 bits of entropy are ensured on Linux. I think it's safe to remove a TODO since Linux is the primary OS used by validators. --- transports/webrtc/src/upgrade.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 03ef616e13c..78b362227ed 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -149,8 +149,6 @@ async fn new_inbound_connection( } fn random_ufrag() -> String { - // TODO: at least 128 bit of entropy - thread_rng() .sample_iter(&Alphanumeric) .take(64) From d2da79332036c4767f049ebfb2d683a6fd6bfbca Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 17:02:28 +1100 Subject: [PATCH 179/244] Add initial test suite for substream state machine --- transports/webrtc/src/substream.rs | 346 +++++++++++++++-------------- 1 file changed, 184 insertions(+), 162 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index c78196e2fa9..c23278e925b 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -28,7 +28,7 @@ use webrtc::data::data_channel::DataChannel; use webrtc::data::data_channel::PollDataChannel; use std::{ - fmt, io, + io, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -53,6 +53,7 @@ const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; pub struct Substream { io: Framed, prost_codec::Codec>, state: State, + read_buffer: Bytes, } impl Substream { @@ -66,15 +67,39 @@ impl Substream { Self { io: Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)), - state: State::Open { - read_buffer: Default::default(), - }, + state: State::Open, + read_buffer: Bytes::default(), } } fn stream_identifier(&self) -> u16 { self.io.get_ref().stream_identifier() } + + /// Gracefully closes the "read-half" of the substream. + pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + match self.state.close_read_barrier()? { + Closing::Requested => { + ready!(self.io.poll_ready_unpin(cx))?; + + self.io.start_send_unpin(Message { + flag: Some(Flag::StopSending.into()), + message: None, + })?; + + continue; + } + Closing::MessageSent => { + ready!(self.io.poll_flush_unpin(cx))?; + + self.state.handle_outbound_flag(Flag::StopSending); + + return Poll::Ready(Ok(())); + } + } + } + } } impl AsyncRead for Substream { @@ -84,43 +109,29 @@ impl AsyncRead for Substream { buf: &mut [u8], ) -> Poll> { loop { - if let Some(read_buffer) = self.state.non_empty_read_buffer_mut() { - let n = std::cmp::min(read_buffer.len(), buf.len()); - let data = read_buffer.split_to(n); + self.state.read_barrier()?; + + if !self.read_buffer.is_empty() { + let n = std::cmp::min(self.read_buffer.len(), buf.len()); + let data = self.read_buffer.split_to(n); buf[0..n].copy_from_slice(&data[..]); return Poll::Ready(Ok(n)); } - let substream_id = self.stream_identifier(); - let Self { state, io } = &mut *self; - - let read_buffer = match state { - State::Open { read_buffer } | State::WriteClosed { read_buffer } => read_buffer, - State::ReadClosed { read_buffer, .. } - | State::ReadWriteClosed { read_buffer, .. } => { - assert!(read_buffer.is_empty()); - return Poll::Ready(Ok(0)); - } - State::ReadReset | State::ReadResetWriteClosed => { - return Poll::Ready(Err(io::Error::from(io::ErrorKind::ConnectionReset))); - } - State::Poisoned => unreachable!(), - }; - - match ready!(io_poll_next(io, cx))? { + match ready!(io_poll_next(&mut self.io, cx))? { Some((flag, message)) => { - assert!(read_buffer.is_empty()); - if let Some(message) = message { - *read_buffer = message.into(); + if let Some(flag) = flag { + self.state.handle_inbound_flag(flag); } - if let Some(flag) = flag { - self.state.handle_flag(flag, substream_id) - }; + debug_assert!(self.read_buffer.is_empty()); + if let Some(message) = message { + self.read_buffer = message.into(); + } } None => { - self.state.handle_flag(Flag::Fin, substream_id); + self.state.handle_inbound_flag(Flag::Fin); return Poll::Ready(Ok(0)); } } @@ -134,9 +145,7 @@ impl AsyncWrite for Substream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - let substream_id = self.stream_identifier(); - // Handle flags iff read side closed. - while let State::ReadClosed { .. } | State::ReadReset = self.state { + while self.state.read_flags_in_async_write() { // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? match io_poll_next(&mut self.io, cx)? { @@ -144,20 +153,14 @@ impl AsyncWrite for Substream { // Read side is closed. Discard any incoming messages. drop(message); // But still handle flags, e.g. a `Flag::StopSending`. - self.state.handle_flag(flag, substream_id) + self.state.handle_inbound_flag(flag) } Poll::Ready(Some((None, message))) => drop(message), Poll::Ready(None) | Poll::Pending => break, } } - match self.state { - State::WriteClosed { .. } - | State::ReadWriteClosed { .. } - | State::ReadResetWriteClosed => return Poll::Ready(Ok(0)), - State::Open { .. } | State::ReadClosed { .. } | State::ReadReset => {} - State::Poisoned => todo!(), - } + self.state.write_barrier()?; ready!(self.io.poll_ready_unpin(cx))?; @@ -177,38 +180,27 @@ impl AsyncWrite for Substream { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match &self.state { - State::WriteClosed { .. } - | State::ReadWriteClosed { .. } - | State::ReadResetWriteClosed { .. } => {} - - State::Open { .. } | State::ReadClosed { .. } | State::ReadReset => { - ready!(self.io.poll_ready_unpin(cx))?; - Pin::new(&mut self.io).start_send(Message { - flag: Some(Flag::Fin.into()), - message: None, - })?; - - match std::mem::replace(&mut self.state, State::Poisoned) { - State::Open { read_buffer } => self.state = State::WriteClosed { read_buffer }, - State::ReadClosed { read_buffer } => { - self.state = State::ReadWriteClosed { read_buffer } - } - State::ReadReset => self.state = State::ReadResetWriteClosed, - State::WriteClosed { .. } - | State::ReadWriteClosed { .. } - | State::ReadResetWriteClosed - | State::Poisoned => { - unreachable!() - } + loop { + match self.state.close_write_barrier()? { + Closing::Requested => { + ready!(self.io.poll_ready_unpin(cx))?; + + self.io.start_send_unpin(Message { + flag: Some(Flag::Fin.into()), + message: None, + })?; + + continue; } - } + Closing::MessageSent => { + ready!(self.io.poll_flush_unpin(cx))?; - State::Poisoned => todo!(), - } + self.state.handle_outbound_flag(Flag::Fin); - // TODO: Is flush the correct thing here? We don't want the underlying layer to close both write and read. - self.io.poll_flush_unpin(cx).map_err(Into::into) + return Poll::Ready(Ok(())); + } + } + } } } @@ -234,106 +226,56 @@ fn io_poll_next( } enum State { - Open { read_buffer: Bytes }, - WriteClosed { read_buffer: Bytes }, - ReadClosed { read_buffer: Bytes }, - ReadWriteClosed { read_buffer: Bytes }, - ReadReset, - ReadResetWriteClosed, - Poisoned, + Open, + ReadClosed, + WriteClosed, + ClosingRead(Closing), + ClosingWrite(Closing), + BothClosed { reset: bool }, } -impl State { - fn handle_flag(&mut self, flag: Flag, substream_id: u16) { - let old_state = format!("{}", self); - match (std::mem::replace(self, State::Poisoned), flag) { - // StopSending - ( - State::Open { read_buffer } | State::WriteClosed { read_buffer }, - Flag::StopSending, - ) => { - *self = State::WriteClosed { read_buffer }; - } - - ( - State::ReadClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - Flag::StopSending, - ) => { - *self = State::ReadWriteClosed { read_buffer }; - } - - (State::ReadReset | State::ReadResetWriteClosed, Flag::StopSending) => { - *self = State::ReadResetWriteClosed; - } - - // Fin - (State::Open { read_buffer } | State::ReadClosed { read_buffer }, Flag::Fin) => { - *self = State::ReadClosed { read_buffer }; - } - - ( - State::WriteClosed { read_buffer } | State::ReadWriteClosed { read_buffer }, - Flag::Fin, - ) => { - *self = State::ReadWriteClosed { read_buffer }; - } - - (State::ReadReset, Flag::Fin) => *self = State::ReadReset, - - (State::ReadResetWriteClosed, Flag::Fin) => *self = State::ReadResetWriteClosed, - - // Reset - (State::ReadClosed { .. } | State::ReadReset | State::Open { .. }, Flag::Reset) => { - *self = State::ReadReset - } +/// Represents the state of closing one half (either read or write) of the connection. +/// +/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively +/// and flushing the underlying connection. +enum Closing { + Requested, + MessageSent, +} - ( - State::ReadWriteClosed { .. } - | State::WriteClosed { .. } - | State::ReadResetWriteClosed, - Flag::Reset, - ) => *self = State::ReadResetWriteClosed, +impl State { + /// Performs a state transition for a flag contained in an inbound message. + fn handle_inbound_flag(&mut self, flag: Flag) {} + + /// Performs a state transition for a flag contained in an outbound message. + fn handle_outbound_flag(&mut self, flag: Flag) {} + + /// Whether we should read from the stream in the [`AsyncWrite`] implementation. + /// + /// This is necessary for read-closed streams because we would otherwise not read any more flags from + /// the socket. + fn read_flags_in_async_write(&self) -> bool { + false + } - (State::Poisoned, _) => unreachable!(), - } + /// Acts as a "barrier" for [`AsyncRead::poll_read`]. + fn read_barrier(&self) -> io::Result<()> { + Ok(()) + } - log::debug!( - "substream={}: got flag {:?}, moved from {} to {}", - substream_id, - flag, - old_state, - *self - ); + /// Acts as a "barrier" for [`AsyncWrite::poll_write`]. + fn write_barrier(&self) -> io::Result<()> { + Ok(()) } - /// Returns a reference to the underlying buffer if possible and the buffer is not empty. - fn non_empty_read_buffer_mut(&mut self) -> Option<&mut Bytes> { - match self { - State::Open { read_buffer } - | State::WriteClosed { read_buffer } - | State::ReadClosed { read_buffer } - | State::ReadWriteClosed { read_buffer } - if !read_buffer.is_empty() => - { - Some(read_buffer) - } - State::Poisoned => unreachable!(), - _ => None, - } + /// Acts as a "barrier" for [`AsyncWrite::poll_close`]. + fn close_write_barrier(&self) -> io::Result { + todo!() } -} -impl fmt::Display for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - State::Open { .. } => write!(f, "Open"), - State::WriteClosed { .. } => write!(f, "WriteClosed"), - State::ReadClosed { .. } => write!(f, "ReadClosed"), - State::ReadWriteClosed { .. } => write!(f, "ReadWriteClosed"), - State::ReadReset => write!(f, "ReadReset"), - State::ReadResetWriteClosed => write!(f, "ReadResetWriteClosed"), - State::Poisoned => write!(f, "Poisoned"), - } + /// Acts as a "barrier" for [`Substream::poll_close_read`]. + fn close_read_barrier(&self) -> io::Result { + todo!() } } @@ -343,8 +285,88 @@ mod tests { use asynchronous_codec::Encoder; use bytes::BytesMut; use prost::Message; + use std::io::ErrorKind; use unsigned_varint::codec::UviBytes; + #[test] + fn cannot_read_after_receiving_fin() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_read_after_sending_stop_sending() { + let mut open = State::Open; + + open.handle_outbound_flag(Flag::StopSending); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_receiving_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::StopSending); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_sending_fin() { + let mut open = State::Open; + + open.handle_outbound_flag(Flag::Fin); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn everything_broken_after_receiving_reset() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Reset); + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + let error3 = open.close_write_barrier().unwrap_err(); + let error4 = open.close_read_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::ConnectionReset); + assert_eq!(error2.kind(), ErrorKind::ConnectionReset); + assert_eq!(error3.kind(), ErrorKind::ConnectionReset); + assert_eq!(error4.kind(), ErrorKind::ConnectionReset); + } + + #[test] + fn should_read_flags_in_async_write_after_read_closed() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin); + + assert!(open.read_flags_in_async_write()) + } + + #[test] + fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin); + open.handle_inbound_flag(Flag::StopSending); + + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::BrokenPipe); + assert_eq!(error2.kind(), ErrorKind::BrokenPipe); + } + #[test] fn max_data_len() { // Largest possible message. From 1b520a9598620e4df32ef650d2009c612a3b8413 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 17:45:28 +1100 Subject: [PATCH 180/244] Precompute substream ID --- transports/webrtc/src/substream.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index c23278e925b..45ee66cd6ac 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -54,6 +54,7 @@ pub struct Substream { io: Framed, prost_codec::Codec>, state: State, read_buffer: Bytes, + substream_id: u16, } impl Substream { @@ -65,17 +66,17 @@ impl Substream { // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. inner.set_read_buf_capacity(8192 * 10); + let io = Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)); + let substream_id = io.get_ref().stream_identifier(); + Self { - io: Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)), + io, state: State::Open, read_buffer: Bytes::default(), + substream_id, } } - fn stream_identifier(&self) -> u16 { - self.io.get_ref().stream_identifier() - } - /// Gracefully closes the "read-half" of the substream. pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { From e483974276cd4617e71b8ba56993dd214cb60551 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 17:51:49 +1100 Subject: [PATCH 181/244] Implement new state machine --- transports/webrtc/src/substream.rs | 404 ++++++++++++++++++++++++++--- 1 file changed, 372 insertions(+), 32 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 45ee66cd6ac..63ba6477ed2 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -81,23 +81,25 @@ impl Substream { pub fn poll_close_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match self.state.close_read_barrier()? { - Closing::Requested => { + Some(Closing::Requested) => { ready!(self.io.poll_ready_unpin(cx))?; self.io.start_send_unpin(Message { flag: Some(Flag::StopSending.into()), message: None, })?; + self.state.close_read_message_sent(); continue; } - Closing::MessageSent => { + Some(Closing::MessageSent) => { ready!(self.io.poll_flush_unpin(cx))?; - self.state.handle_outbound_flag(Flag::StopSending); + self.state.read_closed(); return Poll::Ready(Ok(())); } + None => return Poll::Ready(Ok(())), } } } @@ -120,10 +122,11 @@ impl AsyncRead for Substream { return Poll::Ready(Ok(n)); } + let substream_id = self.substream_id; match ready!(io_poll_next(&mut self.io, cx))? { Some((flag, message)) => { if let Some(flag) = flag { - self.state.handle_inbound_flag(flag); + self.state.handle_inbound_flag(flag, substream_id); } debug_assert!(self.read_buffer.is_empty()); @@ -132,7 +135,7 @@ impl AsyncRead for Substream { } } None => { - self.state.handle_inbound_flag(Flag::Fin); + self.state.handle_inbound_flag(Flag::Fin, substream_id); return Poll::Ready(Ok(0)); } } @@ -149,12 +152,14 @@ impl AsyncWrite for Substream { while self.state.read_flags_in_async_write() { // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + let substream_id = self.substream_id; + match io_poll_next(&mut self.io, cx)? { Poll::Ready(Some((Some(flag), message))) => { // Read side is closed. Discard any incoming messages. drop(message); // But still handle flags, e.g. a `Flag::StopSending`. - self.state.handle_inbound_flag(flag) + self.state.handle_inbound_flag(flag, substream_id) } Poll::Ready(Some((None, message))) => drop(message), Poll::Ready(None) | Poll::Pending => break, @@ -183,23 +188,25 @@ impl AsyncWrite for Substream { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match self.state.close_write_barrier()? { - Closing::Requested => { + Some(Closing::Requested) => { ready!(self.io.poll_ready_unpin(cx))?; self.io.start_send_unpin(Message { flag: Some(Flag::Fin.into()), message: None, })?; + self.state.close_write_message_sent(); continue; } - Closing::MessageSent => { + Some(Closing::MessageSent) => { ready!(self.io.poll_flush_unpin(cx))?; - self.state.handle_outbound_flag(Flag::Fin); + self.state.write_closed(); return Poll::Ready(Ok(())); } + None => return Poll::Ready(Ok(())), } } } @@ -226,19 +233,31 @@ fn io_poll_next( } } +#[derive(Debug, Copy, Clone)] enum State { Open, ReadClosed, WriteClosed, - ClosingRead(Closing), - ClosingWrite(Closing), - BothClosed { reset: bool }, + ClosingRead { + /// Whether the write side of our channel was already closed. + write_closed: bool, + inner: Closing, + }, + ClosingWrite { + /// Whether the write side of our channel was already closed. + read_closed: bool, + inner: Closing, + }, + BothClosed { + reset: bool, + }, } /// Represents the state of closing one half (either read or write) of the connection. /// /// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively /// and flushing the underlying connection. +#[derive(Debug, Copy, Clone)] enum Closing { Requested, MessageSent, @@ -246,37 +265,268 @@ enum Closing { impl State { /// Performs a state transition for a flag contained in an inbound message. - fn handle_inbound_flag(&mut self, flag: Flag) {} + fn handle_inbound_flag(&mut self, flag: Flag, substream_id: u16) { + let current = *self; + + match (current, flag) { + (Self::Open, Flag::Fin) => { + *self = Self::ReadClosed; + } + (Self::WriteClosed, Flag::Fin) => { + *self = Self::BothClosed { reset: false }; + } + (Self::Open, Flag::StopSending) => { + *self = Self::WriteClosed; + } + (Self::ReadClosed, Flag::StopSending) => { + *self = Self::BothClosed { reset: false }; + } + (_, Flag::Reset) => *self = Self::BothClosed { reset: true }, + _ => {} + } + + log::trace!("Transitioned from {current:?} to {self:?} on substream {substream_id}") + } + + fn write_closed(&mut self) { + match self { + State::ClosingWrite { + read_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingWrite { + read_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::WriteClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + fn close_write_message_sent(&mut self) { + match self { + State::ClosingWrite { inner, read_closed } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingWrite { + read_closed: *read_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + fn read_closed(&mut self) { + match self { + State::ClosingRead { + write_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingRead { + write_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::ReadClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } - /// Performs a state transition for a flag contained in an outbound message. - fn handle_outbound_flag(&mut self, flag: Flag) {} + fn close_read_message_sent(&mut self) { + match self { + State::ClosingRead { + inner, + write_closed, + } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingRead { + write_closed: *write_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } /// Whether we should read from the stream in the [`AsyncWrite`] implementation. /// /// This is necessary for read-closed streams because we would otherwise not read any more flags from /// the socket. fn read_flags_in_async_write(&self) -> bool { - false + matches!(self, Self::ReadClosed) } /// Acts as a "barrier" for [`AsyncRead::poll_read`]. fn read_barrier(&self) -> io::Result<()> { - Ok(()) + use State::*; + + let kind = match self { + Open + | WriteClosed + | ClosingWrite { + read_closed: false, .. + } => return Ok(()), + ClosingWrite { + read_closed: true, .. + } + | ReadClosed + | ClosingRead { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) } /// Acts as a "barrier" for [`AsyncWrite::poll_write`]. fn write_barrier(&self) -> io::Result<()> { - Ok(()) + use State::*; + + let kind = match self { + Open + | ReadClosed + | ClosingRead { + write_closed: false, + .. + } => return Ok(()), + ClosingRead { + write_closed: true, .. + } + | WriteClosed + | ClosingWrite { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) } /// Acts as a "barrier" for [`AsyncWrite::poll_close`]. - fn close_write_barrier(&self) -> io::Result { - todo!() + fn close_write_barrier(&mut self) -> io::Result> { + loop { + match &self { + State::WriteClosed => return Ok(None), + + State::ClosingWrite { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingWrite { + read_closed: false, + inner: Closing::Requested, + }; + } + State::ReadClosed => { + *self = Self::ClosingWrite { + read_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingRead { + write_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingRead { + write_closed: false, + .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close read half while closing write half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } } /// Acts as a "barrier" for [`Substream::poll_close_read`]. - fn close_read_barrier(&self) -> io::Result { - todo!() + fn close_read_barrier(&mut self) -> io::Result> { + loop { + match self { + State::ReadClosed => return Ok(None), + + State::ClosingRead { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingRead { + write_closed: false, + inner: Closing::Requested, + }; + } + State::WriteClosed => { + *self = Self::ClosingRead { + write_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingWrite { + read_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingWrite { + read_closed: false, .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close write half while closing read half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } } } @@ -293,17 +543,19 @@ mod tests { fn cannot_read_after_receiving_fin() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin); + open.handle_inbound_flag(Flag::Fin, 0); let error = open.read_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) } #[test] - fn cannot_read_after_sending_stop_sending() { + fn cannot_read_after_closing_read() { let mut open = State::Open; - open.handle_outbound_flag(Flag::StopSending); + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); let error = open.read_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -313,17 +565,19 @@ mod tests { fn cannot_write_after_receiving_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::StopSending); + open.handle_inbound_flag(Flag::StopSending, 0); let error = open.write_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) } #[test] - fn cannot_write_after_sending_fin() { + fn cannot_write_after_closing_write() { let mut open = State::Open; - open.handle_outbound_flag(Flag::Fin); + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); let error = open.write_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -333,7 +587,7 @@ mod tests { fn everything_broken_after_receiving_reset() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Reset); + open.handle_inbound_flag(Flag::Reset, 0); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); let error3 = open.close_write_barrier().unwrap_err(); @@ -349,7 +603,7 @@ mod tests { fn should_read_flags_in_async_write_after_read_closed() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin); + open.handle_inbound_flag(Flag::Fin, 0); assert!(open.read_flags_in_async_write()) } @@ -358,8 +612,8 @@ mod tests { fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin); - open.handle_inbound_flag(Flag::StopSending); + open.handle_inbound_flag(Flag::Fin, 0); + open.handle_inbound_flag(Flag::StopSending, 0); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); @@ -368,6 +622,92 @@ mod tests { assert_eq!(error2.kind(), ErrorKind::BrokenPipe); } + #[test] + fn can_read_after_closing_write() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + open.read_barrier().unwrap(); + } + + #[test] + fn can_write_after_closing_read() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + open.write_barrier().unwrap(); + } + + #[test] + fn cannot_write_after_starting_close() { + let mut open = State::Open; + + open.close_write_barrier().expect("to close in open"); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn cannot_read_after_starting_close() { + let mut open = State::Open; + + open.close_read_barrier().expect("to close in open"); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn can_read_in_open() { + let open = State::Open; + + let result = open.read_barrier(); + + result.unwrap(); + } + + #[test] + fn can_write_in_open() { + let open = State::Open; + + let result = open.write_barrier(); + + result.unwrap(); + } + + #[test] + fn write_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + let maybe = open.close_write_barrier().unwrap(); + + assert!(maybe.is_none()) + } + + #[test] + fn read_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + let maybe = open.close_read_barrier().unwrap(); + + assert!(maybe.is_none()) + } + #[test] fn max_data_len() { // Largest possible message. From 29d6f742b55e748100590c5956928992afdb704e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:12:46 +1100 Subject: [PATCH 182/244] Reset flag clears buffer --- transports/webrtc/src/substream.rs | 59 +++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 63ba6477ed2..4ae63198290 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -122,20 +122,26 @@ impl AsyncRead for Substream { return Poll::Ready(Ok(n)); } - let substream_id = self.substream_id; - match ready!(io_poll_next(&mut self.io, cx))? { + let Self { + substream_id, + read_buffer, + io, + state, + } = &mut *self; + + match ready!(io_poll_next(io, cx))? { Some((flag, message)) => { if let Some(flag) = flag { - self.state.handle_inbound_flag(flag, substream_id); + state.handle_inbound_flag(flag, read_buffer, *substream_id); } - debug_assert!(self.read_buffer.is_empty()); + debug_assert!(read_buffer.is_empty()); if let Some(message) = message { - self.read_buffer = message.into(); + *read_buffer = message.into(); } } None => { - self.state.handle_inbound_flag(Flag::Fin, substream_id); + state.handle_inbound_flag(Flag::Fin, read_buffer, *substream_id); return Poll::Ready(Ok(0)); } } @@ -152,14 +158,20 @@ impl AsyncWrite for Substream { while self.state.read_flags_in_async_write() { // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? - let substream_id = self.substream_id; - match io_poll_next(&mut self.io, cx)? { + let Self { + substream_id, + read_buffer, + io, + state, + } = &mut *self; + + match io_poll_next(io, cx)? { Poll::Ready(Some((Some(flag), message))) => { // Read side is closed. Discard any incoming messages. drop(message); // But still handle flags, e.g. a `Flag::StopSending`. - self.state.handle_inbound_flag(flag, substream_id) + state.handle_inbound_flag(flag, read_buffer, *substream_id) } Poll::Ready(Some((None, message))) => drop(message), Poll::Ready(None) | Poll::Pending => break, @@ -265,7 +277,7 @@ enum Closing { impl State { /// Performs a state transition for a flag contained in an inbound message. - fn handle_inbound_flag(&mut self, flag: Flag, substream_id: u16) { + fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes, substream_id: u16) { let current = *self; match (current, flag) { @@ -281,7 +293,10 @@ impl State { (Self::ReadClosed, Flag::StopSending) => { *self = Self::BothClosed { reset: false }; } - (_, Flag::Reset) => *self = Self::BothClosed { reset: true }, + (_, Flag::Reset) => { + buffer.clear(); + *self = Self::BothClosed { reset: true }; + } _ => {} } @@ -543,7 +558,7 @@ mod tests { fn cannot_read_after_receiving_fin() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); let error = open.read_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -565,7 +580,7 @@ mod tests { fn cannot_write_after_receiving_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::StopSending, 0); + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default(), 0); let error = open.write_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -587,7 +602,7 @@ mod tests { fn everything_broken_after_receiving_reset() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Reset, 0); + open.handle_inbound_flag(Flag::Reset, &mut Bytes::default(), 0); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); let error3 = open.close_write_barrier().unwrap_err(); @@ -603,7 +618,7 @@ mod tests { fn should_read_flags_in_async_write_after_read_closed() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); assert!(open.read_flags_in_async_write()) } @@ -612,8 +627,8 @@ mod tests { fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, 0); - open.handle_inbound_flag(Flag::StopSending, 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default(), 0); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); @@ -708,6 +723,16 @@ mod tests { assert!(maybe.is_none()) } + #[test] + fn reset_flag_clears_buffer() { + let mut open = State::Open; + let mut buffer = Bytes::copy_from_slice(b"foobar"); + + open.handle_inbound_flag(Flag::Reset, &mut buffer, 0); + + assert!(buffer.is_empty()); + } + #[test] fn max_data_len() { // Largest possible message. From d829fda97d15c05a050223f4f2f7f11a52d5ef3c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:14:02 +1100 Subject: [PATCH 183/244] Remove substream ID Logging these state transitions is no longer really worth it because we have changed the design to have many more functions which would all require logging now. --- transports/webrtc/src/substream.rs | 33 +++++++++++------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 4ae63198290..d0accc85255 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -54,7 +54,6 @@ pub struct Substream { io: Framed, prost_codec::Codec>, state: State, read_buffer: Bytes, - substream_id: u16, } impl Substream { @@ -66,14 +65,10 @@ impl Substream { // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. inner.set_read_buf_capacity(8192 * 10); - let io = Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)); - let substream_id = io.get_ref().stream_identifier(); - Self { - io, + io: Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)), state: State::Open, read_buffer: Bytes::default(), - substream_id, } } @@ -123,7 +118,6 @@ impl AsyncRead for Substream { } let Self { - substream_id, read_buffer, io, state, @@ -132,7 +126,7 @@ impl AsyncRead for Substream { match ready!(io_poll_next(io, cx))? { Some((flag, message)) => { if let Some(flag) = flag { - state.handle_inbound_flag(flag, read_buffer, *substream_id); + state.handle_inbound_flag(flag, read_buffer); } debug_assert!(read_buffer.is_empty()); @@ -141,7 +135,7 @@ impl AsyncRead for Substream { } } None => { - state.handle_inbound_flag(Flag::Fin, read_buffer, *substream_id); + state.handle_inbound_flag(Flag::Fin, read_buffer); return Poll::Ready(Ok(0)); } } @@ -160,7 +154,6 @@ impl AsyncWrite for Substream { // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? let Self { - substream_id, read_buffer, io, state, @@ -171,7 +164,7 @@ impl AsyncWrite for Substream { // Read side is closed. Discard any incoming messages. drop(message); // But still handle flags, e.g. a `Flag::StopSending`. - state.handle_inbound_flag(flag, read_buffer, *substream_id) + state.handle_inbound_flag(flag, read_buffer) } Poll::Ready(Some((None, message))) => drop(message), Poll::Ready(None) | Poll::Pending => break, @@ -277,7 +270,7 @@ enum Closing { impl State { /// Performs a state transition for a flag contained in an inbound message. - fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes, substream_id: u16) { + fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { let current = *self; match (current, flag) { @@ -299,8 +292,6 @@ impl State { } _ => {} } - - log::trace!("Transitioned from {current:?} to {self:?} on substream {substream_id}") } fn write_closed(&mut self) { @@ -558,7 +549,7 @@ mod tests { fn cannot_read_after_receiving_fin() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); let error = open.read_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -580,7 +571,7 @@ mod tests { fn cannot_write_after_receiving_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); let error = open.write_barrier().unwrap_err(); assert_eq!(error.kind(), ErrorKind::BrokenPipe) @@ -602,7 +593,7 @@ mod tests { fn everything_broken_after_receiving_reset() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Reset, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::Reset, &mut Bytes::default()); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); let error3 = open.close_write_barrier().unwrap_err(); @@ -618,7 +609,7 @@ mod tests { fn should_read_flags_in_async_write_after_read_closed() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); assert!(open.read_flags_in_async_write()) } @@ -627,8 +618,8 @@ mod tests { fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { let mut open = State::Open; - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default(), 0); - open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default(), 0); + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); let error1 = open.read_barrier().unwrap_err(); let error2 = open.write_barrier().unwrap_err(); @@ -728,7 +719,7 @@ mod tests { let mut open = State::Open; let mut buffer = Bytes::copy_from_slice(b"foobar"); - open.handle_inbound_flag(Flag::Reset, &mut buffer, 0); + open.handle_inbound_flag(Flag::Reset, &mut buffer); assert!(buffer.is_empty()); } From 058a1531032b829b78003835a595423c4bb68a4f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:17:41 +1100 Subject: [PATCH 184/244] Remove use of `map_err` --- transports/webrtc/src/connection.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index cb44ccadee0..8c84e10fac3 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -24,7 +24,7 @@ use futures::{ oneshot::{self, Sender}, }, lock::Mutex as FutMutex, - {future::BoxFuture, prelude::*, ready}, + {future::BoxFuture, ready}, }; use futures_lite::StreamExt; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; @@ -171,10 +171,7 @@ impl StreamMuxer for Connection { let peer_conn = peer_conn.lock().await; // Create a datachannel with label 'data' - let data_channel = peer_conn - .create_data_channel("data", None) - .map_err(Error::WebRTC) - .await?; + let data_channel = peer_conn.create_data_channel("data", None).await?; trace!("Opening outbound substream {}", data_channel.id()); @@ -211,7 +208,9 @@ impl StreamMuxer for Connection { let peer_conn = self.peer_conn.clone(); let fut = self.close_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; - peer_conn.close().await.map_err(Error::WebRTC) + peer_conn.close().await?; + + Ok(()) })); match ready!(fut.as_mut().poll(cx)) { From e1df3c4889fea508e0a05daf287326134315c0e4 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:21:40 +1100 Subject: [PATCH 185/244] Replace error with `Poll::Pending` --- transports/webrtc/src/connection.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 8c84e10fac3..4d867ceeae4 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -149,9 +149,14 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(Substream::new(detached))) } - None => Poll::Ready(Err(Error::Internal( - "incoming_data_channels_rx is closed (no messages left)".to_string(), - ))), + None => { + debug_assert!( + false, + "Sender-end of channel should be owned by `RTCPeerConnection`" + ); + + return Poll::Pending; // Return `Pending` without registering a waker: If the channel is closed, we don't need to be called anymore. + } } } From 8c8feaa2a5ff1bba59b8c1d91808011bfea6a6f7 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:26:18 +1100 Subject: [PATCH 186/244] Remove unnecessary dependency --- transports/webrtc/Cargo.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 04bc70a0a81..5872a676bb5 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -42,10 +42,9 @@ prost-build = "0.11" [dev-dependencies] anyhow = "1.0" env_logger = "0.9" -libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } -rand_core = "0.5" -quickcheck = "1" hex-literal = "0.3" +libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } multihash = { version = "0.16", default-features = false, features = ["sha3"] } +quickcheck = "1" +rand_core = "0.5" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } -asynchronous-codec = { version = "0.6" } From e6c177c6b2f17d065cceea39adbb384528ecc9a9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:26:27 +1100 Subject: [PATCH 187/244] Group imports --- transports/webrtc/src/substream.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index d0accc85255..08d8874b54e 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -34,8 +34,7 @@ use std::{ task::{Context, Poll}, }; -use crate::message_proto::message::Flag; -use crate::message_proto::Message; +use crate::message_proto::{message::Flag, Message}; /// Maximum length of a message, in bytes. const MAX_MSG_LEN: usize = 16384; // 16kiB From b2961a050e0c52ec2959f7759e8c32c2751125fd Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:26:32 +1100 Subject: [PATCH 188/244] Add spec wording to constant --- transports/webrtc/src/substream.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 08d8874b54e..82129d997b9 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -36,7 +36,8 @@ use std::{ use crate::message_proto::{message::Flag, Message}; -/// Maximum length of a message, in bytes. +/// As long as message interleaving is not supported, the sender SHOULD limit the maximum message size to 16 KB to avoid monopolization. +// Source: const MAX_MSG_LEN: usize = 16384; // 16kiB /// Length of varint, in bytes. const VARINT_LEN: usize = 2; From f827f62efcc6e814ad5902904a3c0b6f6618f359 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 18:48:07 +1100 Subject: [PATCH 189/244] Introduce dedicated `State` submodule --- transports/webrtc/src/substream.rs | 481 +--------------------- transports/webrtc/src/substream/state.rs | 488 +++++++++++++++++++++++ 2 files changed, 491 insertions(+), 478 deletions(-) create mode 100644 transports/webrtc/src/substream/state.rs diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 82129d997b9..18cc506fb24 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -27,6 +27,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc::data::data_channel::DataChannel; use webrtc::data::data_channel::PollDataChannel; +use state::{Closing, State}; use std::{ io, pin::Pin, @@ -36,6 +37,8 @@ use std::{ use crate::message_proto::{message::Flag, Message}; +mod state; + /// As long as message interleaving is not supported, the sender SHOULD limit the maximum message size to 16 KB to avoid monopolization. // Source: const MAX_MSG_LEN: usize = 16384; // 16kiB @@ -238,492 +241,14 @@ fn io_poll_next( } } -#[derive(Debug, Copy, Clone)] -enum State { - Open, - ReadClosed, - WriteClosed, - ClosingRead { - /// Whether the write side of our channel was already closed. - write_closed: bool, - inner: Closing, - }, - ClosingWrite { - /// Whether the write side of our channel was already closed. - read_closed: bool, - inner: Closing, - }, - BothClosed { - reset: bool, - }, -} - -/// Represents the state of closing one half (either read or write) of the connection. -/// -/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively -/// and flushing the underlying connection. -#[derive(Debug, Copy, Clone)] -enum Closing { - Requested, - MessageSent, -} - -impl State { - /// Performs a state transition for a flag contained in an inbound message. - fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { - let current = *self; - - match (current, flag) { - (Self::Open, Flag::Fin) => { - *self = Self::ReadClosed; - } - (Self::WriteClosed, Flag::Fin) => { - *self = Self::BothClosed { reset: false }; - } - (Self::Open, Flag::StopSending) => { - *self = Self::WriteClosed; - } - (Self::ReadClosed, Flag::StopSending) => { - *self = Self::BothClosed { reset: false }; - } - (_, Flag::Reset) => { - buffer.clear(); - *self = Self::BothClosed { reset: true }; - } - _ => {} - } - } - - fn write_closed(&mut self) { - match self { - State::ClosingWrite { - read_closed: true, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::BothClosed { reset: false }; - } - State::ClosingWrite { - read_closed: false, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::WriteClosed; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingRead { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - fn close_write_message_sent(&mut self) { - match self { - State::ClosingWrite { inner, read_closed } => { - debug_assert!(matches!(inner, Closing::Requested)); - - *self = State::ClosingWrite { - read_closed: *read_closed, - inner: Closing::MessageSent, - }; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingRead { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - fn read_closed(&mut self) { - match self { - State::ClosingRead { - write_closed: true, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::BothClosed { reset: false }; - } - State::ClosingRead { - write_closed: false, - inner, - } => { - debug_assert!(matches!(inner, Closing::MessageSent)); - - *self = State::ReadClosed; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingWrite { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - fn close_read_message_sent(&mut self) { - match self { - State::ClosingRead { - inner, - write_closed, - } => { - debug_assert!(matches!(inner, Closing::Requested)); - - *self = State::ClosingRead { - write_closed: *write_closed, - inner: Closing::MessageSent, - }; - } - State::Open - | State::ReadClosed - | State::WriteClosed - | State::ClosingWrite { .. } - | State::BothClosed { .. } => { - unreachable!("bad state machine impl") - } - } - } - - /// Whether we should read from the stream in the [`AsyncWrite`] implementation. - /// - /// This is necessary for read-closed streams because we would otherwise not read any more flags from - /// the socket. - fn read_flags_in_async_write(&self) -> bool { - matches!(self, Self::ReadClosed) - } - - /// Acts as a "barrier" for [`AsyncRead::poll_read`]. - fn read_barrier(&self) -> io::Result<()> { - use State::*; - - let kind = match self { - Open - | WriteClosed - | ClosingWrite { - read_closed: false, .. - } => return Ok(()), - ClosingWrite { - read_closed: true, .. - } - | ReadClosed - | ClosingRead { .. } - | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, - BothClosed { reset: true } => io::ErrorKind::ConnectionReset, - }; - - Err(kind.into()) - } - - /// Acts as a "barrier" for [`AsyncWrite::poll_write`]. - fn write_barrier(&self) -> io::Result<()> { - use State::*; - - let kind = match self { - Open - | ReadClosed - | ClosingRead { - write_closed: false, - .. - } => return Ok(()), - ClosingRead { - write_closed: true, .. - } - | WriteClosed - | ClosingWrite { .. } - | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, - BothClosed { reset: true } => io::ErrorKind::ConnectionReset, - }; - - Err(kind.into()) - } - - /// Acts as a "barrier" for [`AsyncWrite::poll_close`]. - fn close_write_barrier(&mut self) -> io::Result> { - loop { - match &self { - State::WriteClosed => return Ok(None), - - State::ClosingWrite { inner, .. } => return Ok(Some(*inner)), - - State::Open => { - *self = Self::ClosingWrite { - read_closed: false, - inner: Closing::Requested, - }; - } - State::ReadClosed => { - *self = Self::ClosingWrite { - read_closed: true, - inner: Closing::Requested, - }; - } - - State::ClosingRead { - write_closed: true, .. - } - | State::BothClosed { reset: false } => { - return Err(io::ErrorKind::BrokenPipe.into()) - } - - State::ClosingRead { - write_closed: false, - .. - } => { - return Err(io::Error::new( - io::ErrorKind::Other, - "cannot close read half while closing write half", - )) - } - - State::BothClosed { reset: true } => { - return Err(io::ErrorKind::ConnectionReset.into()) - } - } - } - } - - /// Acts as a "barrier" for [`Substream::poll_close_read`]. - fn close_read_barrier(&mut self) -> io::Result> { - loop { - match self { - State::ReadClosed => return Ok(None), - - State::ClosingRead { inner, .. } => return Ok(Some(*inner)), - - State::Open => { - *self = Self::ClosingRead { - write_closed: false, - inner: Closing::Requested, - }; - } - State::WriteClosed => { - *self = Self::ClosingRead { - write_closed: true, - inner: Closing::Requested, - }; - } - - State::ClosingWrite { - read_closed: true, .. - } - | State::BothClosed { reset: false } => { - return Err(io::ErrorKind::BrokenPipe.into()) - } - - State::ClosingWrite { - read_closed: false, .. - } => { - return Err(io::Error::new( - io::ErrorKind::Other, - "cannot close write half while closing read half", - )) - } - - State::BothClosed { reset: true } => { - return Err(io::ErrorKind::ConnectionReset.into()) - } - } - } - } -} - #[cfg(test)] mod tests { use super::*; use asynchronous_codec::Encoder; use bytes::BytesMut; use prost::Message; - use std::io::ErrorKind; use unsigned_varint::codec::UviBytes; - #[test] - fn cannot_read_after_receiving_fin() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_read_after_closing_read() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_write_after_receiving_stop_sending() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn cannot_write_after_closing_write() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe) - } - - #[test] - fn everything_broken_after_receiving_reset() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::Reset, &mut Bytes::default()); - let error1 = open.read_barrier().unwrap_err(); - let error2 = open.write_barrier().unwrap_err(); - let error3 = open.close_write_barrier().unwrap_err(); - let error4 = open.close_read_barrier().unwrap_err(); - - assert_eq!(error1.kind(), ErrorKind::ConnectionReset); - assert_eq!(error2.kind(), ErrorKind::ConnectionReset); - assert_eq!(error3.kind(), ErrorKind::ConnectionReset); - assert_eq!(error4.kind(), ErrorKind::ConnectionReset); - } - - #[test] - fn should_read_flags_in_async_write_after_read_closed() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); - - assert!(open.read_flags_in_async_write()) - } - - #[test] - fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { - let mut open = State::Open; - - open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); - open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); - - let error1 = open.read_barrier().unwrap_err(); - let error2 = open.write_barrier().unwrap_err(); - - assert_eq!(error1.kind(), ErrorKind::BrokenPipe); - assert_eq!(error2.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn can_read_after_closing_write() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - - open.read_barrier().unwrap(); - } - - #[test] - fn can_write_after_closing_read() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - - open.write_barrier().unwrap(); - } - - #[test] - fn cannot_write_after_starting_close() { - let mut open = State::Open; - - open.close_write_barrier().expect("to close in open"); - let error = open.write_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn cannot_read_after_starting_close() { - let mut open = State::Open; - - open.close_read_barrier().expect("to close in open"); - let error = open.read_barrier().unwrap_err(); - - assert_eq!(error.kind(), ErrorKind::BrokenPipe); - } - - #[test] - fn can_read_in_open() { - let open = State::Open; - - let result = open.read_barrier(); - - result.unwrap(); - } - - #[test] - fn can_write_in_open() { - let open = State::Open; - - let result = open.write_barrier(); - - result.unwrap(); - } - - #[test] - fn write_close_barrier_returns_ok_when_closed() { - let mut open = State::Open; - - open.close_write_barrier().unwrap(); - open.close_write_message_sent(); - open.write_closed(); - - let maybe = open.close_write_barrier().unwrap(); - - assert!(maybe.is_none()) - } - - #[test] - fn read_close_barrier_returns_ok_when_closed() { - let mut open = State::Open; - - open.close_read_barrier().unwrap(); - open.close_read_message_sent(); - open.read_closed(); - - let maybe = open.close_read_barrier().unwrap(); - - assert!(maybe.is_none()) - } - - #[test] - fn reset_flag_clears_buffer() { - let mut open = State::Open; - let mut buffer = Bytes::copy_from_slice(b"foobar"); - - open.handle_inbound_flag(Flag::Reset, &mut buffer); - - assert!(buffer.is_empty()); - } - #[test] fn max_data_len() { // Largest possible message. diff --git a/transports/webrtc/src/substream/state.rs b/transports/webrtc/src/substream/state.rs new file mode 100644 index 00000000000..f300951c634 --- /dev/null +++ b/transports/webrtc/src/substream/state.rs @@ -0,0 +1,488 @@ +use crate::message_proto::message::Flag; +use bytes::Bytes; +use std::io; + +#[derive(Debug, Copy, Clone)] +pub enum State { + Open, + ReadClosed, + WriteClosed, + ClosingRead { + /// Whether the write side of our channel was already closed. + write_closed: bool, + inner: Closing, + }, + ClosingWrite { + /// Whether the write side of our channel was already closed. + read_closed: bool, + inner: Closing, + }, + BothClosed { + reset: bool, + }, +} + +/// Represents the state of closing one half (either read or write) of the connection. +/// +/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively +/// and flushing the underlying connection. +#[derive(Debug, Copy, Clone)] +pub enum Closing { + Requested, + MessageSent, +} + +impl State { + /// Performs a state transition for a flag contained in an inbound message. + pub(crate) fn handle_inbound_flag(&mut self, flag: Flag, buffer: &mut Bytes) { + let current = *self; + + match (current, flag) { + (Self::Open, Flag::Fin) => { + *self = Self::ReadClosed; + } + (Self::WriteClosed, Flag::Fin) => { + *self = Self::BothClosed { reset: false }; + } + (Self::Open, Flag::StopSending) => { + *self = Self::WriteClosed; + } + (Self::ReadClosed, Flag::StopSending) => { + *self = Self::BothClosed { reset: false }; + } + (_, Flag::Reset) => { + buffer.clear(); + *self = Self::BothClosed { reset: true }; + } + _ => {} + } + } + + pub(crate) fn write_closed(&mut self) { + match self { + State::ClosingWrite { + read_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingWrite { + read_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::WriteClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub(crate) fn close_write_message_sent(&mut self) { + match self { + State::ClosingWrite { inner, read_closed } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingWrite { + read_closed: *read_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingRead { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub(crate) fn read_closed(&mut self) { + match self { + State::ClosingRead { + write_closed: true, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::BothClosed { reset: false }; + } + State::ClosingRead { + write_closed: false, + inner, + } => { + debug_assert!(matches!(inner, Closing::MessageSent)); + + *self = State::ReadClosed; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + pub(crate) fn close_read_message_sent(&mut self) { + match self { + State::ClosingRead { + inner, + write_closed, + } => { + debug_assert!(matches!(inner, Closing::Requested)); + + *self = State::ClosingRead { + write_closed: *write_closed, + inner: Closing::MessageSent, + }; + } + State::Open + | State::ReadClosed + | State::WriteClosed + | State::ClosingWrite { .. } + | State::BothClosed { .. } => { + unreachable!("bad state machine impl") + } + } + } + + /// Whether we should read from the stream in the [`AsyncWrite`] implementation. + /// + /// This is necessary for read-closed streams because we would otherwise not read any more flags from + /// the socket. + pub(crate) fn read_flags_in_async_write(&self) -> bool { + matches!(self, Self::ReadClosed) + } + + /// Acts as a "barrier" for [`AsyncRead::poll_read`]. + pub(crate) fn read_barrier(&self) -> io::Result<()> { + use crate::substream::State::{Open, ReadClosed, WriteClosed}; + use State::*; + + let kind = match self { + Open + | WriteClosed + | ClosingWrite { + read_closed: false, .. + } => return Ok(()), + ClosingWrite { + read_closed: true, .. + } + | ReadClosed + | ClosingRead { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) + } + + /// Acts as a "barrier" for [`AsyncWrite::poll_write`]. + pub(crate) fn write_barrier(&self) -> io::Result<()> { + use crate::substream::State::{Open, ReadClosed, WriteClosed}; + use State::*; + + let kind = match self { + Open + | ReadClosed + | ClosingRead { + write_closed: false, + .. + } => return Ok(()), + ClosingRead { + write_closed: true, .. + } + | WriteClosed + | ClosingWrite { .. } + | BothClosed { reset: false } => io::ErrorKind::BrokenPipe, + BothClosed { reset: true } => io::ErrorKind::ConnectionReset, + }; + + Err(kind.into()) + } + + /// Acts as a "barrier" for [`AsyncWrite::poll_close`]. + pub(crate) fn close_write_barrier(&mut self) -> io::Result> { + loop { + match &self { + State::WriteClosed => return Ok(None), + + State::ClosingWrite { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingWrite { + read_closed: false, + inner: Closing::Requested, + }; + } + State::ReadClosed => { + *self = Self::ClosingWrite { + read_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingRead { + write_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingRead { + write_closed: false, + .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close read half while closing write half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } + } + + /// Acts as a "barrier" for [`Substream::poll_close_read`]. + pub fn close_read_barrier(&mut self) -> io::Result> { + loop { + match self { + State::ReadClosed => return Ok(None), + + State::ClosingRead { inner, .. } => return Ok(Some(*inner)), + + State::Open => { + *self = Self::ClosingRead { + write_closed: false, + inner: Closing::Requested, + }; + } + State::WriteClosed => { + *self = Self::ClosingRead { + write_closed: true, + inner: Closing::Requested, + }; + } + + State::ClosingWrite { + read_closed: true, .. + } + | State::BothClosed { reset: false } => { + return Err(io::ErrorKind::BrokenPipe.into()) + } + + State::ClosingWrite { + read_closed: false, .. + } => { + return Err(io::Error::new( + io::ErrorKind::Other, + "cannot close write half while closing read half", + )) + } + + State::BothClosed { reset: true } => { + return Err(io::ErrorKind::ConnectionReset.into()) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::ErrorKind; + + #[test] + fn cannot_read_after_receiving_fin() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_read_after_closing_read() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_receiving_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn cannot_write_after_closing_write() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe) + } + + #[test] + fn everything_broken_after_receiving_reset() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Reset, &mut Bytes::default()); + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + let error3 = open.close_write_barrier().unwrap_err(); + let error4 = open.close_read_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::ConnectionReset); + assert_eq!(error2.kind(), ErrorKind::ConnectionReset); + assert_eq!(error3.kind(), ErrorKind::ConnectionReset); + assert_eq!(error4.kind(), ErrorKind::ConnectionReset); + } + + #[test] + fn should_read_flags_in_async_write_after_read_closed() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); + + assert!(open.read_flags_in_async_write()) + } + + #[test] + fn cannot_read_or_write_after_receiving_fin_and_stop_sending() { + let mut open = State::Open; + + open.handle_inbound_flag(Flag::Fin, &mut Bytes::default()); + open.handle_inbound_flag(Flag::StopSending, &mut Bytes::default()); + + let error1 = open.read_barrier().unwrap_err(); + let error2 = open.write_barrier().unwrap_err(); + + assert_eq!(error1.kind(), ErrorKind::BrokenPipe); + assert_eq!(error2.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn can_read_after_closing_write() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + open.read_barrier().unwrap(); + } + + #[test] + fn can_write_after_closing_read() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + open.write_barrier().unwrap(); + } + + #[test] + fn cannot_write_after_starting_close() { + let mut open = State::Open; + + open.close_write_barrier().expect("to close in open"); + let error = open.write_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn cannot_read_after_starting_close() { + let mut open = State::Open; + + open.close_read_barrier().expect("to close in open"); + let error = open.read_barrier().unwrap_err(); + + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + } + + #[test] + fn can_read_in_open() { + let open = State::Open; + + let result = open.read_barrier(); + + result.unwrap(); + } + + #[test] + fn can_write_in_open() { + let open = State::Open; + + let result = open.write_barrier(); + + result.unwrap(); + } + + #[test] + fn write_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_write_barrier().unwrap(); + open.close_write_message_sent(); + open.write_closed(); + + let maybe = open.close_write_barrier().unwrap(); + + assert!(maybe.is_none()) + } + + #[test] + fn read_close_barrier_returns_ok_when_closed() { + let mut open = State::Open; + + open.close_read_barrier().unwrap(); + open.close_read_message_sent(); + open.read_closed(); + + let maybe = open.close_read_barrier().unwrap(); + + assert!(maybe.is_none()) + } + + #[test] + fn reset_flag_clears_buffer() { + let mut open = State::Open; + let mut buffer = Bytes::copy_from_slice(b"foobar"); + + open.handle_inbound_flag(Flag::Reset, &mut buffer); + + assert!(buffer.is_empty()); + } +} From 16433db8f935b754a837dd22c9c33d802b7b4a93 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 19:17:13 +1100 Subject: [PATCH 190/244] Send reset flag for dropped substreams --- transports/webrtc/src/connection.rs | 47 ++++++-- transports/webrtc/src/substream.rs | 39 +++++-- .../webrtc/src/substream/drop_listener.rs | 108 ++++++++++++++++++ transports/webrtc/src/substream/framed_dc.rs | 21 ++++ transports/webrtc/src/upgrade.rs | 5 +- 5 files changed, 198 insertions(+), 22 deletions(-) create mode 100644 transports/webrtc/src/substream/drop_listener.rs create mode 100644 transports/webrtc/src/substream/framed_dc.rs diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 4d867ceeae4..feca0b10e53 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -18,29 +18,30 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use futures::stream::FuturesUnordered; use futures::{ channel::{ mpsc, oneshot::{self, Sender}, }, lock::Mutex as FutMutex, + StreamExt, {future::BoxFuture, ready}, }; -use futures_lite::StreamExt; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use log::{debug, error, trace}; use webrtc::data::data_channel::DataChannel as DetachedDataChannel; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; +use std::task::Waker; use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, }; -use crate::error::Error; -use crate::substream::Substream; +use crate::{error::Error, substream, substream::Substream}; const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; @@ -59,6 +60,9 @@ pub struct Connection { /// Future, which, once polled, will result in closing the entire connection. close_fut: Option>>, + + drop_listeners: FuturesUnordered, + no_drop_listeners_waker: Option, } impl Unpin for Connection {} @@ -75,6 +79,8 @@ impl Connection { incoming_data_channels_rx: data_channel_rx, outbound_fut: None, close_fut: None, + drop_listeners: FuturesUnordered::default(), + no_drop_listeners_waker: None, } } @@ -143,11 +149,17 @@ impl StreamMuxer for Connection { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - match ready!(self.incoming_data_channels_rx.poll_next(cx)) { + match ready!(self.incoming_data_channels_rx.poll_next_unpin(cx)) { Some(detached) => { trace!("Incoming substream {}", detached.stream_identifier()); - Poll::Ready(Ok(Substream::new(detached))) + let (substream, drop_listener) = Substream::new(detached); + self.drop_listeners.push(drop_listener); + if let Some(waker) = self.no_drop_listeners_waker.take() { + waker.wake() + } + + Poll::Ready(Ok(substream)) } None => { debug_assert!( @@ -161,10 +173,21 @@ impl StreamMuxer for Connection { } fn poll( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, ) -> Poll> { - Poll::Pending + loop { + match ready!(self.drop_listeners.poll_next_unpin(cx)) { + Some(Ok(())) => {} + Some(Err(e)) => { + log::debug!("a DropListener failed: {e}") + } + None => { + self.no_drop_listeners_waker = Some(cx.waker().clone()); + return Poll::Pending; + } + } + } } fn poll_outbound( @@ -198,7 +221,13 @@ impl StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { self.outbound_fut = None; - Poll::Ready(Ok(Substream::new(detached))) + let (substream, drop_listener) = Substream::new(detached); + self.drop_listeners.push(drop_listener); + if let Some(waker) = self.no_drop_listeners_waker.take() { + waker.wake() + } + + Poll::Ready(Ok(substream)) } Err(e) => { self.outbound_fut = None; diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index 18cc506fb24..b4edd2b8aac 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -20,14 +20,13 @@ use asynchronous_codec::Framed; use bytes::Bytes; +use futures::channel::oneshot; use futures::prelude::*; use futures::ready; use tokio_util::compat::Compat; -use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc::data::data_channel::DataChannel; use webrtc::data::data_channel::PollDataChannel; -use state::{Closing, State}; use std::{ io, pin::Pin, @@ -36,7 +35,12 @@ use std::{ }; use crate::message_proto::{message::Flag, Message}; +use crate::substream::drop_listener::GracefullyClosed; +use crate::substream::framed_dc::FramedDC; +use crate::substream::state::{Closing, State}; +mod drop_listener; +mod framed_dc; mod state; /// As long as message interleaving is not supported, the sender SHOULD limit the maximum message size to 16 KB to avoid monopolization. @@ -49,30 +53,34 @@ const PROTO_OVERHEAD: usize = 5; /// Maximum length of data, in bytes. const MAX_DATA_LEN: usize = MAX_MSG_LEN - VARINT_LEN - PROTO_OVERHEAD; +pub use drop_listener::DropListener; + /// A substream on top of a WebRTC data channel. /// /// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well /// as support a half-closed state which we do by framing messages in a protobuf envelope. pub struct Substream { - io: Framed, prost_codec::Codec>, + io: FramedDC, state: State, read_buffer: Bytes, + /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. + drop_notifier: Option>, } impl Substream { /// Constructs a new `Substream`. - pub(crate) fn new(data_channel: Arc) -> Self { - let mut inner = PollDataChannel::new(data_channel); - - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. - inner.set_read_buf_capacity(8192 * 10); + pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { + let (sender, receiver) = oneshot::channel(); - Self { - io: Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)), + let substream = Self { + io: framed_dc::new(data_channel.clone()), state: State::Open, read_buffer: Bytes::default(), - } + drop_notifier: Some(sender), + }; + let listener = DropListener::new(framed_dc::new(data_channel), receiver); + + (substream, listener) } /// Gracefully closes the "read-half" of the substream. @@ -124,6 +132,7 @@ impl AsyncRead for Substream { read_buffer, io, state, + .. } = &mut *self; match ready!(io_poll_next(io, cx))? { @@ -160,6 +169,7 @@ impl AsyncWrite for Substream { read_buffer, io, state, + .. } = &mut *self; match io_poll_next(io, cx)? { @@ -211,6 +221,11 @@ impl AsyncWrite for Substream { ready!(self.io.poll_flush_unpin(cx))?; self.state.write_closed(); + let _ = self + .drop_notifier + .take() + .expect("to not close twice") + .send(GracefullyClosed {}); return Poll::Ready(Ok(())); } diff --git a/transports/webrtc/src/substream/drop_listener.rs b/transports/webrtc/src/substream/drop_listener.rs new file mode 100644 index 00000000000..26fdc206641 --- /dev/null +++ b/transports/webrtc/src/substream/drop_listener.rs @@ -0,0 +1,108 @@ +use crate::message_proto::{message::Flag, Message}; +use crate::substream::framed_dc::FramedDC; +use futures::channel::oneshot; +use futures::channel::oneshot::Canceled; +use futures::{FutureExt, SinkExt}; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[must_use] +pub struct DropListener { + state: State, +} + +impl DropListener { + pub fn new(stream: FramedDC, receiver: oneshot::Receiver) -> Self { + let substream_id = stream.get_ref().stream_identifier(); + + Self { + state: State::Idle { + stream, + receiver, + substream_id, + }, + } + } +} + +enum State { + /// The [`DropListener`] is idle and waiting to be activated. + Idle { + stream: FramedDC, + receiver: oneshot::Receiver, + substream_id: u16, + }, + /// The stream got dropped and we are sending a reset flag. + SendingReset { + stream: FramedDC, + }, + Flushing { + stream: FramedDC, + }, + /// Bad state transition. + Poisoned, +} + +impl Future for DropListener { + type Output = io::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = &mut self.get_mut().state; + + loop { + match std::mem::replace(state, State::Poisoned) { + State::Idle { + stream, + substream_id, + mut receiver, + } => match receiver.poll_unpin(cx) { + Poll::Ready(Ok(GracefullyClosed {})) => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Err(Canceled)) => { + log::info!("Substream {substream_id} dropped without graceful close, sending Reset"); + *state = State::SendingReset { stream }; + continue; + } + Poll::Pending => { + *state = State::Idle { + stream, + substream_id, + receiver, + }; + return Poll::Pending; + } + }, + State::SendingReset { mut stream } => match stream.poll_ready_unpin(cx)? { + Poll::Ready(()) => { + stream.start_send_unpin(Message { + flag: Some(Flag::Reset.into()), + message: None, + })?; + *state = State::Flushing { stream }; + continue; + } + Poll::Pending => { + *state = State::SendingReset { stream }; + return Poll::Pending; + } + }, + State::Flushing { mut stream } => match stream.poll_flush_unpin(cx)? { + Poll::Ready(()) => return Poll::Ready(Ok(())), + Poll::Pending => { + *state = State::Flushing { stream }; + return Poll::Pending; + } + }, + State::Poisoned => { + unreachable!() + } + } + } + } +} + +/// Indicates that our substream got gracefully closed. +pub struct GracefullyClosed {} diff --git a/transports/webrtc/src/substream/framed_dc.rs b/transports/webrtc/src/substream/framed_dc.rs new file mode 100644 index 00000000000..b28b0d467a0 --- /dev/null +++ b/transports/webrtc/src/substream/framed_dc.rs @@ -0,0 +1,21 @@ +use crate::message_proto::Message; +use crate::substream::MAX_MSG_LEN; +use asynchronous_codec::Framed; +use std::sync::Arc; +use tokio_util::compat::Compat; +use tokio_util::compat::TokioAsyncReadCompatExt; +use webrtc::data::data_channel::{DataChannel, PollDataChannel}; + +pub type FramedDC = Framed, prost_codec::Codec>; + +pub fn new( + data_channel: Arc, +) -> Framed, prost_codec::Codec> { + let mut inner = PollDataChannel::new(data_channel.clone()); + + // TODO: default buffer size is too small to fit some messages. Possibly remove once + // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. + inner.set_read_buf_capacity(8192 * 10); + + Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)) +} diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 1664d5a9db5..2a6999a3d54 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -223,5 +223,8 @@ async fn create_substream_for_noise_handshake( } }; - Ok(Substream::new(channel)) + let (substream, drop_listener) = Substream::new(channel); + drop(drop_listener); // Don't care about cancelled substreams during initial handshake. + + Ok(substream) } From 834ec644496d8bd42631c006d6a4849ac29799f5 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 19:23:38 +1100 Subject: [PATCH 191/244] Fix clippy warnings --- transports/webrtc/src/connection.rs | 2 +- transports/webrtc/src/substream/framed_dc.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index feca0b10e53..d4e765bd85a 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -167,7 +167,7 @@ impl StreamMuxer for Connection { "Sender-end of channel should be owned by `RTCPeerConnection`" ); - return Poll::Pending; // Return `Pending` without registering a waker: If the channel is closed, we don't need to be called anymore. + Poll::Pending // Return `Pending` without registering a waker: If the channel is closed, we don't need to be called anymore. } } } diff --git a/transports/webrtc/src/substream/framed_dc.rs b/transports/webrtc/src/substream/framed_dc.rs index b28b0d467a0..63f985c1f4a 100644 --- a/transports/webrtc/src/substream/framed_dc.rs +++ b/transports/webrtc/src/substream/framed_dc.rs @@ -8,10 +8,8 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; pub type FramedDC = Framed, prost_codec::Codec>; -pub fn new( - data_channel: Arc, -) -> Framed, prost_codec::Codec> { - let mut inner = PollDataChannel::new(data_channel.clone()); +pub fn new(data_channel: Arc) -> FramedDC { + let mut inner = PollDataChannel::new(data_channel); // TODO: default buffer size is too small to fit some messages. Possibly remove once // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. From 7e0e46d728c8ca496649ce7bc807e2e25dead7c6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 11 Oct 2022 19:26:01 +1100 Subject: [PATCH 192/244] Fix docs --- transports/webrtc/src/fingerprint.rs | 2 +- transports/webrtc/src/substream/state.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 0a307edeb36..b9c63fe1474 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -89,7 +89,7 @@ impl Fingerprint { } /// Returns the algorithm used (e.g. "sha-256"). - /// See https://datatracker.ietf.org/doc/html/rfc8122#section-5 + /// See pub fn algorithm(&self) -> String { SHA256.to_owned() } diff --git a/transports/webrtc/src/substream/state.rs b/transports/webrtc/src/substream/state.rs index f300951c634..5baf219b364 100644 --- a/transports/webrtc/src/substream/state.rs +++ b/transports/webrtc/src/substream/state.rs @@ -157,7 +157,7 @@ impl State { } } - /// Whether we should read from the stream in the [`AsyncWrite`] implementation. + /// Whether we should read from the stream in the [`futures::AsyncWrite`] implementation. /// /// This is necessary for read-closed streams because we would otherwise not read any more flags from /// the socket. @@ -165,7 +165,7 @@ impl State { matches!(self, Self::ReadClosed) } - /// Acts as a "barrier" for [`AsyncRead::poll_read`]. + /// Acts as a "barrier" for [`futures::AsyncRead::poll_read`]. pub(crate) fn read_barrier(&self) -> io::Result<()> { use crate::substream::State::{Open, ReadClosed, WriteClosed}; use State::*; @@ -188,7 +188,7 @@ impl State { Err(kind.into()) } - /// Acts as a "barrier" for [`AsyncWrite::poll_write`]. + /// Acts as a "barrier" for [`futures::AsyncWrite::poll_write`]. pub(crate) fn write_barrier(&self) -> io::Result<()> { use crate::substream::State::{Open, ReadClosed, WriteClosed}; use State::*; @@ -212,7 +212,7 @@ impl State { Err(kind.into()) } - /// Acts as a "barrier" for [`AsyncWrite::poll_close`]. + /// Acts as a "barrier" for [`futures::AsyncWrite::poll_close`]. pub(crate) fn close_write_barrier(&mut self) -> io::Result> { loop { match &self { @@ -257,7 +257,7 @@ impl State { } } - /// Acts as a "barrier" for [`Substream::poll_close_read`]. + /// Acts as a "barrier" for [`Substream::poll_close_read`](super::Substream::poll_close_read). pub fn close_read_barrier(&mut self) -> io::Result> { loop { match self { From 35e2c50ba99fd3b487097461112e26e95a7bc15d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 11 Oct 2022 13:35:51 +0400 Subject: [PATCH 193/244] fixed order for fingerprints in Noise prologue https://github.com/libp2p/specs/pull/412#discussion_r978398623 --- transports/webrtc/src/transport.rs | 8 +++--- transports/webrtc/src/upgrade.rs | 16 ++++++------ transports/webrtc/src/upgrade/noise.rs | 36 ++++++++++---------------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 4540e6088b4..43ff87e6f11 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -112,14 +112,14 @@ impl libp2p_core::Transport for Transport { } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (sock_addr, remote_fingerprint, expected_peer_id) = parse_webrtc_dial_addr(&addr) + let (sock_addr, server_fingerprint, expected_peer_id) = parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } let config = self.config.clone(); - let our_fingerprint = self.config.fingerprint; + let client_fingerprint = self.config.fingerprint; let id_keys = self.id_keys.clone(); let udp_mux = self .listeners @@ -134,8 +134,8 @@ impl libp2p_core::Transport for Transport { sock_addr, config.inner, udp_mux, - our_fingerprint, - remote_fingerprint, + client_fingerprint, + server_fingerprint, id_keys, ) .await?; diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 2a6999a3d54..d6614fbfae3 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -47,8 +47,8 @@ pub async fn outbound( addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { log::debug!("new outbound connection to {addr})"); @@ -59,7 +59,7 @@ pub async fn outbound( log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; - let answer = sdp::answer(addr, &remote_fingerprint); + let answer = sdp::answer(addr, &server_fingerprint); log::debug!( "calculated SDP answer for outbound connection: {:?}", answer @@ -69,7 +69,7 @@ pub async fn outbound( let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; let peer_id = - noise::outbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; + noise::outbound(id_keys, data_channel, client_fingerprint, server_fingerprint).await?; Ok((peer_id, Connection::new(peer_connection).await)) } @@ -79,14 +79,14 @@ pub async fn inbound( addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, - our_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, remote_ufrag: String, id_keys: identity::Keypair, ) -> Result<(PeerId, Connection), Error> { log::debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); let peer_connection = - new_inbound_connection(addr, config, udp_mux, &our_fingerprint.to_ufrag()).await?; + new_inbound_connection(addr, config, udp_mux, &server_fingerprint.to_ufrag()).await?; let offer = sdp::offer(addr, &remote_ufrag); log::debug!("calculated SDP offer for inbound connection: {:?}", offer); @@ -97,10 +97,10 @@ pub async fn inbound( peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; - let remote_fingerprint = get_remote_fingerprint(&peer_connection).await; + let client_fingerprint = get_remote_fingerprint(&peer_connection).await; let peer_id = - noise::inbound(id_keys, data_channel, our_fingerprint, remote_fingerprint).await?; + noise::inbound(id_keys, data_channel, server_fingerprint, client_fingerprint).await?; Ok((peer_id, Connection::new(peer_connection).await)) } diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index 9265b3e2bcf..da2e93f0cc5 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -3,13 +3,12 @@ use crate::Error; use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; -use multihash::Multihash; pub async fn outbound( id_keys: identity::Keypair, stream: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -18,7 +17,7 @@ where .into_authentic(&id_keys) .unwrap(); let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut channel) = noise .into_authenticated() @@ -33,8 +32,8 @@ where pub async fn inbound( id_keys: identity::Keypair, stream: T, - our_fingerprint: Fingerprint, - remote_fingerprint: Fingerprint, + server_fingerprint: Fingerprint, + client_fingerprint: Fingerprint, ) -> Result where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -43,7 +42,7 @@ where .into_authentic(&id_keys) .unwrap(); let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(our_fingerprint, remote_fingerprint)); + NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut channel) = noise .into_authenticated() @@ -55,18 +54,14 @@ where Ok(peer_id) } -pub fn noise_prologue(our_fingerprint: Fingerprint, remote_fingerprint: Fingerprint) -> Vec { - let (a, b): (Multihash, Multihash) = ( - our_fingerprint.to_multi_hash(), - remote_fingerprint.to_multi_hash(), - ); - let (a, b) = (a.to_bytes(), b.to_bytes()); - let (first, second) = if a < b { (a, b) } else { (b, a) }; +pub fn noise_prologue(client_fingerprint: Fingerprint, server_fingerprint: Fingerprint) -> Vec { + let client = client_fingerprint.to_multi_hash().to_bytes(); + let server = server_fingerprint.to_multi_hash().to_bytes(); const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; - let mut out = Vec::with_capacity(PREFIX.len() + first.len() + second.len()); + let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); out.extend_from_slice(PREFIX); - out.extend_from_slice(&first); - out.extend_from_slice(&second); + out.extend_from_slice(&client); + out.extend_from_slice(&server); out } @@ -87,10 +82,7 @@ mod tests { let prologue1 = noise_prologue(a, b); let prologue2 = noise_prologue(b, a); - assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); - assert_eq!( - prologue1, prologue2, - "order of fingerprints does not matter" - ); + assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); } } From de191a679caf142d0b3bea0d585b2e603a0db969 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 11 Oct 2022 16:13:50 +0400 Subject: [PATCH 194/244] reverse the noise roles "The above suggestion would still be an optimization, i.e. it would allow the server (webrtc connection responder, here Noise handshake initiator) to send application data 0.5 RTT earlier. Things would stay as is for the client (webrtc connection initiator, here Noise handshake responder)." https://github.com/libp2p/specs/pull/412#discussion_r977698490 --- transports/webrtc/src/upgrade.rs | 24 ++++++++++++++++++------ transports/webrtc/src/upgrade/noise.rs | 8 ++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index d6614fbfae3..9b4e28ef127 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -66,10 +66,16 @@ pub async fn outbound( ); peer_connection.set_remote_description(answer).await?; // This will start the gathering of ICE candidates. + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; - - let peer_id = - noise::outbound(id_keys, data_channel, client_fingerprint, server_fingerprint).await?; + let peer_id = noise::inbound( + id_keys, + data_channel, + server_fingerprint, + client_fingerprint, + ) + .await?; Ok((peer_id, Connection::new(peer_connection).await)) } @@ -96,11 +102,17 @@ pub async fn inbound( log::debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; let client_fingerprint = get_remote_fingerprint(&peer_connection).await; - - let peer_id = - noise::inbound(id_keys, data_channel, server_fingerprint, client_fingerprint).await?; + let peer_id = noise::outbound( + id_keys, + data_channel, + client_fingerprint, + server_fingerprint, + ) + .await?; Ok((peer_id, Connection::new(peer_connection).await)) } diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index da2e93f0cc5..f7f3baed8ed 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -16,8 +16,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let noise = NoiseConfig::xx(dh_keys) + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut channel) = noise .into_authenticated() @@ -41,8 +41,8 @@ where let dh_keys = Keypair::::new() .into_authentic(&id_keys) .unwrap(); - let noise = - NoiseConfig::xx(dh_keys).with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); + let noise = NoiseConfig::xx(dh_keys) + .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); let (peer_id, mut channel) = noise .into_authenticated() From c435df0bfe5fdd1e6ab5b74472fb6bdf6dc296fd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 11 Oct 2022 17:13:58 +0400 Subject: [PATCH 195/244] don't use Multihash in Noise prologue fix algo to SHA-256 https://github.com/libp2p/specs/pull/412#discussion_r968327480 --- transports/webrtc/src/fingerprint.rs | 4 ++++ transports/webrtc/src/upgrade/noise.rs | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index b9c63fe1474..1d441305604 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -73,6 +73,10 @@ impl Fingerprint { Code::Sha2_256.wrap(&self.0).unwrap() } + pub fn to_raw(self) -> [u8; 32] { + self.0 + } + /// Transforms this fingerprint into a ufrag. pub fn to_ufrag(self) -> String { multibase::encode( diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index f7f3baed8ed..a0f5e05a165 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -55,8 +55,8 @@ where } pub fn noise_prologue(client_fingerprint: Fingerprint, server_fingerprint: Fingerprint) -> Vec { - let client = client_fingerprint.to_multi_hash().to_bytes(); - let server = server_fingerprint.to_multi_hash().to_bytes(); + let client = client_fingerprint.to_raw(); + let server = server_fingerprint.to_raw(); const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); out.extend_from_slice(PREFIX); @@ -82,7 +82,7 @@ mod tests { let prologue1 = noise_prologue(a, b); let prologue2 = noise_prologue(b, a); - assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); - assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca87030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b993e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); } } From 95665ec9e488c40096dc93d7301c1ddf279a763e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 12 Oct 2022 16:21:04 +0400 Subject: [PATCH 196/244] add comments to Fingerprint --- transports/webrtc/src/fingerprint.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 1d441305604..6f9461e789e 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -37,6 +37,7 @@ impl Fingerprint { Self(bytes) } + /// Creates a fingerprint from a raw certificate. pub fn from_certificate(bytes: &[u8]) -> Self { let mut h = multihash::Sha2_256::default(); h.update(bytes); @@ -47,6 +48,7 @@ impl Fingerprint { Fingerprint(bytes) } + /// Converts to [`Fingerprint`] from [`RTCDtlsFingerprint`]. pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { if fp.algorithm != SHA256 { return None; @@ -58,6 +60,7 @@ impl Fingerprint { Some(Self(buf)) } + /// Converts to [`Fingerprint`] from [`Multihash`]. pub fn try_from_multihash(hash: Multihash) -> Option { if hash.code() != u64::from(Code::Sha2_256) { // Only support SHA256 for now. @@ -69,10 +72,12 @@ impl Fingerprint { Some(Self(bytes)) } + /// Converts the fingerprint to [`Multihash`]. pub fn to_multi_hash(self) -> Multihash { Code::Sha2_256.wrap(&self.0).unwrap() } + /// Returns raw fingerprint bytes. pub fn to_raw(self) -> [u8; 32] { self.0 } From 67a13b888a6f491973f531021ac004e45ff0d430 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 12 Oct 2022 16:29:51 +0400 Subject: [PATCH 197/244] fix doc links --- transports/webrtc/src/fingerprint.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 6f9461e789e..0434695095a 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -48,7 +48,7 @@ impl Fingerprint { Fingerprint(bytes) } - /// Converts to [`Fingerprint`] from [`RTCDtlsFingerprint`]. + /// Converts [`RTCDtlsFingerprint`] to [`Fingerprint`]. pub fn try_from_rtc_dtls(fp: &RTCDtlsFingerprint) -> Option { if fp.algorithm != SHA256 { return None; @@ -60,7 +60,7 @@ impl Fingerprint { Some(Self(buf)) } - /// Converts to [`Fingerprint`] from [`Multihash`]. + /// Converts [`type@Multihash`] to [`Fingerprint`]. pub fn try_from_multihash(hash: Multihash) -> Option { if hash.code() != u64::from(Code::Sha2_256) { // Only support SHA256 for now. @@ -72,7 +72,7 @@ impl Fingerprint { Some(Self(bytes)) } - /// Converts the fingerprint to [`Multihash`]. + /// Converts this fingerprint to [`type@Multihash`]. pub fn to_multi_hash(self) -> Multihash { Code::Sha2_256.wrap(&self.0).unwrap() } From 806d7e3231d198ea635a8a63f40e4c182042e770 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Oct 2022 09:39:29 +0400 Subject: [PATCH 198/244] Revert "don't use Multihash in Noise prologue" This reverts commit c435df0bfe5fdd1e6ab5b74472fb6bdf6dc296fd. --- transports/webrtc/src/fingerprint.rs | 5 ----- transports/webrtc/src/upgrade/noise.rs | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/fingerprint.rs index 0434695095a..c9ba7314db4 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/fingerprint.rs @@ -77,11 +77,6 @@ impl Fingerprint { Code::Sha2_256.wrap(&self.0).unwrap() } - /// Returns raw fingerprint bytes. - pub fn to_raw(self) -> [u8; 32] { - self.0 - } - /// Transforms this fingerprint into a ufrag. pub fn to_ufrag(self) -> String { multibase::encode( diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index a0f5e05a165..f7f3baed8ed 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -55,8 +55,8 @@ where } pub fn noise_prologue(client_fingerprint: Fingerprint, server_fingerprint: Fingerprint) -> Vec { - let client = client_fingerprint.to_raw(); - let server = server_fingerprint.to_raw(); + let client = client_fingerprint.to_multi_hash().to_bytes(); + let server = server_fingerprint.to_multi_hash().to_bytes(); const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); out.extend_from_slice(PREFIX); @@ -82,7 +82,7 @@ mod tests { let prologue1 = noise_prologue(a, b); let prologue2 = noise_prologue(b, a); - assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a3e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca87030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); - assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a30fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b993e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); + assert_eq!(hex::encode(&prologue1), "6c69627032702d7765627274632d6e6f6973653a12203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b99"); + assert_eq!(hex::encode(&prologue2), "6c69627032702d7765627274632d6e6f6973653a122030fc9f469c207419dfdd0aab5f27a86c973c94e40548db9375cca2e915973b9912203e79af40d6059617a0d83b83a52ce73b0c1f37a72c6043ad2969e2351bdca870"); } } From 114f5399dd5a7e8ebf7ac116653768059bdc0b46 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Oct 2022 09:42:47 +0400 Subject: [PATCH 199/244] rename noise functions https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r993881681 --- transports/webrtc/src/upgrade.rs | 8 ++------ transports/webrtc/src/upgrade/noise.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 9b4e28ef127..f99968089ed 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -66,10 +66,8 @@ pub async fn outbound( ); peer_connection.set_remote_description(answer).await?; // This will start the gathering of ICE candidates. - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; - let peer_id = noise::inbound( + let peer_id = noise::outbound( id_keys, data_channel, server_fingerprint, @@ -102,11 +100,9 @@ pub async fn inbound( log::debug!("created SDP answer for inbound connection: {:?}", answer); peer_connection.set_local_description(answer).await?; // This will start the gathering of ICE candidates. - // Note the roles are reversed because it allows the server (webrtc connection responder) to - // send application data 0.5 RTT earlier. let data_channel = create_substream_for_noise_handshake(&peer_connection).await?; let client_fingerprint = get_remote_fingerprint(&peer_connection).await; - let peer_id = noise::outbound( + let peer_id = noise::inbound( id_keys, data_channel, client_fingerprint, diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/upgrade/noise.rs index f7f3baed8ed..51f3a38e62a 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/upgrade/noise.rs @@ -4,7 +4,7 @@ use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; -pub async fn outbound( +pub async fn inbound( id_keys: identity::Keypair, stream: T, client_fingerprint: Fingerprint, @@ -19,6 +19,8 @@ where let noise = NoiseConfig::xx(dh_keys) .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. let (peer_id, mut channel) = noise .into_authenticated() .upgrade_outbound(stream, info) @@ -29,7 +31,7 @@ where Ok(peer_id) } -pub async fn inbound( +pub async fn outbound( id_keys: identity::Keypair, stream: T, server_fingerprint: Fingerprint, @@ -44,6 +46,8 @@ where let noise = NoiseConfig::xx(dh_keys) .with_prologue(noise_prologue(client_fingerprint, server_fingerprint)); let info = noise.protocol_info().next().unwrap(); + // Note the roles are reversed because it allows the server (webrtc connection responder) to + // send application data 0.5 RTT earlier. let (peer_id, mut channel) = noise .into_authenticated() .upgrade_inbound(stream, info) From 6c8cce99b4e17d79e705af104ccfe74fabc64ced Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Oct 2022 13:03:03 +0400 Subject: [PATCH 200/244] don't clone the raw `mpsc::Sender` https://github.com/libp2p/rust-libp2p/pull/2622/files#r929473339 --- transports/webrtc/src/connection.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index d4e765bd85a..2c90246166c 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -43,6 +43,8 @@ use std::{ use crate::{error::Error, substream, substream::Substream}; +/// Maximum number of unprocessed data channels. +/// See [`Connection::poll_inbound`]. const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. @@ -72,7 +74,11 @@ impl Connection { pub(crate) async fn new(rtc_conn: RTCPeerConnection) -> Self { let (data_channel_tx, data_channel_rx) = mpsc::channel(MAX_DATA_CHANNELS_IN_FLIGHT); - Connection::register_incoming_data_channels_handler(&rtc_conn, data_channel_tx).await; + Connection::register_incoming_data_channels_handler( + &rtc_conn, + Arc::new(FutMutex::new(data_channel_tx)), + ) + .await; Self { peer_conn: Arc::new(FutMutex::new(rtc_conn)), @@ -85,9 +91,14 @@ impl Connection { } /// Registers a handler for incoming data channels. + /// + /// NOTE: `mpsc::Sender` is wrapped in `Arc` because cloning a raw sender would make the channel + /// unbounded. "The channel’s capacity is equal to buffer + num-senders. In other words, each + /// sender gets a guaranteed slot in the channel capacity..." + /// See async fn register_incoming_data_channels_handler( rtc_conn: &RTCPeerConnection, - tx: mpsc::Sender>, + tx: Arc>>>, ) { rtc_conn .on_data_channel(Box::new(move |data_channel: Arc| { @@ -97,7 +108,7 @@ impl Connection { data_channel.id() ); - let mut tx = tx.clone(); + let tx = tx.clone(); Box::pin(async move { data_channel @@ -114,6 +125,7 @@ impl Connection { let data_channel = data_channel.clone(); match data_channel.detach().await { Ok(detached) => { + let mut tx = tx.lock().await; if let Err(e) = tx.try_send(detached.clone()) { error!("Can't send data channel: {}", e); // We're not accepting data channels fast enough => From c89f95ab0abd702a57b5dac0b207505d4ff676e4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Oct 2022 11:30:33 +0400 Subject: [PATCH 201/244] add some comments --- transports/webrtc/src/connection.rs | 29 +++++++++++++++------------ transports/webrtc/src/req_res_chan.rs | 13 +++++++----- transports/webrtc/src/substream.rs | 17 ++++++++-------- transports/webrtc/src/udp_mux.rs | 26 +++++++++++++++++++----- transports/webrtc/src/upgrade.rs | 12 ++++++----- 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/connection.rs index 2c90246166c..b67ab6e0aee 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/connection.rs @@ -29,7 +29,6 @@ use futures::{ {future::BoxFuture, ready}, }; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use log::{debug, error, trace}; use webrtc::data::data_channel::DataChannel as DetachedDataChannel; use webrtc::data_channel::RTCDataChannel; use webrtc::peer_connection::RTCPeerConnection; @@ -63,6 +62,7 @@ pub struct Connection { /// Future, which, once polled, will result in closing the entire connection. close_fut: Option>>, + /// A list of futures, which, once completed, signal that a [`Substream`] has been dropped. drop_listeners: FuturesUnordered, no_drop_listeners_waker: Option, } @@ -102,7 +102,7 @@ impl Connection { ) { rtc_conn .on_data_channel(Box::new(move |data_channel: Arc| { - debug!( + log::debug!( "Incoming data channel '{}'-'{}'", data_channel.label(), data_channel.id() @@ -115,7 +115,7 @@ impl Connection { .on_open({ let data_channel = data_channel.clone(); Box::new(move || { - debug!( + log::debug!( "Data channel '{}'-'{}' open", data_channel.label(), data_channel.id() @@ -127,7 +127,7 @@ impl Connection { Ok(detached) => { let mut tx = tx.lock().await; if let Err(e) = tx.try_send(detached.clone()) { - error!("Can't send data channel: {}", e); + log::error!("Can't send data channel: {}", e); // We're not accepting data channels fast enough => // close this channel. // @@ -135,12 +135,15 @@ impl Connection { // during the negotiation process, but it's not // possible with the current API. if let Err(e) = detached.close().await { - error!("Failed to close data channel: {}", e); + log::error!( + "Failed to close data channel: {}", + e + ); } } } Err(e) => { - error!("Can't detach data channel: {}", e); + log::error!("Can't detach data channel: {}", e); } }; }) @@ -163,7 +166,7 @@ impl StreamMuxer for Connection { ) -> Poll> { match ready!(self.incoming_data_channels_rx.poll_next_unpin(cx)) { Some(detached) => { - trace!("Incoming substream {}", detached.stream_identifier()); + log::trace!("Incoming substream {}", detached.stream_identifier()); let (substream, drop_listener) = Substream::new(detached); self.drop_listeners.push(drop_listener); @@ -213,7 +216,7 @@ impl StreamMuxer for Connection { // Create a datachannel with label 'data' let data_channel = peer_conn.create_data_channel("data", None).await?; - trace!("Opening outbound substream {}", data_channel.id()); + log::trace!("Opening outbound substream {}", data_channel.id()); // No need to hold the lock during the DTLS handshake. drop(peer_conn); @@ -249,7 +252,7 @@ impl StreamMuxer for Connection { } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - debug!("Closing connection"); + log::debug!("Closing connection"); let peer_conn = self.peer_conn.clone(); let fut = self.close_fut.get_or_insert(Box::pin(async move { @@ -281,7 +284,7 @@ pub(crate) async fn register_data_channel_open_handler( .on_open({ let data_channel = data_channel.clone(); Box::new(move || { - debug!( + log::debug!( "Data channel '{}'-'{}' open", data_channel.label(), data_channel.id() @@ -292,14 +295,14 @@ pub(crate) async fn register_data_channel_open_handler( match data_channel.detach().await { Ok(detached) => { if let Err(e) = data_channel_tx.send(detached.clone()) { - error!("Can't send data channel: {:?}", e); + log::error!("Can't send data channel: {:?}", e); if let Err(e) = detached.close().await { - error!("Failed to close data channel: {}", e); + log::error!("Failed to close data channel: {}", e); } } } Err(e) => { - error!("Can't detach data channel: {}", e); + log::error!("Can't detach data channel: {}", e); } }; }) diff --git a/transports/webrtc/src/req_res_chan.rs b/transports/webrtc/src/req_res_chan.rs index 5e350996917..0db5a84fbb4 100644 --- a/transports/webrtc/src/req_res_chan.rs +++ b/transports/webrtc/src/req_res_chan.rs @@ -1,9 +1,12 @@ -use futures::channel::mpsc; -use futures::channel::oneshot; -use futures::SinkExt; +use futures::{ + channel::{mpsc, oneshot}, + SinkExt, +}; use futures_lite::StreamExt; -use std::io; -use std::task::{Context, Poll}; +use std::{ + io, + task::{Context, Poll}, +}; pub fn new(capacity: usize) -> (Sender, Receiver) { let (sender, receiver) = mpsc::channel(capacity); diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/substream.rs index b4edd2b8aac..48c35b3e878 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/substream.rs @@ -20,12 +20,9 @@ use asynchronous_codec::Framed; use bytes::Bytes; -use futures::channel::oneshot; -use futures::prelude::*; -use futures::ready; +use futures::{channel::oneshot, prelude::*, ready}; use tokio_util::compat::Compat; -use webrtc::data::data_channel::DataChannel; -use webrtc::data::data_channel::PollDataChannel; +use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::{ io, @@ -43,8 +40,11 @@ mod drop_listener; mod framed_dc; mod state; -/// As long as message interleaving is not supported, the sender SHOULD limit the maximum message size to 16 KB to avoid monopolization. -// Source: +/// Maximum length of a message. +/// +/// "As long as message interleaving is not supported, the sender SHOULD limit the maximum message +/// size to 16 KB to avoid monopolization." +/// Source: const MAX_MSG_LEN: usize = 16384; // 16kiB /// Length of varint, in bytes. const VARINT_LEN: usize = 2; @@ -68,7 +68,8 @@ pub struct Substream { } impl Substream { - /// Constructs a new `Substream`. + /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream + /// is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index b52adcc5ee5..26c8d00946b 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -19,7 +19,12 @@ // DEALINGS IN THE SOFTWARE. use async_trait::async_trait; -use futures::StreamExt; +use futures::{ + channel::oneshot, + future::{BoxFuture, FutureExt, OptionFuture}, + stream::FuturesUnordered, + StreamExt, +}; use stun::{ attributes::ATTR_USERNAME, message::{is_message as is_stun_message, Message as STUNMessage}, @@ -30,10 +35,6 @@ use tokio_crate as tokio; use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc::util::{Conn, Error}; -use crate::req_res_chan; -use futures::channel::oneshot; -use futures::future::{BoxFuture, FutureExt, OptionFuture}; -use futures::stream::FuturesUnordered; use std::{ collections::{HashMap, HashSet}, io, @@ -43,6 +44,8 @@ use std::{ task::{Context, Poll}, }; +use crate::req_res_chan; + const RECEIVE_MTU: usize = 8192; /// A previously unseen address of a remote who've sent us an ICE binding request. @@ -184,6 +187,7 @@ impl UDPMuxNewAddr { /// muxed connection. pub fn poll(&mut self, cx: &mut Context) -> Poll { loop { + // => Send data to target match self.send_buffer.take() { None => { if let Poll::Ready(Some(((buf, target), response))) = @@ -206,6 +210,7 @@ impl UDPMuxNewAddr { } } + // => Register a new connection if let Poll::Ready(Some(((conn, addr), response))) = self.registration_command.poll_next(cx) { @@ -229,6 +234,7 @@ impl UDPMuxNewAddr { continue; } + // => Get connection with the given ufrag if let Poll::Ready(Some((ufrag, response))) = self.get_conn_command.poll_next(cx) { if self.is_closed { let _ = response.send(Err(Error::ErrUseClosedNetworkConn)); @@ -266,6 +272,7 @@ impl UDPMuxNewAddr { continue; } + // => Close UDPMux if let Poll::Ready(Some(((), response))) = self.close_command.poll_next(cx) { if self.is_closed { let _ = response.send(Err(Error::ErrAlreadyClosed)); @@ -291,6 +298,7 @@ impl UDPMuxNewAddr { continue; } + // => Remove connection with the given ufrag if let Poll::Ready(Some((ufrag, response))) = self.remove_conn_command.poll_next(cx) { // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` // is keyed on `ufrag` their implementation is equivalent. @@ -306,8 +314,10 @@ impl UDPMuxNewAddr { continue; } + // => Remove closed connections let _ = self.close_futures.poll_next_unpin(cx); + // => Write previously received data to local connections match self.write_future.poll_unpin(cx) { Poll::Ready(Some(())) => { self.write_future = OptionFuture::default(); @@ -402,6 +412,8 @@ impl UDPMuxNewAddr { } } +/// Handle which utilizes [`req_res_chan`] to transmit commands (e.g. remove connection) from the +/// WebRTC ICE agent to [`UDPMuxNewAddr::poll`]. pub struct UdpMuxHandle { close_sender: req_res_chan::Sender<(), Result<(), Error>>, get_conn_sender: req_res_chan::Sender, Error>>, @@ -409,6 +421,7 @@ pub struct UdpMuxHandle { } impl UdpMuxHandle { + /// Returns a new `UdpMuxHandle` and `close`, `get_conn` and `remove` receivers. pub fn new() -> ( Self, req_res_chan::Receiver<(), Result<(), Error>>, @@ -457,12 +470,15 @@ impl UDPMux for UdpMuxHandle { } } +/// Handle which utilizes [`req_res_chan`] to transmit commands from [`UDPMuxConn`] connections to +/// [`UDPMuxNewAddr::poll`]. pub struct UdpMuxWriterHandle { registration_channel: req_res_chan::Sender<(UDPMuxConn, SocketAddr), ()>, send_channel: req_res_chan::Sender<(Vec, SocketAddr), Result>, } impl UdpMuxWriterHandle { + /// Returns a new `UdpMuxWriterHandle` and `registration`, `send` receivers. fn new() -> ( Self, req_res_chan::Receiver<(UDPMuxConn, SocketAddr), ()>, diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index f99968089ed..10218184603 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -20,17 +20,12 @@ mod noise; -use crate::error::Error; -use crate::fingerprint::Fingerprint; -use crate::substream::Substream; -use crate::{sdp, Connection}; use futures::channel::oneshot; use futures::future::Either; use futures_timer::Delay; use libp2p_core::{identity, PeerId}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use std::{net::SocketAddr, sync::Arc, time::Duration}; use webrtc::api::setting_engine::SettingEngine; use webrtc::api::APIBuilder; use webrtc::data::data_channel::DataChannel; @@ -42,6 +37,13 @@ use webrtc::ice::udp_network::UDPNetwork; use webrtc::peer_connection::configuration::RTCConfiguration; use webrtc::peer_connection::RTCPeerConnection; +use std::{net::SocketAddr, sync::Arc, time::Duration}; + +use crate::error::Error; +use crate::fingerprint::Fingerprint; +use crate::substream::Substream; +use crate::{sdp, Connection}; + /// Creates a new outbound WebRTC connection. pub async fn outbound( addr: SocketAddr, From 84032f07e45f053f3ba81fc9999c2fa8e778117e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Oct 2022 11:31:10 +0400 Subject: [PATCH 202/244] do not allocate read buffer every time --- transports/webrtc/src/udp_mux.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 26c8d00946b..75f7b6edc75 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -186,6 +186,8 @@ impl UDPMuxNewAddr { /// Reads from the underlying UDP socket and either reports a new address or proxies data to the /// muxed connection. pub fn poll(&mut self, cx: &mut Context) -> Poll { + let mut recv_buf = [0u8; RECEIVE_MTU]; + loop { // => Send data to target match self.send_buffer.take() { @@ -324,8 +326,7 @@ impl UDPMuxNewAddr { continue; } Poll::Ready(None) => { - // TODO: avoid allocating the buffer each time. - let mut recv_buf = [0u8; RECEIVE_MTU]; + // => Read from the socket let mut read = ReadBuf::new(&mut recv_buf); match self.udp_sock.poll_recv_from(cx, &mut read) { From 3d3951f9afa4987bc461b3c7f33cc523e0175e99 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Oct 2022 16:27:03 +0400 Subject: [PATCH 203/244] remove default-features flag from libp2p-core Co-authored-by: Thomas Eizinger --- transports/webrtc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 5872a676bb5..a8c5fb70256 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -19,7 +19,7 @@ futures-lite = "1" futures-timer = "3" hex = "0.4" if-watch = "2.0" -libp2p-core = { version = "0.37.0", path = "../../core", default-features = false } +libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-noise = { version = "0.40.0", path = "../../transports/noise" } log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } From 3fae66348500584a11e506fa00e20edd43bbe78b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Oct 2022 16:57:20 +0400 Subject: [PATCH 204/244] rename tokio-crate to tokio --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/transport.rs | 1 - transports/webrtc/src/udp_mux.rs | 1 - transports/webrtc/tests/smoke.rs | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index a8c5fb70256..83fcf855b6e 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -32,7 +32,7 @@ serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio-crate = { package = "tokio", version = "1.18", features = ["net"]} +tokio = { version = "1.18", features = ["net"]} tokio-util = { version = "0.7", features = ["compat"] } webrtc = "0.5.0" diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/transport.rs index 43ff87e6f11..1819ee46556 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/transport.rs @@ -444,7 +444,6 @@ mod tests { use futures::future::poll_fn; use libp2p_core::{multiaddr::Protocol, Transport as _}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - use tokio_crate as tokio; #[test] fn missing_webrtc_protocol() { diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/udp_mux.rs index 75f7b6edc75..837eef8a830 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/udp_mux.rs @@ -31,7 +31,6 @@ use stun::{ }; use thiserror::Error; use tokio::{io::ReadBuf, net::UdpSocket}; -use tokio_crate as tokio; use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; use webrtc::util::{Conn, Error}; diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index ed18e94c307..68a4049bbb3 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -13,7 +13,6 @@ use libp2p::request_response::{ use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p::webrtc; use rand::RngCore; -use tokio_crate as tokio; use std::{io, iter}; From 05a9b0030e5cc815b3a2221ec76c971b764b118b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Oct 2022 16:57:36 +0400 Subject: [PATCH 205/244] set noise data channel label to `noise` --- transports/webrtc/src/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/upgrade.rs index 10218184603..708fdbfc3e6 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/upgrade.rs @@ -208,7 +208,7 @@ async fn create_substream_for_noise_handshake( // Open a data channel to do Noise on top and verify the remote. let data_channel = conn .create_data_channel( - "data", + "noise", Some(RTCDataChannelInit { negotiated: Some(0), ..RTCDataChannelInit::default() From f7c8ab5823bc9c2032b2ea0399d66435c8760904 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Oct 2022 15:36:28 +0400 Subject: [PATCH 206/244] move transport into `tokio` dir and feature gate it --- transports/webrtc/Cargo.toml | 10 ++++-- transports/webrtc/src/lib.rs | 14 ++------ .../webrtc/src/{ => tokio}/connection.rs | 2 +- transports/webrtc/src/{ => tokio}/error.rs | 0 .../webrtc/src/{ => tokio}/fingerprint.rs | 3 +- transports/webrtc/src/tokio/mod.rs | 33 +++++++++++++++++++ .../webrtc/src/{ => tokio}/req_res_chan.rs | 1 + transports/webrtc/src/{ => tokio}/sdp.rs | 7 ++-- .../webrtc/src/{ => tokio}/substream.rs | 8 +++-- .../{ => tokio}/substream/drop_listener.rs | 6 ++-- .../src/{ => tokio}/substream/framed_dc.rs | 8 +++-- .../webrtc/src/{ => tokio}/substream/state.rs | 8 +++-- .../webrtc/src/{ => tokio}/transport.rs | 4 +-- transports/webrtc/src/{ => tokio}/udp_mux.rs | 2 +- transports/webrtc/src/{ => tokio}/upgrade.rs | 7 ++-- .../webrtc/src/{ => tokio}/upgrade/noise.rs | 5 +-- transports/webrtc/tests/smoke.rs | 2 +- 17 files changed, 77 insertions(+), 43 deletions(-) rename transports/webrtc/src/{ => tokio}/connection.rs (99%) rename transports/webrtc/src/{ => tokio}/error.rs (100%) rename transports/webrtc/src/{ => tokio}/fingerprint.rs (99%) create mode 100644 transports/webrtc/src/tokio/mod.rs rename transports/webrtc/src/{ => tokio}/req_res_chan.rs (99%) rename transports/webrtc/src/{ => tokio}/sdp.rs (99%) rename transports/webrtc/src/{ => tokio}/substream.rs (98%) rename transports/webrtc/src/{ => tokio}/substream/drop_listener.rs (98%) rename transports/webrtc/src/{ => tokio}/substream/framed_dc.rs (94%) rename transports/webrtc/src/{ => tokio}/substream/state.rs (98%) rename transports/webrtc/src/{ => tokio}/transport.rs (99%) rename transports/webrtc/src/{ => tokio}/udp_mux.rs (99%) rename transports/webrtc/src/{ => tokio}/upgrade.rs (97%) rename transports/webrtc/src/{ => tokio}/upgrade/noise.rs (97%) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 83fcf855b6e..319c1d074c8 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -32,9 +32,13 @@ serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" -tokio = { version = "1.18", features = ["net"]} -tokio-util = { version = "0.7", features = ["compat"] } -webrtc = "0.5.0" + +tokio = { version = "1.18", features = ["net"], optional = true} +tokio-util = { version = "0.7", features = ["compat"], optional = true } +webrtc = { version = "0.5.0", optional = true } + +[features] +tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc"] [build-dependencies] prost-build = "0.11" diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index a40534e1090..3210ad38cac 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -80,21 +80,11 @@ //! is to make the hash a part of the remote's multiaddr. On the server side, we turn //! certificate verification off. -mod connection; -mod error; -mod fingerprint; -mod req_res_chan; -mod sdp; -mod substream; -mod transport; -mod udp_mux; -mod upgrade; mod message_proto { #![allow(clippy::derive_partial_eq_without_eq)] include!(concat!(env!("OUT_DIR"), "/webrtc.pb.rs")); } -pub use connection::Connection; -pub use error::Error; -pub use transport::Transport; +#[cfg(feature = "tokio")] +pub mod tokio; diff --git a/transports/webrtc/src/connection.rs b/transports/webrtc/src/tokio/connection.rs similarity index 99% rename from transports/webrtc/src/connection.rs rename to transports/webrtc/src/tokio/connection.rs index b67ab6e0aee..11de1b87851 100644 --- a/transports/webrtc/src/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -40,7 +40,7 @@ use std::{ task::{Context, Poll}, }; -use crate::{error::Error, substream, substream::Substream}; +use crate::tokio::{error::Error, substream, substream::Substream}; /// Maximum number of unprocessed data channels. /// See [`Connection::poll_inbound`]. diff --git a/transports/webrtc/src/error.rs b/transports/webrtc/src/tokio/error.rs similarity index 100% rename from transports/webrtc/src/error.rs rename to transports/webrtc/src/tokio/error.rs diff --git a/transports/webrtc/src/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs similarity index 99% rename from transports/webrtc/src/fingerprint.rs rename to transports/webrtc/src/tokio/fingerprint.rs index c9ba7314db4..1856054830b 100644 --- a/transports/webrtc/src/fingerprint.rs +++ b/transports/webrtc/src/tokio/fingerprint.rs @@ -20,9 +20,10 @@ use multibase::Base; use multihash::{Code, Hasher, Multihash, MultihashDigest}; -use std::fmt; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; +use std::fmt; + const SHA256: &str = "sha-256"; /// A certificate fingerprint that is assumed to be created using the SHA256 hash algorithm. diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs new file mode 100644 index 00000000000..348b9959dbd --- /dev/null +++ b/transports/webrtc/src/tokio/mod.rs @@ -0,0 +1,33 @@ +// Copyright 2022 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. + +mod connection; +mod error; +mod fingerprint; +mod req_res_chan; +mod sdp; +mod substream; +mod transport; +mod udp_mux; +mod upgrade; + +pub use connection::Connection; +pub use error::Error; +pub use transport::Transport; diff --git a/transports/webrtc/src/req_res_chan.rs b/transports/webrtc/src/tokio/req_res_chan.rs similarity index 99% rename from transports/webrtc/src/req_res_chan.rs rename to transports/webrtc/src/tokio/req_res_chan.rs index 0db5a84fbb4..46e49b6f2fc 100644 --- a/transports/webrtc/src/req_res_chan.rs +++ b/transports/webrtc/src/tokio/req_res_chan.rs @@ -3,6 +3,7 @@ use futures::{ SinkExt, }; use futures_lite::StreamExt; + use std::{ io, task::{Context, Poll}, diff --git a/transports/webrtc/src/sdp.rs b/transports/webrtc/src/tokio/sdp.rs similarity index 99% rename from transports/webrtc/src/sdp.rs rename to transports/webrtc/src/tokio/sdp.rs index db4306d72f8..79f3c0b5a86 100644 --- a/transports/webrtc/src/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -19,13 +19,12 @@ // DEALINGS IN THE SOFTWARE. use serde::Serialize; -use std::net::SocketAddr; use tinytemplate::TinyTemplate; - -use std::net::IpAddr; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; -use crate::fingerprint::Fingerprint; +use std::net::{IpAddr, SocketAddr}; + +use crate::tokio::fingerprint::Fingerprint; /// Creates the SDP answer used by the client. pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint) -> RTCSessionDescription { diff --git a/transports/webrtc/src/substream.rs b/transports/webrtc/src/tokio/substream.rs similarity index 98% rename from transports/webrtc/src/substream.rs rename to transports/webrtc/src/tokio/substream.rs index 48c35b3e878..2a8f420d9e2 100644 --- a/transports/webrtc/src/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -32,9 +32,11 @@ use std::{ }; use crate::message_proto::{message::Flag, Message}; -use crate::substream::drop_listener::GracefullyClosed; -use crate::substream::framed_dc::FramedDC; -use crate::substream::state::{Closing, State}; +use crate::tokio::{ + substream::drop_listener::GracefullyClosed, + substream::framed_dc::FramedDC, + substream::state::{Closing, State}, +}; mod drop_listener; mod framed_dc; diff --git a/transports/webrtc/src/substream/drop_listener.rs b/transports/webrtc/src/tokio/substream/drop_listener.rs similarity index 98% rename from transports/webrtc/src/substream/drop_listener.rs rename to transports/webrtc/src/tokio/substream/drop_listener.rs index 26fdc206641..8606b526e4e 100644 --- a/transports/webrtc/src/substream/drop_listener.rs +++ b/transports/webrtc/src/tokio/substream/drop_listener.rs @@ -1,13 +1,15 @@ -use crate::message_proto::{message::Flag, Message}; -use crate::substream::framed_dc::FramedDC; use futures::channel::oneshot; use futures::channel::oneshot::Canceled; use futures::{FutureExt, SinkExt}; + use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; +use crate::message_proto::{message::Flag, Message}; +use crate::tokio::substream::framed_dc::FramedDC; + #[must_use] pub struct DropListener { state: State, diff --git a/transports/webrtc/src/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs similarity index 94% rename from transports/webrtc/src/substream/framed_dc.rs rename to transports/webrtc/src/tokio/substream/framed_dc.rs index 63f985c1f4a..6579dcd5968 100644 --- a/transports/webrtc/src/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -1,11 +1,13 @@ -use crate::message_proto::Message; -use crate::substream::MAX_MSG_LEN; use asynchronous_codec::Framed; -use std::sync::Arc; use tokio_util::compat::Compat; use tokio_util::compat::TokioAsyncReadCompatExt; use webrtc::data::data_channel::{DataChannel, PollDataChannel}; +use std::sync::Arc; + +use crate::message_proto::Message; +use crate::tokio::substream::MAX_MSG_LEN; + pub type FramedDC = Framed, prost_codec::Codec>; pub fn new(data_channel: Arc) -> FramedDC { diff --git a/transports/webrtc/src/substream/state.rs b/transports/webrtc/src/tokio/substream/state.rs similarity index 98% rename from transports/webrtc/src/substream/state.rs rename to transports/webrtc/src/tokio/substream/state.rs index 5baf219b364..41560c76750 100644 --- a/transports/webrtc/src/substream/state.rs +++ b/transports/webrtc/src/tokio/substream/state.rs @@ -1,7 +1,9 @@ -use crate::message_proto::message::Flag; use bytes::Bytes; + use std::io; +use crate::message_proto::message::Flag; + #[derive(Debug, Copy, Clone)] pub enum State { Open, @@ -167,7 +169,7 @@ impl State { /// Acts as a "barrier" for [`futures::AsyncRead::poll_read`]. pub(crate) fn read_barrier(&self) -> io::Result<()> { - use crate::substream::State::{Open, ReadClosed, WriteClosed}; + use crate::tokio::substream::State::{Open, ReadClosed, WriteClosed}; use State::*; let kind = match self { @@ -190,7 +192,7 @@ impl State { /// Acts as a "barrier" for [`futures::AsyncWrite::poll_write`]. pub(crate) fn write_barrier(&self) -> io::Result<()> { - use crate::substream::State::{Open, ReadClosed, WriteClosed}; + use crate::tokio::substream::State::{Open, ReadClosed, WriteClosed}; use State::*; let kind = match self { diff --git a/transports/webrtc/src/transport.rs b/transports/webrtc/src/tokio/transport.rs similarity index 99% rename from transports/webrtc/src/transport.rs rename to transports/webrtc/src/tokio/transport.rs index 1819ee46556..4176d04d0d2 100644 --- a/transports/webrtc/src/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -26,10 +26,10 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, PeerId, }; +use rand::distributions::DistString; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; -use rand::distributions::DistString; use std::net::IpAddr; use std::{ net::SocketAddr, @@ -37,7 +37,7 @@ use std::{ task::{Context, Poll}, }; -use crate::{ +use crate::tokio::{ connection::Connection, error::Error, fingerprint::Fingerprint, diff --git a/transports/webrtc/src/udp_mux.rs b/transports/webrtc/src/tokio/udp_mux.rs similarity index 99% rename from transports/webrtc/src/udp_mux.rs rename to transports/webrtc/src/tokio/udp_mux.rs index 837eef8a830..19d93ca840c 100644 --- a/transports/webrtc/src/udp_mux.rs +++ b/transports/webrtc/src/tokio/udp_mux.rs @@ -43,7 +43,7 @@ use std::{ task::{Context, Poll}, }; -use crate::req_res_chan; +use crate::tokio::req_res_chan; const RECEIVE_MTU: usize = 8192; diff --git a/transports/webrtc/src/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs similarity index 97% rename from transports/webrtc/src/upgrade.rs rename to transports/webrtc/src/tokio/upgrade.rs index 708fdbfc3e6..913552557ab 100644 --- a/transports/webrtc/src/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -39,10 +39,7 @@ use webrtc::peer_connection::RTCPeerConnection; use std::{net::SocketAddr, sync::Arc, time::Duration}; -use crate::error::Error; -use crate::fingerprint::Fingerprint; -use crate::substream::Substream; -use crate::{sdp, Connection}; +use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, substream::Substream, Connection}; /// Creates a new outbound WebRTC connection. pub async fn outbound( @@ -219,7 +216,7 @@ async fn create_substream_for_noise_handshake( let (tx, rx) = oneshot::channel::>(); // Wait until the data channel is opened and detach it. - crate::connection::register_data_channel_open_handler(data_channel, tx).await; + crate::tokio::connection::register_data_channel_open_handler(data_channel, tx).await; let channel = match futures::future::select(rx, Delay::new(Duration::from_secs(10))).await { Either::Left((Ok(channel), _)) => channel, diff --git a/transports/webrtc/src/upgrade/noise.rs b/transports/webrtc/src/tokio/upgrade/noise.rs similarity index 97% rename from transports/webrtc/src/upgrade/noise.rs rename to transports/webrtc/src/tokio/upgrade/noise.rs index 51f3a38e62a..5b9ae076bd0 100644 --- a/transports/webrtc/src/upgrade/noise.rs +++ b/transports/webrtc/src/tokio/upgrade/noise.rs @@ -1,9 +1,10 @@ -use crate::fingerprint::Fingerprint; -use crate::Error; use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; +use crate::tokio::fingerprint::Fingerprint; +use crate::tokio::Error; + pub async fn inbound( id_keys: identity::Keypair, stream: T, diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 68a4049bbb3..a7b577c1287 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -11,7 +11,7 @@ use libp2p::request_response::{ RequestResponseEvent, RequestResponseMessage, }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use libp2p::webrtc; +use libp2p::webrtc::tokio as webrtc; use rand::RngCore; use std::{io, iter}; From 1785a858b963448d60a826225b9368394dcc6787 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Oct 2022 16:25:25 +0400 Subject: [PATCH 207/244] enable tokio feature on libp2p-webrtc if global tokio feature is enabled --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ffb6a258d5b..ca74f7dbae2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,7 @@ rsa = ["libp2p-core/rsa"] ecdsa = ["libp2p-core/ecdsa"] serde = ["libp2p-core/serde", "libp2p-kad?/serde", "libp2p-gossipsub?/serde"] webrtc = ["dep:libp2p-webrtc"] -tokio = ["libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio"] +tokio = ["libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-webrtc?/tokio"] async-std = ["libp2p-mdns?/async-io", "libp2p-tcp?/async-io", "libp2p-dns?/async-std"] [package.metadata.docs.rs] From 8ddd225e26f6fd08a1ab16035eabf5b700cdffec Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Oct 2022 17:47:21 +0400 Subject: [PATCH 208/244] empty data channel labels --- transports/webrtc/src/tokio/connection.rs | 2 +- transports/webrtc/src/tokio/upgrade.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 11de1b87851..7b17892b3f2 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -214,7 +214,7 @@ impl StreamMuxer for Connection { let peer_conn = peer_conn.lock().await; // Create a datachannel with label 'data' - let data_channel = peer_conn.create_data_channel("data", None).await?; + let data_channel = peer_conn.create_data_channel("", None).await?; log::trace!("Opening outbound substream {}", data_channel.id()); diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 913552557ab..96ba309ff12 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -205,7 +205,7 @@ async fn create_substream_for_noise_handshake( // Open a data channel to do Noise on top and verify the remote. let data_channel = conn .create_data_channel( - "noise", + "", Some(RTCDataChannelInit { negotiated: Some(0), ..RTCDataChannelInit::default() From 63261317fbda5115708c7edd960b0b62f2c73b82 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 08:08:29 +0400 Subject: [PATCH 209/244] apply suggestions from @mxinden Co-authored-by: Max Inden --- transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/tokio/connection.rs | 2 +- transports/webrtc/src/tokio/error.rs | 2 +- transports/webrtc/src/tokio/udp_mux.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 319c1d074c8..dfaeb85dcf5 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-webrtc" -version = "0.1.0" +version = "0.1.0-alpha" authors = ["Parity Technologies "] description = "WebRTC transport for libp2p" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 7b17892b3f2..a222af223f5 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -48,7 +48,7 @@ const MAX_DATA_CHANNELS_IN_FLIGHT: usize = 10; /// A WebRTC connection, wrapping [`RTCPeerConnection`] and implementing [`StreamMuxer`] trait. pub struct Connection { - /// `RTCPeerConnection` to the remote peer. + /// [`RTCPeerConnection`] to the remote peer. /// /// Uses futures mutex because used in async code (see poll_outbound and poll_close). peer_conn: Arc>, diff --git a/transports/webrtc/src/tokio/error.rs b/transports/webrtc/src/tokio/error.rs index 7dc620ee1f5..f91011ffd46 100644 --- a/transports/webrtc/src/tokio/error.rs +++ b/transports/webrtc/src/tokio/error.rs @@ -35,7 +35,7 @@ pub enum Error { #[error("invalid peer ID (expected {expected}, got {got})")] InvalidPeerID { expected: PeerId, got: PeerId }, - #[error("no active listeners")] + #[error("no active listeners, can not dial without a previous listen")] NoListeners, #[error("UDP mux error: {0}")] diff --git a/transports/webrtc/src/tokio/udp_mux.rs b/transports/webrtc/src/tokio/udp_mux.rs index 19d93ca840c..be11a09a008 100644 --- a/transports/webrtc/src/tokio/udp_mux.rs +++ b/transports/webrtc/src/tokio/udp_mux.rs @@ -47,7 +47,7 @@ use crate::tokio::req_res_chan; const RECEIVE_MTU: usize = 8192; -/// A previously unseen address of a remote who've sent us an ICE binding request. +/// A previously unseen address of a remote which has sent us an ICE binding request. #[derive(Debug)] pub struct NewAddr { pub addr: SocketAddr, From fe844276500970506543f9999460eeb1fbeaa187 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:14:54 +0400 Subject: [PATCH 210/244] bump prost-codec version --- misc/prost-codec/CHANGELOG.md | 6 ++++++ misc/prost-codec/Cargo.toml | 2 +- protocols/dcutr/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/relay/Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/misc/prost-codec/CHANGELOG.md b/misc/prost-codec/CHANGELOG.md index d9380ea34ca..d34e0ec2e2b 100644 --- a/misc/prost-codec/CHANGELOG.md +++ b/misc/prost-codec/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.3.0 + +- Implement `From` trait for `std::io::Error`. See [PR 2622]. + +[PR 2622]: https://github.com/libp2p/rust-libp2p/pull/2622/ + # 0.2.0 - Update to prost(-build) `v0.11`. See [PR 2788]. diff --git a/misc/prost-codec/Cargo.toml b/misc/prost-codec/Cargo.toml index f4d1e90f844..d5cee6cfd60 100644 --- a/misc/prost-codec/Cargo.toml +++ b/misc/prost-codec/Cargo.toml @@ -3,7 +3,7 @@ name = "prost-codec" edition = "2021" rust-version = "1.56.1" description = "Asynchronous de-/encoding of Protobuf structs using asynchronous-codec, unsigned-varint and prost." -version = "0.2.0" +version = "0.3.0" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/dcutr/Cargo.toml b/protocols/dcutr/Cargo.toml index d9cc5861bc5..d1e65c4c3b4 100644 --- a/protocols/dcutr/Cargo.toml +++ b/protocols/dcutr/Cargo.toml @@ -20,7 +20,7 @@ instant = "0.1.11" libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4" -prost-codec = { version = "0.2", path = "../../misc/prost-codec" } +prost-codec = { version = "0.3", path = "../../misc/prost-codec" } prost = "0.11" thiserror = "1.0" void = "1" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index c24c2e8a9c6..d4fb0b53e07 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -18,7 +18,7 @@ libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4.1" lru = "0.8.0" -prost-codec = { version = "0.2", path = "../../misc/prost-codec" } +prost-codec = { version = "0.3", path = "../../misc/prost-codec" } prost = "0.11" smallvec = "1.6.1" thiserror = "1.0" diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 19837380355..3c13a1d202d 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -21,7 +21,7 @@ libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4" pin-project = "1" -prost-codec = { version = "0.2", path = "../../misc/prost-codec" } +prost-codec = { version = "0.3", path = "../../misc/prost-codec" } prost = "0.11" rand = "0.8.4" smallvec = "1.6.1" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index dfaeb85dcf5..d795b818699 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -25,7 +25,7 @@ log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } multibase = "0.9" prost = "0.11" -prost-codec = { version = "0.2", path = "../../misc/prost-codec" } +prost-codec = { version = "0.3", path = "../../misc/prost-codec" } rand = "0.8" rcgen = "0.9.3" serde = { version = "1.0", features = ["derive"] } From 0810bedf017b8480cfca9ed2d5ce00e5c4ad3469 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:15:16 +0400 Subject: [PATCH 211/244] replace unwrap with expect --- transports/webrtc/src/tokio/fingerprint.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/tokio/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs index 1856054830b..d2862583378 100644 --- a/transports/webrtc/src/tokio/fingerprint.rs +++ b/transports/webrtc/src/tokio/fingerprint.rs @@ -75,14 +75,19 @@ impl Fingerprint { /// Converts this fingerprint to [`type@Multihash`]. pub fn to_multi_hash(self) -> Multihash { - Code::Sha2_256.wrap(&self.0).unwrap() + Code::Sha2_256 + .wrap(&self.0) + .expect("fingerprint's len to be 32 bytes") } /// Transforms this fingerprint into a ufrag. pub fn to_ufrag(self) -> String { multibase::encode( Base::Base64Url, - Code::Sha2_256.wrap(&self.0).unwrap().to_bytes(), + Code::Sha2_256 + .wrap(&self.0) + .expect("fingerprint's len to be 32 bytes") + .to_bytes(), ) } From 5429e55197eb55fd47aaf87f8e80b124198c2b56 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:15:45 +0400 Subject: [PATCH 212/244] add missing licences --- transports/webrtc/src/tokio/req_res_chan.rs | 20 +++++++++++++++++++ .../src/tokio/substream/drop_listener.rs | 20 +++++++++++++++++++ .../webrtc/src/tokio/substream/framed_dc.rs | 20 +++++++++++++++++++ .../webrtc/src/tokio/substream/state.rs | 20 +++++++++++++++++++ transports/webrtc/src/tokio/upgrade/noise.rs | 20 +++++++++++++++++++ transports/webrtc/tests/smoke.rs | 20 +++++++++++++++++++ 6 files changed, 120 insertions(+) diff --git a/transports/webrtc/src/tokio/req_res_chan.rs b/transports/webrtc/src/tokio/req_res_chan.rs index 46e49b6f2fc..a0c93c9452e 100644 --- a/transports/webrtc/src/tokio/req_res_chan.rs +++ b/transports/webrtc/src/tokio/req_res_chan.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 futures::{ channel::{mpsc, oneshot}, SinkExt, diff --git a/transports/webrtc/src/tokio/substream/drop_listener.rs b/transports/webrtc/src/tokio/substream/drop_listener.rs index 8606b526e4e..f099e844fff 100644 --- a/transports/webrtc/src/tokio/substream/drop_listener.rs +++ b/transports/webrtc/src/tokio/substream/drop_listener.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 futures::channel::oneshot; use futures::channel::oneshot::Canceled; use futures::{FutureExt, SinkExt}; diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index 6579dcd5968..ef15a8f071a 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 asynchronous_codec::Framed; use tokio_util::compat::Compat; use tokio_util::compat::TokioAsyncReadCompatExt; diff --git a/transports/webrtc/src/tokio/substream/state.rs b/transports/webrtc/src/tokio/substream/state.rs index 41560c76750..6a5340fb1f1 100644 --- a/transports/webrtc/src/tokio/substream/state.rs +++ b/transports/webrtc/src/tokio/substream/state.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 bytes::Bytes; use std::io; diff --git a/transports/webrtc/src/tokio/upgrade/noise.rs b/transports/webrtc/src/tokio/upgrade/noise.rs index 5b9ae076bd0..b87eccab2b4 100644 --- a/transports/webrtc/src/tokio/upgrade/noise.rs +++ b/transports/webrtc/src/tokio/upgrade/noise.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; use libp2p_noise::{Keypair, NoiseConfig, X25519Spec}; diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index a7b577c1287..145646ae1f8 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -1,3 +1,23 @@ +// Copyright 2022 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 anyhow::Result; use async_trait::async_trait; use futures::{ From 82edf3fdda20cc041876fc8afd03c9bf5b92e872 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:16:09 +0400 Subject: [PATCH 213/244] rename FramedDC to FramedDc and listen_multi_address fn --- transports/webrtc/src/tokio/substream.rs | 4 ++-- transports/webrtc/src/tokio/substream/drop_listener.rs | 10 +++++----- transports/webrtc/src/tokio/substream/framed_dc.rs | 4 ++-- transports/webrtc/src/tokio/transport.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 2a8f420d9e2..75f7a90d460 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -34,7 +34,7 @@ use std::{ use crate::message_proto::{message::Flag, Message}; use crate::tokio::{ substream::drop_listener::GracefullyClosed, - substream::framed_dc::FramedDC, + substream::framed_dc::FramedDc, substream::state::{Closing, State}, }; @@ -62,7 +62,7 @@ pub use drop_listener::DropListener; /// To be a proper libp2p substream, we need to implement [`AsyncRead`] and [`AsyncWrite`] as well /// as support a half-closed state which we do by framing messages in a protobuf envelope. pub struct Substream { - io: FramedDC, + io: FramedDc, state: State, read_buffer: Bytes, /// Dropping this will close the oneshot and notify the receiver by emitting `Canceled`. diff --git a/transports/webrtc/src/tokio/substream/drop_listener.rs b/transports/webrtc/src/tokio/substream/drop_listener.rs index f099e844fff..892d9c876a0 100644 --- a/transports/webrtc/src/tokio/substream/drop_listener.rs +++ b/transports/webrtc/src/tokio/substream/drop_listener.rs @@ -28,7 +28,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use crate::message_proto::{message::Flag, Message}; -use crate::tokio::substream::framed_dc::FramedDC; +use crate::tokio::substream::framed_dc::FramedDc; #[must_use] pub struct DropListener { @@ -36,7 +36,7 @@ pub struct DropListener { } impl DropListener { - pub fn new(stream: FramedDC, receiver: oneshot::Receiver) -> Self { + pub fn new(stream: FramedDc, receiver: oneshot::Receiver) -> Self { let substream_id = stream.get_ref().stream_identifier(); Self { @@ -52,16 +52,16 @@ impl DropListener { enum State { /// The [`DropListener`] is idle and waiting to be activated. Idle { - stream: FramedDC, + stream: FramedDc, receiver: oneshot::Receiver, substream_id: u16, }, /// The stream got dropped and we are sending a reset flag. SendingReset { - stream: FramedDC, + stream: FramedDc, }, Flushing { - stream: FramedDC, + stream: FramedDc, }, /// Bad state transition. Poisoned, diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index ef15a8f071a..b62200a2de8 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -28,9 +28,9 @@ use std::sync::Arc; use crate::message_proto::Message; use crate::tokio::substream::MAX_MSG_LEN; -pub type FramedDC = Framed, prost_codec::Codec>; +pub type FramedDc = Framed, prost_codec::Codec>; -pub fn new(data_channel: Arc) -> FramedDC { +pub fn new(data_channel: Arc) -> FramedDc { let mut inner = PollDataChannel::new(data_channel); // TODO: default buffer size is too small to fit some messages. Possibly remove once diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 4176d04d0d2..65232ba80ce 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -248,7 +248,7 @@ impl ListenStream { { return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, - listen_addr: self.listen_multi_address(ip), + listen_addr: self.listen_multiaddress(ip), }); } } @@ -259,7 +259,7 @@ impl ListenStream { { return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, - listen_addr: self.listen_multi_address(ip), + listen_addr: self.listen_multiaddress(ip), }); } } @@ -276,7 +276,7 @@ impl ListenStream { } /// Constructs a [`Multiaddr`] for the given IP address that represents our listen address. - fn listen_multi_address(&self, ip: IpAddr) -> Multiaddr { + fn listen_multiaddress(&self, ip: IpAddr) -> Multiaddr { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)) From 7af1e80899c6ae7f29e29908e2b2ec07e01bf70a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:16:32 +0400 Subject: [PATCH 214/244] remove obsolete comment --- transports/webrtc/src/tokio/udp_mux.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc/src/tokio/udp_mux.rs b/transports/webrtc/src/tokio/udp_mux.rs index be11a09a008..eeb4c509691 100644 --- a/transports/webrtc/src/tokio/udp_mux.rs +++ b/transports/webrtc/src/tokio/udp_mux.rs @@ -101,7 +101,6 @@ pub struct UDPMuxNewAddr { impl UDPMuxNewAddr { pub fn listen_on(addr: SocketAddr) -> Result { - // XXX: `UdpSocket::bind` is async, so use a std socket and convert let std_sock = std::net::UdpSocket::bind(addr)?; std_sock.set_nonblocking(true)?; From 7eb35f258b0e108b71e31616c050e726eecd043f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:35:49 +0400 Subject: [PATCH 215/244] specify correct version of libp2p-webrtc --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ca74f7dbae2..31c82fe1198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ libp2p-swarm = { version = "0.40.1", path = "swarm" } libp2p-swarm-derive = { version = "0.30.1", path = "swarm-derive" } libp2p-uds = { version = "0.36.0", path = "transports/uds", optional = true } libp2p-wasm-ext = { version = "0.37.0", path = "transports/wasm-ext", optional = true } -libp2p-webrtc = { version = "0.1.0", path = "transports/webrtc", optional = true } +libp2p-webrtc = { version = "0.1.0-alpha", path = "transports/webrtc", optional = true } libp2p-yamux = { version = "0.41.0", path = "muxers/yamux", optional = true } multiaddr = { version = "0.15.0" } parking_lot = "0.12.0" From 532b41025924b7e6551e21ceb566df0073c8d0e3 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 12:36:16 +0400 Subject: [PATCH 216/244] remove duplicated check https://github.com/libp2p/rust-libp2p/blob/4d4833f8c2622018b5b775896905a534b8624d3c/swarm/src/connection/pool.rs#L666-L680 --- transports/webrtc/src/tokio/transport.rs | 34 +++++++----------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 65232ba80ce..b9aa35b8b34 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -112,7 +112,7 @@ impl libp2p_core::Transport for Transport { } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (sock_addr, server_fingerprint, expected_peer_id) = parse_webrtc_dial_addr(&addr) + let (sock_addr, server_fingerprint) = parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); @@ -130,7 +130,7 @@ impl libp2p_core::Transport for Transport { .udp_mux_handle(); Ok(async move { - let (actual_peer_id, connection) = upgrade::outbound( + let (peer_id, connection) = upgrade::outbound( sock_addr, config.inner, udp_mux, @@ -140,14 +140,7 @@ impl libp2p_core::Transport for Transport { ) .await?; - if actual_peer_id != expected_peer_id { - return Err(Error::InvalidPeerID { - expected: expected_peer_id, - got: actual_peer_id, - }); - } - - Ok((actual_peer_id, connection)) + Ok((peer_id, connection)) } .boxed()) } @@ -400,7 +393,7 @@ fn parse_webrtc_listen_addr(addr: &Multiaddr) -> Option { } /// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. -fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, PeerId)> { +fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { let mut iter = addr.iter(); let ip = match iter.next()? { @@ -414,17 +407,16 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, let certhash = iter.next()?; let p2p = iter.next()?; - let (port, fingerprint, peer_id) = match (port, webrtc, certhash, p2p) { + let (port, fingerprint) = match (port, webrtc, certhash, p2p) { ( Protocol::Udp(port), Protocol::WebRTC, Protocol::Certhash(cert_hash), - Protocol::P2p(peer_hash), + Protocol::P2p(_), ) => { let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; - let peer_id = PeerId::from_multihash(peer_hash).ok()?; - (port, fingerprint, peer_id) + (port, fingerprint) } _ => return None, }; @@ -433,7 +425,7 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint, return None; } - Some((SocketAddr::new(ip, port), fingerprint, peer_id)) + Some((SocketAddr::new(ip, port), fingerprint)) } // Tests ////////////////////////////////////////////////////////////////////////////////////////// @@ -468,10 +460,7 @@ mod tests { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), Fingerprint::raw(hex_literal::hex!( "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )), - "12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" - .parse::() - .unwrap() + )) )) ); } @@ -513,10 +502,7 @@ mod tests { SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 12345), Fingerprint::raw(hex_literal::hex!( "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" - )), - "12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" - .parse::() - .unwrap() + )) )) ); } From 9238efc65369c082c46df4d136399cddd4289e3a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 17:16:10 +0400 Subject: [PATCH 217/244] don't do framing during noise handshake --- transports/webrtc/src/tokio/mod.rs | 1 + .../webrtc/src/tokio/noise_substream.rs | 82 +++++++++++++++++++ transports/webrtc/src/tokio/substream.rs | 2 +- transports/webrtc/src/tokio/upgrade.rs | 11 ++- 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 transports/webrtc/src/tokio/noise_substream.rs diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index 348b9959dbd..a09ec687230 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -21,6 +21,7 @@ mod connection; mod error; mod fingerprint; +mod noise_substream; mod req_res_chan; mod sdp; mod substream; diff --git a/transports/webrtc/src/tokio/noise_substream.rs b/transports/webrtc/src/tokio/noise_substream.rs new file mode 100644 index 00000000000..e5db05e272e --- /dev/null +++ b/transports/webrtc/src/tokio/noise_substream.rs @@ -0,0 +1,82 @@ +// Copyright 2022 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 futures::prelude::*; +use webrtc::data::data_channel::{DataChannel, PollDataChannel}; + +use std::{ + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +/// A substream on top of a WebRTC data channel without framing (see [`Substream`]). +#[derive(Debug)] +pub struct NoiseSubstream(PollDataChannel); + +impl NoiseSubstream { + /// Constructs a new [`NoiseSubstream`]. + pub fn new(data_channel: Arc) -> Self { + Self(PollDataChannel::new(data_channel)) + } +} + +impl AsyncRead for NoiseSubstream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut read_buf = tokio::io::ReadBuf::new(buf); + futures::ready!(tokio::io::AsyncRead::poll_read( + Pin::new(&mut self.0), + cx, + &mut read_buf + ))?; + Poll::Ready(Ok(read_buf.filled().len())) + } +} + +impl AsyncWrite for NoiseSubstream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + tokio::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs) + } +} diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 75f7a90d460..9db926b90ad 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -70,7 +70,7 @@ pub struct Substream { } impl Substream { - /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream + /// Returns a new [`Substream`] and a listener, which will notify the receiver when/if the substream /// is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 96ba309ff12..40459a27cd2 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -39,7 +39,9 @@ use webrtc::peer_connection::RTCPeerConnection; use std::{net::SocketAddr, sync::Arc, time::Duration}; -use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, substream::Substream, Connection}; +use crate::tokio::{ + error::Error, fingerprint::Fingerprint, noise_substream::NoiseSubstream, sdp, Connection, +}; /// Creates a new outbound WebRTC connection. pub async fn outbound( @@ -201,7 +203,7 @@ async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { async fn create_substream_for_noise_handshake( conn: &RTCPeerConnection, -) -> Result { +) -> Result { // Open a data channel to do Noise on top and verify the remote. let data_channel = conn .create_data_channel( @@ -230,8 +232,5 @@ async fn create_substream_for_noise_handshake( } }; - let (substream, drop_listener) = Substream::new(channel); - drop(drop_listener); // Don't care about cancelled substreams during initial handshake. - - Ok(substream) + Ok(NoiseSubstream::new(channel)) } From 4d1cb7cdbb365d9ccdd0c1e57cc1a383ee78c365 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Oct 2022 22:35:21 +0400 Subject: [PATCH 218/244] Revert "don't do framing during noise handshake" This reverts commit 9238efc65369c082c46df4d136399cddd4289e3a. --- transports/webrtc/src/tokio/mod.rs | 1 - .../webrtc/src/tokio/noise_substream.rs | 82 ------------------- transports/webrtc/src/tokio/substream.rs | 2 +- transports/webrtc/src/tokio/upgrade.rs | 11 +-- 4 files changed, 7 insertions(+), 89 deletions(-) delete mode 100644 transports/webrtc/src/tokio/noise_substream.rs diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index a09ec687230..348b9959dbd 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -21,7 +21,6 @@ mod connection; mod error; mod fingerprint; -mod noise_substream; mod req_res_chan; mod sdp; mod substream; diff --git a/transports/webrtc/src/tokio/noise_substream.rs b/transports/webrtc/src/tokio/noise_substream.rs deleted file mode 100644 index e5db05e272e..00000000000 --- a/transports/webrtc/src/tokio/noise_substream.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 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 futures::prelude::*; -use webrtc::data::data_channel::{DataChannel, PollDataChannel}; - -use std::{ - io, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -/// A substream on top of a WebRTC data channel without framing (see [`Substream`]). -#[derive(Debug)] -pub struct NoiseSubstream(PollDataChannel); - -impl NoiseSubstream { - /// Constructs a new [`NoiseSubstream`]. - pub fn new(data_channel: Arc) -> Self { - Self(PollDataChannel::new(data_channel)) - } -} - -impl AsyncRead for NoiseSubstream { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - let mut read_buf = tokio::io::ReadBuf::new(buf); - futures::ready!(tokio::io::AsyncRead::poll_read( - Pin::new(&mut self.0), - cx, - &mut read_buf - ))?; - Poll::Ready(Ok(read_buf.filled().len())) - } -} - -impl AsyncWrite for NoiseSubstream { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx) - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx) - } - - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - tokio::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs) - } -} diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 9db926b90ad..75f7a90d460 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -70,7 +70,7 @@ pub struct Substream { } impl Substream { - /// Returns a new [`Substream`] and a listener, which will notify the receiver when/if the substream + /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream /// is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 40459a27cd2..96ba309ff12 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -39,9 +39,7 @@ use webrtc::peer_connection::RTCPeerConnection; use std::{net::SocketAddr, sync::Arc, time::Duration}; -use crate::tokio::{ - error::Error, fingerprint::Fingerprint, noise_substream::NoiseSubstream, sdp, Connection, -}; +use crate::tokio::{error::Error, fingerprint::Fingerprint, sdp, substream::Substream, Connection}; /// Creates a new outbound WebRTC connection. pub async fn outbound( @@ -203,7 +201,7 @@ async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { async fn create_substream_for_noise_handshake( conn: &RTCPeerConnection, -) -> Result { +) -> Result { // Open a data channel to do Noise on top and verify the remote. let data_channel = conn .create_data_channel( @@ -232,5 +230,8 @@ async fn create_substream_for_noise_handshake( } }; - Ok(NoiseSubstream::new(channel)) + let (substream, drop_listener) = Substream::new(channel); + drop(drop_listener); // Don't care about cancelled substreams during initial handshake. + + Ok(substream) } From 33838721e88c900e1594e945afd6b775ebee2f73 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 19 Oct 2022 12:46:01 +0400 Subject: [PATCH 219/244] set max-message-size to 16384 to align it with the max frame size --- transports/webrtc/src/tokio/sdp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index 79f3c0b5a86..41e36cfd86d 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -136,7 +136,7 @@ a=ice-pwd:{pwd} a=fingerprint:{fingerprint_algorithm} {fingerprint_value} a=setup:actpass a=sctp-port:5000 -a=max-message-size:100000 +a=max-message-size:16384 "; // See [`CLIENT_SESSION_DESCRIPTION`]. @@ -189,7 +189,7 @@ a=fingerprint:{fingerprint_algorithm} {fingerprint_value} a=setup:passive a=sctp-port:5000 -a=max-message-size:100000 +a=max-message-size:16384 a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host "; From 93ea5d484b4bfb0a77232d9abed54691546c6777 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 19 Oct 2022 16:56:33 +0400 Subject: [PATCH 220/244] add end-of-candidates attribute to server's SDP --- transports/webrtc/src/tokio/sdp.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index 41e36cfd86d..d90b1977471 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -174,6 +174,10 @@ a=max-message-size:16384 // a=candidate: // // A transport address for a candidate that can be used for connectivity checks (RFC8839). +// +// a=end-of-candidates +// +// Indicate that no more candidates will ever be sent (RFC8838). const SERVER_SESSION_DESCRIPTION: &str = "v=0 o=- 0 0 IN {ip_version} {target_ip} s=- @@ -191,6 +195,7 @@ a=setup:passive a=sctp-port:5000 a=max-message-size:16384 a=candidate:1 1 UDP 1 {target_ip} {target_port} typ host +a=end-of-candidates "; /// Indicates the IP version used in WebRTC: `IP4` or `IP6`. From ce8e7426729aca3a6d19f4843311cc4d591ecd49 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 20 Oct 2022 11:52:35 +0400 Subject: [PATCH 221/244] make certificate an argument in `Transport::new` by analogue with `id_keys`. See https://github.com/paritytech/substrate/pull/12529#discussion_r999581906 Both peer's identity and certificate will need to be stored somewhere (disk) and reused across restarts. Since this is a dominant use-case, I've switched `Config` API to accept a certificate and opted out of generating one completely. --- transports/webrtc/src/tokio/transport.rs | 82 +++++++++++++++--------- transports/webrtc/tests/smoke.rs | 18 ++++-- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index b9aa35b8b34..7fe7be47de0 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -26,7 +26,6 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, PeerId, }; -use rand::distributions::DistString; use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; @@ -47,20 +46,37 @@ use crate::tokio::{ /// A WebRTC transport with direct p2p communication (without a STUN server). pub struct Transport { - /// The config which holds this peer's certificate(s). + /// The config which holds this peer's keys and certificate. config: Config, - /// `Keypair` identifying this peer - id_keys: identity::Keypair, /// All the active listeners. listeners: SelectAll, } impl Transport { /// Creates a new WebRTC transport. - pub fn new(id_keys: identity::Keypair) -> Self { + /// + /// # Example + /// + /// ``` + /// use libp2p_core::identity; + /// use webrtc::peer_connection::certificate::RTCCertificate; + /// use rand::distributions::DistString; + /// use libp2p_webrtc::tokio::Transport; + /// + /// let id_keys = identity::Keypair::generate_ed25519(); + /// let certificate = { + /// let mut params = rcgen::CertificateParams::new(vec![ + /// rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + /// ]); + /// params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + /// RTCCertificate::from_params(params).expect("default params to work") + /// }; + /// + /// let transport = Transport::new(id_keys, certificate); + /// ``` + pub fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self { Self { - config: Config::new(), - id_keys, + config: Config::new(id_keys, certificate), listeners: SelectAll::new(), } } @@ -84,7 +100,6 @@ impl libp2p_core::Transport for Transport { id, self.config.clone(), udp_mux, - self.id_keys.clone(), IfWatcher::new().map_err(|io| TransportError::Other(Error::Io(io)))?, )); @@ -120,7 +135,6 @@ impl libp2p_core::Transport for Transport { let config = self.config.clone(); let client_fingerprint = self.config.fingerprint; - let id_keys = self.id_keys.clone(); let udp_mux = self .listeners .iter() @@ -136,7 +150,7 @@ impl libp2p_core::Transport for Transport { udp_mux, client_fingerprint, server_fingerprint, - id_keys, + config.id_keys, ) .await?; @@ -177,9 +191,6 @@ struct ListenStream { /// The UDP muxer that manages all ICE connections. udp_mux: UDPMuxNewAddr, - /// `Keypair` identifying this peer - id_keys: identity::Keypair, - /// Set to `Some` if this listener should close. /// /// Optionally contains a [`TransportEvent::ListenerClosed`] that should be @@ -200,7 +211,6 @@ impl ListenStream { listener_id: ListenerId, config: Config, udp_mux: UDPMuxNewAddr, - id_keys: identity::Keypair, if_watcher: IfWatcher, ) -> Self { ListenStream { @@ -208,7 +218,6 @@ impl ListenStream { listen_addr: udp_mux.listen_addr(), config, udp_mux, - id_keys, report_closed: None, if_watcher, } @@ -305,7 +314,7 @@ impl Stream for ListenStream { self.udp_mux.udp_mux_handle(), self.config.fingerprint, new_addr.ufrag, - self.id_keys.clone(), + self.config.id_keys.clone(), ) .boxed(); @@ -324,31 +333,36 @@ impl Stream for ListenStream { } } +/// A config which holds peer's keys and a x509Cert used to authenticate WebRTC communications. #[derive(Clone)] struct Config { inner: RTCConfiguration, fingerprint: Fingerprint, + id_keys: identity::Keypair, } impl Config { - fn new() -> Self { - let mut params = rcgen::CertificateParams::new(vec![ - rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) - ]); - params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let certificate = RTCCertificate::from_params(params).expect("default params to work"); - - let fingerprints = certificate.get_fingerprints().expect("to never fail"); // TODO: Remove `Result` upstream? + /// Returns a new [`Config`] with the given keys and certificate. + /// + /// # Panics + /// + /// This function will panic if there's no fingerprint with the SHA-256 algorithm (see + /// [`RTCCertificate::get_fingerprints`]). + fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self { + let fingerprints = certificate.get_fingerprints().expect("to never fail"); + let sha256_fingerprint = fingerprints + .iter() + .find(|f| f.algorithm == "sha-256") + .expect("a SHA-256 fingerprint"); Self { + id_keys, inner: RTCConfiguration { certificates: vec![certificate], ..RTCConfiguration::default() }, - fingerprint: Fingerprint::try_from_rtc_dtls( - fingerprints.first().expect("at least one certificate"), - ) - .expect("we specified SHA-256"), + fingerprint: Fingerprint::try_from_rtc_dtls(sha256_fingerprint) + .expect("we specified SHA-256"), } } } @@ -435,6 +449,7 @@ mod tests { use super::*; use futures::future::poll_fn; use libp2p_core::{multiaddr::Protocol, Transport as _}; + use rand::distributions::DistString; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[test] @@ -535,7 +550,8 @@ mod tests { #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); - let mut transport = Transport::new(id_keys); + let certificate = generate_certificate(); + let mut transport = Transport::new(id_keys, certificate); assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) .now_or_never() @@ -584,4 +600,12 @@ mod tests { assert!(transport.listeners.is_empty()); } } + + fn generate_certificate() -> RTCCertificate { + let mut params = rcgen::CertificateParams::new(vec![ + rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + ]); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + RTCCertificate::from_params(params).expect("default params to work") + } } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 145646ae1f8..338fd98f36d 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use ::webrtc::peer_connection::certificate::RTCCertificate; use anyhow::Result; use async_trait::async_trait; use futures::{ @@ -32,6 +33,7 @@ use libp2p::request_response::{ }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p::webrtc::tokio as webrtc; +use rand::distributions::DistString; use rand::RngCore; use std::{io, iter}; @@ -468,9 +470,11 @@ impl RequestResponseCodec for PingCodec { } fn create_swarm() -> Result>> { - let keypair = generate_tls_keypair(); - let peer_id = keypair.public().to_peer_id(); - let transport = webrtc::Transport::new(keypair); + let id_keys = identity::Keypair::generate_ed25519(); + let peer_id = id_keys.public().to_peer_id(); + let certificate = generate_certificate(); + let transport = webrtc::Transport::new(id_keys, certificate); + let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let behaviour = RequestResponse::new(PingCodec(), protocols, cfg); @@ -485,6 +489,10 @@ fn create_swarm() -> Result>> { .build()) } -fn generate_tls_keypair() -> identity::Keypair { - identity::Keypair::generate_ed25519() +fn generate_certificate() -> RTCCertificate { + let mut params = rcgen::CertificateParams::new(vec![ + rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + ]); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + RTCCertificate::from_params(params).expect("default params to work") } From 03cbefd1a4c4851835032702abd7df7251f0a5ed Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:10:00 +0400 Subject: [PATCH 222/244] set `PollDataChannel` read buffer size to the frame max size https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r1002535038 --- transports/webrtc/src/tokio/substream/framed_dc.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index b62200a2de8..f09d67dcb72 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -32,10 +32,7 @@ pub type FramedDc = Framed, prost_codec::Codec> pub fn new(data_channel: Arc) -> FramedDc { let mut inner = PollDataChannel::new(data_channel); - - // TODO: default buffer size is too small to fit some messages. Possibly remove once - // https://github.com/webrtc-rs/webrtc/issues/273 is fixed. - inner.set_read_buf_capacity(8192 * 10); + inner.set_read_buf_capacity(16384); Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)) } From 61b00f961b7db8e97fd473319117287d0fabd107 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:42:57 +0400 Subject: [PATCH 223/244] remove pathed multiaddr --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31c82fe1198..a2137b988de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,10 +137,6 @@ libp2p-websocket = { version = "0.39.0", path = "transports/websocket", optional [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.42.1", path = "protocols/gossipsub", optional = true } -[patch.crates-io] -# TODO: remove once a new version of rust-multiaddr is released -multiaddr = { version = "0.15.0", git = "https://github.com/multiformats/rust-multiaddr.git", branch = "master" } - [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } async-trait = "0.1" From ba964f3b8c0996022c0ecb0c04523dd33cdfa26e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:43:50 +0400 Subject: [PATCH 224/244] bump prost-codec to 0.2.1 (not 0.3.0) --- misc/prost-codec/CHANGELOG.md | 2 +- misc/prost-codec/Cargo.toml | 2 +- protocols/dcutr/Cargo.toml | 2 +- protocols/identify/Cargo.toml | 2 +- protocols/relay/Cargo.toml | 2 +- transports/webrtc/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/misc/prost-codec/CHANGELOG.md b/misc/prost-codec/CHANGELOG.md index d34e0ec2e2b..99fbc5d0674 100644 --- a/misc/prost-codec/CHANGELOG.md +++ b/misc/prost-codec/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.3.0 +# 0.2.1 - Implement `From` trait for `std::io::Error`. See [PR 2622]. diff --git a/misc/prost-codec/Cargo.toml b/misc/prost-codec/Cargo.toml index d5cee6cfd60..cd0a769c763 100644 --- a/misc/prost-codec/Cargo.toml +++ b/misc/prost-codec/Cargo.toml @@ -3,7 +3,7 @@ name = "prost-codec" edition = "2021" rust-version = "1.56.1" description = "Asynchronous de-/encoding of Protobuf structs using asynchronous-codec, unsigned-varint and prost." -version = "0.3.0" +version = "0.2.1" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/dcutr/Cargo.toml b/protocols/dcutr/Cargo.toml index d1e65c4c3b4..d9cc5861bc5 100644 --- a/protocols/dcutr/Cargo.toml +++ b/protocols/dcutr/Cargo.toml @@ -20,7 +20,7 @@ instant = "0.1.11" libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4" -prost-codec = { version = "0.3", path = "../../misc/prost-codec" } +prost-codec = { version = "0.2", path = "../../misc/prost-codec" } prost = "0.11" thiserror = "1.0" void = "1" diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index d4fb0b53e07..c24c2e8a9c6 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -18,7 +18,7 @@ libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4.1" lru = "0.8.0" -prost-codec = { version = "0.3", path = "../../misc/prost-codec" } +prost-codec = { version = "0.2", path = "../../misc/prost-codec" } prost = "0.11" smallvec = "1.6.1" thiserror = "1.0" diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 3c13a1d202d..19837380355 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -21,7 +21,7 @@ libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-swarm = { version = "0.40.0", path = "../../swarm" } log = "0.4" pin-project = "1" -prost-codec = { version = "0.3", path = "../../misc/prost-codec" } +prost-codec = { version = "0.2", path = "../../misc/prost-codec" } prost = "0.11" rand = "0.8.4" smallvec = "1.6.1" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index d795b818699..940cfc505e8 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -25,7 +25,7 @@ log = "0.4" multihash = { version = "0.16", default-features = false, features = ["sha2"] } multibase = "0.9" prost = "0.11" -prost-codec = { version = "0.3", path = "../../misc/prost-codec" } +prost-codec = { version = "0.2.1", path = "../../misc/prost-codec" } rand = "0.8" rcgen = "0.9.3" serde = { version = "1.0", features = ["derive"] } From f5757b99e4cc67d9a9e0e8d131402cafc1e6907e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:44:21 +0400 Subject: [PATCH 225/244] remove unused deps move rcgen to dev deps --- transports/webrtc/Cargo.toml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 940cfc505e8..fd30cef8a09 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -10,10 +10,9 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -asynchronous-codec = "0.6" async-trait = "0.1" +asynchronous-codec = "0.6" bytes = "1" -fnv = "1.0" futures = "0.3" futures-lite = "1" futures-timer = "3" @@ -22,17 +21,15 @@ if-watch = "2.0" libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-noise = { version = "0.40.0", path = "../../transports/noise" } log = "0.4" -multihash = { version = "0.16", default-features = false, features = ["sha2"] } multibase = "0.9" +multihash = { version = "0.16", default-features = false, features = ["sha2"] } prost = "0.11" prost-codec = { version = "0.2.1", path = "../../misc/prost-codec" } rand = "0.8" -rcgen = "0.9.3" serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" tinytemplate = "1.2" - tokio = { version = "1.18", features = ["net"], optional = true} tokio-util = { version = "0.7", features = ["compat"], optional = true } webrtc = { version = "0.5.0", optional = true } @@ -48,7 +45,5 @@ anyhow = "1.0" env_logger = "0.9" hex-literal = "0.3" libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } -multihash = { version = "0.16", default-features = false, features = ["sha3"] } -quickcheck = "1" -rand_core = "0.5" +rcgen = "0.9.3" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } From 0e9fcf2a23b25f2705ae3e8df55e797471549e6b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:44:51 +0400 Subject: [PATCH 226/244] release `peer_conn` lock as soon as possible --- transports/webrtc/src/tokio/connection.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index a222af223f5..656da1475bb 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -213,14 +213,13 @@ impl StreamMuxer for Connection { let fut = self.outbound_fut.get_or_insert(Box::pin(async move { let peer_conn = peer_conn.lock().await; - // Create a datachannel with label 'data' let data_channel = peer_conn.create_data_channel("", None).await?; - log::trace!("Opening outbound substream {}", data_channel.id()); - // No need to hold the lock during the DTLS handshake. drop(peer_conn); + log::trace!("Opening outbound substream {}", data_channel.id()); + let (tx, rx) = oneshot::channel::>(); // Wait until the data channel is opened and detach it. From c179ecdc92fc8d0e8bcaad96e2c72623426d5cff Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:48:10 +0400 Subject: [PATCH 227/244] update comment when creating noise stream --- transports/webrtc/src/tokio/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 96ba309ff12..8c3606b7b82 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -202,12 +202,12 @@ async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint { async fn create_substream_for_noise_handshake( conn: &RTCPeerConnection, ) -> Result { - // Open a data channel to do Noise on top and verify the remote. + // NOTE: the data channel w/ `negotiated` flag set to `true` MUST be created on both ends. let data_channel = conn .create_data_channel( "", Some(RTCDataChannelInit { - negotiated: Some(0), + negotiated: Some(0), // 0 is reserved for the Noise substream ..RTCDataChannelInit::default() }), ) From 7d9d53139ec553ffe2f58d9a0fdff7beffc6c1ec Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 11:52:13 +0400 Subject: [PATCH 228/244] remove TODO from poll_flush looks like we don't need to check `self.state.write_barrier()?`. The assumption is that callers / implementation won't issue poll_flush calls after having closed the writing side of a stream. --- transports/webrtc/src/tokio/substream.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transports/webrtc/src/tokio/substream.rs b/transports/webrtc/src/tokio/substream.rs index 75f7a90d460..8622719a37c 100644 --- a/transports/webrtc/src/tokio/substream.rs +++ b/transports/webrtc/src/tokio/substream.rs @@ -202,7 +202,6 @@ impl AsyncWrite for Substream { } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // TODO: Double check that we don't have to depend on self.state here. self.io.poll_flush_unpin(cx).map_err(Into::into) } From c1e4297642b17f20ba208f8631b5fc161b4ccdf7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 16:49:31 +0400 Subject: [PATCH 229/244] make peer ID optional https://github.com/libp2p/specs/commit/b8dc6fdac7079c1beddf7e99382aea147e409bb3 --- transports/webrtc/src/tokio/transport.rs | 39 +++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 7fe7be47de0..2a795c2de61 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -419,15 +419,9 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> let port = iter.next()?; let webrtc = iter.next()?; let certhash = iter.next()?; - let p2p = iter.next()?; - - let (port, fingerprint) = match (port, webrtc, certhash, p2p) { - ( - Protocol::Udp(port), - Protocol::WebRTC, - Protocol::Certhash(cert_hash), - Protocol::P2p(_), - ) => { + + let (port, fingerprint) = match (port, webrtc, certhash) { + (Protocol::Udp(port), Protocol::WebRTC, Protocol::Certhash(cert_hash)) => { let fingerprint = Fingerprint::try_from_multihash(cert_hash)?; (port, fingerprint) @@ -435,8 +429,12 @@ fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> _ => return None, }; - if iter.next().is_some() { - return None; + match iter.next() { + Some(Protocol::P2p(_)) => {} + // peer ID is optional + None => {} + // unexpected protocol + Some(_) => return None, } Some((SocketAddr::new(ip, port), fingerprint)) @@ -480,6 +478,25 @@ mod tests { ); } + #[test] + fn peer_id_is_not_required() { + let addr = "/ip4/127.0.0.1/udp/39901/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" + .parse() + .unwrap(); + + let maybe_parsed = parse_webrtc_dial_addr(&addr); + + assert_eq!( + maybe_parsed, + Some(( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 39901), + Fingerprint::raw(hex_literal::hex!( + "e2929e4a5548242ed6b512350df8829b1e4f9d50183c5732a07f99d7c4b2b8eb" + )) + )) + ); + } + #[test] fn tcp_is_invalid_protocol() { let addr = "/ip4/127.0.0.1/tcp/12345/webrtc/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w" From b4472a85e513bd6bc5ab65e4868843573b7a83df Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Oct 2022 17:37:49 +0400 Subject: [PATCH 230/244] use the same ufrag for both client and server https://github.com/libp2p/rust-libp2p/pull/2622#discussion_r997252687 --- transports/webrtc/Cargo.toml | 1 - transports/webrtc/src/tokio/fingerprint.rs | 12 ------------ transports/webrtc/src/tokio/sdp.rs | 8 ++++++-- transports/webrtc/src/tokio/upgrade.rs | 11 +++++------ 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index fd30cef8a09..ce87f2dd397 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -21,7 +21,6 @@ if-watch = "2.0" libp2p-core = { version = "0.37.0", path = "../../core" } libp2p-noise = { version = "0.40.0", path = "../../transports/noise" } log = "0.4" -multibase = "0.9" multihash = { version = "0.16", default-features = false, features = ["sha2"] } prost = "0.11" prost-codec = { version = "0.2.1", path = "../../misc/prost-codec" } diff --git a/transports/webrtc/src/tokio/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs index d2862583378..6349e75bddc 100644 --- a/transports/webrtc/src/tokio/fingerprint.rs +++ b/transports/webrtc/src/tokio/fingerprint.rs @@ -18,7 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multibase::Base; use multihash::{Code, Hasher, Multihash, MultihashDigest}; use webrtc::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint; @@ -80,17 +79,6 @@ impl Fingerprint { .expect("fingerprint's len to be 32 bytes") } - /// Transforms this fingerprint into a ufrag. - pub fn to_ufrag(self) -> String { - multibase::encode( - Base::Base64Url, - Code::Sha2_256 - .wrap(&self.0) - .expect("fingerprint's len to be 32 bytes") - .to_bytes(), - ) - } - /// Formats this fingerprint as uppercase hex, separated by colons (`:`). /// /// This is the format described in . diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index d90b1977471..659057788ff 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -27,12 +27,16 @@ use std::net::{IpAddr, SocketAddr}; use crate::tokio::fingerprint::Fingerprint; /// Creates the SDP answer used by the client. -pub fn answer(addr: SocketAddr, server_fingerprint: &Fingerprint) -> RTCSessionDescription { +pub fn answer( + addr: SocketAddr, + server_fingerprint: &Fingerprint, + client_ufrag: &str, +) -> RTCSessionDescription { RTCSessionDescription::answer(render_description( SERVER_SESSION_DESCRIPTION, addr, server_fingerprint, - &server_fingerprint.to_ufrag(), + client_ufrag, )) .unwrap() } diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 8c3606b7b82..b4b219778ea 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -52,13 +52,13 @@ pub async fn outbound( ) -> Result<(PeerId, Connection), Error> { log::debug!("new outbound connection to {addr})"); - let peer_connection = new_outbound_connection(addr, config, udp_mux).await?; + let (peer_connection, ufrag) = new_outbound_connection(addr, config, udp_mux).await?; let offer = peer_connection.create_offer(None).await?; log::debug!("created SDP offer for outbound connection: {:?}", offer.sdp); peer_connection.set_local_description(offer).await?; - let answer = sdp::answer(addr, &server_fingerprint); + let answer = sdp::answer(addr, &server_fingerprint, &ufrag); log::debug!( "calculated SDP answer for outbound connection: {:?}", answer @@ -88,8 +88,7 @@ pub async fn inbound( ) -> Result<(PeerId, Connection), Error> { log::debug!("new inbound connection from {addr} (ufrag: {remote_ufrag})"); - let peer_connection = - new_inbound_connection(addr, config, udp_mux, &server_fingerprint.to_ufrag()).await?; + let peer_connection = new_inbound_connection(addr, config, udp_mux, &remote_ufrag).await?; let offer = sdp::offer(addr, &remote_ufrag); log::debug!("calculated SDP offer for inbound connection: {:?}", offer); @@ -116,7 +115,7 @@ async fn new_outbound_connection( addr: SocketAddr, config: RTCConfiguration, udp_mux: Arc, -) -> Result { +) -> Result<(RTCPeerConnection, String), Error> { let ufrag = random_ufrag(); let se = setting_engine(udp_mux, &ufrag, addr); @@ -126,7 +125,7 @@ async fn new_outbound_connection( .new_peer_connection(config) .await?; - Ok(connection) + Ok((connection, ufrag)) } async fn new_inbound_connection( From 9a80510748a8cee357ec7d9ded92b8b6cbaf4d33 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 25 Oct 2022 10:21:00 +0400 Subject: [PATCH 231/244] replace futures_lite w/ futures --- transports/webrtc/Cargo.toml | 1 - transports/webrtc/src/tokio/req_res_chan.rs | 10 ++++++---- transports/webrtc/src/tokio/udp_mux.rs | 13 ++++++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index ce87f2dd397..6db64c79890 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -14,7 +14,6 @@ async-trait = "0.1" asynchronous-codec = "0.6" bytes = "1" futures = "0.3" -futures-lite = "1" futures-timer = "3" hex = "0.4" if-watch = "2.0" diff --git a/transports/webrtc/src/tokio/req_res_chan.rs b/transports/webrtc/src/tokio/req_res_chan.rs index a0c93c9452e..a102aa0357a 100644 --- a/transports/webrtc/src/tokio/req_res_chan.rs +++ b/transports/webrtc/src/tokio/req_res_chan.rs @@ -20,9 +20,8 @@ use futures::{ channel::{mpsc, oneshot}, - SinkExt, + SinkExt, StreamExt, }; -use futures_lite::StreamExt; use std::{ io, @@ -67,7 +66,10 @@ pub struct Receiver { } impl Receiver { - pub fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll)>> { - self.inner.poll_next(cx) + pub fn poll_next_unpin( + &mut self, + cx: &mut Context<'_>, + ) -> Poll)>> { + self.inner.poll_next_unpin(cx) } } diff --git a/transports/webrtc/src/tokio/udp_mux.rs b/transports/webrtc/src/tokio/udp_mux.rs index eeb4c509691..8a18ec648e6 100644 --- a/transports/webrtc/src/tokio/udp_mux.rs +++ b/transports/webrtc/src/tokio/udp_mux.rs @@ -191,7 +191,7 @@ impl UDPMuxNewAddr { match self.send_buffer.take() { None => { if let Poll::Ready(Some(((buf, target), response))) = - self.send_command.poll_next(cx) + self.send_command.poll_next_unpin(cx) { self.send_buffer = Some((buf, target, response)); continue; @@ -212,7 +212,7 @@ impl UDPMuxNewAddr { // => Register a new connection if let Poll::Ready(Some(((conn, addr), response))) = - self.registration_command.poll_next(cx) + self.registration_command.poll_next_unpin(cx) { let key = conn.key(); @@ -235,7 +235,8 @@ impl UDPMuxNewAddr { } // => Get connection with the given ufrag - if let Poll::Ready(Some((ufrag, response))) = self.get_conn_command.poll_next(cx) { + if let Poll::Ready(Some((ufrag, response))) = self.get_conn_command.poll_next_unpin(cx) + { if self.is_closed { let _ = response.send(Err(Error::ErrUseClosedNetworkConn)); continue; @@ -273,7 +274,7 @@ impl UDPMuxNewAddr { } // => Close UDPMux - if let Poll::Ready(Some(((), response))) = self.close_command.poll_next(cx) { + if let Poll::Ready(Some(((), response))) = self.close_command.poll_next_unpin(cx) { if self.is_closed { let _ = response.send(Err(Error::ErrAlreadyClosed)); continue; @@ -299,7 +300,9 @@ impl UDPMuxNewAddr { } // => Remove connection with the given ufrag - if let Poll::Ready(Some((ufrag, response))) = self.remove_conn_command.poll_next(cx) { + if let Poll::Ready(Some((ufrag, response))) = + self.remove_conn_command.poll_next_unpin(cx) + { // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` // is keyed on `ufrag` their implementation is equivalent. From e5e9c46d2ef5419502bb28c78e44d193b57bd908 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 26 Oct 2022 11:07:39 +1100 Subject: [PATCH 232/244] Append `PeerId` to listen address --- transports/webrtc/src/tokio/transport.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 2a795c2de61..05128c5abb1 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -250,7 +250,8 @@ impl ListenStream { { return Poll::Ready(TransportEvent::NewAddress { listener_id: self.listener_id, - listen_addr: self.listen_multiaddress(ip), + listen_addr: self + .listen_multiaddress(ip, self.config.id_keys.public().to_peer_id()), }); } } @@ -261,7 +262,8 @@ impl ListenStream { { return Poll::Ready(TransportEvent::AddressExpired { listener_id: self.listener_id, - listen_addr: self.listen_multiaddress(ip), + listen_addr: self + .listen_multiaddress(ip, self.config.id_keys.public().to_peer_id()), }); } } @@ -278,10 +280,11 @@ impl ListenStream { } /// Constructs a [`Multiaddr`] for the given IP address that represents our listen address. - fn listen_multiaddress(&self, ip: IpAddr) -> Multiaddr { + fn listen_multiaddress(&self, ip: IpAddr, local_peer_id: PeerId) -> Multiaddr { let socket_addr = SocketAddr::new(ip, self.listen_addr.port()); socketaddr_to_multiaddr(&socket_addr, Some(self.config.fingerprint)) + .with(Protocol::P2p(*local_peer_id.as_ref())) } } From 36012edd69b5430926af25af415c3c4d42ad0590 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 27 Oct 2022 12:25:49 +0400 Subject: [PATCH 233/244] prepare for deterministic certificates Co-authored-by: Thomas Eizinger --- transports/webrtc/Cargo.toml | 6 +- transports/webrtc/src/tokio/certificate.rs | 77 ++++++++++++++++++++++ transports/webrtc/src/tokio/mod.rs | 2 + transports/webrtc/src/tokio/transport.rs | 51 ++++---------- transports/webrtc/tests/smoke.rs | 18 ++--- 5 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 transports/webrtc/src/tokio/certificate.rs diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 6db64c79890..13acae4396c 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -24,6 +24,7 @@ multihash = { version = "0.16", default-features = false, features = ["sha2"] } prost = "0.11" prost-codec = { version = "0.2.1", path = "../../misc/prost-codec" } rand = "0.8" +rcgen = "0.9.3" serde = { version = "1.0", features = ["derive"] } stun = "0.4" thiserror = "1" @@ -43,5 +44,8 @@ anyhow = "1.0" env_logger = "0.9" hex-literal = "0.3" libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } -rcgen = "0.9.3" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } + +[[test]] +name = "smoke" +required-features = ["tokio"] diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs new file mode 100644 index 00000000000..519cf9a283c --- /dev/null +++ b/transports/webrtc/src/tokio/certificate.rs @@ -0,0 +1,77 @@ +// Copyright 2022 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 rand::{distributions::DistString, CryptoRng, Rng}; +use webrtc::peer_connection::certificate::RTCCertificate; + +use crate::tokio::fingerprint::Fingerprint; + +#[derive(Clone, PartialEq)] +pub struct Certificate { + inner: RTCCertificate, +} + +impl Certificate { + /// Generate new certificate. + /// + /// TODO: make use of `rng` + pub fn generate(_rng: &mut R) -> Result + where + R: CryptoRng + Rng, + { + let mut params = rcgen::CertificateParams::new(vec![ + rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + ]); + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + Ok(Self { + inner: RTCCertificate::from_params(params).expect("default params to work"), + }) + } + + /// Returns SHA-256 fingerprint of this certificate. + /// + /// # Panics + /// + /// This function will panic if there's no fingerprint with the SHA-256 algorithm (see + /// [`RTCCertificate::get_fingerprints`]). + pub fn fingerprint(&self) -> Fingerprint { + let fingerprints = self.inner.get_fingerprints().expect("to never fail"); + let sha256_fingerprint = fingerprints + .iter() + .find(|f| f.algorithm == "sha-256") + .expect("a SHA-256 fingerprint"); + + Fingerprint::try_from_rtc_dtls(sha256_fingerprint).expect("we filtered by sha-256") + } + + /// Extract the [`RTCCertificate`] from this wrapper. + /// + /// This function is `pub(crate)` to avoid leaking the `webrtc` dependency to our users. + pub(crate) fn to_rtc_certificate(&self) -> RTCCertificate { + self.inner.clone() + } +} + +#[derive(thiserror::Error, Debug)] +#[error("Failed to generate certificate")] +pub struct Error(#[from] Kind); + +#[derive(thiserror::Error, Debug)] +enum Kind {} diff --git a/transports/webrtc/src/tokio/mod.rs b/transports/webrtc/src/tokio/mod.rs index 348b9959dbd..81775c6d0f6 100644 --- a/transports/webrtc/src/tokio/mod.rs +++ b/transports/webrtc/src/tokio/mod.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +pub mod certificate; mod connection; mod error; mod fingerprint; @@ -28,6 +29,7 @@ mod transport; mod udp_mux; mod upgrade; +pub use certificate::Certificate; pub use connection::Connection; pub use error::Error; pub use transport::Transport; diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 05128c5abb1..25a328a0d46 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -26,7 +26,6 @@ use libp2p_core::{ transport::{ListenerId, TransportError, TransportEvent}, PeerId, }; -use webrtc::peer_connection::certificate::RTCCertificate; use webrtc::peer_connection::configuration::RTCConfiguration; use std::net::IpAddr; @@ -37,6 +36,7 @@ use std::{ }; use crate::tokio::{ + certificate::Certificate, connection::Connection, error::Error, fingerprint::Fingerprint, @@ -59,22 +59,13 @@ impl Transport { /// /// ``` /// use libp2p_core::identity; - /// use webrtc::peer_connection::certificate::RTCCertificate; - /// use rand::distributions::DistString; - /// use libp2p_webrtc::tokio::Transport; + /// use rand::thread_rng; + /// use libp2p_webrtc::tokio::{Transport, Certificate}; /// /// let id_keys = identity::Keypair::generate_ed25519(); - /// let certificate = { - /// let mut params = rcgen::CertificateParams::new(vec![ - /// rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) - /// ]); - /// params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - /// RTCCertificate::from_params(params).expect("default params to work") - /// }; - /// - /// let transport = Transport::new(id_keys, certificate); + /// let transport = Transport::new(id_keys, Certificate::generate(&mut thread_rng()).unwrap()); /// ``` - pub fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self { + pub fn new(id_keys: identity::Keypair, certificate: Certificate) -> Self { Self { config: Config::new(id_keys, certificate), listeners: SelectAll::new(), @@ -346,26 +337,16 @@ struct Config { impl Config { /// Returns a new [`Config`] with the given keys and certificate. - /// - /// # Panics - /// - /// This function will panic if there's no fingerprint with the SHA-256 algorithm (see - /// [`RTCCertificate::get_fingerprints`]). - fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self { - let fingerprints = certificate.get_fingerprints().expect("to never fail"); - let sha256_fingerprint = fingerprints - .iter() - .find(|f| f.algorithm == "sha-256") - .expect("a SHA-256 fingerprint"); + fn new(id_keys: identity::Keypair, certificate: Certificate) -> Self { + let fingerprint = certificate.fingerprint(); Self { id_keys, inner: RTCConfiguration { - certificates: vec![certificate], + certificates: vec![certificate.to_rtc_certificate()], ..RTCConfiguration::default() }, - fingerprint: Fingerprint::try_from_rtc_dtls(sha256_fingerprint) - .expect("we specified SHA-256"), + fingerprint, } } } @@ -450,7 +431,7 @@ mod tests { use super::*; use futures::future::poll_fn; use libp2p_core::{multiaddr::Protocol, Transport as _}; - use rand::distributions::DistString; + use rand::thread_rng; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[test] @@ -570,8 +551,8 @@ mod tests { #[tokio::test] async fn close_listener() { let id_keys = identity::Keypair::generate_ed25519(); - let certificate = generate_certificate(); - let mut transport = Transport::new(id_keys, certificate); + let mut transport = + Transport::new(id_keys, Certificate::generate(&mut thread_rng()).unwrap()); assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx)) .now_or_never() @@ -620,12 +601,4 @@ mod tests { assert!(transport.listeners.is_empty()); } } - - fn generate_certificate() -> RTCCertificate { - let mut params = rcgen::CertificateParams::new(vec![ - rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) - ]); - params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - RTCCertificate::from_params(params).expect("default params to work") - } } diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 338fd98f36d..d100f905d1c 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -18,7 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use ::webrtc::peer_connection::certificate::RTCCertificate; use anyhow::Result; use async_trait::async_trait; use futures::{ @@ -33,8 +32,7 @@ use libp2p::request_response::{ }; use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent}; use libp2p::webrtc::tokio as webrtc; -use rand::distributions::DistString; -use rand::RngCore; +use rand::{thread_rng, RngCore}; use std::{io, iter}; @@ -472,8 +470,10 @@ impl RequestResponseCodec for PingCodec { fn create_swarm() -> Result>> { let id_keys = identity::Keypair::generate_ed25519(); let peer_id = id_keys.public().to_peer_id(); - let certificate = generate_certificate(); - let transport = webrtc::Transport::new(id_keys, certificate); + let transport = webrtc::Transport::new( + id_keys, + webrtc::Certificate::generate(&mut thread_rng()).unwrap(), + ); let protocols = iter::once((PingProtocol(), ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -488,11 +488,3 @@ fn create_swarm() -> Result>> { })) .build()) } - -fn generate_certificate() -> RTCCertificate { - let mut params = rcgen::CertificateParams::new(vec![ - rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16) - ]); - params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - RTCCertificate::from_params(params).expect("default params to work") -} From 19a8a6c9d6d5a8b07ebb17a36e256af7a57521c2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 28 Oct 2022 11:27:28 +0400 Subject: [PATCH 234/244] prefix ufrag https://github.com/libp2p/specs/pull/412#discussion_r1007462445 --- transports/webrtc/src/tokio/upgrade.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index b4b219778ea..9375d58417c 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -154,12 +154,16 @@ async fn new_inbound_connection( Ok(connection) } +/// Generates a random ufrag and adds a prefix according to the spec. fn random_ufrag() -> String { - thread_rng() - .sample_iter(&Alphanumeric) - .take(64) - .map(char::from) - .collect() + format!( + "libp2p+webrtc+v1/{}", + thread_rng() + .sample_iter(&Alphanumeric) + .take(64) + .map(char::from) + .collect::() + ) } fn setting_engine( From c41e9304b64d2359acd3113121d6267ab19cd88c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 31 Oct 2022 20:03:40 +1100 Subject: [PATCH 235/244] Add `listen_ping` example --- transports/webrtc/Cargo.toml | 7 +++- transports/webrtc/examples/listen_ping.rs | 48 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 transports/webrtc/examples/listen_ping.rs diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 13acae4396c..13c08799d40 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -43,9 +43,14 @@ prost-build = "0.11" anyhow = "1.0" env_logger = "0.9" hex-literal = "0.3" -libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false } +libp2p = { path = "../..", features = ["request-response", "webrtc", "ping"], default-features = false } +tokio = { version = "1.18", features = ["full"] } unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } [[test]] name = "smoke" required-features = ["tokio"] + +[[example]] +name = "listen_ping" +required-features = ["tokio"] diff --git a/transports/webrtc/examples/listen_ping.rs b/transports/webrtc/examples/listen_ping.rs new file mode 100644 index 00000000000..228daba9213 --- /dev/null +++ b/transports/webrtc/examples/listen_ping.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use futures::StreamExt; +use libp2p::swarm::{keep_alive, SwarmBuilder}; +use libp2p::{ping, Swarm}; +use libp2p::Transport; +use libp2p_core::identity; +use libp2p_core::muxing::StreamMuxerBox; +use rand::thread_rng; + +/// An example WebRTC server that will accept connections and run the ping protocol on them. +#[tokio::main] +async fn main() -> Result<()> { + let mut swarm = create_swarm()?; + + swarm.listen_on("/ip4/127.0.0.1/udp/0/webrtc".parse()?)?; + + loop { + let event = swarm.next().await.unwrap(); + eprintln!("New event: {event:?}") + } +} + +fn create_swarm() -> Result> { + let id_keys = identity::Keypair::generate_ed25519(); + let peer_id = id_keys.public().to_peer_id(); + let transport = libp2p_webrtc::tokio::Transport::new( + id_keys, + libp2p_webrtc::tokio::Certificate::generate(&mut thread_rng())?, + ); + + let transport = transport + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) + .boxed(); + + Ok( + SwarmBuilder::new(transport, Behaviour::default(), peer_id) + .executor(Box::new(|fut| { + tokio::spawn(fut); + })) + .build(), + ) +} + +#[derive(libp2p::NetworkBehaviour, Default)] +struct Behaviour { + ping: ping::Behaviour, + keep_alive: keep_alive::Behaviour +} From d1c50b5a50fb83b6b3015b69d5e2091e2327dada Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 3 Nov 2022 14:22:57 +0800 Subject: [PATCH 236/244] don't log empty label --- transports/webrtc/examples/listen_ping.rs | 16 ++++----- transports/webrtc/src/tokio/connection.rs | 42 +++++++++++------------ 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/transports/webrtc/examples/listen_ping.rs b/transports/webrtc/examples/listen_ping.rs index 228daba9213..98cb7cbdedc 100644 --- a/transports/webrtc/examples/listen_ping.rs +++ b/transports/webrtc/examples/listen_ping.rs @@ -1,8 +1,8 @@ use anyhow::Result; use futures::StreamExt; use libp2p::swarm::{keep_alive, SwarmBuilder}; -use libp2p::{ping, Swarm}; use libp2p::Transport; +use libp2p::{ping, Swarm}; use libp2p_core::identity; use libp2p_core::muxing::StreamMuxerBox; use rand::thread_rng; @@ -32,17 +32,15 @@ fn create_swarm() -> Result> { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(); - Ok( - SwarmBuilder::new(transport, Behaviour::default(), peer_id) - .executor(Box::new(|fut| { - tokio::spawn(fut); - })) - .build(), - ) + Ok(SwarmBuilder::new(transport, Behaviour::default(), peer_id) + .executor(Box::new(|fut| { + tokio::spawn(fut); + })) + .build()) } #[derive(libp2p::NetworkBehaviour, Default)] struct Behaviour { ping: ping::Behaviour, - keep_alive: keep_alive::Behaviour + keep_alive: keep_alive::Behaviour, } diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 656da1475bb..8add108f3c1 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -102,11 +102,7 @@ impl Connection { ) { rtc_conn .on_data_channel(Box::new(move |data_channel: Arc| { - log::debug!( - "Incoming data channel '{}'-'{}'", - data_channel.label(), - data_channel.id() - ); + log::debug!("Incoming data channel {}", data_channel.id()); let tx = tx.clone(); @@ -115,19 +111,20 @@ impl Connection { .on_open({ let data_channel = data_channel.clone(); Box::new(move || { - log::debug!( - "Data channel '{}'-'{}' open", - data_channel.label(), - data_channel.id() - ); + log::debug!("Data channel {} open", data_channel.id()); Box::pin(async move { let data_channel = data_channel.clone(); + let id = data_channel.id(); match data_channel.detach().await { Ok(detached) => { let mut tx = tx.lock().await; if let Err(e) = tx.try_send(detached.clone()) { - log::error!("Can't send data channel: {}", e); + log::error!( + "Can't send data channel {}: {}", + id, + e + ); // We're not accepting data channels fast enough => // close this channel. // @@ -136,14 +133,15 @@ impl Connection { // possible with the current API. if let Err(e) = detached.close().await { log::error!( - "Failed to close data channel: {}", + "Failed to close data channel {}: {}", + id, e ); } } } Err(e) => { - log::error!("Can't detach data channel: {}", e); + log::error!("Can't detach data channel {}: {}", id, e); } }; }) @@ -218,7 +216,7 @@ impl StreamMuxer for Connection { // No need to hold the lock during the DTLS handshake. drop(peer_conn); - log::trace!("Opening outbound substream {}", data_channel.id()); + log::trace!("Opening data channel {}", data_channel.id()); let (tx, rx) = oneshot::channel::>(); @@ -235,6 +233,9 @@ impl StreamMuxer for Connection { match ready!(fut.as_mut().poll(cx)) { Ok(detached) => { self.outbound_fut = None; + + log::trace!("Outbound substream {}", detached.stream_identifier()); + let (substream, drop_listener) = Substream::new(detached); self.drop_listeners.push(drop_listener); if let Some(waker) = self.no_drop_listeners_waker.take() { @@ -283,25 +284,22 @@ pub(crate) async fn register_data_channel_open_handler( .on_open({ let data_channel = data_channel.clone(); Box::new(move || { - log::debug!( - "Data channel '{}'-'{}' open", - data_channel.label(), - data_channel.id() - ); + log::debug!("Data channel {} open", data_channel.id()); Box::pin(async move { let data_channel = data_channel.clone(); + let id = data_channel.id(); match data_channel.detach().await { Ok(detached) => { if let Err(e) = data_channel_tx.send(detached.clone()) { - log::error!("Can't send data channel: {:?}", e); + log::error!("Can't send data channel {}: {:?}", id, e); if let Err(e) = detached.close().await { - log::error!("Failed to close data channel: {}", e); + log::error!("Failed to close data channel {}: {}", id, e); } } } Err(e) => { - log::error!("Can't detach data channel: {}", e); + log::error!("Can't detach data channel {}: {}", id, e); } }; }) From 35e02d236250630d156c2f91c932c8994101c02e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 4 Nov 2022 15:23:05 +0800 Subject: [PATCH 237/244] set high watermark on Framed to frame size --- Cargo.toml | 5 ++++- transports/webrtc/src/tokio/substream/framed_dc.rs | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c9984ede5e1..0a06dd7272b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,9 @@ libp2p-tls = { version = "0.1.0-alpha", path = "transports/tls", optional = true [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.42.1", path = "protocols/gossipsub", optional = true } +[patch.crates-io] +asynchronous-codec = { version = "0.6", git = "https://github.com/melekes/asynchronous-codec.git", branch = "anton/set-watermark" } + [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } async-trait = "0.1" @@ -206,7 +209,7 @@ required-features = ["full"] name = "distributed-key-value-store" required-features = ["full"] -# Passing arguments to the docsrs builder in order to properly document cfg's. +# Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index f09d67dcb72..770ef3db088 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -32,7 +32,9 @@ pub type FramedDc = Framed, prost_codec::Codec> pub fn new(data_channel: Arc) -> FramedDc { let mut inner = PollDataChannel::new(data_channel); - inner.set_read_buf_capacity(16384); + inner.set_read_buf_capacity(MAX_MSG_LEN); - Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)) + let mut framed = Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)); + framed.set_send_high_water_mark(MAX_MSG_LEN); + framed } From c7cb26b3e12c64eff49c8bb4caf6e16b9acfb1ef Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 5 Nov 2022 09:05:26 +0800 Subject: [PATCH 238/244] correct max for `Framed` codec and `high_water_mark` --- transports/webrtc/src/tokio/substream/framed_dc.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/transports/webrtc/src/tokio/substream/framed_dc.rs b/transports/webrtc/src/tokio/substream/framed_dc.rs index 770ef3db088..39bd117f1f1 100644 --- a/transports/webrtc/src/tokio/substream/framed_dc.rs +++ b/transports/webrtc/src/tokio/substream/framed_dc.rs @@ -25,8 +25,8 @@ use webrtc::data::data_channel::{DataChannel, PollDataChannel}; use std::sync::Arc; +use super::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; use crate::message_proto::Message; -use crate::tokio::substream::MAX_MSG_LEN; pub type FramedDc = Framed, prost_codec::Codec>; @@ -34,7 +34,12 @@ pub fn new(data_channel: Arc) -> FramedDc { let mut inner = PollDataChannel::new(data_channel); inner.set_read_buf_capacity(MAX_MSG_LEN); - let mut framed = Framed::new(inner.compat(), prost_codec::Codec::new(MAX_MSG_LEN)); - framed.set_send_high_water_mark(MAX_MSG_LEN); + let mut framed = Framed::new( + inner.compat(), + prost_codec::Codec::new(MAX_MSG_LEN - VARINT_LEN), + ); + // If not set, `Framed` buffers up to 131kB of data before sending, which leads to "outbound + // packet larger than maximum message size" error in webrtc-rs. + framed.set_send_high_water_mark(MAX_DATA_LEN); framed } From 58210d4a613e4c8c6c722987e86f2a4b772ccee2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 7 Nov 2022 16:12:20 +0800 Subject: [PATCH 239/244] update webrtc-rs it contains a potential fix for missing data problems --- Cargo.toml | 1 + transports/webrtc/src/tokio/certificate.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c469819f509..8286af2a547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,7 @@ libp2p-gossipsub = { version = "0.43.0", path = "protocols/gossipsub", optional [patch.crates-io] asynchronous-codec = { version = "0.6", git = "https://github.com/melekes/asynchronous-codec.git", branch = "anton/set-watermark" } +webrtc = { version = "0.5.0", git = "https://github.com/webrtc-rs/webrtc.git", branch = "anton/poll-stream-fixes-2" } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 519cf9a283c..55952048ec4 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -52,7 +52,7 @@ impl Certificate { /// This function will panic if there's no fingerprint with the SHA-256 algorithm (see /// [`RTCCertificate::get_fingerprints`]). pub fn fingerprint(&self) -> Fingerprint { - let fingerprints = self.inner.get_fingerprints().expect("to never fail"); + let fingerprints = self.inner.get_fingerprints(); let sha256_fingerprint = fingerprints .iter() .find(|f| f.algorithm == "sha-256") From e1ba1d53573c96cd4ae12022ea04223e8731f4bd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 8 Nov 2022 18:21:58 +0800 Subject: [PATCH 240/244] asynchronous-codec 0.6.1 --- Cargo.toml | 1 - transports/webrtc/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8286af2a547..30c6bacef13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,7 +126,6 @@ libp2p-tls = { version = "0.1.0-alpha", path = "transports/tls", optional = true libp2p-gossipsub = { version = "0.43.0", path = "protocols/gossipsub", optional = true } [patch.crates-io] -asynchronous-codec = { version = "0.6", git = "https://github.com/melekes/asynchronous-codec.git", branch = "anton/set-watermark" } webrtc = { version = "0.5.0", git = "https://github.com/webrtc-rs/webrtc.git", branch = "anton/poll-stream-fixes-2" } [dev-dependencies] diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index c76412916cb..4097b2e5246 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-trait = "0.1" -asynchronous-codec = "0.6" +asynchronous-codec = "0.6.1" bytes = "1" futures = "0.3" futures-timer = "3" From 430f6a8d05d9be2bc262c6e61d012e284df5768b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 9 Nov 2022 12:03:04 +0800 Subject: [PATCH 241/244] rename to_multi_hash to to_multihash --- transports/webrtc/src/tokio/fingerprint.rs | 2 +- transports/webrtc/src/tokio/transport.rs | 2 +- transports/webrtc/src/tokio/upgrade/noise.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transports/webrtc/src/tokio/fingerprint.rs b/transports/webrtc/src/tokio/fingerprint.rs index 6349e75bddc..55cfa1d51d6 100644 --- a/transports/webrtc/src/tokio/fingerprint.rs +++ b/transports/webrtc/src/tokio/fingerprint.rs @@ -73,7 +73,7 @@ impl Fingerprint { } /// Converts this fingerprint to [`type@Multihash`]. - pub fn to_multi_hash(self) -> Multihash { + pub fn to_multihash(self) -> Multihash { Code::Sha2_256 .wrap(&self.0) .expect("fingerprint's len to be 32 bytes") diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 25a328a0d46..24839c6030d 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -359,7 +359,7 @@ fn socketaddr_to_multiaddr(socket_addr: &SocketAddr, certhash: Option Vec { - let client = client_fingerprint.to_multi_hash().to_bytes(); - let server = server_fingerprint.to_multi_hash().to_bytes(); + let client = client_fingerprint.to_multihash().to_bytes(); + let server = server_fingerprint.to_multihash().to_bytes(); const PREFIX: &[u8] = b"libp2p-webrtc-noise:"; let mut out = Vec::with_capacity(PREFIX.len() + client.len() + server.len()); out.extend_from_slice(PREFIX); From fc4bedb9f5a066659b0e21ca3aede62770764f20 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 16 Nov 2022 12:10:49 +0800 Subject: [PATCH 242/244] add from_pem and serialize_pem to Certificate update webrtc to 0.6.0 --- Cargo.toml | 3 - transports/webrtc/Cargo.toml | 3 +- transports/webrtc/src/tokio/certificate.rs | 49 +++++++- transports/webrtc/src/tokio/connection.rs | 126 ++++++++++----------- 4 files changed, 106 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30c6bacef13..b3568ab20a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,9 +125,6 @@ libp2p-tls = { version = "0.1.0-alpha", path = "transports/tls", optional = true [target.'cfg(not(target_os = "unknown"))'.dependencies] libp2p-gossipsub = { version = "0.43.0", path = "protocols/gossipsub", optional = true } -[patch.crates-io] -webrtc = { version = "0.5.0", git = "https://github.com/webrtc-rs/webrtc.git", branch = "anton/poll-stream-fixes-2" } - [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } async-trait = "0.1" diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 4097b2e5246..12e2abbbc6b 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -31,10 +31,11 @@ thiserror = "1" tinytemplate = "1.2" tokio = { version = "1.18", features = ["net"], optional = true} tokio-util = { version = "0.7", features = ["compat"], optional = true } -webrtc = { version = "0.5.0", optional = true } +webrtc = { version = "0.6.0", optional = true } [features] tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc"] +pem = ["webrtc?/pem"] [build-dependencies] prost-build = "0.11" diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 55952048ec4..4317624650a 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -23,7 +23,7 @@ use webrtc::peer_connection::certificate::RTCCertificate; use crate::tokio::fingerprint::Fingerprint; -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Certificate { inner: RTCCertificate, } @@ -31,7 +31,7 @@ pub struct Certificate { impl Certificate { /// Generate new certificate. /// - /// TODO: make use of `rng` + /// `_rng` argument is ignored for now. See https://github.com/melekes/rust-libp2p/pull/12. pub fn generate(_rng: &mut R) -> Result where R: CryptoRng + Rng, @@ -61,6 +61,24 @@ impl Certificate { Fingerprint::try_from_rtc_dtls(sha256_fingerprint).expect("we filtered by sha-256") } + /// Parses a certificate from the ASCII PEM format. + /// + /// See [`RTCCertificate::from_pem`] + #[cfg(feature = "pem")] + pub fn from_pem(pem_str: &str) -> Result { + Ok(Self { + inner: RTCCertificate::from_pem(pem_str).map_err(Kind::InvalidPEM)?, + }) + } + + /// Serializes the certificate (including the private key) in PKCS#8 format in PEM. + /// + /// See [`RTCCertificate::serialize_pem`] + #[cfg(feature = "pem")] + pub fn serialize_pem(&self) -> String { + self.inner.serialize_pem() + } + /// Extract the [`RTCCertificate`] from this wrapper. /// /// This function is `pub(crate)` to avoid leaking the `webrtc` dependency to our users. @@ -74,4 +92,29 @@ impl Certificate { pub struct Error(#[from] Kind); #[derive(thiserror::Error, Debug)] -enum Kind {} +enum Kind { + #[error(transparent)] + InvalidPEM(#[from] webrtc::Error), +} + +#[cfg(test)] +mod test { + #[cfg(feature = "pem")] + use anyhow::Result; + + #[cfg(feature = "pem")] + #[test] + fn test_certificate_serialize_pem_and_from_pem() -> Result<()> { + use super::*; + use rand::thread_rng; + + let cert = Certificate::generate(&mut thread_rng()).unwrap(); + + let pem = cert.serialize_pem(); + let loaded_cert = Certificate::from_pem(&pem)?; + + assert_eq!(loaded_cert, cert); + + Ok(()) + } +} diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 8add108f3c1..72e39ce525f 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -100,57 +100,49 @@ impl Connection { rtc_conn: &RTCPeerConnection, tx: Arc>>>, ) { - rtc_conn - .on_data_channel(Box::new(move |data_channel: Arc| { - log::debug!("Incoming data channel {}", data_channel.id()); + rtc_conn.on_data_channel(Box::new(move |data_channel: Arc| { + log::debug!("Incoming data channel {}", data_channel.id()); - let tx = tx.clone(); + let tx = tx.clone(); - Box::pin(async move { - data_channel - .on_open({ + Box::pin(async move { + data_channel.on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + log::debug!("Data channel {} open", data_channel.id()); + + Box::pin(async move { let data_channel = data_channel.clone(); - Box::new(move || { - log::debug!("Data channel {} open", data_channel.id()); - - Box::pin(async move { - let data_channel = data_channel.clone(); - let id = data_channel.id(); - match data_channel.detach().await { - Ok(detached) => { - let mut tx = tx.lock().await; - if let Err(e) = tx.try_send(detached.clone()) { - log::error!( - "Can't send data channel {}: {}", - id, - e - ); - // We're not accepting data channels fast enough => - // close this channel. - // - // Ideally we'd refuse to accept a data channel - // during the negotiation process, but it's not - // possible with the current API. - if let Err(e) = detached.close().await { - log::error!( - "Failed to close data channel {}: {}", - id, - e - ); - } - } - } - Err(e) => { - log::error!("Can't detach data channel {}: {}", id, e); + let id = data_channel.id(); + match data_channel.detach().await { + Ok(detached) => { + let mut tx = tx.lock().await; + if let Err(e) = tx.try_send(detached.clone()) { + log::error!("Can't send data channel {}: {}", id, e); + // We're not accepting data channels fast enough => + // close this channel. + // + // Ideally we'd refuse to accept a data channel + // during the negotiation process, but it's not + // possible with the current API. + if let Err(e) = detached.close().await { + log::error!( + "Failed to close data channel {}: {}", + id, + e + ); } - }; - }) - }) + } + } + Err(e) => { + log::error!("Can't detach data channel {}: {}", id, e); + } + }; }) - .await; - }) - })) - .await; + }) + }); + }) + })); } } @@ -280,30 +272,28 @@ pub(crate) async fn register_data_channel_open_handler( data_channel: Arc, data_channel_tx: Sender>, ) { - data_channel - .on_open({ - let data_channel = data_channel.clone(); - Box::new(move || { - log::debug!("Data channel {} open", data_channel.id()); - - Box::pin(async move { - let data_channel = data_channel.clone(); - let id = data_channel.id(); - match data_channel.detach().await { - Ok(detached) => { - if let Err(e) = data_channel_tx.send(detached.clone()) { - log::error!("Can't send data channel {}: {:?}", id, e); - if let Err(e) = detached.close().await { - log::error!("Failed to close data channel {}: {}", id, e); - } + data_channel.on_open({ + let data_channel = data_channel.clone(); + Box::new(move || { + log::debug!("Data channel {} open", data_channel.id()); + + Box::pin(async move { + let data_channel = data_channel.clone(); + let id = data_channel.id(); + match data_channel.detach().await { + Ok(detached) => { + if let Err(e) = data_channel_tx.send(detached.clone()) { + log::error!("Can't send data channel {}: {:?}", id, e); + if let Err(e) = detached.close().await { + log::error!("Failed to close data channel {}: {}", id, e); } } - Err(e) => { - log::error!("Can't detach data channel {}: {}", id, e); - } - }; - }) + } + Err(e) => { + log::error!("Can't detach data channel {}: {}", id, e); + } + }; }) }) - .await; + }); } From 06d53d9681e3a27eb6f08d25669e5b28aa891224 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 17 Nov 2022 09:30:03 +0800 Subject: [PATCH 243/244] format code --- transports/webrtc/examples/listen_ping.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/transports/webrtc/examples/listen_ping.rs b/transports/webrtc/examples/listen_ping.rs index dfbf58d3133..55e1904483b 100644 --- a/transports/webrtc/examples/listen_ping.rs +++ b/transports/webrtc/examples/listen_ping.rs @@ -33,7 +33,11 @@ fn create_swarm() -> Result> { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) .boxed(); - Ok(Swarm::with_tokio_executor(transport, Behaviour::default(), peer_id)) + Ok(Swarm::with_tokio_executor( + transport, + Behaviour::default(), + peer_id, + )) } #[derive(NetworkBehaviour, Default)] From 0b0be1d83b976b88fbfcb65326bd8902426331c1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 17 Nov 2022 09:31:51 +0800 Subject: [PATCH 244/244] remove webrtc-pem feature and fix link --- Cargo.toml | 3 +-- transports/webrtc/src/tokio/certificate.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ba0a5e5ef7..f6186f8ba39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,7 @@ uds = ["dep:libp2p-uds"] wasm-bindgen = ["futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js"] wasm-ext = ["dep:libp2p-wasm-ext"] wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"] -webrtc = ["dep:libp2p-webrtc"] -webrtc-pem = ["webrtc", "libp2p-webrtc?/pem"] +webrtc = ["dep:libp2p-webrtc", "libp2p-webrtc?/pem"] websocket = ["dep:libp2p-websocket"] yamux = ["dep:libp2p-yamux"] diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 4317624650a..748cfdb6ffd 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -31,7 +31,7 @@ pub struct Certificate { impl Certificate { /// Generate new certificate. /// - /// `_rng` argument is ignored for now. See https://github.com/melekes/rust-libp2p/pull/12. + /// `_rng` argument is ignored for now. See . pub fn generate(_rng: &mut R) -> Result where R: CryptoRng + Rng,