Skip to content

Commit a714864

Browse files
authored
feat: Add WebRTC transport (#2622)
Hey 👋 This is a WebRTC transport implemented in accordance w/ the [spec](libp2p/specs#412). It's based on the [webrtc-rs](https://github.com/webrtc-rs/webrtc) library. Resolves: #1066.
1 parent 43fdfe2 commit a714864

26 files changed

+4220
-3
lines changed

Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ full = [
4444
"wasm-bindgen",
4545
"wasm-ext",
4646
"wasm-ext-websocket",
47+
"webrtc",
4748
"websocket",
4849
"yamux",
4950
]
@@ -75,11 +76,12 @@ rsa = ["libp2p-core/rsa"]
7576
secp256k1 = ["libp2p-core/secp256k1"]
7677
serde = ["libp2p-core/serde", "libp2p-kad?/serde", "libp2p-gossipsub?/serde"]
7778
tcp = ["dep:libp2p-tcp"]
78-
tokio = ["libp2p-swarm/tokio", "libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-quic?/tokio"]
79+
tokio = ["libp2p-swarm/tokio", "libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-quic?/tokio", "libp2p-webrtc?/tokio"]
7980
uds = ["dep:libp2p-uds"]
8081
wasm-bindgen = ["futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js"]
8182
wasm-ext = ["dep:libp2p-wasm-ext"]
8283
wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"]
84+
webrtc = ["dep:libp2p-webrtc", "libp2p-webrtc?/pem"]
8385
websocket = ["dep:libp2p-websocket"]
8486
yamux = ["dep:libp2p-yamux"]
8587

@@ -108,6 +110,7 @@ libp2p-request-response = { version = "0.23.0", path = "protocols/request-respon
108110
libp2p-swarm = { version = "0.41.0", path = "swarm" }
109111
libp2p-uds = { version = "0.37.0", path = "transports/uds", optional = true }
110112
libp2p-wasm-ext = { version = "0.38.0", path = "transports/wasm-ext", optional = true }
113+
libp2p-webrtc = { version = "0.1.0-alpha", path = "transports/webrtc", optional = true }
111114
libp2p-yamux = { version = "0.42.0", path = "muxers/yamux", optional = true }
112115
multiaddr = { version = "0.16.0" }
113116
parking_lot = "0.12.0"
@@ -168,7 +171,8 @@ members = [
168171
"transports/tcp",
169172
"transports/uds",
170173
"transports/websocket",
171-
"transports/wasm-ext"
174+
"transports/wasm-ext",
175+
"transports/webrtc"
172176
]
173177

174178
[[example]]

misc/prost-codec/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
12
# 0.3.0 [unreleased]
23

4+
- Implement `From` trait for `std::io::Error`. See [PR 2622].
35
- Don't leak `prost` dependency in `Error` type. See [PR 3058].
46

7+
[PR 2622]: https://github.com/libp2p/rust-libp2p/pull/2622/
58
[PR 3058]: https://github.com/libp2p/rust-libp2p/pull/3058/
69

710
# 0.2.0

misc/prost-codec/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] }
2020
[dev-dependencies]
2121
prost-build = "0.11"
2222

23-
# Passing arguments to the docsrs builder in order to properly document cfg's.
23+
# Passing arguments to the docsrs builder in order to properly document cfg's.
2424
# More information: https://docs.rs/about/builds#cross-compiling
2525
[package.metadata.docs.rs]
2626
all-features = true

misc/prost-codec/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@ impl<In, Out: Message + Default> Decoder for Codec<In, Out> {
6565
#[derive(thiserror::Error, Debug)]
6666
#[error("Failed to encode/decode message")]
6767
pub struct Error(#[from] std::io::Error);
68+
69+
impl From<Error> for std::io::Error {
70+
fn from(e: Error) -> Self {
71+
e.0
72+
}
73+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ pub use libp2p_uds as uds;
125125
#[cfg(feature = "wasm-ext")]
126126
#[doc(inline)]
127127
pub use libp2p_wasm_ext as wasm_ext;
128+
#[cfg(feature = "webrtc")]
129+
#[cfg_attr(docsrs, doc(cfg(feature = "webrtc")))]
130+
#[doc(inline)]
131+
pub use libp2p_webrtc as webrtc;
128132
#[cfg(feature = "websocket")]
129133
#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))]
130134
#[doc(inline)]

transports/webrtc/Cargo.toml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[package]
2+
name = "libp2p-webrtc"
3+
version = "0.1.0-alpha"
4+
authors = ["Parity Technologies <[email protected]>"]
5+
description = "WebRTC transport for libp2p"
6+
repository = "https://github.com/libp2p/rust-libp2p"
7+
license = "MIT"
8+
edition = "2021"
9+
keywords = ["peer-to-peer", "libp2p", "networking"]
10+
categories = ["network-programming", "asynchronous"]
11+
12+
[dependencies]
13+
async-trait = "0.1"
14+
asynchronous-codec = "0.6.1"
15+
bytes = "1"
16+
futures = "0.3"
17+
futures-timer = "3"
18+
hex = "0.4"
19+
if-watch = "2.0"
20+
libp2p-core = { version = "0.38.0", path = "../../core" }
21+
libp2p-noise = { version = "0.41.0", path = "../../transports/noise" }
22+
log = "0.4"
23+
multihash = { version = "0.16", default-features = false, features = ["sha2"] }
24+
prost = "0.11"
25+
prost-codec = { version = "0.3.0", path = "../../misc/prost-codec" }
26+
rand = "0.8"
27+
rcgen = "0.9.3"
28+
serde = { version = "1.0", features = ["derive"] }
29+
stun = "0.4"
30+
thiserror = "1"
31+
tinytemplate = "1.2"
32+
tokio = { version = "1.19", features = ["net"], optional = true}
33+
tokio-util = { version = "0.7", features = ["compat"], optional = true }
34+
webrtc = { version = "0.6.0", optional = true }
35+
36+
[features]
37+
tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc"]
38+
pem = ["webrtc?/pem"]
39+
40+
[build-dependencies]
41+
prost-build = "0.11"
42+
43+
[dev-dependencies]
44+
anyhow = "1.0"
45+
env_logger = "0.9"
46+
hex-literal = "0.3"
47+
libp2p = { path = "../..", features = ["full"] }
48+
tokio = { version = "1.19", features = ["full"] }
49+
unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] }
50+
void = "1"
51+
52+
[[test]]
53+
name = "smoke"
54+
required-features = ["tokio"]
55+
56+
[[example]]
57+
name = "listen_ping"
58+
required-features = ["tokio"]

transports/webrtc/build.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2022 Protocol Labs.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the "Software"),
5+
// to deal in the Software without restriction, including without limitation
6+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
// and/or sell copies of the Software, and to permit persons to whom the
8+
// Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
// DEALINGS IN THE SOFTWARE.
20+
21+
fn main() {
22+
prost_build::compile_protos(&["src/message.proto"], &["src"]).unwrap();
23+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use anyhow::Result;
2+
use futures::StreamExt;
3+
use libp2p::swarm::{keep_alive, NetworkBehaviour};
4+
use libp2p::Transport;
5+
use libp2p::{ping, Swarm};
6+
use libp2p_core::identity;
7+
use libp2p_core::muxing::StreamMuxerBox;
8+
use rand::thread_rng;
9+
use void::Void;
10+
11+
/// An example WebRTC server that will accept connections and run the ping protocol on them.
12+
#[tokio::main]
13+
async fn main() -> Result<()> {
14+
let mut swarm = create_swarm()?;
15+
16+
swarm.listen_on("/ip4/127.0.0.1/udp/0/webrtc".parse()?)?;
17+
18+
loop {
19+
let event = swarm.next().await.unwrap();
20+
eprintln!("New event: {event:?}")
21+
}
22+
}
23+
24+
fn create_swarm() -> Result<Swarm<Behaviour>> {
25+
let id_keys = identity::Keypair::generate_ed25519();
26+
let peer_id = id_keys.public().to_peer_id();
27+
let transport = libp2p_webrtc::tokio::Transport::new(
28+
id_keys,
29+
libp2p_webrtc::tokio::Certificate::generate(&mut thread_rng())?,
30+
);
31+
32+
let transport = transport
33+
.map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn)))
34+
.boxed();
35+
36+
Ok(Swarm::with_tokio_executor(
37+
transport,
38+
Behaviour::default(),
39+
peer_id,
40+
))
41+
}
42+
43+
#[derive(NetworkBehaviour, Default)]
44+
#[behaviour(out_event = "Event", event_process = false)]
45+
struct Behaviour {
46+
ping: ping::Behaviour,
47+
keep_alive: keep_alive::Behaviour,
48+
}
49+
50+
#[derive(Debug)]
51+
#[allow(clippy::large_enum_variant)]
52+
enum Event {
53+
Ping(ping::Event),
54+
}
55+
56+
impl From<ping::Event> for Event {
57+
fn from(e: ping::Event) -> Self {
58+
Event::Ping(e)
59+
}
60+
}
61+
62+
impl From<Void> for Event {
63+
fn from(event: Void) -> Self {
64+
void::unreachable(event)
65+
}
66+
}

