libp2p/tutorials/
ping.rs

1// Copyright 2021 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//! # Ping Tutorial - Getting started with rust-libp2p
22//!
23//! This tutorial aims to give newcomers a hands-on overview of how to use the
24//! Rust libp2p implementation. People new to Rust likely want to get started on
25//! [Rust](https://www.rust-lang.org/) itself, before diving into all the
26//! networking fun. This library makes heavy use of asynchronous Rust. In case
27//! you are not familiar with this concept, the Rust
28//! [async-book](https://rust-lang.github.io/async-book/) should prove useful.
29//! People new to libp2p might prefer to get a general overview at
30//! [libp2p.io](https://libp2p.io/)
31//! first, although libp2p knowledge is not required for this tutorial.
32//!
33//! We are going to build a small `ping` clone, sending a ping to a peer,
34//! expecting a pong as a response.
35//!
36//! ## Scaffolding
37//!
38//! Let's start off by
39//!
40//! 1. Updating to the latest Rust toolchain, e.g.: `rustup update`
41//!
42//! 2. Creating a new crate: `cargo init rust-libp2p-tutorial`
43//!
44//! 3. Adding `libp2p` as well as `futures` as dependencies in the
45//!    `Cargo.toml` file. Current crate versions may be found at
46//!    [crates.io](https://crates.io/).
47//!    We will also include `async-std` with the
48//!    "attributes" feature to allow for an `async main`.
49//!    At the time of writing we have:
50//!
51//!    ```yaml
52//!    [package]
53//!        name = "rust-libp2p-tutorial"
54//!        version = "0.1.0"
55//!        edition = "2021"
56//!
57//!    [dependencies]
58//!        libp2p = { version = "0.52", features = ["tcp", "tls", "dns", "async-std", "noise", "yamux", "websocket", "ping", "macros"] }
59//!        futures = "0.3.21"
60//!        async-std = { version = "1.12.0", features = ["attributes"] }
61//!        tracing-subscriber = { version = "0.3", features = ["env-filter"] }
62//!    ```
63//!
64//! ## Network identity
65//!
66//! With all the scaffolding in place, we can dive into the libp2p specifics.
67//! First we need to create a network identity for our local node in `async fn
68//! main()`, annotated with an attribute to allow `main` to be `async`.
69//! Identities in libp2p are handled via a public/private key pair.
70//! Nodes identify each other via their [`PeerId`](crate::PeerId) which is
71//! derived from their public key. Now, replace the contents of main.rs by:
72//!
73//! ```rust
74//! use std::error::Error;
75//! use tracing_subscriber::EnvFilter;
76//!
77//! #[async_std::main]
78//! async fn main() -> Result<(), Box<dyn Error>> {
79//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
80//!
81//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity();
82//!
83//!     Ok(())
84//! }
85//! ```
86//!
87//! Go ahead and build and run the above code with: `cargo run`. Nothing happening thus far.
88//!
89//! ## Transport
90//!
91//! Next up we need to construct a transport. Each transport in libp2p provides encrypted streams.
92//! E.g. combining TCP to establish connections, TLS to encrypt these connections and Yamux to run
93//! one or more streams on a connection. Another libp2p transport is QUIC, providing encrypted
94//! streams out-of-the-box. We will stick to TCP for now. Each of these implement the [`Transport`]
95//! trait.
96//!
97//! ```rust
98//! use std::error::Error;
99//! use tracing_subscriber::EnvFilter;
100//!
101//! #[async_std::main]
102//! async fn main() -> Result<(), Box<dyn Error>> {
103//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
104//!
105//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
106//!         .with_async_std()
107//!         .with_tcp(
108//!             libp2p::tcp::Config::default(),
109//!             libp2p::tls::Config::new,
110//!             libp2p::yamux::Config::default,
111//!         )?;
112//!
113//!     Ok(())
114//! }
115//! ```
116//!
117//! ## Network behaviour
118//!
119//! Now it is time to look at another core trait of rust-libp2p: the
120//! [`NetworkBehaviour`]. While the previously introduced trait [`Transport`]
121//! defines _how_ to send bytes on the network, a [`NetworkBehaviour`] defines
122//! _what_ bytes and to _whom_ to send on the network.
123//!
124//! To make this more concrete, let's take a look at a simple implementation of
125//! the [`NetworkBehaviour`] trait: the [`ping::Behaviour`](crate::ping::Behaviour).
126//! As you might have guessed, similar to the good old ICMP `ping` network tool,
127//! libp2p [`ping::Behaviour`](crate::ping::Behaviour) sends a ping to a peer and expects
128//! to receive a pong in turn. The [`ping::Behaviour`](crate::ping::Behaviour) does not care _how_
129//! the ping and pong messages are sent on the network, whether they are sent via
130//! TCP, whether they are encrypted via [noise](crate::noise) or just in
131//! [plaintext](crate::plaintext). It only cares about _what_ messages and to _whom_ to sent on the
132//! network.
133//!
134//! The two traits [`Transport`] and [`NetworkBehaviour`] allow us to cleanly
135//! separate _how_ to send bytes from _what_ bytes and to _whom_ to send.
136//!
137//! With the above in mind, let's extend our example, creating a [`ping::Behaviour`](crate::ping::Behaviour) at the end:
138//!
139//! ```rust
140//! use libp2p::ping;
141//! use tracing_subscriber::EnvFilter;
142//! use std::error::Error;
143//!
144//! #[async_std::main]
145//! async fn main() -> Result<(), Box<dyn Error>> {
146//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
147//!
148//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
149//!         .with_async_std()
150//!         .with_tcp(
151//!             libp2p::tcp::Config::default(),
152//!             libp2p::tls::Config::new,
153//!             libp2p::yamux::Config::default,
154//!         )?
155//!         .with_behaviour(|_| ping::Behaviour::default())?;
156//!
157//!     Ok(())
158//! }
159//! ```
160//!
161//! ## Swarm
162//!
163//! Now that we have a [`Transport`] and a [`NetworkBehaviour`], we can build the [`Swarm`]
164//! which connects the two, allowing both to make progress. Put simply, a [`Swarm`] drives both a
165//! [`Transport`] and a [`NetworkBehaviour`] forward, passing commands from the [`NetworkBehaviour`]
166//! to the [`Transport`] as well as events from the [`Transport`] to the [`NetworkBehaviour`].
167//!
168//! ```rust
169//! use libp2p::ping;
170//! use std::error::Error;
171//! use tracing_subscriber::EnvFilter;
172//!
173//! #[async_std::main]
174//! async fn main() -> Result<(), Box<dyn Error>> {
175//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
176//!
177//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
178//!         .with_async_std()
179//!         .with_tcp(
180//!             libp2p::tcp::Config::default(),
181//!             libp2p::tls::Config::new,
182//!             libp2p::yamux::Config::default,
183//!         )?
184//!         .with_behaviour(|_| ping::Behaviour::default())?
185//!         .build();
186//!
187//!     Ok(())
188//! }
189//! ```
190//!
191//! ## Idle connection timeout
192//!
193//! Now, for this example in particular, we need set the idle connection timeout.
194//! Otherwise, the connection will be closed immediately.
195//!
196//! Whether you need to set this in your application too depends on your usecase.
197//! Typically, connections are kept alive if they are "in use" by a certain protocol.
198//! The ping protocol however is only an "auxiliary" kind of protocol.
199//! Thus, without any other behaviour in place, we would not be able to observe the pings.
200//!
201//! ```rust
202//! use libp2p::ping;
203//! use std::error::Error;
204//! use std::time::Duration;
205//! use tracing_subscriber::EnvFilter;
206//!
207//! #[async_std::main]
208//! async fn main() -> Result<(), Box<dyn Error>> {
209//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
210//!
211//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
212//!         .with_async_std()
213//!         .with_tcp(
214//!             libp2p::tcp::Config::default(),
215//!             libp2p::tls::Config::new,
216//!             libp2p::yamux::Config::default,
217//!         )?
218//!         .with_behaviour(|_| ping::Behaviour::default())?
219//!         .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely.
220//!         .build();
221//!
222//!     Ok(())
223//! }
224//! ```
225//!
226//! ## Multiaddr
227//!
228//! With the [`Swarm`] in place, we are all set to listen for incoming
229//! connections. We only need to pass an address to the [`Swarm`], just like for
230//! [`std::net::TcpListener::bind`]. But instead of passing an IP address, we
231//! pass a [`Multiaddr`] which is yet another core concept of libp2p worth
232//! taking a look at.
233//!
234//! A [`Multiaddr`] is a self-describing network address and protocol stack that
235//! is used to establish connections to peers. A good introduction to
236//! [`Multiaddr`] can be found at
237//! [docs.libp2p.io/concepts/addressing](https://docs.libp2p.io/concepts/addressing/)
238//! and its specification repository
239//! [github.com/multiformats/multiaddr](https://github.com/multiformats/multiaddr/).
240//!
241//! Let's make our local node listen on a new socket.
242//! This socket is listening on multiple network interfaces at the same time. For
243//! each network interface, a new listening address is created. These may change
244//! over time as interfaces become available or unavailable.
245//! For example, in case of our TCP transport it may (among others) listen on the
246//! loopback interface (localhost) `/ip4/127.0.0.1/tcp/24915` as well as the local
247//! network `/ip4/192.168.178.25/tcp/24915`.
248//!
249//! In addition, if provided on the CLI, let's instruct our local node to dial a
250//! remote peer.
251//!
252//! ```rust
253//! use libp2p::{ping, Multiaddr};
254//! use std::error::Error;
255//! use std::time::Duration;
256//! use tracing_subscriber::EnvFilter;
257//!
258//! #[async_std::main]
259//! async fn main() -> Result<(), Box<dyn Error>> {
260//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
261//!
262//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
263//!         .with_async_std()
264//!         .with_tcp(
265//!             libp2p::tcp::Config::default(),
266//!             libp2p::tls::Config::new,
267//!             libp2p::yamux::Config::default,
268//!         )?
269//!         .with_behaviour(|_| ping::Behaviour::default())?
270//!         .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely..
271//!         .build();
272//!
273//!     // Tell the swarm to listen on all interfaces and a random, OS-assigned
274//!     // port.
275//!     swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
276//!
277//!     // Dial the peer identified by the multi-address given as the second
278//!     // command-line argument, if any.
279//!     if let Some(addr) = std::env::args().nth(1) {
280//!         let remote: Multiaddr = addr.parse()?;
281//!         swarm.dial(remote)?;
282//!         println!("Dialed {addr}")
283//!     }
284//!
285//!     Ok(())
286//! }
287//! ```
288//!
289//! ## Continuously polling the Swarm
290//!
291//! We have everything in place now. The last step is to drive the [`Swarm`] in
292//! a loop, allowing it to listen for incoming connections and establish an
293//! outgoing connection in case we specify an address on the CLI.
294//!
295//! ```no_run
296//! use futures::prelude::*;
297//! use libp2p::swarm::SwarmEvent;
298//! use libp2p::{ping, Multiaddr};
299//! use std::error::Error;
300//! use std::time::Duration;
301//! use tracing_subscriber::EnvFilter;
302//!
303//! #[async_std::main]
304//! async fn main() -> Result<(), Box<dyn Error>> {
305//!     tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
306//!
307//!     let mut swarm = libp2p::SwarmBuilder::with_new_identity()
308//!         .with_async_std()
309//!         .with_tcp(
310//!             libp2p::tcp::Config::default(),
311//!             libp2p::tls::Config::new,
312//!             libp2p::yamux::Config::default,
313//!         )?
314//!         .with_behaviour(|_| ping::Behaviour::default())?
315//!         .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely.
316//!         .build();
317//!
318//!     // Tell the swarm to listen on all interfaces and a random, OS-assigned
319//!     // port.
320//!     swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
321//!
322//!     // Dial the peer identified by the multi-address given as the second
323//!     // command-line argument, if any.
324//!     if let Some(addr) = std::env::args().nth(1) {
325//!         let remote: Multiaddr = addr.parse()?;
326//!         swarm.dial(remote)?;
327//!         println!("Dialed {addr}")
328//!     }
329//!
330//!     loop {
331//!         match swarm.select_next_some().await {
332//!             SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
333//!             SwarmEvent::Behaviour(event) => println!("{event:?}"),
334//!             _ => {}
335//!         }
336//!     }
337//! }
338//! ```
339//!
340//! ## Running two nodes
341//!
342//! For convenience the example created above is also implemented in full in
343//! `examples/ping.rs`. Thus, you can either run the commands below from your
344//! own project created during the tutorial, or from the root of the rust-libp2p
345//! repository. Note that in the former case you need to ignore the `--example
346//! ping` argument.
347//!
348//! You need two terminals. In the first terminal window run:
349//!
350//! ```sh
351//! cargo run --example ping
352//! ```
353//!
354//! It will print the new listening addresses, e.g.
355//! ```sh
356//! Listening on "/ip4/127.0.0.1/tcp/24915"
357//! Listening on "/ip4/192.168.178.25/tcp/24915"
358//! Listening on "/ip4/172.17.0.1/tcp/24915"
359//! Listening on "/ip6/::1/tcp/24915"
360//! ```
361//!
362//! In the second terminal window, start a new instance of the example with:
363//!
364//! ```sh
365//! cargo run --example ping -- /ip4/127.0.0.1/tcp/24915
366//! ```
367//!
368//! Note: The [`Multiaddr`] at the end being one of the [`Multiaddr`] printed
369//! earlier in terminal window one.
370//! Both peers have to be in the same network with which the address is associated.
371//! In our case any printed addresses can be used, as both peers run on the same
372//! device.
373//!
374//! The two nodes will establish a connection and send each other ping and pong
375//! messages every 15 seconds.
376//!
377//! [`Multiaddr`]: crate::core::Multiaddr
378//! [`NetworkBehaviour`]: crate::swarm::NetworkBehaviour
379//! [`Transport`]: crate::core::Transport
380//! [`PeerId`]: crate::core::PeerId
381//! [`Swarm`]: crate::swarm::Swarm