Skip to content

Commit 411a049

Browse files
authored
feat(upnp): add implementation based on IGD protocol
Implements UPnP via the IGD protocol. The usage of IGD is an implementation detail and is planned to be extended to support NATpnp. Resolves: #3903. Pull-Request: #4156.
1 parent b0c3da7 commit 411a049

File tree

14 files changed

+989
-1
lines changed

14 files changed

+989
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [`libp2p-relay` CHANGELOG](protocols/relay/CHANGELOG.md)
2121
- [`libp2p-request-response` CHANGELOG](protocols/request-response/CHANGELOG.md)
2222
- [`libp2p-rendezvous` CHANGELOG](protocols/rendezvous/CHANGELOG.md)
23+
- [`libp2p-upnp` CHANGELOG](protocols/upnp/CHANGELOG.md)
2324

2425
## Transport Protocols & Upgrades
2526

Cargo.lock

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ members = [
1313
"examples/ping-example",
1414
"examples/relay-server",
1515
"examples/rendezvous",
16+
"examples/upnp",
1617
"identity",
1718
"interop-tests",
1819
"misc/allow-block-list",
@@ -40,6 +41,7 @@ members = [
4041
"protocols/relay",
4142
"protocols/rendezvous",
4243
"protocols/request-response",
44+
"protocols/upnp",
4345
"swarm",
4446
"swarm-derive",
4547
"swarm-test",
@@ -90,6 +92,7 @@ libp2p-pnet = { version = "0.23.0", path = "transports/pnet" }
9092
libp2p-quic = { version = "0.9.2", path = "transports/quic" }
9193
libp2p-relay = { version = "0.16.1", path = "protocols/relay" }
9294
libp2p-rendezvous = { version = "0.13.0", path = "protocols/rendezvous" }
95+
libp2p-upnp = { version = "0.1.0", path = "protocols/upnp" }
9396
libp2p-request-response = { version = "0.25.1", path = "protocols/request-response" }
9497
libp2p-server = { version = "0.12.3", path = "misc/server" }
9598
libp2p-swarm = { version = "0.43.4", path = "swarm" }

examples/upnp/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "upnp-example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
license = "MIT"
7+
8+
[dependencies]
9+
tokio = { version = "1", features = [ "rt-multi-thread", "macros"] }
10+
futures = "0.3.28"
11+
libp2p = { path = "../../libp2p", features = ["tokio", "dns", "macros", "noise", "ping", "tcp", "websocket", "yamux", "upnp"] }

examples/upnp/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Description
2+
3+
The upnp example showcases how to use the upnp network behaviour to externally open ports on the network gateway.
4+
5+
6+
## Usage
7+
8+
To run the example, follow these steps:
9+
10+
1. In a terminal window, run the following command:
11+
12+
```sh
13+
cargo run
14+
```
15+
16+
2. This command will start the swarm and print the `NewExternalAddr` if the gateway supports `UPnP` or
17+
`GatewayNotFound` if it doesn't.
18+
19+
20+
## Conclusion
21+
22+
The upnp example demonstrates the usage of **libp2p** to externally open a port on the gateway if it
23+
supports [`UPnP`](https://en.wikipedia.org/wiki/Universal_Plug_and_Play).

examples/upnp/src/main.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2023 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+
#![doc = include_str!("../README.md")]
22+
23+
use futures::prelude::*;
24+
use libp2p::core::upgrade::Version;
25+
use libp2p::{
26+
identity, noise,
27+
swarm::{SwarmBuilder, SwarmEvent},
28+
tcp, upnp, yamux, Multiaddr, PeerId, Transport,
29+
};
30+
use std::error::Error;
31+
32+
#[tokio::main]
33+
async fn main() -> Result<(), Box<dyn Error>> {
34+
let local_key = identity::Keypair::generate_ed25519();
35+
let local_peer_id = PeerId::from(local_key.public());
36+
println!("Local peer id: {local_peer_id:?}");
37+
38+
let transport = tcp::tokio::Transport::default()
39+
.upgrade(Version::V1Lazy)
40+
.authenticate(noise::Config::new(&local_key)?)
41+
.multiplex(yamux::Config::default())
42+
.boxed();
43+
44+
let mut swarm = SwarmBuilder::with_tokio_executor(
45+
transport,
46+
upnp::tokio::Behaviour::default(),
47+
local_peer_id,
48+
)
49+
.build();
50+
51+
// Tell the swarm to listen on all interfaces and a random, OS-assigned
52+
// port.
53+
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
54+
55+
// Dial the peer identified by the multi-address given as the second
56+
// command-line argument, if any.
57+
if let Some(addr) = std::env::args().nth(1) {
58+
let remote: Multiaddr = addr.parse()?;
59+
swarm.dial(remote)?;
60+
println!("Dialed {addr}")
61+
}
62+
63+
loop {
64+
match swarm.select_next_some().await {
65+
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
66+
SwarmEvent::Behaviour(upnp::Event::NewExternalAddr(addr)) => {
67+
println!("New external address: {addr}");
68+
}
69+
SwarmEvent::Behaviour(upnp::Event::GatewayNotFound) => {
70+
println!("Gateway does not support UPnP");
71+
break;
72+
}
73+
SwarmEvent::Behaviour(upnp::Event::NonRoutableGateway) => {
74+
println!("Gateway is not exposed directly to the public Internet, i.e. it itself has a private IP address.");
75+
break;
76+
}
77+
_ => {}
78+
}
79+
}
80+
Ok(())
81+
}

libp2p/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
- Add `json` feature which exposes `request_response::json`.
1111
See [PR 4188].
1212

13+
- Add support for UPnP via the IGD protocol.
14+
See [PR 4156].
15+
1316
- Add `libp2p-memory-connection-limits` providing memory usage based connection limit configurations.
1417
See [PR 4281].
1518

1619
[PR 4188]: https://github.com/libp2p/rust-libp2p/pull/4188
20+
[PR 4156]: https://github.com/libp2p/rust-libp2p/pull/4156
1721
[PR 4217]: https://github.com/libp2p/rust-libp2p/pull/4217
1822
[PR 4281]: https://github.com/libp2p/rust-libp2p/pull/4281
1923

libp2p/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ full = [
5050
"websocket",
5151
"webtransport-websys",
5252
"yamux",
53+
"upnp"
5354
]
5455

5556
async-std = ["libp2p-swarm/async-std", "libp2p-mdns?/async-io", "libp2p-tcp?/async-io", "libp2p-dns?/async-std", "libp2p-quic?/async-std"]
@@ -82,14 +83,15 @@ secp256k1 = ["libp2p-identity/secp256k1"]
8283
serde = ["libp2p-core/serde", "libp2p-kad?/serde", "libp2p-gossipsub?/serde"]
8384
tcp = ["dep:libp2p-tcp"]
8485
tls = ["dep:libp2p-tls"]
85-
tokio = ["libp2p-swarm/tokio", "libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-quic?/tokio"]
86+
tokio = ["libp2p-swarm/tokio", "libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-quic?/tokio", "libp2p-upnp?/tokio"]
8687
uds = ["dep:libp2p-uds"]
8788
wasm-bindgen = ["futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js", "libp2p-swarm/wasm-bindgen", "libp2p-gossipsub?/wasm-bindgen"]
8889
wasm-ext = ["dep:libp2p-wasm-ext"]
8990
wasm-ext-websocket = ["wasm-ext", "libp2p-wasm-ext?/websocket"]
9091
websocket = ["dep:libp2p-websocket"]
9192
webtransport-websys = ["dep:libp2p-webtransport-websys"]
9293
yamux = ["dep:libp2p-yamux"]
94+
upnp = ["dep:libp2p-upnp"]
9395

9496
[dependencies]
9597
bytes = "1"
@@ -133,6 +135,7 @@ libp2p-quic = { workspace = true, optional = true }
133135
libp2p-tcp = { workspace = true, optional = true }
134136
libp2p-tls = { workspace = true, optional = true }
135137
libp2p-uds = { workspace = true, optional = true }
138+
libp2p-upnp = { workspace = true, optional = true }
136139
libp2p-websocket = { workspace = true, optional = true }
137140

138141
[dev-dependencies]

libp2p/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ pub use libp2p_tls as tls;
127127
#[cfg(not(target_arch = "wasm32"))]
128128
#[doc(inline)]
129129
pub use libp2p_uds as uds;
130+
#[cfg(feature = "upnp")]
131+
#[cfg(not(target_arch = "wasm32"))]
132+
#[doc(inline)]
133+
pub use libp2p_upnp as upnp;
130134
#[cfg(feature = "wasm-ext")]
131135
#[doc(inline)]
132136
pub use libp2p_wasm_ext as wasm_ext;

protocols/upnp/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.1.0 - unreleased
2+
3+
- Initial version

protocols/upnp/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "libp2p-upnp"
3+
edition = "2021"
4+
rust-version = "1.60.0"
5+
description = "UPnP support for libp2p transports"
6+
version = "0.1.0"
7+
license = "MIT"
8+
repository = "https://github.com/libp2p/rust-libp2p"
9+
keywords = ["peer-to-peer", "libp2p", "networking"]
10+
categories = ["network-programming", "asynchronous"]
11+
publish = false
12+
13+
[dependencies]
14+
futures = "0.3.28"
15+
futures-timer = "3.0.2"
16+
igd-next = "0.14.2"
17+
libp2p-core = { workspace = true }
18+
libp2p-swarm = { workspace = true }
19+
log = "0.4.19"
20+
void = "1.0.2"
21+
tokio = { version = "1.29", default-features = false, features = ["rt"], optional = true }
22+
23+
[features]
24+
tokio = ["igd-next/aio_tokio", "dep:tokio"]
25+

0 commit comments

Comments
 (0)