transports/webrtc/src/lib.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2022 Parity Technologies (UK) Ltd.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the "Software"),
5+
// to deal in the Software without restriction, including without limitation
6+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
// and/or sell copies of the Software, and to permit persons to whom the
8+
// Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
// DEALINGS IN THE SOFTWARE.
20+
21+
//! Implementation of the [`libp2p_core::Transport`] trait for WebRTC protocol without a signaling
22+
//! server.
23+
//!
24+
//! # Overview
25+
//!
26+
//! ## ICE
27+
//!
28+
//! RFCs: 8839, 8445 See also:
29+
//! <https://tools.ietf.org/id/draft-ietf-rtcweb-sdp-08.html#rfc.section.5.2.3>
30+
//!
31+
//! The WebRTC protocol uses ICE in order to establish a connection.
32+
//!
33+
//! In a typical ICE setup, there are two endpoints, called agents, that want to communicate. One
34+
//! of these two agents can be the local browser, while the other agent is the target of the
35+
//! connection.
36+
//!
37+
//! Even though in this specific context all we want is a simple client-server communication, it is
38+
//! helpful to keep in mind that ICE was designed to solve the problem of NAT traversal.
39+
//!
40+
//! The ICE workflow works as follows:
41+
//!
42+
//! - An "offerer" determines ways in which it could be accessible (either an
43+
//! IP address or through a relay using a TURN server), which are called "candidates". It then
44+
//! generates a small text payload in a format called SDP, that describes the request for a
45+
//! connection.
46+
//! - The offerer sends this SDP-encoded message to the answerer. The medium through which this
47+
//! exchange is done is out of scope of the ICE protocol.
48+
//! - The answerer then finds its own candidates, and generates an answer, again in the SDP format.
49+
//! This answer is sent back to the offerer.
50+
//! - Each agent then tries to connect to the remote's candidates.
51+
//!
52+
//! We pretend to send the offer to the remote agent (the target of the connection), then pretend
53+
//! that it has found a valid IP address for itself (i.e. a candidate), then pretend that the SDP
54+
//! answer containing this candidate has been sent back. This will cause the offerer to execute
55+
//! step 4: try to connect to the remote's candidate.
56+
//!
57+
//! ## TCP or UDP
58+
//!
59+
//! WebRTC by itself doesn't hardcode any specific protocol for media streams. Instead, it is the
60+
//! SDP message of the offerer that specifies which protocol to use. In our use case (one or more
61+
//! data channels), we know that the offerer will always request either TCP+DTLS+SCTP, or
62+
//! UDP+DTLS+SCTP.
63+
//!
64+
//! The implementation only supports UDP at the moment, so if the offerer requests TCP+DTLS+SCTP, it
65+
//! will not respond. Support for TCP may be added in the future (see
66+
//! <https://github.com/webrtc-rs/webrtc/issues/132>).
67+
//!
68+
//! ## DTLS+SCTP
69+
//!
70+
//! RFCs: 8841, 8832
71+
//!
72+
//! In both cases (TCP or UDP), the next layer is DTLS. DTLS is similar to the well-known TLS
73+
//! protocol, except that it doesn't guarantee ordering of delivery (as this is instead provided by
74+
//! the SCTP layer on top of DTLS). In other words, once the TCP or UDP connection is established,
75+
//! the browser will try to perform a DTLS handshake.
76+
//!
77+
//! During the ICE negotiation, each agent must include in its SDP packet a hash of the self-signed
78+
//! certificate that it will use during the DTLS handshake. In our use-case, where we try to
79+
//! hand-crate the SDP answer generated by the remote, this is problematic. A way to solve this
80+
//! is to make the hash a part of the remote's multiaddr. On the server side, we turn
81+
//! certificate verification off.
82+
83+
mod message_proto {
84+
#![allow(clippy::derive_partial_eq_without_eq)]
85+
86+
include!(concat!(env!("OUT_DIR"), "/webrtc.pb.rs"));
87+
}
88+
89+
#[cfg(feature = "tokio")]
90+
pub mod tokio;

transports/webrtc/src/message.proto

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
syntax = "proto2";
2+
3+
package webrtc.pb;
4+
5+
message Message {
6+
enum Flag {
7+
// The sender will no longer send messages on the stream.
8+
FIN = 0;
9+
// The sender will no longer read messages on the stream. Incoming data is
10+
// being discarded on receipt.
11+
STOP_SENDING = 1;
12+
// The sender abruptly terminates the sending part of the stream. The
13+
// receiver can discard any data that it already received on that stream.
14+
RESET = 2;
15+
}
16+
17+
optional Flag flag=1;
18+
19+
optional bytes message = 2;
20+
}

0 commit comments

Comments
 (0)