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