From 879d5941b326126b95e2c77c73b64cc9d50ff852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Wed, 24 Jul 2024 12:03:40 +0100 Subject: [PATCH 01/35] fix: update Cargo.lock To address [RUSTSEC-2024-0357](https://rustsec.org/advisories/RUSTSEC-2024-0357.html) Pull-Request: #5511. --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80a186f0026..3fda6b57aa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4092,9 +4092,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.60" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -4124,9 +4124,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", From 4a16b6181d36cd703066a227deff57c6ebe91f7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:28:52 +0000 Subject: [PATCH 02/35] deps: bump obi1kenobi/cargo-semver-checks-action from 2.5 to 2.6 Pull-Request: #5512. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8ff0521ce5..54ddcf47028 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -310,7 +310,7 @@ jobs: - uses: actions/checkout@v4 - run: wget -q -O- https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.31.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C ~/.cargo/bin shell: bash - - uses: obi1kenobi/cargo-semver-checks-action@ca26a44cfb670b2078c8f757d06e696a7c3820cf # v2 + - uses: obi1kenobi/cargo-semver-checks-action@7272cc2caa468d3e009a2b0a9cc366839348237b # v2 rustfmt: runs-on: ubuntu-latest From 055b179a2354ea174dbb8beb38804719d655667c Mon Sep 17 00:00:00 2001 From: dewmal Date: Mon, 29 Jul 2024 19:35:19 +0530 Subject: [PATCH 03/35] docs(README): Add Ceylon to the notable users section. I have added Project Ceylon: A Multi-Agent System (MAS) Development Framework to the notable users list. Pull-Request: #5513. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4d72626e413..77323a1d659 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,4 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). - [Substrate](https://github.com/paritytech/substrate) - Framework for blockchain innovation, used by [Polkadot](https://www.parity.io/technologies/polkadot/). - [Taple](https://github.com/opencanarias/taple-core) - Sustainable DLT for asset and process traceability by [OpenCanarias](https://www.opencanarias.com/en/). +- [Ceylon](https://github.com/ceylonai/ceylon) - A Multi-Agent System (MAS) Development Framwork. From 41e2d5dc4ad37af4b3256dc7c089188d1589e6e5 Mon Sep 17 00:00:00 2001 From: stormshield-frb <144998884+stormshield-frb@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:43:39 +0200 Subject: [PATCH 04/35] fix(relay): wake the relay Listener on close When closing a relayed `Listener` manually, the `TransportEvent::ListenerClosed` generated by the `relay::priv_client::Transport` is never forwarded back up to the `Swarm`, causing the `Swarm` to never remove the corresponding listener and never emitting the `SwarmEvent::ListenerClosed` event. This happens because, when stopping a relayed listener manually, the call to the [`close()` function](https://github.com/libp2p/rust-libp2p/blob/master/protocols/relay/src/priv_client/transport.rs#L324), is done outside the `poll` function, which mean nothing is triggering a wake up call to wake up the polling. Unfortunately, even if the [`listeners` (`SelectAll`) is always polled](https://github.com/libp2p/rust-libp2p/blob/master/protocols/relay/src/priv_client/transport.rs#L241) after a call to the `close` method, since `SelectAll` uses a `FuturesUnordered` internally, the poll does nothing. Indeed, the `FuturesUnordered` states that: ```rust /// This structure is optimized to manage a large number of futures. /// Futures managed by [`FuturesUnordered`] will only be polled when they /// generate wake-up notifications. This reduces the required amount of work /// needed to poll large numbers of futures. ``` Since means that when closing a relayed listener manually (calling `swarm.remove_listener`), it is never removed. This PR fixes that by triggering a `waker` when calling the `close` function. Pull-Request: #5491. --- protocols/relay/CHANGELOG.md | 3 +++ protocols/relay/src/priv_client/transport.rs | 26 +++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index f49e57af0f7..97638d1ae6a 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,9 +1,12 @@ ## 0.17.3 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). +- Fix manual closure of relayed listener. + See [PR 5491](https://github.com/libp2p/rust-libp2p/pull/5491) - Add resource limits to `CircuitReq` to be set See [PR 5493](https://github.com/libp2p/rust-libp2p/pull/5493) + ## 0.17.2 - Fix support for unlimited relay connection according to spec. diff --git a/protocols/relay/src/priv_client/transport.rs b/protocols/relay/src/priv_client/transport.rs index 7147f0b5e55..b4374aa4672 100644 --- a/protocols/relay/src/priv_client/transport.rs +++ b/protocols/relay/src/priv_client/transport.rs @@ -27,7 +27,6 @@ use crate::RequestId; use futures::channel::mpsc; use futures::channel::oneshot; use futures::future::{ready, BoxFuture, FutureExt, Ready}; -use futures::ready; use futures::sink::SinkExt; use futures::stream::SelectAll; use futures::stream::{Stream, StreamExt}; @@ -36,7 +35,7 @@ use libp2p_core::transport::{ListenerId, TransportError, TransportEvent}; use libp2p_identity::PeerId; use std::collections::VecDeque; use std::pin::Pin; -use std::task::{Context, Poll}; +use std::task::{Context, Poll, Waker}; use thiserror::Error; /// A [`Transport`] enabling client relay capabilities. @@ -151,6 +150,7 @@ impl libp2p_core::Transport for Transport { queued_events: Default::default(), from_behaviour, is_closed: false, + waker: None, }; self.listeners.push(listener); Ok(()) @@ -313,6 +313,7 @@ pub(crate) struct Listener { /// The listener can be closed either manually with [`Transport::remove_listener`](libp2p_core::Transport) or if /// the sender side of the `from_behaviour` channel is dropped. is_closed: bool, + waker: Option, } impl Listener { @@ -328,6 +329,10 @@ impl Listener { reason, }); self.is_closed = true; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } } } @@ -337,18 +342,27 @@ impl Stream for Listener { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if let Some(event) = self.queued_events.pop_front() { + self.waker = None; return Poll::Ready(Some(event)); } if self.is_closed { // Terminate the stream if the listener closed and all remaining events have been reported. + self.waker = None; return Poll::Ready(None); } - let Some(msg) = ready!(self.from_behaviour.poll_next_unpin(cx)) else { - // Sender of `from_behaviour` has been dropped, signaling listener to close. - self.close(Ok(())); - continue; + let msg = match self.from_behaviour.poll_next_unpin(cx) { + Poll::Ready(Some(msg)) => msg, + Poll::Ready(None) => { + // Sender of `from_behaviour` has been dropped, signaling listener to close. + self.close(Ok(())); + continue; + } + Poll::Pending => { + self.waker = Some(cx.waker().clone()); + return Poll::Pending; + } }; match msg { From 06b5847a89bfea8b95c443e088eba08a66a04418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Tue, 30 Jul 2024 17:03:00 +0100 Subject: [PATCH 05/35] chore: address clippy beta lints Pull-Request: #5515. --- hole-punching-tests/src/main.rs | 4 +--- interop-tests/src/arch.rs | 3 +-- protocols/ping/src/protocol.rs | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/hole-punching-tests/src/main.rs b/hole-punching-tests/src/main.rs index f09fbb1e6a0..bbecc948e08 100644 --- a/hole-punching-tests/src/main.rs +++ b/hole-punching-tests/src/main.rs @@ -294,9 +294,7 @@ impl RedisClient { tracing::debug!("Pushing {key}={value} to redis"); - self.inner.rpush(key, value).await?; - - Ok(()) + self.inner.rpush(key, value).await.map_err(Into::into) } async fn pop(&mut self, key: &str) -> Result diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index fb229434981..a7755b95977 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -179,8 +179,7 @@ pub(crate) mod native { pub(crate) async fn rpush(&self, key: &str, value: String) -> Result<()> { let mut conn = self.0.get_async_connection().await?; - conn.rpush(key, value).await?; - Ok(()) + conn.rpush(key, value).await.map_err(Into::into) } } } diff --git a/protocols/ping/src/protocol.rs b/protocols/ping/src/protocol.rs index 9b4d7da98db..8fc18505d35 100644 --- a/protocols/ping/src/protocol.rs +++ b/protocols/ping/src/protocol.rs @@ -44,8 +44,7 @@ pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/ping/1.0.0" /// > Nagle's algorithm, delayed acks and similar configuration options /// > which can affect latencies especially on otherwise low-volume /// > connections. -#[derive(Default, Debug, Copy, Clone)] -pub(crate) struct Ping; + const PING_SIZE: usize = 32; /// Sends a ping and waits for the pong. From 5b4c43cc4361bfdd1a0f7d06ce4d700796717d2f Mon Sep 17 00:00:00 2001 From: Hannes <55623006+umgefahren@users.noreply.github.com> Date: Fri, 2 Aug 2024 00:21:21 +0200 Subject: [PATCH 06/35] chore: Update cargo semver binary I updated the semver binary to the new version, this should make the CI run again. Pull-Request: #5520. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54ddcf47028..648945820fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -308,9 +308,9 @@ jobs: RUSTFLAGS: '' steps: - uses: actions/checkout@v4 - - run: wget -q -O- https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.31.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C ~/.cargo/bin + - run: wget -q -O- https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.33.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C ~/.cargo/bin shell: bash - - uses: obi1kenobi/cargo-semver-checks-action@7272cc2caa468d3e009a2b0a9cc366839348237b # v2 + - uses: obi1kenobi/cargo-semver-checks-action@7272cc2caa468d3e009a2b0a9cc366839348237b # v2.6 rustfmt: runs-on: ubuntu-latest From 5c11f4a9e42f7675c58c1604ed7039a7740e582d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Fri, 2 Aug 2024 12:05:54 +0100 Subject: [PATCH 07/35] docs(swarm): doc DialError::Denied Pull-Request: #5510. --- swarm/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index 31eb2aa28f5..03e30240771 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -1538,9 +1538,7 @@ impl Config { #[derive(Debug)] pub enum DialError { /// The peer identity obtained on the connection matches the local peer. - LocalPeerId { - endpoint: ConnectedPoint, - }, + LocalPeerId { endpoint: ConnectedPoint }, /// No addresses have been provided by [`NetworkBehaviour::handle_pending_outbound_connection`] and [`DialOpts`]. NoAddresses, /// The provided [`dial_opts::PeerCondition`] evaluated to false and thus @@ -1553,9 +1551,10 @@ pub enum DialError { obtained: PeerId, endpoint: ConnectedPoint, }, - Denied { - cause: ConnectionDenied, - }, + /// One of the [`NetworkBehaviour`]s rejected the outbound connection + /// via [`NetworkBehaviour::handle_pending_outbound_connection`] or + /// [`NetworkBehaviour::handle_established_outbound_connection`]. + Denied { cause: ConnectionDenied }, /// An error occurred while negotiating the transport protocol(s) on a connection. Transport(Vec<(Multiaddr, TransportError)>), } From d733dfe10dc576ce8e408846103e98241d341df7 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Fri, 2 Aug 2024 16:29:14 +0300 Subject: [PATCH 08/35] fix(websocket-websys): Unsubscribe from websocket events on drop Avoid use-after-free handler invocation from JS side. Fixes #5490 Pull-Request: #5521. --- Cargo.lock | 2 +- Cargo.toml | 2 +- transports/websocket-websys/CHANGELOG.md | 5 +++++ transports/websocket-websys/Cargo.toml | 2 +- transports/websocket-websys/src/lib.rs | 6 ++++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fda6b57aa8..b4d877e49d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3515,7 +3515,7 @@ dependencies = [ [[package]] name = "libp2p-websocket-websys" -version = "0.3.2" +version = "0.3.3" dependencies = [ "bytes", "futures", diff --git a/Cargo.toml b/Cargo.toml index 55fc43d5b5a..52226f6c6e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ libp2p-webrtc = { version = "0.7.1-alpha", path = "transports/webrtc" } libp2p-webrtc-utils = { version = "0.2.1", path = "misc/webrtc-utils" } libp2p-webrtc-websys = { version = "0.3.0-alpha", path = "transports/webrtc-websys" } libp2p-websocket = { version = "0.43.2", path = "transports/websocket" } -libp2p-websocket-websys = { version = "0.3.2", path = "transports/websocket-websys" } +libp2p-websocket-websys = { version = "0.3.3", path = "transports/websocket-websys" } libp2p-webtransport-websys = { version = "0.3.0", path = "transports/webtransport-websys" } libp2p-yamux = { version = "0.45.2", path = "muxers/yamux" } multiaddr = "0.18.1" diff --git a/transports/websocket-websys/CHANGELOG.md b/transports/websocket-websys/CHANGELOG.md index f92b76ebeaa..c16ad6cc406 100644 --- a/transports/websocket-websys/CHANGELOG.md +++ b/transports/websocket-websys/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.3 + +- Fix use-after-free handler invocation from JS side. + See [PR 5521](https://github.com/libp2p/rust-libp2p/pull/5521). + ## 0.3.2 - Change close code in drop implementation to `1000` given that in browsers only diff --git a/transports/websocket-websys/Cargo.toml b/transports/websocket-websys/Cargo.toml index 48bf8d9818a..a2127986ddc 100644 --- a/transports/websocket-websys/Cargo.toml +++ b/transports/websocket-websys/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-websocket-websys" edition = "2021" rust-version = "1.60.0" description = "WebSocket for libp2p under WASM environment" -version = "0.3.2" +version = "0.3.3" authors = ["Vince Vasta "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/websocket-websys/src/lib.rs b/transports/websocket-websys/src/lib.rs index ca56d5727a5..c96d1d6aa3a 100644 --- a/transports/websocket-websys/src/lib.rs +++ b/transports/websocket-websys/src/lib.rs @@ -434,6 +434,12 @@ impl AsyncWrite for Connection { impl Drop for Connection { fn drop(&mut self) { + // Unset event listeners, as otherwise they will be called by JS after the handlers have already been dropped. + self.inner.socket.set_onclose(None); + self.inner.socket.set_onerror(None); + self.inner.socket.set_onopen(None); + self.inner.socket.set_onmessage(None); + // In browsers, userland code is not allowed to use any other status code than 1000: https://websockets.spec.whatwg.org/#dom-websocket-close const REGULAR_CLOSE: u16 = 1000; // See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1. From 53eb339a01245a8d017ae0f11f88ba9496cfc31b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 2 Aug 2024 15:01:13 +0100 Subject: [PATCH 09/35] docs: remove myself as a maintainer This reflects the current state of things. Pull-Request: #5514. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 77323a1d659..f43cf044ef3 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,6 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). (In alphabetical order.) - João Oliveira ([@jxs](https://github.com/jxs)) -- Thomas Eizinger ([@thomaseizinger](https://github.com/thomaseizinger)) ## Notable users From 079b2d6b99262f4f0ca42994571614787a5c98f7 Mon Sep 17 00:00:00 2001 From: Hannes <55623006+umgefahren@users.noreply.github.com> Date: Sat, 3 Aug 2024 16:05:59 +0200 Subject: [PATCH 10/35] refactor(*): Transport redesign Resolves: #4226. Resolves: #3953. Resolves: #3889. Pull-Request: #4568. --- Cargo.lock | 38 +- Cargo.toml | 39 +- core/CHANGELOG.md | 11 + core/Cargo.toml | 2 +- core/src/connection.rs | 13 +- core/src/either.rs | 36 +- core/src/lib.rs | 3 - core/src/transport.rs | 54 +-- core/src/transport/and_then.rs | 35 +- core/src/transport/boxed.rs | 42 +- core/src/transport/choice.rs | 55 +-- core/src/transport/dummy.rs | 13 +- core/src/transport/global_only.rs | 40 +- core/src/transport/map.rs | 27 +- core/src/transport/map_err.rs | 22 +- core/src/transport/memory.rs | 65 ++- core/src/transport/optional.rs | 23 +- core/src/transport/timeout.rs | 21 +- core/src/transport/upgrade.rs | 46 +- core/src/upgrade/ready.rs | 2 +- core/tests/transport_upgrade.rs | 15 +- examples/autonat/Cargo.toml | 2 +- examples/dcutr/src/main.rs | 2 +- examples/stream/Cargo.toml | 2 +- hole-punching-tests/src/main.rs | 2 +- libp2p/CHANGELOG.md | 11 + misc/allow-block-list/src/lib.rs | 2 + misc/connection-limits/src/lib.rs | 4 +- misc/memory-connection-limits/src/lib.rs | 3 +- .../tests/util/mod.rs | 3 +- misc/metrics/src/bandwidth.rs | 21 +- misc/server/src/main.rs | 2 +- muxers/mplex/benches/split_send_size.rs | 13 +- protocols/autonat/CHANGELOG.md | 8 + protocols/autonat/Cargo.toml | 2 +- protocols/autonat/src/behaviour.rs | 13 +- protocols/autonat/src/behaviour/as_server.rs | 1 + protocols/autonat/tests/test_server.rs | 2 + protocols/dcutr/CHANGELOG.md | 4 + protocols/dcutr/Cargo.toml | 2 +- protocols/dcutr/src/behaviour.rs | 3 + protocols/floodsub/CHANGELOG.md | 4 + protocols/floodsub/Cargo.toml | 2 +- protocols/floodsub/src/layer.rs | 2 + protocols/gossipsub/CHANGELOG.md | 4 +- protocols/gossipsub/src/behaviour.rs | 5 +- protocols/gossipsub/src/behaviour/tests.rs | 6 + protocols/identify/CHANGELOG.md | 5 + protocols/identify/src/behaviour.rs | 127 ++++- protocols/identify/tests/smoke.rs | 8 +- protocols/kad/CHANGELOG.md | 1 + protocols/kad/src/behaviour.rs | 4 +- protocols/kad/src/behaviour/test.rs | 3 + protocols/mdns/CHANGELOG.md | 3 + protocols/mdns/Cargo.toml | 2 +- protocols/mdns/src/behaviour.rs | 2 + protocols/mdns/src/behaviour/iface/query.rs | 9 +- protocols/perf/CHANGELOG.md | 2 + protocols/perf/src/client/behaviour.rs | 3 +- protocols/perf/src/server/behaviour.rs | 2 + protocols/ping/CHANGELOG.md | 5 +- protocols/ping/Cargo.toml | 2 +- protocols/ping/src/lib.rs | 2 + protocols/ping/src/protocol.rs | 11 +- protocols/relay/CHANGELOG.md | 4 + protocols/relay/Cargo.toml | 2 +- protocols/relay/src/behaviour.rs | 3 + protocols/relay/src/priv_client.rs | 2 + protocols/relay/src/priv_client/transport.rs | 41 +- protocols/rendezvous/CHANGELOG.md | 4 + protocols/rendezvous/Cargo.toml | 2 +- protocols/rendezvous/src/client.rs | 11 +- protocols/rendezvous/src/server.rs | 11 +- protocols/request-response/CHANGELOG.md | 4 + protocols/request-response/Cargo.toml | 2 +- protocols/request-response/src/lib.rs | 3 +- protocols/stream/CHANGELOG.md | 4 + protocols/stream/Cargo.toml | 2 +- protocols/stream/src/behaviour.rs | 3 +- protocols/upnp/CHANGELOG.md | 4 + protocols/upnp/Cargo.toml | 2 +- protocols/upnp/src/behaviour.rs | 7 +- swarm-derive/src/lib.rs | 4 +- swarm/CHANGELOG.md | 8 + swarm/benches/connection_handler.rs | 1 + swarm/src/behaviour.rs | 6 +- swarm/src/behaviour/either.rs | 4 + swarm/src/behaviour/toggle.rs | 3 + swarm/src/connection.rs | 11 +- swarm/src/connection/pool.rs | 16 +- swarm/src/dial_opts.rs | 79 ++-- swarm/src/dummy.rs | 2 + swarm/src/lib.rs | 76 ++- swarm/src/test.rs | 12 +- {core => swarm}/src/translation.rs | 7 +- swarm/tests/connection_close.rs | 2 + swarm/tests/listener.rs | 7 +- swarm/tests/swarm_derive.rs | 4 +- transports/dns/CHANGELOG.md | 5 + transports/dns/Cargo.toml | 2 +- transports/dns/src/lib.rs | 67 ++- transports/quic/CHANGELOG.md | 5 + transports/quic/Cargo.toml | 2 +- transports/quic/src/transport.rs | 251 +++++----- transports/quic/tests/smoke.rs | 87 ++-- transports/quic/tests/stream_compliance.rs | 14 +- transports/tcp/CHANGELOG.md | 9 + transports/tcp/src/lib.rs | 447 ++++++------------ transports/uds/CHANGELOG.md | 5 + transports/uds/Cargo.toml | 2 +- transports/uds/src/lib.rs | 35 +- transports/webrtc-websys/CHANGELOG.md | 5 + transports/webrtc-websys/Cargo.toml | 2 +- transports/webrtc-websys/src/transport.rs | 22 +- transports/webrtc/CHANGELOG.md | 5 + transports/webrtc/Cargo.toml | 2 +- transports/webrtc/src/tokio/transport.rs | 31 +- transports/webrtc/tests/smoke.rs | 16 +- transports/websocket-websys/CHANGELOG.md | 4 + transports/websocket-websys/Cargo.toml | 2 +- transports/websocket-websys/src/lib.rs | 22 +- transports/websocket/CHANGELOG.md | 4 + transports/websocket/Cargo.toml | 2 +- transports/websocket/src/framed.rs | 38 +- transports/websocket/src/lib.rs | 29 +- transports/webtransport-websys/CHANGELOG.md | 5 + transports/webtransport-websys/Cargo.toml | 2 +- .../webtransport-websys/src/transport.rs | 25 +- wasm-tests/webtransport-tests/src/lib.rs | 51 +- 129 files changed, 1294 insertions(+), 1175 deletions(-) rename {core => swarm}/src/translation.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index b4d877e49d6..cf0cde790c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2659,7 +2659,7 @@ dependencies = [ [[package]] name = "libp2p-autonat" -version = "0.12.1" +version = "0.13.0" dependencies = [ "async-std", "async-trait", @@ -2698,7 +2698,7 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.41.3" +version = "0.42.0" dependencies = [ "async-std", "either", @@ -2729,7 +2729,7 @@ dependencies = [ [[package]] name = "libp2p-dcutr" -version = "0.11.1" +version = "0.12.0" dependencies = [ "async-std", "asynchronous-codec", @@ -2763,7 +2763,7 @@ dependencies = [ [[package]] name = "libp2p-dns" -version = "0.41.1" +version = "0.42.0" dependencies = [ "async-std", "async-std-resolver", @@ -2781,7 +2781,7 @@ dependencies = [ [[package]] name = "libp2p-floodsub" -version = "0.44.0" +version = "0.45.0" dependencies = [ "asynchronous-codec", "bytes", @@ -2926,7 +2926,7 @@ dependencies = [ [[package]] name = "libp2p-mdns" -version = "0.45.2" +version = "0.46.0" dependencies = [ "async-io 2.3.3", "async-std", @@ -3081,7 +3081,7 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.44.2" +version = "0.45.0" dependencies = [ "async-std", "either", @@ -3140,7 +3140,7 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.10.3" +version = "0.11.0" dependencies = [ "async-std", "bytes", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.17.3" +version = "0.18.0" dependencies = [ "asynchronous-codec", "bytes", @@ -3198,7 +3198,7 @@ dependencies = [ [[package]] name = "libp2p-rendezvous" -version = "0.14.1" +version = "0.15.0" dependencies = [ "async-trait", "asynchronous-codec", @@ -3228,7 +3228,7 @@ dependencies = [ [[package]] name = "libp2p-request-response" -version = "0.26.4" +version = "0.27.0" dependencies = [ "anyhow", "async-std", @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "libp2p-stream" -version = "0.1.0-alpha.1" +version = "0.2.0-alpha" dependencies = [ "futures", "libp2p-core", @@ -3395,7 +3395,7 @@ dependencies = [ [[package]] name = "libp2p-uds" -version = "0.40.0" +version = "0.41.0" dependencies = [ "async-std", "futures", @@ -3407,7 +3407,7 @@ dependencies = [ [[package]] name = "libp2p-upnp" -version = "0.2.2" +version = "0.3.0" dependencies = [ "futures", "futures-timer", @@ -3421,7 +3421,7 @@ dependencies = [ [[package]] name = "libp2p-webrtc" -version = "0.7.1-alpha" +version = "0.8.0-alpha" dependencies = [ "async-trait", "bytes", @@ -3472,7 +3472,7 @@ dependencies = [ [[package]] name = "libp2p-webrtc-websys" -version = "0.3.0-alpha" +version = "0.4.0-alpha" dependencies = [ "bytes", "futures", @@ -3492,7 +3492,7 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.43.2" +version = "0.44.0" dependencies = [ "async-std", "either", @@ -3515,7 +3515,7 @@ dependencies = [ [[package]] name = "libp2p-websocket-websys" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bytes", "futures", @@ -3534,7 +3534,7 @@ dependencies = [ [[package]] name = "libp2p-webtransport-websys" -version = "0.3.0" +version = "0.4.0" dependencies = [ "futures", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 52226f6c6e3..7eb1517d24c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,45 +76,44 @@ futures-bounded = { version = "0.2.4" } futures-rustls = { version = "0.26.0", default-features = false } libp2p = { version = "0.54.0", path = "libp2p" } libp2p-allow-block-list = { version = "0.3.0", path = "misc/allow-block-list" } -libp2p-autonat = { version = "0.12.1", path = "protocols/autonat" } +libp2p-autonat = { version = "0.13.0", path = "protocols/autonat" } libp2p-connection-limits = { version = "0.3.1", path = "misc/connection-limits" } -libp2p-core = { version = "0.41.3", path = "core" } -libp2p-dcutr = { version = "0.11.1", path = "protocols/dcutr" } -libp2p-dns = { version = "0.41.1", path = "transports/dns" } -libp2p-floodsub = { version = "0.44.0", path = "protocols/floodsub" } +libp2p-core = { version = "0.42.0", path = "core" } +libp2p-dcutr = { version = "0.12.0", path = "protocols/dcutr" } +libp2p-dns = { version = "0.42.0", path = "transports/dns" } +libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" } libp2p-gossipsub = { version = "0.47.0", path = "protocols/gossipsub" } libp2p-identify = { version = "0.45.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.9" } libp2p-kad = { version = "0.46.0", path = "protocols/kad" } -libp2p-mdns = { version = "0.45.2", path = "protocols/mdns" } +libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } libp2p-memory-connection-limits = { version = "0.2.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.14.2", path = "misc/metrics" } libp2p-mplex = { version = "0.41.0", path = "muxers/mplex" } -libp2p-muxer-test-harness = { path = "muxers/test-harness" } libp2p-noise = { version = "0.44.0", path = "transports/noise" } libp2p-perf = { version = "0.4.0", path = "protocols/perf" } -libp2p-ping = { version = "0.44.2", path = "protocols/ping" } +libp2p-ping = { version = "0.45.0", path = "protocols/ping" } libp2p-plaintext = { version = "0.41.0", path = "transports/plaintext" } libp2p-pnet = { version = "0.24.0", path = "transports/pnet" } -libp2p-quic = { version = "0.10.3", path = "transports/quic" } -libp2p-relay = { version = "0.17.3", path = "protocols/relay" } -libp2p-rendezvous = { version = "0.14.1", path = "protocols/rendezvous" } -libp2p-request-response = { version = "0.26.4", path = "protocols/request-response" } +libp2p-quic = { version = "0.11.0", path = "transports/quic" } +libp2p-relay = { version = "0.18.0", path = "protocols/relay" } +libp2p-rendezvous = { version = "0.15.0", path = "protocols/rendezvous" } +libp2p-request-response = { version = "0.27.0", path = "protocols/request-response" } libp2p-server = { version = "0.12.7", path = "misc/server" } -libp2p-stream = { version = "0.1.0-alpha.1", path = "protocols/stream" } +libp2p-stream = { version = "0.2.0-alpha", path = "protocols/stream" } libp2p-swarm = { version = "0.45.0", path = "swarm" } libp2p-swarm-derive = { version = "=0.34.2", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. libp2p-swarm-test = { version = "0.3.0", path = "swarm-test" } libp2p-tcp = { version = "0.42.0", path = "transports/tcp" } libp2p-tls = { version = "0.4.1", path = "transports/tls" } -libp2p-uds = { version = "0.40.0", path = "transports/uds" } -libp2p-upnp = { version = "0.2.2", path = "protocols/upnp" } -libp2p-webrtc = { version = "0.7.1-alpha", path = "transports/webrtc" } +libp2p-uds = { version = "0.41.0", path = "transports/uds" } +libp2p-upnp = { version = "0.3.0", path = "protocols/upnp" } +libp2p-webrtc = { version = "0.8.0-alpha", path = "transports/webrtc" } libp2p-webrtc-utils = { version = "0.2.1", path = "misc/webrtc-utils" } -libp2p-webrtc-websys = { version = "0.3.0-alpha", path = "transports/webrtc-websys" } -libp2p-websocket = { version = "0.43.2", path = "transports/websocket" } -libp2p-websocket-websys = { version = "0.3.3", path = "transports/websocket-websys" } -libp2p-webtransport-websys = { version = "0.3.0", path = "transports/webtransport-websys" } +libp2p-webrtc-websys = { version = "0.4.0-alpha", path = "transports/webrtc-websys" } +libp2p-websocket = { version = "0.44.0", path = "transports/websocket" } +libp2p-websocket-websys = { version = "0.4.0", path = "transports/websocket-websys" } +libp2p-webtransport-websys = { version = "0.4.0", path = "transports/webtransport-websys" } libp2p-yamux = { version = "0.45.2", path = "muxers/yamux" } multiaddr = "0.18.1" multihash = "0.19.1" diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index b7f3c3c4640..5ed4b4e181d 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.42.0 + +- Update `Transport::dial` function signature with a `DialOpts` param and remove `Transport::dial_as_listener`: + - `DialOpts` struct contains `PortUse` and `Endpoint`, + - `PortUse` allows controling port allocation of new connections (defaults to `PortUse::Reuse`) - + - Add `port_use` field to `ConnectedPoint` + - Set `endpoint` field in `DialOpts` to `Endpoint::Listener` to dial as a listener +- Remove `Transport::address_translation` and relocate functionality to `libp2p_swarm` + +See [PR 4568]. + ## 0.41.3 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/core/Cargo.toml b/core/Cargo.toml index 76021c15186..3f9bd540f23 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-core" edition = "2021" rust-version = { workspace = true } description = "Core traits and structs of libp2p" -version = "0.41.3" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/core/src/connection.rs b/core/src/connection.rs index ff613c5cce0..bb6639842c9 100644 --- a/core/src/connection.rs +++ b/core/src/connection.rs @@ -18,7 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::multiaddr::{Multiaddr, Protocol}; +use crate::{ + multiaddr::{Multiaddr, Protocol}, + transport::PortUse, +}; /// The endpoint roles associated with a peer-to-peer communication channel. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -80,6 +83,13 @@ pub enum ConnectedPoint { /// connection as a dialer and one peer dial the other and upgrade the /// connection _as a listener_ overriding its role. role_override: Endpoint, + /// Whether the port for the outgoing connection was reused from a listener + /// or a new port was allocated. This is useful for address translation. + /// + /// The port use is implemented on a best-effort basis. It is not guaranteed + /// that [`PortUse::Reuse`] actually reused a port. A good example is the case + /// where there is no listener available to reuse a port from. + port_use: PortUse, }, /// We received the node. Listener { @@ -133,6 +143,7 @@ impl ConnectedPoint { ConnectedPoint::Dialer { address, role_override: _, + port_use: _, } => address, ConnectedPoint::Listener { local_addr, .. } => local_addr, } diff --git a/core/src/either.rs b/core/src/either.rs index 3f79b2b37a9..2593174290c 100644 --- a/core/src/either.rs +++ b/core/src/either.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::muxing::StreamMuxerEvent; +use crate::transport::DialOpts; use crate::{ muxing::StreamMuxer, transport::{ListenerId, Transport, TransportError, TransportEvent}, @@ -172,48 +173,23 @@ where } } - fn dial(&mut self, addr: Multiaddr) -> Result> { - use TransportError::*; - match self { - Either::Left(a) => match a.dial(addr) { - Ok(connec) => Ok(EitherFuture::First(connec)), - Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), - Err(Other(err)) => Err(Other(Either::Left(err))), - }, - Either::Right(b) => match b.dial(addr) { - Ok(connec) => Ok(EitherFuture::Second(connec)), - Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), - Err(Other(err)) => Err(Other(Either::Right(err))), - }, - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, - ) -> Result> - where - Self: Sized, - { + opts: DialOpts, + ) -> Result> { use TransportError::*; match self { - Either::Left(a) => match a.dial_as_listener(addr) { + Either::Left(a) => match a.dial(addr, opts) { Ok(connec) => Ok(EitherFuture::First(connec)), Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), Err(Other(err)) => Err(Other(Either::Left(err))), }, - Either::Right(b) => match b.dial_as_listener(addr) { + Either::Right(b) => match b.dial(addr, opts) { Ok(connec) => Ok(EitherFuture::Second(connec)), Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), Err(Other(err)) => Err(Other(Either::Right(err))), }, } } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - match self { - Either::Left(a) => a.address_translation(server, observed), - Either::Right(b) => b.address_translation(server, observed), - } - } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 3ba153e1927..a42f56773df 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -45,8 +45,6 @@ mod proto { pub use multiaddr; pub type Negotiated = multistream_select::Negotiated; -mod translation; - pub mod connection; pub mod either; pub mod muxing; @@ -62,7 +60,6 @@ pub use multihash; pub use muxing::StreamMuxer; pub use peer_record::PeerRecord; pub use signed_envelope::SignedEnvelope; -pub use translation::address_translation; pub use transport::Transport; pub use upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; diff --git a/core/src/transport.rs b/core/src/transport.rs index 2246f3db747..28ce2dbf650 100644 --- a/core/src/transport.rs +++ b/core/src/transport.rs @@ -48,7 +48,7 @@ pub mod upgrade; mod boxed; mod optional; -use crate::ConnectedPoint; +use crate::{ConnectedPoint, Endpoint}; pub use self::boxed::Boxed; pub use self::choice::OrTransport; @@ -58,6 +58,30 @@ pub use self::upgrade::Upgrade; static NEXT_LISTENER_ID: AtomicUsize = AtomicUsize::new(1); +/// The port use policy for a new connection. +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] +pub enum PortUse { + /// Always allocate a new port for the dial. + New, + /// Best effor reusing of an existing port. + /// + /// If there is no listener present that can be used to dial, a new port is allocated. + #[default] + Reuse, +} + +/// Options to customize the behaviour during dialing. +#[derive(Debug, Copy, Clone)] +pub struct DialOpts { + /// The endpoint establishing a new connection. + /// + /// When attempting a hole-punch, both parties simultaneously "dial" each other but one party has to be the "listener" on the final connection. + /// This option specifies the role of this node in the final connection. + pub role: Endpoint, + /// The port use policy for a new connection. + pub port_use: PortUse, +} + /// A transport provides connection-oriented communication between two peers /// through ordered streams of data (i.e. connections). /// @@ -129,16 +153,10 @@ pub trait Transport { /// /// If [`TransportError::MultiaddrNotSupported`] is returned, it may be desirable to /// try an alternative [`Transport`], if available. - fn dial(&mut self, addr: Multiaddr) -> Result>; - - /// As [`Transport::dial`] but has the local node act as a listener on the outgoing connection. - /// - /// This option is needed for NAT and firewall hole punching. - /// - /// See [`ConnectedPoint::Dialer`] for related option. - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result>; /// Poll for [`TransportEvent`]s. @@ -157,24 +175,6 @@ pub trait Transport { cx: &mut Context<'_>, ) -> Poll>; - /// Performs a transport-specific mapping of an address `observed` by a remote onto a - /// local `listen` address to yield an address for the local node that may be reachable - /// for other peers. - /// - /// This is relevant for transports where Network Address Translation (NAT) can occur - /// so that e.g. the peer is observed at a different IP than the IP of the local - /// listening address. See also [`address_translation`][crate::address_translation]. - /// - /// Within [`libp2p::Swarm`]() this is - /// used when extending the listening addresses of the local peer with external addresses - /// observed by remote peers. - /// On transports where this is not relevant (i.e. no NATs are present) `None` should be - /// returned for the sake of de-duplication. - /// - /// Note: if the listen or observed address is not a valid address of this transport, - /// `None` should be returned as well. - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option; - /// Boxes the transport, including custom transport errors. fn boxed(self) -> boxed::Boxed where diff --git a/core/src/transport/and_then.rs b/core/src/transport/and_then.rs index 6e0c7e32067..e85703f77fb 100644 --- a/core/src/transport/and_then.rs +++ b/core/src/transport/and_then.rs @@ -19,8 +19,8 @@ // DEALINGS IN THE SOFTWARE. use crate::{ - connection::{ConnectedPoint, Endpoint}, - transport::{ListenerId, Transport, TransportError, TransportEvent}, + connection::ConnectedPoint, + transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}, }; use either::Either; use futures::prelude::*; @@ -68,32 +68,14 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let dialed_fut = self - .transport - .dial(addr.clone()) - .map_err(|err| err.map(Either::Left))?; - let future = AndThenFuture { - inner: Either::Left(Box::pin(dialed_fut)), - args: Some(( - self.fun.clone(), - ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - }, - )), - _marker: PhantomPinned, - }; - Ok(future) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let dialed_fut = self .transport - .dial_as_listener(addr.clone()) + .dial(addr.clone(), opts) .map_err(|err| err.map(Either::Left))?; let future = AndThenFuture { inner: Either::Left(Box::pin(dialed_fut)), @@ -101,7 +83,8 @@ where self.fun.clone(), ConnectedPoint::Dialer { address: addr, - role_override: Endpoint::Listener, + role_override: opts.role, + port_use: opts.port_use, }, )), _marker: PhantomPinned, @@ -109,10 +92,6 @@ where Ok(future) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/boxed.rs b/core/src/transport/boxed.rs index 1cede676c8e..596ab262221 100644 --- a/core/src/transport/boxed.rs +++ b/core/src/transport/boxed.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use futures::{prelude::*, stream::FusedStream}; use multiaddr::Multiaddr; use std::{ @@ -58,9 +58,11 @@ trait Abstract { addr: Multiaddr, ) -> Result<(), TransportError>; fn remove_listener(&mut self, id: ListenerId) -> bool; - fn dial(&mut self, addr: Multiaddr) -> Result, TransportError>; - fn dial_as_listener(&mut self, addr: Multiaddr) -> Result, TransportError>; - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option; + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result, TransportError>; fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -86,24 +88,17 @@ where Transport::remove_listener(self, id) } - fn dial(&mut self, addr: Multiaddr) -> Result, TransportError> { - let fut = Transport::dial(self, addr) - .map(|r| r.map_err(box_err)) - .map_err(|e| e.map(box_err))?; - Ok(Box::pin(fut) as Dial<_>) - } - - fn dial_as_listener(&mut self, addr: Multiaddr) -> Result, TransportError> { - let fut = Transport::dial_as_listener(self, addr) + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result, TransportError> { + let fut = Transport::dial(self, addr, opts) .map(|r| r.map_err(box_err)) .map_err(|e| e.map(box_err))?; Ok(Box::pin(fut) as Dial<_>) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - Transport::address_translation(self, server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -143,19 +138,12 @@ impl Transport for Boxed { self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.inner.dial(addr) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - self.inner.dial_as_listener(addr) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) + self.inner.dial(addr, opts) } fn poll( diff --git a/core/src/transport/choice.rs b/core/src/transport/choice.rs index aa3acfc3231..4339f6bba71 100644 --- a/core/src/transport/choice.rs +++ b/core/src/transport/choice.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::either::EitherFuture; -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use either::Either; use futures::future; use multiaddr::Multiaddr; @@ -92,19 +92,23 @@ where self.0.remove_listener(id) || self.1.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { tracing::trace!( address=%addr, - "Attempting to dial address using {}", + "Attempting to dial using {}", std::any::type_name::() ); - let addr = match self.0.dial(addr) { + let addr = match self.0.dial(addr, opts) { Ok(connec) => return Ok(EitherFuture::First(connec)), Err(TransportError::MultiaddrNotSupported(addr)) => { tracing::debug!( address=%addr, - "Failed to dial address using {}", - std::any::type_name::() + "Failed to dial using {}", + std::any::type_name::(), ); addr } @@ -115,16 +119,16 @@ where tracing::trace!( address=%addr, - "Attempting to dial address using {}", + "Attempting to dial {}", std::any::type_name::() ); - let addr = match self.1.dial(addr) { + let addr = match self.1.dial(addr, opts) { Ok(connec) => return Ok(EitherFuture::Second(connec)), Err(TransportError::MultiaddrNotSupported(addr)) => { tracing::debug!( address=%addr, - "Failed to dial address using {}", - std::any::type_name::() + "Failed to dial using {}", + std::any::type_name::(), ); addr } @@ -136,37 +140,6 @@ where Err(TransportError::MultiaddrNotSupported(addr)) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - let addr = match self.0.dial_as_listener(addr) { - Ok(connec) => return Ok(EitherFuture::First(connec)), - Err(TransportError::MultiaddrNotSupported(addr)) => addr, - Err(TransportError::Other(err)) => { - return Err(TransportError::Other(Either::Left(err))) - } - }; - - let addr = match self.1.dial_as_listener(addr) { - Ok(connec) => return Ok(EitherFuture::Second(connec)), - Err(TransportError::MultiaddrNotSupported(addr)) => addr, - Err(TransportError::Other(err)) => { - return Err(TransportError::Other(Either::Right(err))) - } - }; - - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - if let Some(addr) = self.0.address_translation(server, observed) { - Some(addr) - } else { - self.1.address_translation(server, observed) - } - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/dummy.rs b/core/src/transport/dummy.rs index 0aab94b0396..72558d34a79 100644 --- a/core/src/transport/dummy.rs +++ b/core/src/transport/dummy.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use crate::Multiaddr; use futures::{prelude::*, task::Context, task::Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin}; @@ -71,21 +71,14 @@ impl Transport for DummyTransport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + _opts: DialOpts, ) -> Result> { Err(TransportError::MultiaddrNotSupported(addr)) } - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( self: Pin<&mut Self>, _: &mut Context<'_>, diff --git a/core/src/transport/global_only.rs b/core/src/transport/global_only.rs index 0671b0e9984..83774f37004 100644 --- a/core/src/transport/global_only.rs +++ b/core/src/transport/global_only.rs @@ -20,7 +20,7 @@ use crate::{ multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, }; use std::{ pin::Pin, @@ -287,47 +287,25 @@ impl crate::Transport for Transport { self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - match addr.iter().next() { - Some(Protocol::Ip4(a)) => { - if !ipv4_global::is_global(a) { - tracing::debug!(ip=%a, "Not dialing non global IP address"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - self.inner.dial(addr) - } - Some(Protocol::Ip6(a)) => { - if !ipv6_global::is_global(a) { - tracing::debug!(ip=%a, "Not dialing non global IP address"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - self.inner.dial(addr) - } - _ => { - tracing::debug!(address=%addr, "Not dialing unsupported Multiaddress"); - Err(TransportError::MultiaddrNotSupported(addr)) - } - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { match addr.iter().next() { Some(Protocol::Ip4(a)) => { if !ipv4_global::is_global(a) { - tracing::debug!(ip=?a, "Not dialing non global IP address"); + tracing::debug!(ip=%a, "Not dialing non global IP address"); return Err(TransportError::MultiaddrNotSupported(addr)); } - self.inner.dial_as_listener(addr) + self.inner.dial(addr, opts) } Some(Protocol::Ip6(a)) => { if !ipv6_global::is_global(a) { - tracing::debug!(ip=?a, "Not dialing non global IP address"); + tracing::debug!(ip=%a, "Not dialing non global IP address"); return Err(TransportError::MultiaddrNotSupported(addr)); } - self.inner.dial_as_listener(addr) + self.inner.dial(addr, opts) } _ => { tracing::debug!(address=%addr, "Not dialing unsupported Multiaddress"); @@ -336,10 +314,6 @@ impl crate::Transport for Transport { } } - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(listen, observed) - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/map.rs b/core/src/transport/map.rs index 553f3e6338d..9aab84ba8b1 100644 --- a/core/src/transport/map.rs +++ b/core/src/transport/map.rs @@ -18,8 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::transport::DialOpts; use crate::{ - connection::{ConnectedPoint, Endpoint}, + connection::ConnectedPoint, transport::{Transport, TransportError, TransportEvent}, }; use futures::prelude::*; @@ -73,26 +74,16 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let future = self.transport.dial(addr.clone())?; - let p = ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - }; - Ok(MapFuture { - inner: future, - args: Some((self.fun.clone(), p)), - }) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - let future = self.transport.dial_as_listener(addr.clone())?; + let future = self.transport.dial(addr.clone(), opts)?; let p = ConnectedPoint::Dialer { address: addr, - role_override: Endpoint::Listener, + role_override: opts.role, + port_use: opts.port_use, }; Ok(MapFuture { inner: future, @@ -100,10 +91,6 @@ where }) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/map_err.rs b/core/src/transport/map_err.rs index 56e1ebf2929..5d44af9af2e 100644 --- a/core/src/transport/map_err.rs +++ b/core/src/transport/map_err.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use futures::prelude::*; use multiaddr::Multiaddr; use std::{error, pin::Pin, task::Context, task::Poll}; @@ -65,23 +65,13 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let map = self.map.clone(); - match self.transport.dial(addr) { - Ok(future) => Ok(MapErrDial { - inner: future, - map: Some(map), - }), - Err(err) => Err(err.map(map)), - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let map = self.map.clone(); - match self.transport.dial_as_listener(addr) { + match self.transport.dial(addr, opts) { Ok(future) => Ok(MapErrDial { inner: future, map: Some(map), @@ -90,10 +80,6 @@ where } } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/memory.rs b/core/src/transport/memory.rs index 6e4b9322ee5..85680265e8b 100644 --- a/core/src/transport/memory.rs +++ b/core/src/transport/memory.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use fnv::FnvHashMap; use futures::{channel::mpsc, future::Ready, prelude::*, task::Context, task::Poll}; use multiaddr::{Multiaddr, Protocol}; @@ -208,7 +208,11 @@ impl Transport for MemoryTransport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + _opts: DialOpts, + ) -> Result> { let port = if let Ok(port) = parse_memory_addr(&addr) { if let Some(port) = NonZeroU64::new(port) { port @@ -222,17 +226,6 @@ impl Transport for MemoryTransport { DialFuture::new(port).ok_or(TransportError::Other(MemoryTransportError::Unreachable)) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -405,6 +398,8 @@ impl Drop for Chan { #[cfg(test)] mod tests { + use crate::{transport::PortUse, Endpoint}; + use super::*; #[test] @@ -489,7 +484,13 @@ mod tests { fn port_not_in_use() { let mut transport = MemoryTransport::default(); assert!(transport - .dial("/memory/810172461024613".parse().unwrap()) + .dial( + "/memory/810172461024613".parse().unwrap(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New + } + ) .is_err()); transport .listen_on( @@ -498,7 +499,13 @@ mod tests { ) .unwrap(); assert!(transport - .dial("/memory/810172461024613".parse().unwrap()) + .dial( + "/memory/810172461024613".parse().unwrap(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New + } + ) .is_ok()); } @@ -565,7 +572,17 @@ mod tests { let mut t2 = MemoryTransport::default(); let dialer = async move { - let mut socket = t2.dial(cloned_t1_addr).unwrap().await.unwrap(); + let mut socket = t2 + .dial( + cloned_t1_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) + .unwrap() + .await + .unwrap(); socket.write_all(&msg).await.unwrap(); }; @@ -601,7 +618,13 @@ mod tests { let dialer = async move { MemoryTransport::default() - .dial(listener_addr_cloned) + .dial( + listener_addr_cloned, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap() .await .unwrap(); @@ -652,7 +675,13 @@ mod tests { let dialer = async move { let chan = MemoryTransport::default() - .dial(listener_addr_cloned) + .dial( + listener_addr_cloned, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap() .await .unwrap(); diff --git a/core/src/transport/optional.rs b/core/src/transport/optional.rs index 839f55a4000..f18bfa441b0 100644 --- a/core/src/transport/optional.rs +++ b/core/src/transport/optional.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; use multiaddr::Multiaddr; use std::{pin::Pin, task::Context, task::Poll}; @@ -80,33 +80,18 @@ where } } - fn dial(&mut self, addr: Multiaddr) -> Result> { - if let Some(inner) = self.0.as_mut() { - inner.dial(addr) - } else { - Err(TransportError::MultiaddrNotSupported(addr)) - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { if let Some(inner) = self.0.as_mut() { - inner.dial_as_listener(addr) + inner.dial(addr, opts) } else { Err(TransportError::MultiaddrNotSupported(addr)) } } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - if let Some(inner) = &self.0 { - inner.address_translation(server, observed) - } else { - None - } - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/timeout.rs b/core/src/transport/timeout.rs index 0e8ab3f5201..830ed099629 100644 --- a/core/src/transport/timeout.rs +++ b/core/src/transport/timeout.rs @@ -24,6 +24,7 @@ //! underlying `Transport`. // TODO: add example +use crate::transport::DialOpts; use crate::{ transport::{ListenerId, TransportError, TransportEvent}, Multiaddr, Transport, @@ -99,24 +100,14 @@ where self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let dial = self - .inner - .dial(addr) - .map_err(|err| err.map(TransportTimeoutError::Other))?; - Ok(Timeout { - inner: dial, - timer: Delay::new(self.outgoing_timeout), - }) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let dial = self .inner - .dial_as_listener(addr) + .dial(addr, opts) .map_err(|err| err.map(TransportTimeoutError::Other))?; Ok(Timeout { inner: dial, @@ -124,10 +115,6 @@ where }) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/upgrade.rs b/core/src/transport/upgrade.rs index 9626312b5fb..66b9e7509af 100644 --- a/core/src/transport/upgrade.rs +++ b/core/src/transport/upgrade.rs @@ -22,6 +22,7 @@ pub use crate::upgrade::Version; +use crate::transport::DialOpts; use crate::{ connection::ConnectedPoint, muxing::{StreamMuxer, StreamMuxerBox}, @@ -335,21 +336,18 @@ where type ListenerUpgrade = T::ListenerUpgrade; type Dial = T::Dial; - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.0.dial(addr) + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { + self.0.dial(addr, opts) } fn remove_listener(&mut self, id: ListenerId) -> bool { self.0.remove_listener(id) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.0.dial_as_listener(addr) - } - fn listen_on( &mut self, id: ListenerId, @@ -358,10 +356,6 @@ where self.0.listen_on(id, addr) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.0.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -404,10 +398,14 @@ where type ListenerUpgrade = ListenerUpgradeFuture; type Dial = DialUpgradeFuture; - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { let future = self .inner - .dial(addr) + .dial(addr, opts) .map_err(|err| err.map(TransportUpgradeError::Transport))?; Ok(DialUpgradeFuture { future: Box::pin(future), @@ -419,20 +417,6 @@ where self.inner.remove_listener(id) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - let future = self - .inner - .dial_as_listener(addr) - .map_err(|err| err.map(TransportUpgradeError::Transport))?; - Ok(DialUpgradeFuture { - future: Box::pin(future), - upgrade: future::Either::Left(Some(self.upgrade.clone())), - }) - } - fn listen_on( &mut self, id: ListenerId, @@ -443,10 +427,6 @@ where .map_err(|err| err.map(TransportUpgradeError::Transport)) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/upgrade/ready.rs b/core/src/upgrade/ready.rs index 323f1f73f32..7e235902651 100644 --- a/core/src/upgrade/ready.rs +++ b/core/src/upgrade/ready.rs @@ -31,7 +31,7 @@ pub struct ReadyUpgrade

{ } impl

ReadyUpgrade

{ - pub fn new(protocol_name: P) -> Self { + pub const fn new(protocol_name: P) -> Self { Self { protocol_name } } } diff --git a/core/tests/transport_upgrade.rs b/core/tests/transport_upgrade.rs index a8872051618..d8bec6f2b59 100644 --- a/core/tests/transport_upgrade.rs +++ b/core/tests/transport_upgrade.rs @@ -19,10 +19,11 @@ // DEALINGS IN THE SOFTWARE. use futures::prelude::*; -use libp2p_core::transport::{ListenerId, MemoryTransport, Transport}; +use libp2p_core::transport::{DialOpts, ListenerId, MemoryTransport, PortUse, Transport}; use libp2p_core::upgrade::{ self, InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo, }; +use libp2p_core::Endpoint; use libp2p_identity as identity; use libp2p_mplex::MplexConfig; use libp2p_noise as noise; @@ -121,7 +122,17 @@ fn upgrade_pipeline() { }; let client = async move { - let (peer, _mplex) = dialer_transport.dial(listen_addr2).unwrap().await.unwrap(); + let (peer, _mplex) = dialer_transport + .dial( + listen_addr2, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) + .unwrap() + .await + .unwrap(); assert_eq!(peer, listener_id); }; diff --git a/examples/autonat/Cargo.toml b/examples/autonat/Cargo.toml index 9bdf0be393d..010b76623e0 100644 --- a/examples/autonat/Cargo.toml +++ b/examples/autonat/Cargo.toml @@ -3,7 +3,7 @@ name = "autonat-example" version = "0.1.0" edition = "2021" publish = false -license = "MIT" +license = "MIT or Apache-2.0" [package.metadata.release] release = false diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index 51df670f8a7..630d4b2b1f3 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -89,7 +89,7 @@ async fn main() -> Result<(), Box> { libp2p::SwarmBuilder::with_existing_identity(generate_ed25519(opts.secret_key_seed)) .with_tokio() .with_tcp( - tcp::Config::default().port_reuse(true).nodelay(true), + tcp::Config::default().nodelay(true), noise::Config::new, yamux::Config::default, )? diff --git a/examples/stream/Cargo.toml b/examples/stream/Cargo.toml index de46d204c77..ba31d4d9e13 100644 --- a/examples/stream/Cargo.toml +++ b/examples/stream/Cargo.toml @@ -12,7 +12,7 @@ release = false anyhow = "1" futures = { workspace = true } libp2p = { path = "../../libp2p", features = [ "tokio", "quic"] } -libp2p-stream = { path = "../../protocols/stream", version = "0.1.0-alpha" } +libp2p-stream = { path = "../../protocols/stream", version = "0.2.0-alpha" } rand = "0.8" tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } diff --git a/hole-punching-tests/src/main.rs b/hole-punching-tests/src/main.rs index bbecc948e08..02229e16262 100644 --- a/hole-punching-tests/src/main.rs +++ b/hole-punching-tests/src/main.rs @@ -64,7 +64,7 @@ async fn main() -> Result<()> { let mut swarm = libp2p::SwarmBuilder::with_new_identity() .with_tokio() .with_tcp( - tcp::Config::new().port_reuse(true).nodelay(true), + tcp::Config::new().nodelay(true), noise::Config::new, yamux::Config::default, )? diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index 977d9f91924..7e1b95c6e75 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -7,12 +7,23 @@ - Raise MSRV to 1.73. See [PR 5266](https://github.com/libp2p/rust-libp2p/pull/5266). +- Implement refactored `Transport`. + See [PR 4568]. + +- Move `address_translation` from `libp2p-core` to `libp2p-swarm` and `libp2p-identify`. To now get + address translation behaviour, i.e. discovery of extern address (candidates) based on connecting + to other peers, one needs to use `libp2p-identify` now. This pertains to you if your nodes can be + behind NATs and they aren't aware of their true external address. + See [PR 4568]. + - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). - Remove redundant async signature from builder methods. See [PR 5468](https://github.com/libp2p/rust-libp2p/pull/5468). +[PR 4568]: https://github.com/libp2p/rust-libp2p/pull/4568 + ## 0.53.2 - Allow `SwarmBuilder::with_bandwidth_metrics` after `SwarmBuilder::with_websocket`. diff --git a/misc/allow-block-list/src/lib.rs b/misc/allow-block-list/src/lib.rs index c1d31433db1..c877ab09c9b 100644 --- a/misc/allow-block-list/src/lib.rs +++ b/misc/allow-block-list/src/lib.rs @@ -61,6 +61,7 @@ //! # } //! ``` +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -225,6 +226,7 @@ where peer: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.state.enforce(&peer)?; diff --git a/misc/connection-limits/src/lib.rs b/misc/connection-limits/src/lib.rs index dbe68a8ad11..b02e52f25a1 100644 --- a/misc/connection-limits/src/lib.rs +++ b/misc/connection-limits/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::{ConnectionEstablished, DialFailure, ListenFailure}, @@ -278,6 +278,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.pending_outbound_connections.remove(&connection_id); @@ -569,6 +570,7 @@ mod tests { _peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result, ConnectionDenied> { Err(ConnectionDenied::new(std::io::Error::new( std::io::ErrorKind::Other, diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index 08c45154221..7b5803a61aa 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, @@ -177,6 +177,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/misc/memory-connection-limits/tests/util/mod.rs b/misc/memory-connection-limits/tests/util/mod.rs index f40ce319929..d18aa78fd22 100644 --- a/misc/memory-connection-limits/tests/util/mod.rs +++ b/misc/memory-connection-limits/tests/util/mod.rs @@ -20,7 +20,7 @@ use std::task::{Context, Poll}; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, @@ -102,6 +102,7 @@ impl NetworkBehaviour _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.handle_established(); Ok(dummy::ConnectionHandler) diff --git a/misc/metrics/src/bandwidth.rs b/misc/metrics/src/bandwidth.rs index ac6a4178ce9..8a0f54e5b65 100644 --- a/misc/metrics/src/bandwidth.rs +++ b/misc/metrics/src/bandwidth.rs @@ -7,7 +7,7 @@ use futures::{ }; use libp2p_core::{ muxing::{StreamMuxer, StreamMuxerEvent}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, Multiaddr, }; use libp2p_identity::PeerId; @@ -84,33 +84,20 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let metrics = ConnectionMetrics::from_family_and_addr(&self.metrics, &addr); - Ok(self - .transport - .dial(addr.clone())? - .map_ok(Box::new(|(peer_id, stream_muxer)| { - (peer_id, Muxer::new(stream_muxer, metrics)) - }))) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { let metrics = ConnectionMetrics::from_family_and_addr(&self.metrics, &addr); Ok(self .transport - .dial_as_listener(addr.clone())? + .dial(addr.clone(), dial_opts)? .map_ok(Box::new(|(peer_id, stream_muxer)| { (peer_id, Muxer::new(stream_muxer, metrics)) }))) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/misc/server/src/main.rs b/misc/server/src/main.rs index eb28b9ad5c2..820921beaed 100644 --- a/misc/server/src/main.rs +++ b/misc/server/src/main.rs @@ -71,7 +71,7 @@ async fn main() -> Result<(), Box> { let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_keypair) .with_tokio() .with_tcp( - tcp::Config::default().port_reuse(true).nodelay(true), + tcp::Config::default().nodelay(true), noise::Config::new, yamux::Config::default, )? diff --git a/muxers/mplex/benches/split_send_size.rs b/muxers/mplex/benches/split_send_size.rs index 0125d49dcef..44eafa884ac 100644 --- a/muxers/mplex/benches/split_send_size.rs +++ b/muxers/mplex/benches/split_send_size.rs @@ -28,6 +28,7 @@ use futures::prelude::*; use futures::{channel::oneshot, future::join}; use libp2p_core::muxing::StreamMuxerExt; use libp2p_core::transport::ListenerId; +use libp2p_core::Endpoint; use libp2p_core::{multiaddr::multiaddr, muxing, transport, upgrade, Multiaddr, Transport}; use libp2p_identity as identity; use libp2p_identity::PeerId; @@ -146,7 +147,17 @@ fn run( // Spawn and block on the sender, i.e. until all data is sent. let sender = async move { let addr = addr_receiver.await.unwrap(); - let (_peer, mut conn) = sender_trans.dial(addr).unwrap().await.unwrap(); + let (_peer, mut conn) = sender_trans + .dial( + addr, + transport::DialOpts { + role: Endpoint::Dialer, + port_use: transport::PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); // Just calling `poll_outbound` without `poll` is fine here because mplex makes progress through all `poll_` functions. It is hacky though. let mut stream = poll_fn(|cx| conn.poll_outbound_unpin(cx)).await.unwrap(); let mut off = 0; diff --git a/protocols/autonat/CHANGELOG.md b/protocols/autonat/CHANGELOG.md index 6a25dc173dd..2a799221f7c 100644 --- a/protocols/autonat/CHANGELOG.md +++ b/protocols/autonat/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.13.0 + +- Due to the refactor of `Transport` it's no longer required to create a seperate transport for +AutoNAT where port reuse is disabled. This information is now passed by the behaviour. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568). + + + ## 0.12.1 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 970dd27bc53..f047cb7d5ba 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" rust-version = { workspace = true } description = "NAT and firewall detection for libp2p" authors = ["David Craven ", "Elena Frank "] -version = "0.12.1" +version = "0.13.0" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a47852e5206..64bebfb6233 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -28,6 +28,7 @@ pub use as_client::{OutboundProbeError, OutboundProbeEvent}; use as_server::AsServer; pub use as_server::{InboundProbeError, InboundProbeEvent}; use futures_timer::Delay; +use libp2p_core::transport::PortUse; use libp2p_core::{multiaddr::Protocol, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::{ @@ -338,6 +339,7 @@ impl Behaviour { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + .. } => { if let Some(event) = self.as_server().on_outbound_connection(&peer, address) { self.pending_actions @@ -347,6 +349,7 @@ impl Behaviour { ConnectedPoint::Dialer { address: _, role_override: Endpoint::Listener, + .. } => { // Outgoing connection was dialed as a listener. In other words outgoing connection // was dialed as part of a hole punch. `libp2p-autonat` never attempts to hole @@ -512,9 +515,15 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_swarm_event(&mut self, event: FromSwarm) { diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs index af6be799e21..8f1d6642de5 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -135,6 +135,7 @@ impl<'a> HandleInnerEvent for AsServer<'a> { NonZeroU8::new(1).expect("1 > 0"), ) .addresses(addrs) + .allocate_new_port() .build(), }, ]) diff --git a/protocols/autonat/tests/test_server.rs b/protocols/autonat/tests/test_server.rs index b0610ef59a4..fd97b1a9132 100644 --- a/protocols/autonat/tests/test_server.rs +++ b/protocols/autonat/tests/test_server.rs @@ -92,6 +92,7 @@ async fn test_dial_back() { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + .. }, num_established, concurrent_dial_errors, @@ -300,6 +301,7 @@ async fn test_dial_multiple_addr() { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + .. }, concurrent_dial_errors, .. diff --git a/protocols/dcutr/CHANGELOG.md b/protocols/dcutr/CHANGELOG.md index 4865a0540bb..0ddc4aa1148 100644 --- a/protocols/dcutr/CHANGELOG.md +++ b/protocols/dcutr/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.0 + + + ## 0.11.1 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/protocols/dcutr/Cargo.toml b/protocols/dcutr/Cargo.toml index 24a5560f5b5..cd46840caf3 100644 --- a/protocols/dcutr/Cargo.toml +++ b/protocols/dcutr/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-dcutr" edition = "2021" rust-version = { workspace = true } description = "Direct connection upgrade through relay" -version = "0.11.1" +version = "0.12.0" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/dcutr/src/behaviour.rs b/protocols/dcutr/src/behaviour.rs index 4409b5f6a64..574c96205fa 100644 --- a/protocols/dcutr/src/behaviour.rs +++ b/protocols/dcutr/src/behaviour.rs @@ -24,6 +24,7 @@ use crate::{handler, protocol}; use either::Either; use libp2p_core::connection::ConnectedPoint; use libp2p_core::multiaddr::Protocol; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::{ConnectionClosed, DialFailure, FromSwarm}; @@ -206,12 +207,14 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { if is_relayed(addr) { return Ok(Either::Left(handler::relayed::Handler::new( ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }, self.observed_addresses(), ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. diff --git a/protocols/floodsub/CHANGELOG.md b/protocols/floodsub/CHANGELOG.md index 8e3cb70ddf1..4192e0ea58d 100644 --- a/protocols/floodsub/CHANGELOG.md +++ b/protocols/floodsub/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.45.0 + + + ## 0.44.0 - Change publish to require `data: impl Into` to internally avoid any costly cloning / allocation. diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index 8bcbc4a3557..9f0557c6d01 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-floodsub" edition = "2021" rust-version = { workspace = true } description = "Floodsub protocol for libp2p" -version = "0.44.0" +version = "0.45.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/floodsub/src/layer.rs b/protocols/floodsub/src/layer.rs index 35711408a8d..1a70d2213b2 100644 --- a/protocols/floodsub/src/layer.rs +++ b/protocols/floodsub/src/layer.rs @@ -27,6 +27,7 @@ use crate::FloodsubConfig; use bytes::Bytes; use cuckoofilter::{CuckooError, CuckooFilter}; use fnv::FnvHashSet; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}; @@ -346,6 +347,7 @@ impl NetworkBehaviour for Floodsub { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Default::default()) } diff --git a/protocols/gossipsub/CHANGELOG.md b/protocols/gossipsub/CHANGELOG.md index 7c43b98f0a7..8e115052d31 100644 --- a/protocols/gossipsub/CHANGELOG.md +++ b/protocols/gossipsub/CHANGELOG.md @@ -1,4 +1,6 @@ ## 0.47.0 + + - Add ConnectionError to FromSwarm::ConnectionClosed. See [PR 5485](https://github.com/libp2p/rust-libp2p/pull/5485). @@ -9,7 +11,7 @@ ## 0.46.1 - Deprecate `Rpc` in preparation for removing it from the public API because it is an internal type. - See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). + See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). ## 0.46.0 diff --git a/protocols/gossipsub/src/behaviour.rs b/protocols/gossipsub/src/behaviour.rs index 7980f362acf..0d1af1ada0c 100644 --- a/protocols/gossipsub/src/behaviour.rs +++ b/protocols/gossipsub/src/behaviour.rs @@ -34,7 +34,9 @@ use futures_ticker::Ticker; use prometheus_client::registry::Registry; use rand::{seq::SliceRandom, thread_rng}; -use libp2p_core::{multiaddr::Protocol::Ip4, multiaddr::Protocol::Ip6, Endpoint, Multiaddr}; +use libp2p_core::{ + multiaddr::Protocol::Ip4, multiaddr::Protocol::Ip6, transport::PortUse, Endpoint, Multiaddr, +}; use libp2p_identity::Keypair; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -3012,6 +3014,7 @@ where _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Handler::new(self.config.protocol_config())) } diff --git a/protocols/gossipsub/src/behaviour/tests.rs b/protocols/gossipsub/src/behaviour/tests.rs index fe861a674dd..c44152ed2f9 100644 --- a/protocols/gossipsub/src/behaviour/tests.rs +++ b/protocols/gossipsub/src/behaviour/tests.rs @@ -206,6 +206,7 @@ where ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } } else { ConnectedPoint::Listener { @@ -256,6 +257,7 @@ where let fake_endpoint = ConnectedPoint::Dialer { address: Multiaddr::empty(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }; // this is not relevant // peer_connections.connections should never be empty. @@ -562,6 +564,7 @@ fn test_join() { endpoint: &ConnectedPoint::Dialer { address: "/ip4/127.0.0.1".parse::().unwrap(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 0, @@ -4052,6 +4055,7 @@ fn test_scoring_p6() { endpoint: &ConnectedPoint::Dialer { address: addr.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 0, @@ -4073,6 +4077,7 @@ fn test_scoring_p6() { endpoint: &ConnectedPoint::Dialer { address: addr2.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 1, @@ -4103,6 +4108,7 @@ fn test_scoring_p6() { endpoint: &ConnectedPoint::Dialer { address: addr, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 2, diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index b8ab3f8df50..275f7114b28 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.45.0 +- Address translation is moved here from `libp2p-core`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + + + - Add `ConnectionId` in `Event`. See [PR 4981](https://github.com/libp2p/rust-libp2p/pull/4981). diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index 92a0dc46103..6590ccd6588 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -20,6 +20,8 @@ use crate::handler::{self, Handler, InEvent}; use crate::protocol::{Info, UpgradeError}; +use libp2p_core::multiaddr::Protocol; +use libp2p_core::transport::PortUse; use libp2p_core::{multiaddr, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_identity::PublicKey; @@ -27,6 +29,7 @@ use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, DialFailu use libp2p_swarm::{ ConnectionDenied, DialError, ExternalAddresses, ListenAddresses, NetworkBehaviour, NotifyHandler, PeerAddresses, StreamUpgradeError, THandlerInEvent, ToSwarm, + _address_translation, }; use libp2p_swarm::{ConnectionId, THandler, THandlerOutEvent}; @@ -39,6 +42,50 @@ use std::{ time::Duration, }; +/// Whether an [`Multiaddr`] is a valid for the QUIC transport. +fn is_quic_addr(addr: &Multiaddr, v1: bool) -> bool { + use Protocol::*; + let mut iter = addr.iter(); + let Some(first) = iter.next() else { + return false; + }; + let Some(second) = iter.next() else { + return false; + }; + let Some(third) = iter.next() else { + return false; + }; + let fourth = iter.next(); + let fifth = iter.next(); + + matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) + && matches!(second, Udp(_)) + && if v1 { + matches!(third, QuicV1) + } else { + matches!(third, Quic) + } + && matches!(fourth, Some(P2p(_)) | None) + && fifth.is_none() +} + +fn is_tcp_addr(addr: &Multiaddr) -> bool { + use Protocol::*; + + let mut iter = addr.iter(); + + let first = match iter.next() { + None => return false, + Some(p) => p, + }; + let second = match iter.next() { + None => return false, + Some(p) => p, + }; + + matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) && matches!(second, Tcp(_)) +} + /// Network behaviour that automatically identifies nodes periodically, returns information /// about them, and answers identify queries from other nodes. /// @@ -52,6 +99,9 @@ pub struct Behaviour { /// The address a remote observed for us. our_observed_addresses: HashMap, + /// The outbound connections established without port reuse (require translation) + outbound_connections_with_ephemeral_port: HashSet, + /// Pending events to be emitted when polled. events: VecDeque>, /// The addresses of all peers that we have discovered. @@ -153,6 +203,7 @@ impl Behaviour { config, connected: HashMap::new(), our_observed_addresses: Default::default(), + outbound_connections_with_ephemeral_port: Default::default(), events: VecDeque::new(), discovered_peers, listen_addresses: Default::default(), @@ -213,6 +264,58 @@ impl Behaviour { .cloned() .collect() } + + fn emit_new_external_addr_candidate_event( + &mut self, + connection_id: ConnectionId, + observed: &Multiaddr, + ) { + if self + .outbound_connections_with_ephemeral_port + .contains(&connection_id) + { + // Apply address translation to the candidate address. + // For TCP without port-reuse, the observed address contains an ephemeral port which needs to be replaced by the port of a listen address. + let translated_addresses = { + let mut addrs: Vec<_> = self + .listen_addresses + .iter() + .filter_map(|server| { + if (is_tcp_addr(server) && is_tcp_addr(observed)) + || (is_quic_addr(server, true) && is_quic_addr(observed, true)) + || (is_quic_addr(server, false) && is_quic_addr(observed, false)) + { + _address_translation(server, observed) + } else { + None + } + }) + .collect(); + + // remove duplicates + addrs.sort_unstable(); + addrs.dedup(); + addrs + }; + + // If address translation yielded nothing, broadcast the original candidate address. + if translated_addresses.is_empty() { + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(observed.clone())); + } else { + for addr in translated_addresses { + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(addr)); + } + } + return; + } + + // outgoing connection dialed with port reuse + // incomming connection + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(observed.clone())); + } } impl NetworkBehaviour for Behaviour { @@ -239,11 +342,25 @@ impl NetworkBehaviour for Behaviour { fn handle_established_outbound_connection( &mut self, - _: ConnectionId, + connection_id: ConnectionId, peer: PeerId, addr: &Multiaddr, _: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { + // Contrary to inbound events, outbound events are full-p2p qualified + // so we remove /p2p/ in order to be homogeneous + // this will avoid Autonatv2 to probe twice the same address (fully-p2p-qualified + not fully-p2p-qualified) + let mut addr = addr.clone(); + if matches!(addr.iter().last(), Some(multiaddr::Protocol::P2p(_))) { + addr.pop(); + } + + if port_use == PortUse::New { + self.outbound_connections_with_ephemeral_port + .insert(connection_id); + } + Ok(Handler::new( self.config.interval, peer, @@ -289,8 +406,7 @@ impl NetworkBehaviour for Behaviour { match self.our_observed_addresses.entry(connection_id) { Entry::Vacant(not_yet_observed) => { not_yet_observed.insert(observed.clone()); - self.events - .push_back(ToSwarm::NewExternalAddrCandidate(observed)); + self.emit_new_external_addr_candidate_event(connection_id, &observed); } Entry::Occupied(already_observed) if already_observed.get() == &observed => { // No-op, we already observed this address. @@ -303,8 +419,7 @@ impl NetworkBehaviour for Behaviour { ); *already_observed.get_mut() = observed.clone(); - self.events - .push_back(ToSwarm::NewExternalAddrCandidate(observed)); + self.emit_new_external_addr_candidate_event(connection_id, &observed); } } } @@ -403,6 +518,8 @@ impl NetworkBehaviour for Behaviour { } self.our_observed_addresses.remove(&connection_id); + self.outbound_connections_with_ephemeral_port + .remove(&connection_id); } FromSwarm::DialFailure(DialFailure { peer_id, error, .. }) => { if let (Some(peer_id), Some(cache), DialError::Transport(errors)) = diff --git a/protocols/identify/tests/smoke.rs b/protocols/identify/tests/smoke.rs index dd92d10979a..49ae9f0726f 100644 --- a/protocols/identify/tests/smoke.rs +++ b/protocols/identify/tests/smoke.rs @@ -1,5 +1,4 @@ use futures::StreamExt; -use libp2p_core::multiaddr::Protocol; use libp2p_identify as identify; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; @@ -59,12 +58,7 @@ async fn periodic_identify() { assert_eq!(s1_info.protocol_version, "c"); assert_eq!(s1_info.agent_version, "d"); assert!(!s1_info.protocols.is_empty()); - assert_eq!( - s1_info.observed_addr, - swarm1_memory_listen - .clone() - .with(Protocol::P2p(swarm1_peer_id)) - ); + assert_eq!(s1_info.observed_addr, swarm1_memory_listen); assert!(s1_info.listen_addrs.contains(&swarm2_tcp_listen_addr)); assert!(s1_info.listen_addrs.contains(&swarm2_memory_listen)); diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index f88484bafa1..a5b404ae6bd 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -21,6 +21,7 @@ See [PR 5317](https://github.com/libp2p/rust-libp2p/pull/5317). - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + - Correctly handle the `NoKnownPeers` error on automatic bootstrap. See [PR 5349](https://github.com/libp2p/rust-libp2p/pull/5349). - Improve automatic bootstrap triggering conditions: diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 069eec1a5b4..8d6f86d7e0f 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -35,7 +35,7 @@ use crate::record::{ use crate::{bootstrap, K_VALUE}; use crate::{jobs::*, protocol}; use fnv::FnvHashSet; -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::{ AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm, @@ -2168,10 +2168,12 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let connected_point = ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }; let mut handler = Handler::new( diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index b82ec966f89..c4859f2f138 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -1310,6 +1310,7 @@ fn network_behaviour_on_address_change() { let endpoint = ConnectedPoint::Dialer { address: old_address.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }; // Mimick a connection being established. @@ -1360,10 +1361,12 @@ fn network_behaviour_on_address_change() { old: &ConnectedPoint::Dialer { address: old_address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, new: &ConnectedPoint::Dialer { address: new_address.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, })); diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index 18fe0e76e6f..67b1d669f60 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.46.0 + + ## 0.45.2 - Add `#[track_caller]` on all `spawn` wrappers. diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 724c8818621..0c8229465d6 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-mdns" edition = "2021" rust-version = { workspace = true } -version = "0.45.2" +version = "0.46.0" description = "Implementation of the libp2p mDNS discovery method" authors = ["Parity Technologies "] license = "MIT" diff --git a/protocols/mdns/src/behaviour.rs b/protocols/mdns/src/behaviour.rs index d4a0a707a01..6355fbf4943 100644 --- a/protocols/mdns/src/behaviour.rs +++ b/protocols/mdns/src/behaviour.rs @@ -28,6 +28,7 @@ use crate::Config; use futures::channel::mpsc; use futures::{Stream, StreamExt}; use if_watch::IfEvent; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::FromSwarm; @@ -263,6 +264,7 @@ where _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/protocols/mdns/src/behaviour/iface/query.rs b/protocols/mdns/src/behaviour/iface/query.rs index eeb699fca6b..70b84816d0f 100644 --- a/protocols/mdns/src/behaviour/iface/query.rs +++ b/protocols/mdns/src/behaviour/iface/query.rs @@ -24,10 +24,9 @@ use hickory_proto::{ op::Message, rr::{Name, RData}, }; -use libp2p_core::{ - address_translation, - multiaddr::{Multiaddr, Protocol}, -}; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_swarm::_address_translation; + use libp2p_identity::PeerId; use std::time::Instant; use std::{fmt, net::SocketAddr, str, time::Duration}; @@ -179,7 +178,7 @@ impl MdnsResponse { let new_expiration = now + peer.ttl(); peer.addresses().iter().filter_map(move |address| { - let new_addr = address_translation(address, &observed)?; + let new_addr = _address_translation(address, &observed)?; let new_addr = new_addr.with_p2p(*peer.id()).ok()?; Some((*peer.id(), new_addr, new_expiration)) diff --git a/protocols/perf/CHANGELOG.md b/protocols/perf/CHANGELOG.md index b347f21e9e0..abeca9fad25 100644 --- a/protocols/perf/CHANGELOG.md +++ b/protocols/perf/CHANGELOG.md @@ -1,4 +1,6 @@ ## 0.4.0 + + - Add ConnectionError to FromSwarm::ConnectionClosed. See [PR 5485](https://github.com/libp2p/rust-libp2p/pull/5485). diff --git a/protocols/perf/src/client/behaviour.rs b/protocols/perf/src/client/behaviour.rs index 5e430f8f0c1..1b181557acc 100644 --- a/protocols/perf/src/client/behaviour.rs +++ b/protocols/perf/src/client/behaviour.rs @@ -25,7 +25,7 @@ use std::{ task::{Context, Poll}, }; -use libp2p_core::Multiaddr; +use libp2p_core::{transport::PortUse, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ derive_prelude::ConnectionEstablished, ConnectionClosed, ConnectionId, FromSwarm, @@ -92,6 +92,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &Multiaddr, _role_override: libp2p_core::Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(Handler::default()) } diff --git a/protocols/perf/src/server/behaviour.rs b/protocols/perf/src/server/behaviour.rs index da24d763606..5408029e85d 100644 --- a/protocols/perf/src/server/behaviour.rs +++ b/protocols/perf/src/server/behaviour.rs @@ -25,6 +25,7 @@ use std::{ task::{Context, Poll}, }; +use libp2p_core::transport::PortUse; use libp2p_identity::PeerId; use libp2p_swarm::{ ConnectionId, FromSwarm, NetworkBehaviour, THandlerInEvent, THandlerOutEvent, ToSwarm, @@ -71,6 +72,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &libp2p_core::Multiaddr, _role_override: libp2p_core::Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(Handler::default()) } diff --git a/protocols/ping/CHANGELOG.md b/protocols/ping/CHANGELOG.md index 7c7d7f3a54f..c0a124333e9 100644 --- a/protocols/ping/CHANGELOG.md +++ b/protocols/ping/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.45.0 + + + ## 0.44.2 - Use `web-time` instead of `instant`. @@ -12,7 +16,6 @@ `ping::Event` can now be shared between threads. See [PR 5250] - [PR 5250]: https://github.com/libp2p/rust-libp2p/pull/5250 ## 0.44.0 diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index 79c0a43aa69..2c347adb2c4 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-ping" edition = "2021" rust-version = { workspace = true } description = "Ping protocol for libp2p" -version = "0.44.2" +version = "0.45.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/ping/src/lib.rs b/protocols/ping/src/lib.rs index 5eaa6d4952a..82f240cab6b 100644 --- a/protocols/ping/src/lib.rs +++ b/protocols/ping/src/lib.rs @@ -51,6 +51,7 @@ mod handler; mod protocol; use handler::Handler; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ @@ -124,6 +125,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Handler::new(self.config.clone())) } diff --git a/protocols/ping/src/protocol.rs b/protocols/ping/src/protocol.rs index 8fc18505d35..566e5e258e2 100644 --- a/protocols/ping/src/protocol.rs +++ b/protocols/ping/src/protocol.rs @@ -86,7 +86,8 @@ mod tests { use futures::StreamExt; use libp2p_core::{ multiaddr::multiaddr, - transport::{memory::MemoryTransport, ListenerId, Transport}, + transport::{memory::MemoryTransport, DialOpts, ListenerId, PortUse, Transport}, + Endpoint, }; #[test] @@ -110,7 +111,13 @@ mod tests { async_std::task::block_on(async move { let c = MemoryTransport::new() - .dial(listener_addr) + .dial( + listener_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) .unwrap() .await .unwrap(); diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index 97638d1ae6a..fc71ccedad5 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.18.0 + + + ## 0.17.3 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 4981c94d9c8..1a0e7836158 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-relay" edition = "2021" rust-version = { workspace = true } description = "Communications relaying for libp2p" -version = "0.17.3" +version = "0.18.0" authors = ["Parity Technologies ", "Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index cf0e76e3662..463febf9f2f 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -28,6 +28,7 @@ use crate::proto; use crate::protocol::{inbound_hop, outbound_stop}; use either::Either; use libp2p_core::multiaddr::Protocol; +use libp2p_core::transport::PortUse; use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::{ConnectionClosed, FromSwarm}; @@ -328,6 +329,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { if addr.is_relayed() { // Deny all substreams on relayed connection. @@ -343,6 +345,7 @@ impl NetworkBehaviour for Behaviour { ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }, ))) } diff --git a/protocols/relay/src/priv_client.rs b/protocols/relay/src/priv_client.rs index e414852ef81..f8d1d9c9eb2 100644 --- a/protocols/relay/src/priv_client.rs +++ b/protocols/relay/src/priv_client.rs @@ -34,6 +34,7 @@ use futures::io::{AsyncRead, AsyncWrite}; use futures::ready; use futures::stream::StreamExt; use libp2p_core::multiaddr::Protocol; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}; @@ -178,6 +179,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { if addr.is_relayed() { return Ok(Either::Right(dummy::ConnectionHandler)); diff --git a/protocols/relay/src/priv_client/transport.rs b/protocols/relay/src/priv_client/transport.rs index b4374aa4672..ec1e8ca5fb8 100644 --- a/protocols/relay/src/priv_client/transport.rs +++ b/protocols/relay/src/priv_client/transport.rs @@ -31,7 +31,7 @@ use futures::sink::SinkExt; use futures::stream::SelectAll; use futures::stream::{Stream, StreamExt}; use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use libp2p_core::transport::{ListenerId, TransportError, TransportEvent}; +use libp2p_core::transport::{DialOpts, ListenerId, TransportError, TransportEvent}; use libp2p_identity::PeerId; use std::collections::VecDeque; use std::pin::Pin; @@ -49,7 +49,7 @@ use thiserror::Error; /// 1. Establish relayed connections by dialing `/p2p-circuit` addresses. /// /// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport}; +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, transport::{DialOpts, PortUse}, connection::Endpoint}; /// # use libp2p_core::transport::memory::MemoryTransport; /// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay as relay; @@ -66,7 +66,10 @@ use thiserror::Error; /// .with(Protocol::P2p(relay_id.into())) // Relay peer id. /// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. /// .with(Protocol::P2p(destination_id.into())); // Destination peer id. -/// transport.dial(dst_addr_via_relay).unwrap(); +/// transport.dial(dst_addr_via_relay, DialOpts { +/// port_use: PortUse::Reuse, +/// role: Endpoint::Dialer, +/// }).unwrap(); /// ``` /// /// 3. Listen for incoming relayed connections via specific relay. @@ -165,7 +168,19 @@ impl libp2p_core::Transport for Transport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + // [`Endpoint::Listener`] is used for NAT and firewall + // traversal. One would coordinate such traversal via a previously + // established relayed connection, but never using a relayed connection + // itself. + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let RelayedMultiaddr { relay_peer_id, relay_addr, @@ -198,24 +213,6 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> - where - Self: Sized, - { - // [`Transport::dial_as_listener`] is used for NAT and firewall - // traversal. One would coordinate such traversal via a previously - // established relayed connection, but never using a relayed connection - // itself. - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/protocols/rendezvous/CHANGELOG.md b/protocols/rendezvous/CHANGELOG.md index 4aa18985f1e..1ed9e5bc3b0 100644 --- a/protocols/rendezvous/CHANGELOG.md +++ b/protocols/rendezvous/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.15.0 + + + ## 0.14.1 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index 6879a4093ce..32b233813e3 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-rendezvous" edition = "2021" rust-version = { workspace = true } description = "Rendezvous protocol for libp2p" -version = "0.14.1" +version = "0.15.0" authors = ["The COMIT guys "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 92d7884758b..a794252ff0b 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -24,6 +24,7 @@ use futures::future::BoxFuture; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr, PeerRecord}; use libp2p_identity::{Keypair, PeerId, SigningError}; use libp2p_request_response::{OutboundRequestId, ProtocolSupport}; @@ -208,9 +209,15 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_connection_handler_event( diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index bee91f28e88..45a525d9573 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -24,6 +24,7 @@ use bimap::BiMap; use futures::future::BoxFuture; use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::ProtocolSupport; @@ -139,9 +140,15 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_connection_handler_event( diff --git a/protocols/request-response/CHANGELOG.md b/protocols/request-response/CHANGELOG.md index beee817f024..db0d9126516 100644 --- a/protocols/request-response/CHANGELOG.md +++ b/protocols/request-response/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.27.0 + + + ## 0.26.4 - Use `web-time` instead of `instant`. diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index 4bc2378ae54..af70fd9a83e 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-request-response" edition = "2021" rust-version = { workspace = true } description = "Generic Request/Response Protocols" -version = "0.26.4" +version = "0.27.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/request-response/src/lib.rs b/protocols/request-response/src/lib.rs index 4362b3255ad..e627f5668ff 100644 --- a/protocols/request-response/src/lib.rs +++ b/protocols/request-response/src/lib.rs @@ -79,7 +79,7 @@ pub use handler::ProtocolSupport; use crate::handler::OutboundMessage; use futures::channel::oneshot; use handler::Handler; -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::{AddressChange, ConnectionClosed, DialFailure, FromSwarm}, @@ -772,6 +772,7 @@ where peer: PeerId, remote_address: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { let mut handler = Handler::new( self.inbound_protocols.clone(), diff --git a/protocols/stream/CHANGELOG.md b/protocols/stream/CHANGELOG.md index 1e3b85da0b9..2532970d3c6 100644 --- a/protocols/stream/CHANGELOG.md +++ b/protocols/stream/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0-alpha + + + ## 0.1.0-alpha.1 - Implement Error for `OpenStreamError`. See [PR 5169](https://github.com/libp2p/rust-libp2p/pull/5169). diff --git a/protocols/stream/Cargo.toml b/protocols/stream/Cargo.toml index a8d88399c0b..9aa9559a2d6 100644 --- a/protocols/stream/Cargo.toml +++ b/protocols/stream/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-stream" -version = "0.1.0-alpha.1" +version = "0.2.0-alpha" edition = "2021" rust-version.workspace = true description = "Generic stream protocols for libp2p" diff --git a/protocols/stream/src/behaviour.rs b/protocols/stream/src/behaviour.rs index e02aca884b7..07549ccef54 100644 --- a/protocols/stream/src/behaviour.rs +++ b/protocols/stream/src/behaviour.rs @@ -5,7 +5,7 @@ use std::{ }; use futures::{channel::mpsc, StreamExt}; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ self as swarm, dial_opts::DialOpts, ConnectionDenied, ConnectionId, FromSwarm, @@ -82,6 +82,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Handler::new( peer, diff --git a/protocols/upnp/CHANGELOG.md b/protocols/upnp/CHANGELOG.md index 9472c7153c6..21e90f9534b 100644 --- a/protocols/upnp/CHANGELOG.md +++ b/protocols/upnp/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0 + + + ## 0.2.2 - Fix a panic caused when `upnp::Gateway` is dropped and its events queue receiver is no longer available. diff --git a/protocols/upnp/Cargo.toml b/protocols/upnp/Cargo.toml index 944b3323842..50ed9db0f6f 100644 --- a/protocols/upnp/Cargo.toml +++ b/protocols/upnp/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-upnp" edition = "2021" rust-version = "1.60.0" description = "UPnP support for libp2p transports" -version = "0.2.2" +version = "0.3.0" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] diff --git a/protocols/upnp/src/behaviour.rs b/protocols/upnp/src/behaviour.rs index a94ef9526dd..29a7fbf84a4 100644 --- a/protocols/upnp/src/behaviour.rs +++ b/protocols/upnp/src/behaviour.rs @@ -36,7 +36,11 @@ use crate::tokio::{is_addr_global, Gateway}; use futures::{channel::oneshot, Future, StreamExt}; use futures_timer::Delay; use igd_next::PortMappingProtocol; -use libp2p_core::{multiaddr, transport::ListenerId, Endpoint, Multiaddr}; +use libp2p_core::{ + multiaddr, + transport::{ListenerId, PortUse}, + Endpoint, Multiaddr, +}; use libp2p_swarm::{ derive_prelude::PeerId, dummy, ConnectionDenied, ConnectionId, ExpiredListenAddr, FromSwarm, NetworkBehaviour, NewListenAddr, ToSwarm, @@ -248,6 +252,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/swarm-derive/src/lib.rs b/swarm-derive/src/lib.rs index 2e7daf7acc4..258c0b976c8 100644 --- a/swarm-derive/src/lib.rs +++ b/swarm-derive/src/lib.rs @@ -76,6 +76,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result syn::Result syn::Result Result<#t_handler, #connection_denied> { Ok(#handle_established_outbound_connection) } diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index f4901947b8b..9aeaa5a1ccc 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,6 +1,14 @@ ## 0.45.0 +- Implement refactored `Transport`. + See [PR 4568] +- Move `address_translation` into swarm and into `libp2p-identify`. + See [PR 4568] + +[PR 4568]: https://github.com/libp2p/rust-libp2p/pull/4568 + +## 0.44.3 - Optimize internal connection `fn poll`. New implementation now scales much better with number of listen protocols active. No changes to public API introduced. See [PR 5026](https://github.com/libp2p/rust-libp2p/pull/5026) diff --git a/swarm/benches/connection_handler.rs b/swarm/benches/connection_handler.rs index b9986d9649f..09340421f83 100644 --- a/swarm/benches/connection_handler.rs +++ b/swarm/benches/connection_handler.rs @@ -223,6 +223,7 @@ impl NetworkBehaviour for SpinningBehaviour { _peer: libp2p_identity::PeerId, _addr: &libp2p_core::Multiaddr, _role_override: libp2p_core::Endpoint, + _port_use: libp2p_core::transport::PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(SpinningHandler { iter_count: self.iter_count, diff --git a/swarm/src/behaviour.rs b/swarm/src/behaviour.rs index 8a8418739c8..35aed12fba5 100644 --- a/swarm/src/behaviour.rs +++ b/swarm/src/behaviour.rs @@ -35,7 +35,10 @@ use crate::{ ConnectionDenied, ConnectionError, ConnectionHandler, DialError, ListenError, THandler, THandlerInEvent, THandlerOutEvent, }; -use libp2p_core::{transport::ListenerId, ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{ + transport::{ListenerId, PortUse}, + ConnectedPoint, Endpoint, Multiaddr, +}; use libp2p_identity::PeerId; use std::{task::Context, task::Poll}; @@ -196,6 +199,7 @@ pub trait NetworkBehaviour: 'static { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied>; /// Informs the behaviour about an event from the [`Swarm`](crate::Swarm). diff --git a/swarm/src/behaviour/either.rs b/swarm/src/behaviour/either.rs index 25da83fa11f..7a51303e74d 100644 --- a/swarm/src/behaviour/either.rs +++ b/swarm/src/behaviour/either.rs @@ -22,6 +22,7 @@ use crate::behaviour::{self, NetworkBehaviour, ToSwarm}; use crate::connection::ConnectionId; use crate::{ConnectionDenied, THandler, THandlerInEvent, THandlerOutEvent}; use either::Either; +use libp2p_core::transport::PortUse; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; use std::{task::Context, task::Poll}; @@ -103,6 +104,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let handler = match self { Either::Left(inner) => Either::Left(inner.handle_established_outbound_connection( @@ -110,12 +112,14 @@ where peer, addr, role_override, + port_use, )?), Either::Right(inner) => Either::Right(inner.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?), }; diff --git a/swarm/src/behaviour/toggle.rs b/swarm/src/behaviour/toggle.rs index e81c5343701..398c919ae86 100644 --- a/swarm/src/behaviour/toggle.rs +++ b/swarm/src/behaviour/toggle.rs @@ -30,6 +30,7 @@ use crate::{ }; use either::Either; use futures::future; +use libp2p_core::transport::PortUse; use libp2p_core::{upgrade::DeniedUpgrade, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use std::{task::Context, task::Poll}; @@ -139,6 +140,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let inner = match self.inner.as_mut() { None => return Ok(ToggleConnectionHandler { inner: None }), @@ -150,6 +152,7 @@ where peer, addr, role_override, + port_use, )?; Ok(ToggleConnectionHandler { diff --git a/swarm/src/connection.rs b/swarm/src/connection.rs index 9c5e39830ed..603a5b0d7c4 100644 --- a/swarm/src/connection.rs +++ b/swarm/src/connection.rs @@ -27,6 +27,7 @@ pub use error::ConnectionError; pub(crate) use error::{ PendingConnectionError, PendingInboundConnectionError, PendingOutboundConnectionError, }; +use libp2p_core::transport::PortUse; pub use supported_protocols::SupportedProtocols; use crate::handler::{ @@ -1352,6 +1353,7 @@ enum PendingPoint { Dialer { /// Same as [`ConnectedPoint::Dialer`] `role_override`. role_override: Endpoint, + port_use: PortUse, }, /// The socket comes from a listener. Listener { @@ -1365,7 +1367,14 @@ enum PendingPoint { impl From for PendingPoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { role_override, .. } => PendingPoint::Dialer { role_override }, + ConnectedPoint::Dialer { + role_override, + port_use, + .. + } => PendingPoint::Dialer { + role_override, + port_use, + }, ConnectedPoint::Listener { local_addr, send_back_addr, diff --git a/swarm/src/connection/pool.rs b/swarm/src/connection/pool.rs index 48ba92f1020..07f6968dec9 100644 --- a/swarm/src/connection/pool.rs +++ b/swarm/src/connection/pool.rs @@ -39,6 +39,7 @@ use futures::{ }; use libp2p_core::connection::Endpoint; use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerExt}; +use libp2p_core::transport::PortUse; use std::task::Waker; use std::{ collections::HashMap, @@ -424,6 +425,7 @@ where >, peer: Option, role_override: Endpoint, + port_use: PortUse, dial_concurrency_factor_override: Option, connection_id: ConnectionId, ) { @@ -444,7 +446,10 @@ where .instrument(span), ); - let endpoint = PendingPoint::Dialer { role_override }; + let endpoint = PendingPoint::Dialer { + role_override, + port_use, + }; self.counters.inc_pending(&endpoint); self.pending.insert( @@ -650,10 +655,17 @@ where self.counters.dec_pending(&endpoint); let (endpoint, concurrent_dial_errors) = match (endpoint, outgoing) { - (PendingPoint::Dialer { role_override }, Some((address, errors))) => ( + ( + PendingPoint::Dialer { + role_override, + port_use, + }, + Some((address, errors)), + ) => ( ConnectedPoint::Dialer { address, role_override, + port_use, }, Some(errors), ), diff --git a/swarm/src/dial_opts.rs b/swarm/src/dial_opts.rs index 4442d913847..4f5b621327c 100644 --- a/swarm/src/dial_opts.rs +++ b/swarm/src/dial_opts.rs @@ -22,10 +22,38 @@ use crate::ConnectionId; use libp2p_core::connection::Endpoint; use libp2p_core::multiaddr::Protocol; +use libp2p_core::transport::PortUse; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use std::num::NonZeroU8; +macro_rules! fn_override_role { + () => { + /// Override role of local node on connection. I.e. execute the dial _as a + /// listener_. + /// + /// See + /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) + /// for details. + pub fn override_role(mut self) -> Self { + self.role_override = Endpoint::Listener; + self + } + }; +} + +macro_rules! fn_allocate_new_port { + () => { + /// Enforce the allocation of a new port. + /// Default behaviour is best effort reuse of existing ports. If there is no existing + /// fitting listener, a new port is allocated. + pub fn allocate_new_port(mut self) -> Self { + self.port_use = PortUse::New; + self + } + }; +} + /// Options to configure a dial to a known or unknown peer. /// /// Used in [`Swarm::dial`](crate::Swarm::dial) and @@ -45,6 +73,7 @@ pub struct DialOpts { role_override: Endpoint, dial_concurrency_factor_override: Option, connection_id: ConnectionId, + port_use: PortUse, } impl DialOpts { @@ -65,6 +94,7 @@ impl DialOpts { condition: Default::default(), role_override: Endpoint::Dialer, dial_concurrency_factor_override: Default::default(), + port_use: PortUse::Reuse, } } @@ -124,6 +154,10 @@ impl DialOpts { pub(crate) fn role_override(&self) -> Endpoint { self.role_override } + + pub(crate) fn port_use(&self) -> PortUse { + self.port_use + } } impl From for DialOpts { @@ -144,6 +178,7 @@ pub struct WithPeerId { condition: PeerCondition, role_override: Endpoint, dial_concurrency_factor_override: Option, + port_use: PortUse, } impl WithPeerId { @@ -169,19 +204,12 @@ impl WithPeerId { extend_addresses_through_behaviour: false, role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, + port_use: self.port_use, } } - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); /// Build the final [`DialOpts`]. pub fn build(self) -> DialOpts { @@ -193,6 +221,7 @@ impl WithPeerId { role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } @@ -205,6 +234,7 @@ pub struct WithPeerIdWithAddresses { extend_addresses_through_behaviour: bool, role_override: Endpoint, dial_concurrency_factor_override: Option, + port_use: PortUse, } impl WithPeerIdWithAddresses { @@ -221,16 +251,8 @@ impl WithPeerIdWithAddresses { self } - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); /// Override /// Number of addresses concurrently dialed for a single outbound connection attempt. @@ -249,6 +271,7 @@ impl WithPeerIdWithAddresses { role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } @@ -262,6 +285,7 @@ impl WithoutPeerId { WithoutPeerIdWithAddress { address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } } } @@ -270,19 +294,13 @@ impl WithoutPeerId { pub struct WithoutPeerIdWithAddress { address: Multiaddr, role_override: Endpoint, + port_use: PortUse, } impl WithoutPeerIdWithAddress { - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); + /// Build the final [`DialOpts`]. pub fn build(self) -> DialOpts { DialOpts { @@ -293,6 +311,7 @@ impl WithoutPeerIdWithAddress { role_override: self.role_override, dial_concurrency_factor_override: None, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } diff --git a/swarm/src/dummy.rs b/swarm/src/dummy.rs index 86df676443b..6e1b4d56eb9 100644 --- a/swarm/src/dummy.rs +++ b/swarm/src/dummy.rs @@ -7,6 +7,7 @@ use crate::{ ConnectionDenied, ConnectionHandlerEvent, StreamUpgradeError, SubstreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, }; +use libp2p_core::transport::PortUse; use libp2p_core::upgrade::DeniedUpgrade; use libp2p_core::Endpoint; use libp2p_core::Multiaddr; @@ -37,6 +38,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(ConnectionHandler) } diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index 03e30240771..81b1ca1a68d 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -68,6 +68,7 @@ pub mod dial_opts; pub mod dummy; pub mod handler; mod listen_opts; +mod translation; /// Bundles all symbols required for the [`libp2p_swarm_derive::NetworkBehaviour`] macro. #[doc(hidden)] @@ -99,7 +100,7 @@ pub mod derive_prelude { pub use crate::ToSwarm; pub use either::Either; pub use futures::prelude as futures; - pub use libp2p_core::transport::ListenerId; + pub use libp2p_core::transport::{ListenerId, PortUse}; pub use libp2p_core::ConnectedPoint; pub use libp2p_core::Endpoint; pub use libp2p_core::Multiaddr; @@ -134,13 +135,15 @@ use connection::{ }; use dial_opts::{DialOpts, PeerCondition}; use futures::{prelude::*, stream::FusedStream}; + use libp2p_core::{ connection::ConnectedPoint, muxing::StreamMuxerBox, transport::{self, ListenerId, TransportError, TransportEvent}, - Endpoint, Multiaddr, Transport, + Multiaddr, Transport, }; use libp2p_identity::PeerId; + use smallvec::SmallVec; use std::collections::{HashMap, HashSet, VecDeque}; use std::num::{NonZeroU32, NonZeroU8, NonZeroUsize}; @@ -151,6 +154,8 @@ use std::{ task::{Context, Poll}, }; use tracing::Instrument; +#[doc(hidden)] +pub use translation::_address_translation; /// Event generated by the [`NetworkBehaviour`] that the swarm will report back. type TBehaviourOutEvent = ::ToSwarm; @@ -528,18 +533,15 @@ where .into_iter() .map(|a| match peer_id.map_or(Ok(a.clone()), |p| a.with_p2p(p)) { Ok(address) => { - let (dial, span) = match dial_opts.role_override() { - Endpoint::Dialer => ( - self.transport.dial(address.clone()), - tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial", %address), - ), - Endpoint::Listener => ( - self.transport.dial_as_listener(address.clone()), - tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial_as_listener", %address), - ), - }; + let dial = self.transport.dial( + address.clone(), + transport::DialOpts { + role: dial_opts.role_override(), + port_use: dial_opts.port_use(), + }, + ); + let span = tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial", %address); span.follows_from(tracing::Span::current()); - match dial { Ok(fut) => fut .map(|r| (address, r.map_err(TransportError::Other))) @@ -560,6 +562,7 @@ where dials, peer_id, dial_opts.role_override(), + dial_opts.port_use(), dial_opts.dial_concurrency_override(), connection_id, ); @@ -706,12 +709,14 @@ where ConnectedPoint::Dialer { address, role_override, + port_use, } => { match self.behaviour.handle_established_outbound_connection( id, peer_id, &address, role_override, + port_use, ) { Ok(handler) => handler, Err(cause) => { @@ -1135,40 +1140,12 @@ where self.pending_handler_event = Some((peer_id, handler, event)); } ToSwarm::NewExternalAddrCandidate(addr) => { - // Apply address translation to the candidate address. - // For TCP without port-reuse, the observed address contains an ephemeral port which needs to be replaced by the port of a listen address. - let translated_addresses = { - let mut addrs: Vec<_> = self - .listened_addrs - .values() - .flatten() - .filter_map(|server| self.transport.address_translation(server, &addr)) - .collect(); - - // remove duplicates - addrs.sort_unstable(); - addrs.dedup(); - addrs - }; - - // If address translation yielded nothing, broadcast the original candidate address. - if translated_addresses.is_empty() { - self.behaviour - .on_swarm_event(FromSwarm::NewExternalAddrCandidate( - NewExternalAddrCandidate { addr: &addr }, - )); - self.pending_swarm_events - .push_back(SwarmEvent::NewExternalAddrCandidate { address: addr }); - } else { - for addr in translated_addresses { - self.behaviour - .on_swarm_event(FromSwarm::NewExternalAddrCandidate( - NewExternalAddrCandidate { addr: &addr }, - )); - self.pending_swarm_events - .push_back(SwarmEvent::NewExternalAddrCandidate { address: addr }); - } - } + self.behaviour + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr: &addr }, + )); + self.pending_swarm_events + .push_back(SwarmEvent::NewExternalAddrCandidate { address: addr }); } ToSwarm::ExternalAddrConfirmed(addr) => { self.add_external_address(addr.clone()); @@ -1784,7 +1761,9 @@ mod tests { use crate::test::{CallTraceBehaviour, MockBehaviour}; use libp2p_core::multiaddr::multiaddr; use libp2p_core::transport::memory::MemoryTransportError; - use libp2p_core::{multiaddr, upgrade}; + use libp2p_core::transport::{PortUse, TransportEvent}; + use libp2p_core::Endpoint; + use libp2p_core::{multiaddr, transport, upgrade}; use libp2p_identity as identity; use libp2p_plaintext as plaintext; use libp2p_yamux as yamux; @@ -2179,6 +2158,7 @@ mod tests { ConnectedPoint::Dialer { address: other_addr, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } ); } diff --git a/swarm/src/test.rs b/swarm/src/test.rs index d49b504392a..a6cb7c4d4eb 100644 --- a/swarm/src/test.rs +++ b/swarm/src/test.rs @@ -26,6 +26,7 @@ use crate::{ ConnectionDenied, ConnectionHandler, ConnectionId, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; +use libp2p_core::transport::PortUse; use libp2p_core::{multiaddr::Multiaddr, transport::ListenerId, ConnectedPoint, Endpoint}; use libp2p_identity::PeerId; use std::collections::HashMap; @@ -91,6 +92,7 @@ where _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result { Ok(self.handler_proto.clone()) } @@ -427,6 +429,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { self.handle_established_outbound_connection.push(( peer, @@ -434,8 +437,13 @@ where role_override, connection_id, )); - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_swarm_event(&mut self, event: FromSwarm) { diff --git a/core/src/translation.rs b/swarm/src/translation.rs similarity index 95% rename from core/src/translation.rs rename to swarm/src/translation.rs index efddae31052..baa80c907b5 100644 --- a/core/src/translation.rs +++ b/swarm/src/translation.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multiaddr::{Multiaddr, Protocol}; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; /// Perform IP address translation. /// @@ -35,7 +35,8 @@ use multiaddr::{Multiaddr, Protocol}; /// address and vice versa. /// /// If the first [`Protocol`]s are not IP addresses, `None` is returned instead. -pub fn address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option { +#[doc(hidden)] +pub fn _address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option { original.replace(0, move |proto| match proto { Protocol::Ip4(_) | Protocol::Ip6(_) @@ -106,7 +107,7 @@ mod tests { for test in tests.iter() { assert_eq!( - address_translation(&test.original, &test.observed), + _address_translation(&test.original, &test.observed), Some(test.expected.clone()) ); } diff --git a/swarm/tests/connection_close.rs b/swarm/tests/connection_close.rs index 4efe8d17e49..4d530f47684 100644 --- a/swarm/tests/connection_close.rs +++ b/swarm/tests/connection_close.rs @@ -1,3 +1,4 @@ +use libp2p_core::transport::PortUse; use libp2p_core::upgrade::DeniedUpgrade; use libp2p_core::{Endpoint, Multiaddr}; use libp2p_identity::PeerId; @@ -66,6 +67,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(HandlerWithState { precious_state: self.state, diff --git a/swarm/tests/listener.rs b/swarm/tests/listener.rs index 8d22acc90e2..160b1f5b064 100644 --- a/swarm/tests/listener.rs +++ b/swarm/tests/listener.rs @@ -3,7 +3,11 @@ use std::{ task::{Context, Poll}, }; -use libp2p_core::{multiaddr::Protocol, transport::ListenerId, Endpoint, Multiaddr}; +use libp2p_core::{ + multiaddr::Protocol, + transport::{ListenerId, PortUse}, + Endpoint, Multiaddr, +}; use libp2p_identity::PeerId; use libp2p_swarm::{ derive_prelude::NewListener, dummy, ConnectionDenied, ConnectionId, FromSwarm, ListenOpts, @@ -93,6 +97,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/swarm/tests/swarm_derive.rs b/swarm/tests/swarm_derive.rs index 12f0d0a5ba8..919ed0cab7f 100644 --- a/swarm/tests/swarm_derive.rs +++ b/swarm/tests/swarm_derive.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use futures::StreamExt; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identify as identify; use libp2p_ping as ping; use libp2p_swarm::{ @@ -404,6 +404,7 @@ fn with_generics_constrained() { _: libp2p_identity::PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } @@ -565,6 +566,7 @@ fn custom_out_event_no_type_parameters() { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/transports/dns/CHANGELOG.md b/transports/dns/CHANGELOG.md index 91cfbc00883..e4f951f157f 100644 --- a/transports/dns/CHANGELOG.md +++ b/transports/dns/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.42.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.41.1 - Add hidden API that removes unnecessary async for `async-std`. diff --git a/transports/dns/Cargo.toml b/transports/dns/Cargo.toml index 1e50ad444b8..b728509364a 100644 --- a/transports/dns/Cargo.toml +++ b/transports/dns/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-dns" edition = "2021" rust-version = { workspace = true } description = "DNS transport implementation for libp2p" -version = "0.41.1" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/dns/src/lib.rs b/transports/dns/src/lib.rs index 63bd9bbad26..7d92cc8ecfc 100644 --- a/transports/dns/src/lib.rs +++ b/transports/dns/src/lib.rs @@ -149,9 +149,8 @@ pub mod tokio { use async_trait::async_trait; use futures::{future::BoxFuture, prelude::*}; use libp2p_core::{ - connection::Endpoint, multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, }; use parking_lot::Mutex; use smallvec::SmallVec; @@ -231,19 +230,12 @@ where self.inner.lock().remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - Ok(self.do_dial(addr, Endpoint::Dialer)) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { - Ok(self.do_dial(addr, Endpoint::Listener)) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.lock().address_translation(server, observed) + Ok(self.do_dial(addr, dial_opts)) } fn poll( @@ -269,7 +261,7 @@ where fn do_dial( &mut self, addr: Multiaddr, - role_override: Endpoint, + dial_opts: DialOpts, ) -> ::Dial { let resolver = self.resolver.clone(); let inner = self.inner.clone(); @@ -355,10 +347,7 @@ where tracing::debug!(address=%addr, "Dialing address"); let transport = inner.clone(); - let dial = match role_override { - Endpoint::Dialer => transport.lock().dial(addr), - Endpoint::Listener => transport.lock().dial_as_listener(addr), - }; + let dial = transport.lock().dial(addr, dial_opts); let result = match dial { Ok(out) => { // We only count attempts that the inner transport @@ -625,7 +614,12 @@ where #[cfg(all(test, any(feature = "tokio", feature = "async-std")))] mod tests { use super::*; - use libp2p_core::Transport; + use futures::future::BoxFuture; + use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{PortUse, TransportError, TransportEvent}, + Endpoint, Transport, + }; use libp2p_identity::PeerId; #[test] @@ -655,7 +649,11 @@ mod tests { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + _: DialOpts, + ) -> Result> { // Check that all DNS components have been resolved, i.e. replaced. assert!(!addr.iter().any(|p| matches!( p, @@ -664,17 +662,6 @@ mod tests { Ok(Box::pin(future::ready(Ok(())))) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option { - None - } - fn poll( self: Pin<&mut Self>, _: &mut Context<'_>, @@ -690,30 +677,34 @@ mod tests { T::Dial: Send, R: Clone + Send + Sync + Resolver + 'static, { + let dial_opts = DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }; // Success due to existing A record for example.com. let _ = transport - .dial("/dns4/example.com/tcp/20000".parse().unwrap()) + .dial("/dns4/example.com/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to existing AAAA record for example.com. let _ = transport - .dial("/dns6/example.com/tcp/20000".parse().unwrap()) + .dial("/dns6/example.com/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to pass-through, i.e. nothing to resolve. let _ = transport - .dial("/ip4/1.2.3.4/tcp/20000".parse().unwrap()) + .dial("/ip4/1.2.3.4/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to the DNS TXT records at _dnsaddr.bootstrap.libp2p.io. let _ = transport - .dial("/dnsaddr/bootstrap.libp2p.io".parse().unwrap()) + .dial("/dnsaddr/bootstrap.libp2p.io".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); @@ -722,7 +713,7 @@ mod tests { // an entry with suffix `/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`, // i.e. a bootnode with such a peer ID. let _ = transport - .dial("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap()) + .dial("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); @@ -734,6 +725,7 @@ mod tests { format!("/dnsaddr/bootstrap.libp2p.io/p2p/{}", PeerId::random()) .parse() .unwrap(), + dial_opts, ) .unwrap() .await @@ -745,7 +737,10 @@ mod tests { // Failure due to no records. match transport - .dial("/dns4/example.invalid/tcp/20000".parse().unwrap()) + .dial( + "/dns4/example.invalid/tcp/20000".parse().unwrap(), + dial_opts, + ) .unwrap() .await { diff --git a/transports/quic/CHANGELOG.md b/transports/quic/CHANGELOG.md index 3ec52ec0ddb..2593af605df 100644 --- a/transports/quic/CHANGELOG.md +++ b/transports/quic/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.11.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.10.3 - Update `quinn` to 0.11 and `libp2p-tls` to 0.4.0. diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index c7e031b1cfd..d2d8c6fc2b0 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-quic" -version = "0.10.3" +version = "0.11.0" authors = ["Parity Technologies "] edition = "2021" rust-version = { workspace = true } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 9bd4c035cec..057d0f978d7 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -31,6 +31,8 @@ use futures::{prelude::*, stream::SelectAll}; use if_watch::IfEvent; +use libp2p_core::transport::{DialOpts, PortUse}; +use libp2p_core::Endpoint; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, transport::{ListenerId, TransportError, TransportEvent}, @@ -195,6 +197,21 @@ impl GenTransport

{ Ok(socket.into()) } + + fn bound_socket(&mut self, socket_addr: SocketAddr) -> Result { + let socket_family = socket_addr.ip().into(); + if let Some(waker) = self.waker.take() { + waker.wake(); + } + let listen_socket_addr = match socket_family { + SocketFamily::Ipv4 => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0), + SocketFamily::Ipv6 => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0), + }; + let socket = UdpSocket::bind(listen_socket_addr)?; + let endpoint_config = self.quinn_config.endpoint_config.clone(); + let endpoint = Self::new_endpoint(endpoint_config, None, socket)?; + Ok(endpoint) + } } impl Transport for GenTransport

{ @@ -247,119 +264,110 @@ impl Transport for GenTransport

{ } } - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { - if !is_quic_addr(listen, self.support_draft_29) - || !is_quic_addr(observed, self.support_draft_29) - { - return None; - } - Some(observed.clone()) - } - - fn dial(&mut self, addr: Multiaddr) -> Result> { - let (socket_addr, version, _peer_id) = self.remote_multiaddr_to_socketaddr(addr, true)?; - - let endpoint = match self.eligible_listener(&socket_addr) { - None => { - // No listener. Get or create an explicit dialer. - let socket_family = socket_addr.ip().into(); - let dialer = match self.dialer.entry(socket_family) { - Entry::Occupied(occupied) => occupied.get().clone(), - Entry::Vacant(vacant) => { - if let Some(waker) = self.waker.take() { - waker.wake(); - } - let listen_socket_addr = match socket_family { - SocketFamily::Ipv4 => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0), - SocketFamily::Ipv6 => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0), - }; - let socket = - UdpSocket::bind(listen_socket_addr).map_err(Self::Error::from)?; - let endpoint_config = self.quinn_config.endpoint_config.clone(); - let endpoint = Self::new_endpoint(endpoint_config, None, socket)?; - - vacant.insert(endpoint.clone()); - endpoint - } - }; - dialer - } - Some(listener) => listener.endpoint.clone(), - }; - let handshake_timeout = self.handshake_timeout; - let mut client_config = self.quinn_config.client_config.clone(); - if version == ProtocolVersion::Draft29 { - client_config.version(0xff00_001d); - } - Ok(Box::pin(async move { - // This `"l"` seems necessary because an empty string is an invalid domain - // name. While we don't use domain names, the underlying rustls library - // is based upon the assumption that we do. - let connecting = endpoint - .connect_with(client_config, socket_addr, "l") - .map_err(ConnectError)?; - Connecting::new(connecting, handshake_timeout).await - })) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { - let (socket_addr, _version, peer_id) = + let (socket_addr, version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone(), true)?; - let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr.clone()))?; - - let socket = self - .eligible_listener(&socket_addr) - .ok_or(TransportError::Other( - Error::NoActiveListenerForDialAsListener, - ))? - .try_clone_socket() - .map_err(Self::Error::from)?; - - tracing::debug!("Preparing for hole-punch from {addr}"); - - let hole_puncher = hole_puncher::

(socket, socket_addr, self.handshake_timeout); - - let (sender, receiver) = oneshot::channel(); - - match self.hole_punch_attempts.entry(socket_addr) { - Entry::Occupied(mut sender_entry) => { - // Stale senders, i.e. from failed hole punches are not removed. - // Thus, we can just overwrite a stale sender. - if !sender_entry.get().is_canceled() { - return Err(TransportError::Other(Error::HolePunchInProgress( - socket_addr, - ))); + + match (dial_opts.role, dial_opts.port_use) { + (Endpoint::Dialer, _) | (Endpoint::Listener, PortUse::Reuse) => { + let endpoint = if let Some(listener) = dial_opts + .port_use + .eq(&PortUse::Reuse) + .then(|| self.eligible_listener(&socket_addr)) + .flatten() + { + listener.endpoint.clone() + } else { + let socket_family = socket_addr.ip().into(); + let dialer = if dial_opts.port_use == PortUse::Reuse { + if let Some(occupied) = self.dialer.get(&socket_family) { + occupied.clone() + } else { + let endpoint = self.bound_socket(socket_addr)?; + self.dialer.insert(socket_family, endpoint.clone()); + endpoint + } + } else { + self.bound_socket(socket_addr)? + }; + dialer + }; + let handshake_timeout = self.handshake_timeout; + let mut client_config = self.quinn_config.client_config.clone(); + if version == ProtocolVersion::Draft29 { + client_config.version(0xff00_001d); } - sender_entry.insert(sender); - } - Entry::Vacant(entry) => { - entry.insert(sender); + Ok(Box::pin(async move { + // This `"l"` seems necessary because an empty string is an invalid domain + // name. While we don't use domain names, the underlying rustls library + // is based upon the assumption that we do. + let connecting = endpoint + .connect_with(client_config, socket_addr, "l") + .map_err(ConnectError)?; + Connecting::new(connecting, handshake_timeout).await + })) } - }; + (Endpoint::Listener, _) => { + let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr.clone()))?; + + let socket = self + .eligible_listener(&socket_addr) + .ok_or(TransportError::Other( + Error::NoActiveListenerForDialAsListener, + ))? + .try_clone_socket() + .map_err(Self::Error::from)?; + + tracing::debug!("Preparing for hole-punch from {addr}"); + + let hole_puncher = hole_puncher::

(socket, socket_addr, self.handshake_timeout); + + let (sender, receiver) = oneshot::channel(); + + match self.hole_punch_attempts.entry(socket_addr) { + Entry::Occupied(mut sender_entry) => { + // Stale senders, i.e. from failed hole punches are not removed. + // Thus, we can just overwrite a stale sender. + if !sender_entry.get().is_canceled() { + return Err(TransportError::Other(Error::HolePunchInProgress( + socket_addr, + ))); + } + sender_entry.insert(sender); + } + Entry::Vacant(entry) => { + entry.insert(sender); + } + }; - Ok(Box::pin(async move { - futures::pin_mut!(hole_puncher); - match futures::future::select(receiver, hole_puncher).await { - Either::Left((message, _)) => { - let (inbound_peer_id, connection) = message - .expect("hole punch connection sender is never dropped before receiver") - .await?; - if inbound_peer_id != peer_id { - tracing::warn!( - peer=%peer_id, - inbound_peer=%inbound_peer_id, - socket_address=%socket_addr, - "expected inbound connection from socket_address to resolve to peer but got inbound peer" - ); + Ok(Box::pin(async move { + futures::pin_mut!(hole_puncher); + match futures::future::select(receiver, hole_puncher).await { + Either::Left((message, _)) => { + let (inbound_peer_id, connection) = message + .expect( + "hole punch connection sender is never dropped before receiver", + ) + .await?; + if inbound_peer_id != peer_id { + tracing::warn!( + peer=%peer_id, + inbound_peer=%inbound_peer_id, + socket_address=%socket_addr, + "expected inbound connection from socket_address to resolve to peer but got inbound peer" + ); + } + Ok((inbound_peer_id, connection)) + } + Either::Right((hole_punch_err, _)) => Err(hole_punch_err), } - Ok((inbound_peer_id, connection)) - } - Either::Right((hole_punch_err, _)) => Err(hole_punch_err), + })) } - })) + } } fn poll( @@ -722,33 +730,6 @@ fn multiaddr_to_socketaddr( } } -/// Whether an [`Multiaddr`] is a valid for the QUIC transport. -fn is_quic_addr(addr: &Multiaddr, support_draft_29: bool) -> bool { - use Protocol::*; - let mut iter = addr.iter(); - let Some(first) = iter.next() else { - return false; - }; - let Some(second) = iter.next() else { - return false; - }; - let Some(third) = iter.next() else { - return false; - }; - let fourth = iter.next(); - let fifth = iter.next(); - - matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) - && matches!(second, Udp(_)) - && if support_draft_29 { - matches!(third, QuicV1 | Quic) - } else { - matches!(third, QuicV1) - } - && matches!(fourth, Some(P2p(_)) | None) - && fifth.is_none() -} - /// Turns an IP address and port into the corresponding QUIC multiaddr. fn socketaddr_to_multiaddr(socket_addr: &SocketAddr, version: ProtocolVersion) -> Multiaddr { let quic_proto = match version { @@ -921,7 +902,13 @@ mod tests { let mut transport = crate::tokio::Transport::new(config); let _dial = transport - .dial("/ip4/123.45.67.8/udp/1234/quic-v1".parse().unwrap()) + .dial( + "/ip4/123.45.67.8/udp/1234/quic-v1".parse().unwrap(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) .unwrap(); assert!(transport.dialer.contains_key(&SocketFamily::Ipv4)); diff --git a/transports/quic/tests/smoke.rs b/transports/quic/tests/smoke.rs index 74423076780..6a760f9997c 100644 --- a/transports/quic/tests/smoke.rs +++ b/transports/quic/tests/smoke.rs @@ -7,8 +7,9 @@ use futures::stream::StreamExt; use futures::{future, AsyncReadExt, AsyncWriteExt, FutureExt, SinkExt}; use futures_timer::Delay; use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerExt, SubstreamBox}; -use libp2p_core::transport::{Boxed, OrTransport, TransportEvent}; +use libp2p_core::transport::{Boxed, DialOpts, OrTransport, PortUse, TransportEvent}; use libp2p_core::transport::{ListenerId, TransportError}; +use libp2p_core::Endpoint; use libp2p_core::{multiaddr::Protocol, upgrade, Multiaddr, Transport}; use libp2p_identity::PeerId; use libp2p_noise as noise; @@ -90,6 +91,8 @@ async fn ipv4_dial_ipv6() { #[cfg(feature = "async-std")] #[async_std::test] async fn wrapped_with_delay() { + use libp2p_core::transport::DialOpts; + let _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .try_init(); @@ -114,18 +117,14 @@ async fn wrapped_with_delay() { self.0.lock().unwrap().remove_listener(id) } - fn address_translation( - &self, - listen: &Multiaddr, - observed: &Multiaddr, - ) -> Option { - self.0.lock().unwrap().address_translation(listen, observed) - } - /// Delayed dial, i.e. calling [`Transport::dial`] on the inner [`Transport`] not within the /// synchronous [`Transport::dial`] method, but within the [`Future`] returned by the outer /// [`Transport::dial`]. - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { let t = self.0.clone(); Ok(async move { // Simulate DNS lookup. Giving the `Transport::poll` the chance to return @@ -133,24 +132,21 @@ async fn wrapped_with_delay() { // on the inner transport below. Delay::new(Duration::from_millis(100)).await; - let dial = t.lock().unwrap().dial(addr).map_err(|e| match e { - TransportError::MultiaddrNotSupported(_) => { - panic!() - } - TransportError::Other(e) => e, - })?; + let dial = t + .lock() + .unwrap() + .dial(addr, dial_opts) + .map_err(|e| match e { + TransportError::MultiaddrNotSupported(_) => { + panic!() + } + TransportError::Other(e) => e, + })?; dial.await } .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.0.lock().unwrap().dial_as_listener(addr) - } - fn poll( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, @@ -183,7 +179,15 @@ async fn wrapped_with_delay() { // Note that the dial is spawned on a different task than the transport allowing the transport // task to poll the transport once and then suspend, waiting for the wakeup from the dial. let dial = async_std::task::spawn({ - let dial = b_transport.dial(a_addr).unwrap(); + let dial = b_transport + .dial( + a_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap(); async { dial.await.unwrap().0 } }); async_std::task::spawn(async move { b_transport.next().await }); @@ -315,7 +319,13 @@ async fn draft_29_support() { let (_, mut c_transport) = create_transport::(|cfg| cfg.support_draft_29 = false); assert!(matches!( - c_transport.dial(a_quic_addr), + c_transport.dial( + a_quic_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New + } + ), Err(TransportError::MultiaddrNotSupported(_)) )); @@ -331,7 +341,15 @@ async fn draft_29_support() { )); let d_quic_v1_addr = start_listening(&mut d_transport, "/ip4/127.0.0.1/udp/0/quic-v1").await; let d_quic_addr_mapped = swap_protocol!(d_quic_v1_addr, QuicV1 => Quic); - let dial = b_transport.dial(d_quic_addr_mapped).unwrap(); + let dial = b_transport + .dial( + d_quic_addr_mapped, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap(); let drive_transports = poll_fn::<(), _>(|cx| { let _ = b_transport.poll_next_unpin(cx); let _ = d_transport.poll_next_unpin(cx); @@ -408,7 +426,7 @@ async fn write_after_peer_dropped_stream() { .try_init(); let (stream_a, mut stream_b) = build_streams::().await; drop(stream_a); - futures_timer::Delay::new(Duration::from_millis(10)).await; + futures_timer::Delay::new(Duration::from_millis(100)).await; let data = vec![0; 10]; stream_b.write_all(&data).await.expect("Write failed."); @@ -765,7 +783,20 @@ async fn dial( transport: &mut Boxed<(PeerId, StreamMuxerBox)>, addr: Multiaddr, ) -> io::Result<(PeerId, StreamMuxerBox)> { - match future::select(transport.dial(addr).unwrap(), transport.next()).await { + match future::select( + transport + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap(), + transport.next(), + ) + .await + { Either::Left((conn, _)) => conn, Either::Right((event, _)) => { panic!("Unexpected event: {event:?}") diff --git a/transports/quic/tests/stream_compliance.rs b/transports/quic/tests/stream_compliance.rs index 0eff0584588..b0536473215 100644 --- a/transports/quic/tests/stream_compliance.rs +++ b/transports/quic/tests/stream_compliance.rs @@ -1,7 +1,7 @@ use futures::channel::oneshot; use futures::StreamExt; -use libp2p_core::transport::ListenerId; -use libp2p_core::Transport; +use libp2p_core::transport::{DialOpts, ListenerId, PortUse}; +use libp2p_core::{Endpoint, Transport}; use libp2p_quic as quic; use std::time::Duration; @@ -47,7 +47,15 @@ async fn connected_peers() -> (quic::Connection, quic::Connection) { listener.next().await; } }); - let dial_fut = dialer.dial(listen_address).unwrap(); + let dial_fut = dialer + .dial( + listen_address, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap(); async_std::task::spawn(async move { let connection = dial_fut.await.unwrap().1; diff --git a/transports/tcp/CHANGELOG.md b/transports/tcp/CHANGELOG.md index f0204f1ba85..107d0d13ece 100644 --- a/transports/tcp/CHANGELOG.md +++ b/transports/tcp/CHANGELOG.md @@ -1,5 +1,14 @@ ## 0.42.0 +- Implement refactored `Transport`. + See [PR 4568] +- Deprecate `port_reuse` setting, as this is now decided by the behaviour, not the transport. + See [PR 4568] + +[PR 4568]: https://github.com/libp2p/rust-libp2p/pull/4568 + +## 0.41.1 + - Disable Nagle's algorithm (i.e. `TCP_NODELAY`) by default. See [PR 4916](https://github.com/libp2p/rust-libp2p/pull/4916) diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index 3d881a6421c..386caa78b0f 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -40,9 +40,8 @@ use futures::{future::Ready, prelude::*, stream::SelectAll}; use futures_timer::Delay; use if_watch::IfEvent; use libp2p_core::{ - address_translation, multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, PortUse, TransportError, TransportEvent}, }; use provider::{Incoming, Provider}; use socket2::{Domain, Socket, Type}; @@ -65,27 +64,16 @@ pub struct Config { nodelay: Option, /// Size of the listen backlog for listen sockets. backlog: u32, - /// Whether port reuse should be enabled. - enable_port_reuse: bool, } type Port = u16; /// The configuration for port reuse of listening sockets. -#[derive(Debug, Clone)] -enum PortReuse { - /// Port reuse is disabled, i.e. ephemeral local ports are - /// used for outgoing TCP connections. - Disabled, - /// Port reuse when dialing is enabled, i.e. the local - /// address and port that a new socket for an outgoing - /// connection is bound to are chosen from an existing - /// listening socket, if available. - Enabled { - /// The addresses and ports of the listening sockets - /// registered as eligible for port reuse when dialing. - listen_addrs: Arc>>, - }, +#[derive(Debug, Clone, Default)] +struct PortReuse { + /// The addresses and ports of the listening sockets + /// registered as eligible for port reuse when dialing + listen_addrs: Arc>>, } impl PortReuse { @@ -93,26 +81,22 @@ impl PortReuse { /// /// Has no effect if port reuse is disabled. fn register(&mut self, ip: IpAddr, port: Port) { - if let PortReuse::Enabled { listen_addrs } = self { - tracing::trace!(%ip, %port, "Registering for port reuse"); - listen_addrs - .write() - .expect("`register()` and `unregister()` never panic while holding the lock") - .insert((ip, port)); - } + tracing::trace!(%ip, %port, "Registering for port reuse"); + self.listen_addrs + .write() + .expect("`register()` and `unregister()` never panic while holding the lock") + .insert((ip, port)); } /// Unregisters a socket address for port reuse. /// /// Has no effect if port reuse is disabled. fn unregister(&mut self, ip: IpAddr, port: Port) { - if let PortReuse::Enabled { listen_addrs } = self { - tracing::trace!(%ip, %port, "Unregistering for port reuse"); - listen_addrs - .write() - .expect("`register()` and `unregister()` never panic while holding the lock") - .remove(&(ip, port)); - } + tracing::trace!(%ip, %port, "Unregistering for port reuse"); + self.listen_addrs + .write() + .expect("`register()` and `unregister()` never panic while holding the lock") + .remove(&(ip, port)); } /// Selects a listening socket address suitable for use @@ -125,20 +109,17 @@ impl PortReuse { /// Returns `None` if port reuse is disabled or no suitable /// listening socket address is found. fn local_dial_addr(&self, remote_ip: &IpAddr) -> Option { - if let PortReuse::Enabled { listen_addrs } = self { - for (ip, port) in listen_addrs - .read() - .expect("`local_dial_addr` never panic while holding the lock") - .iter() - { - if ip.is_ipv4() == remote_ip.is_ipv4() - && ip.is_loopback() == remote_ip.is_loopback() - { - if remote_ip.is_ipv4() { - return Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)); - } else { - return Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)); - } + for (ip, port) in self + .listen_addrs + .read() + .expect("`local_dial_addr` never panic while holding the lock") + .iter() + { + if ip.is_ipv4() == remote_ip.is_ipv4() && ip.is_loopback() == remote_ip.is_loopback() { + if remote_ip.is_ipv4() { + return Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)); + } else { + return Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)); } } } @@ -163,7 +144,6 @@ impl Config { ttl: None, nodelay: Some(false), // Disable Nagle's algorithm by default backlog: 1024, - enable_port_reuse: false, } } @@ -189,101 +169,55 @@ impl Config { /// reuse of listening ports for outgoing connections to /// enhance NAT traversal capabilities. /// - /// Please refer to e.g. [RFC 4787](https://tools.ietf.org/html/rfc4787) - /// section 4 and 5 for some of the NAT terminology used here. - /// - /// There are two main use-cases for port reuse among local - /// sockets: - /// - /// 1. Creating multiple listening sockets for the same address - /// and port to allow accepting connections on multiple threads - /// without having to synchronise access to a single listen socket. - /// - /// 2. Creating outgoing connections whose local socket is bound to - /// the same address and port as a listening socket. In the rare - /// case of simple NATs with both endpoint-independent mapping and - /// endpoint-independent filtering, this can on its own already - /// permit NAT traversal by other nodes sharing the observed - /// external address of the local node. For the common case of - /// NATs with address-dependent or address and port-dependent - /// filtering, port reuse for outgoing connections can facilitate - /// further TCP hole punching techniques for NATs that perform - /// endpoint-independent mapping. Port reuse cannot facilitate - /// NAT traversal in the presence of "symmetric" NATs that employ - /// both address/port-dependent mapping and filtering, unless - /// there is some means of port prediction. - /// - /// Both use-cases are enabled when port reuse is enabled, with port reuse - /// for outgoing connections (`2.` above) always being implied. - /// - /// > **Note**: Due to the identification of a TCP socket by a 4-tuple - /// > of source IP address, source port, destination IP address and - /// > destination port, with port reuse enabled there can be only - /// > a single outgoing connection to a particular address and port - /// > of a peer per local listening socket address. - /// - /// [`Transport`] keeps track of the listen socket addresses as they - /// are reported by polling it. It is possible to listen on multiple - /// addresses, enabling port reuse for each, knowing exactly which listen - /// address is reused when dialing with a specific [`Transport`], as in the - /// following example: - /// - /// ```no_run - /// # use futures::StreamExt; - /// # use libp2p_core::transport::{ListenerId, TransportEvent}; - /// # use libp2p_core::{Multiaddr, Transport}; - /// # use std::pin::Pin; - /// # #[cfg(not(feature = "async-io"))] - /// # fn main() {} - /// # - /// #[cfg(feature = "async-io")] - /// #[async_std::main] - /// async fn main() -> std::io::Result<()> { - /// - /// let listen_addr1: Multiaddr = "/ip4/127.0.0.1/tcp/9001".parse().unwrap(); - /// let listen_addr2: Multiaddr = "/ip4/127.0.0.1/tcp/9002".parse().unwrap(); - /// - /// let mut tcp1 = libp2p_tcp::async_io::Transport::new(libp2p_tcp::Config::new().port_reuse(true)).boxed(); - /// tcp1.listen_on(ListenerId::next(), listen_addr1.clone()).expect("listener"); - /// match tcp1.select_next_some().await { - /// TransportEvent::NewAddress { listen_addr, .. } => { - /// println!("Listening on {:?}", listen_addr); - /// let mut stream = tcp1.dial(listen_addr2.clone()).unwrap().await?; - /// // `stream` has `listen_addr1` as its local socket address. - /// } - /// _ => {} - /// } + /// # Deprecation Notice /// - /// let mut tcp2 = libp2p_tcp::async_io::Transport::new(libp2p_tcp::Config::new().port_reuse(true)).boxed(); - /// tcp2.listen_on(ListenerId::next(), listen_addr2).expect("listener"); - /// match tcp2.select_next_some().await { - /// TransportEvent::NewAddress { listen_addr, .. } => { - /// println!("Listening on {:?}", listen_addr); - /// let mut socket = tcp2.dial(listen_addr1).unwrap().await?; - /// // `stream` has `listen_addr2` as its local socket address. - /// } - /// _ => {} - /// } - /// Ok(()) - /// } - /// ``` + /// The new implementation works on a per-connaction basis, defined by the behaviour. This + /// removes the necessaity to configure the transport for port reuse, instead the behaviour + /// requiring this behaviour can decide wether to use port reuse or not. /// - /// If a wildcard listen socket address is used to listen on any interface, - /// there can be multiple such addresses registered for port reuse. In this - /// case, one is chosen whose IP protocol version and loopback status is the - /// same as that of the remote address. Consequently, for maximum control of - /// the local listening addresses and ports that are used for outgoing - /// connections, a new [`Transport`] should be created for each listening - /// socket, avoiding the use of wildcard addresses which bind a socket to - /// all network interfaces. + /// The API to configure port reuse is part of [`Transport`] and the option can be found in + /// [`libp2p_core::transport::DialOpts`]. /// - /// When this option is enabled on a unix system, the socket - /// option `SO_REUSEPORT` is set, if available, to permit - /// reuse of listening ports for multiple sockets. - pub fn port_reuse(mut self, port_reuse: bool) -> Self { - self.enable_port_reuse = port_reuse; + /// If [`PortUse::Reuse`] is enabled, the transport will try to reuse the local port of the + /// listener. If that's not possible, i.e. there is no listener or the transport doesn't allow + /// a direct control over ports, a new port (or the default behaviour) is used. If port reuse + /// is enabled for a connection, this option will be treated on a best-effor basis. + #[deprecated( + since = "0.42.0", + note = "This option does nothing now, since the port reuse policy is now decided on a per-connection basis by the behaviour. The function will be removed in a future release." + )] + pub fn port_reuse(self, _port_reuse: bool) -> Self { self } + + fn create_socket(&self, socket_addr: SocketAddr, port_use: PortUse) -> io::Result { + let socket = Socket::new( + Domain::for_address(socket_addr), + Type::STREAM, + Some(socket2::Protocol::TCP), + )?; + if socket_addr.is_ipv6() { + socket.set_only_v6(true)?; + } + if let Some(ttl) = self.ttl { + socket.set_ttl(ttl)?; + } + if let Some(nodelay) = self.nodelay { + socket.set_nodelay(nodelay)?; + } + socket.set_reuse_address(true)?; + #[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))] + if port_use == PortUse::Reuse { + socket.set_reuse_port(true)?; + } + + #[cfg(not(all(unix, not(any(target_os = "solaris", target_os = "illumos")))))] + let _ = port_use; // silence the unused warning on non-unix platforms (i.e. Windows) + + socket.set_nonblocking(true)?; + + Ok(socket) + } } impl Default for Config { @@ -328,49 +262,18 @@ where /// - [`tokio::Transport::new`] /// - [`async_io::Transport::new`] pub fn new(config: Config) -> Self { - let port_reuse = if config.enable_port_reuse { - PortReuse::Enabled { - listen_addrs: Arc::new(RwLock::new(HashSet::new())), - } - } else { - PortReuse::Disabled - }; Transport { config, - port_reuse, ..Default::default() } } - fn create_socket(&self, socket_addr: SocketAddr) -> io::Result { - let socket = Socket::new( - Domain::for_address(socket_addr), - Type::STREAM, - Some(socket2::Protocol::TCP), - )?; - if socket_addr.is_ipv6() { - socket.set_only_v6(true)?; - } - if let Some(ttl) = self.config.ttl { - socket.set_ttl(ttl)?; - } - if let Some(nodelay) = self.config.nodelay { - socket.set_nodelay(nodelay)?; - } - socket.set_reuse_address(true)?; - #[cfg(unix)] - if let PortReuse::Enabled { .. } = &self.port_reuse { - socket.set_reuse_port(true)?; - } - Ok(socket) - } - fn do_listen( &mut self, id: ListenerId, socket_addr: SocketAddr, ) -> io::Result> { - let socket = self.create_socket(socket_addr)?; + let socket = self.config.create_socket(socket_addr, PortUse::Reuse)?; socket.bind(&socket_addr.into())?; socket.listen(self.config.backlog as _)?; socket.set_nonblocking(true)?; @@ -404,17 +307,9 @@ where /// /// This transport will have port-reuse disabled. fn default() -> Self { - let config = Config::default(); - let port_reuse = if config.enable_port_reuse { - PortReuse::Enabled { - listen_addrs: Arc::new(RwLock::new(HashSet::new())), - } - } else { - PortReuse::Disabled - }; Transport { - port_reuse, - config, + port_reuse: PortReuse::default(), + config: Config::default(), listeners: SelectAll::new(), pending_events: VecDeque::new(), } @@ -456,7 +351,11 @@ where } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { let socket_addr = if let Ok(socket_addr) = multiaddr_to_socketaddr(addr.clone()) { if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); @@ -468,26 +367,45 @@ where tracing::debug!(address=%socket_addr, "dialing address"); let socket = self - .create_socket(socket_addr) + .config + .create_socket(socket_addr, opts.port_use) .map_err(TransportError::Other)?; - if let Some(addr) = self.port_reuse.local_dial_addr(&socket_addr.ip()) { - tracing::trace!(address=%addr, "Binding dial socket to listen socket address"); - socket.bind(&addr.into()).map_err(TransportError::Other)?; - } + let bind_addr = match self.port_reuse.local_dial_addr(&socket_addr.ip()) { + Some(socket_addr) if opts.port_use == PortUse::Reuse => { + tracing::trace!(address=%addr, "Binding dial socket to listen socket address"); + Some(socket_addr) + } + _ => None, + }; - socket - .set_nonblocking(true) - .map_err(TransportError::Other)?; + let local_config = self.config.clone(); Ok(async move { + if let Some(bind_addr) = bind_addr { + socket.bind(&bind_addr.into())?; + } + // [`Transport::dial`] should do no work unless the returned [`Future`] is polled. Thus // do the `connect` call within the [`Future`]. - match socket.connect(&socket_addr.into()) { - Ok(()) => {} - Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {} - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - Err(err) => return Err(err), + let socket = match (socket.connect(&socket_addr.into()), bind_addr) { + (Ok(()), _) => socket, + (Err(err), _) if err.raw_os_error() == Some(libc::EINPROGRESS) => socket, + (Err(err), _) if err.kind() == io::ErrorKind::WouldBlock => socket, + (Err(err), Some(bind_addr)) if err.kind() == io::ErrorKind::AddrNotAvailable => { + // The socket was bound to a local address that is no longer available. + // Retry without binding. + tracing::debug!(connect_addr = %socket_addr, ?bind_addr, "Failed to connect using existing socket because we already have a connection, re-dialing with new port"); + std::mem::drop(socket); + let socket = local_config.create_socket(socket_addr, PortUse::New)?; + match socket.connect(&socket_addr.into()) { + Ok(()) => socket, + Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => socket, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => socket, + Err(err) => return Err(err), + } + } + (Err(err), _) => return Err(err), }; let stream = T::new_stream(socket.into()).await?; @@ -496,40 +414,6 @@ where .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - /// When port reuse is disabled and hence ephemeral local ports are - /// used for outgoing connections, the returned address is the - /// `observed` address with the port replaced by the port of the - /// `listen` address. - /// - /// If port reuse is enabled, `Some(observed)` is returned, as there - /// is a chance that the `observed` address _and_ port are reachable - /// for other peers if there is a NAT in the way that does endpoint- - /// independent filtering. Furthermore, even if that is not the case - /// and TCP hole punching techniques must be used for NAT traversal, - /// the `observed` address is still the one that a remote should connect - /// to for the purpose of the hole punching procedure, as it represents - /// the mapped IP and port of the NAT device in front of the local - /// node. - /// - /// `None` is returned if one of the given addresses is not a TCP/IP - /// address. - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { - if !is_tcp_addr(listen) || !is_tcp_addr(observed) { - return None; - } - match &self.port_reuse { - PortReuse::Disabled => address_translation(listen, observed), - PortReuse::Enabled { .. } => Some(observed.clone()), - } - } - /// Poll all listeners. #[tracing::instrument(level = "trace", name = "Transport::poll", skip(self, cx))] fn poll( @@ -819,23 +703,6 @@ fn ip_to_multiaddr(ip: IpAddr, port: u16) -> Multiaddr { Multiaddr::empty().with(ip.into()).with(Protocol::Tcp(port)) } -fn is_tcp_addr(addr: &Multiaddr) -> bool { - use Protocol::*; - - let mut iter = addr.iter(); - - let first = match iter.next() { - None => return false, - Some(p) => p, - }; - let second = match iter.next() { - None => return false, - Some(p) => p, - }; - - matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) && matches!(second, Tcp(_)) -} - #[cfg(test)] mod tests { use super::*; @@ -843,8 +710,8 @@ mod tests { channel::{mpsc, oneshot}, future::poll_fn, }; + use libp2p_core::Endpoint; use libp2p_core::Transport as _; - use libp2p_identity::PeerId; #[test] fn multiaddr_to_tcp_conversion() { @@ -927,7 +794,17 @@ mod tests { let mut tcp = Transport::::default(); // Obtain a future socket through dialing - let mut socket = tcp.dial(addr.clone()).unwrap().await.unwrap(); + let mut socket = tcp + .dial( + addr.clone(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); let mut buf = [0u8; 3]; @@ -1003,7 +880,16 @@ mod tests { async fn dialer(mut ready_rx: mpsc::Receiver) { let dest_addr = ready_rx.next().await.unwrap(); let mut tcp = Transport::::default(); - tcp.dial(dest_addr).unwrap().await.unwrap(); + tcp.dial( + dest_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) + .unwrap() + .await + .unwrap(); } fn test(addr: Multiaddr) { @@ -1083,7 +969,7 @@ mod tests { port_reuse_tx: oneshot::Sender>, ) { let dest_addr = ready_rx.next().await.unwrap(); - let mut tcp = Transport::::new(Config::new().port_reuse(true)); + let mut tcp = Transport::::new(Config::new()); tcp.listen_on(ListenerId::next(), addr).unwrap(); match poll_fn(|cx| Pin::new(&mut tcp).poll(cx)).await { TransportEvent::NewAddress { .. } => { @@ -1102,7 +988,17 @@ mod tests { .ok(); // Obtain a future socket through dialing - let mut socket = tcp.dial(dest_addr).unwrap().await.unwrap(); + let mut socket = tcp + .dial( + dest_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap(); // socket.flush().await; let mut buf = [0u8; 3]; @@ -1153,7 +1049,7 @@ mod tests { .try_init(); async fn listen_twice(addr: Multiaddr) { - let mut tcp = Transport::::new(Config::new().port_reuse(true)); + let mut tcp = Transport::::new(Config::new()); tcp.listen_on(ListenerId::next(), addr).unwrap(); match poll_fn(|cx| Pin::new(&mut tcp).poll(cx)).await { TransportEvent::NewAddress { @@ -1262,55 +1158,6 @@ mod tests { test("/ip4/127.0.0.1/tcp/12345/tcp/12345".parse().unwrap()); } - #[cfg(feature = "async-io")] - #[test] - fn test_address_translation_async_io() { - test_address_translation::() - } - - #[cfg(feature = "tokio")] - #[test] - fn test_address_translation_tokio() { - test_address_translation::() - } - - fn test_address_translation() - where - T: Default + libp2p_core::Transport, - { - let transport = T::default(); - - let port = 42; - let tcp_listen_addr = Multiaddr::empty() - .with(Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) - .with(Protocol::Tcp(port)); - let observed_ip = Ipv4Addr::new(123, 45, 67, 8); - let tcp_observed_addr = Multiaddr::empty() - .with(Protocol::Ip4(observed_ip)) - .with(Protocol::Tcp(1)) - .with(Protocol::P2p(PeerId::random())); - - let translated = transport - .address_translation(&tcp_listen_addr, &tcp_observed_addr) - .unwrap(); - let mut iter = translated.iter(); - assert_eq!(iter.next(), Some(Protocol::Ip4(observed_ip))); - assert_eq!(iter.next(), Some(Protocol::Tcp(port))); - assert_eq!(iter.next(), None); - - let quic_addr = Multiaddr::empty() - .with(Protocol::Ip4(Ipv4Addr::new(87, 65, 43, 21))) - .with(Protocol::Udp(1)) - .with(Protocol::QuicV1); - - assert!(transport - .address_translation(&tcp_listen_addr, &quic_addr) - .is_none()); - assert!(transport - .address_translation(&quic_addr, &tcp_observed_addr) - .is_none()); - } - #[test] fn test_remove_listener() { let _ = tracing_subscriber::fmt() @@ -1373,7 +1220,7 @@ mod tests { .build() .unwrap(); rt.block_on(async { - test::(); + test::(); }); } } diff --git a/transports/uds/CHANGELOG.md b/transports/uds/CHANGELOG.md index aad61d21547..aa068fe3877 100644 --- a/transports/uds/CHANGELOG.md +++ b/transports/uds/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.41.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.40.0 diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index 13642a38a48..df5159f3c02 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-uds" edition = "2021" rust-version = { workspace = true } description = "Unix domain sockets transport for libp2p" -version = "0.40.0" +version = "0.41.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/uds/src/lib.rs b/transports/uds/src/lib.rs index 4b1c7a1670d..5c57e255b4d 100644 --- a/transports/uds/src/lib.rs +++ b/transports/uds/src/lib.rs @@ -46,7 +46,7 @@ use futures::{ use libp2p_core::transport::ListenerId; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{TransportError, TransportEvent}, + transport::{DialOpts, TransportError, TransportEvent}, Transport, }; use std::collections::VecDeque; @@ -159,7 +159,7 @@ macro_rules! codegen { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial(&mut self, addr: Multiaddr, _dial_opts: DialOpts) -> Result> { // TODO: Should we dial at all? if let Ok(path) = multiaddr_to_path(&addr) { tracing::debug!(address=%addr, "Dialing address"); @@ -169,21 +169,6 @@ macro_rules! codegen { } } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation( - &self, - _server: &Multiaddr, - _observed: &Multiaddr, - ) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -260,8 +245,8 @@ mod tests { use futures::{channel::oneshot, prelude::*}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::ListenerId, - Transport, + transport::{DialOpts, ListenerId, PortUse}, + Endpoint, Transport, }; use std::{borrow::Cow, path::Path}; @@ -318,7 +303,17 @@ mod tests { async_std::task::block_on(async move { let mut uds = UdsConfig::new(); let addr = rx.await.unwrap(); - let mut socket = uds.dial(addr).unwrap().await.unwrap(); + let mut socket = uds + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); let _ = socket.write(&[1, 2, 3]).await.unwrap(); }); } diff --git a/transports/webrtc-websys/CHANGELOG.md b/transports/webrtc-websys/CHANGELOG.md index 634120c53c3..475b13727e6 100644 --- a/transports/webrtc-websys/CHANGELOG.md +++ b/transports/webrtc-websys/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0-alpha + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.3.0-alpha - Bump version in order to publish a new version dependent on latest `libp2p-core`. diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index 4a8b8dfcdf0..c874b33bfc7 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "libp2p-webrtc-websys" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -version = "0.3.0-alpha" +version = "0.4.0-alpha" publish = true [dependencies] diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index ecf137eab8a..836acb0b9f6 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -4,6 +4,7 @@ use super::Error; use futures::future::FutureExt; use libp2p_core::multiaddr::Multiaddr; use libp2p_core::muxing::StreamMuxerBox; +use libp2p_core::transport::DialOpts; use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; use libp2p_identity::{Keypair, PeerId}; use std::future::Future; @@ -62,7 +63,15 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + if maybe_local_firefox() { return Err(TransportError::Other( "Firefox does not support WebRTC over localhost or 127.0.0.1" @@ -89,23 +98,12 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } /// Checks if local Firefox. diff --git a/transports/webrtc/CHANGELOG.md b/transports/webrtc/CHANGELOG.md index 930526d58d5..90d4ce83df3 100644 --- a/transports/webrtc/CHANGELOG.md +++ b/transports/webrtc/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.8.0-alpha + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.7.1-alpha - Bump `libp2p-webrtc-utils` dependency to `0.2.0`. diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 4fb4a2f8a45..a205810e7c4 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-webrtc" -version = "0.7.1-alpha" +version = "0.8.0-alpha" authors = ["Parity Technologies "] description = "WebRTC transport for libp2p" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 2e73ac9c459..62049c8f59b 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -22,7 +22,7 @@ use futures::{future::BoxFuture, prelude::*, stream::SelectAll}; use if_watch::{tokio::IfWatcher, IfEvent}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, }; use libp2p_identity as identity; use libp2p_identity::PeerId; @@ -118,7 +118,19 @@ impl libp2p_core::Transport for Transport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + // 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 + tracing::warn!("WebRTC hole punch is not yet supported"); + } + let (sock_addr, server_fingerprint) = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { @@ -150,21 +162,6 @@ impl libp2p_core::Transport for Transport { } .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) - } } /// A stream of incoming connections on one or more interfaces. diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 76e168edfd6..d606d66c41f 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -23,8 +23,8 @@ use futures::future::{BoxFuture, Either}; use futures::stream::StreamExt; use futures::{future, ready, AsyncReadExt, AsyncWriteExt, FutureExt, SinkExt}; use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerExt}; -use libp2p_core::transport::{Boxed, ListenerId, TransportEvent}; -use libp2p_core::{Multiaddr, Transport}; +use libp2p_core::transport::{Boxed, DialOpts, ListenerId, PortUse, TransportEvent}; +use libp2p_core::{Endpoint, Multiaddr, Transport}; use libp2p_identity::PeerId; use libp2p_webrtc as webrtc; use rand::{thread_rng, RngCore}; @@ -322,7 +322,17 @@ struct Dial<'a> { impl<'a> Dial<'a> { fn new(dialer: &'a mut Boxed<(PeerId, StreamMuxerBox)>, addr: Multiaddr) -> Self { Self { - dial_task: dialer.dial(addr).unwrap().map(|r| r.unwrap()).boxed(), + dial_task: dialer + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .map(|r| r.unwrap()) + .boxed(), dialer, } } diff --git a/transports/websocket-websys/CHANGELOG.md b/transports/websocket-websys/CHANGELOG.md index c16ad6cc406..70d866e6141 100644 --- a/transports/websocket-websys/CHANGELOG.md +++ b/transports/websocket-websys/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) ## 0.3.3 - Fix use-after-free handler invocation from JS side. diff --git a/transports/websocket-websys/Cargo.toml b/transports/websocket-websys/Cargo.toml index a2127986ddc..0b3148a8b92 100644 --- a/transports/websocket-websys/Cargo.toml +++ b/transports/websocket-websys/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-websocket-websys" edition = "2021" rust-version = "1.60.0" description = "WebSocket for libp2p under WASM environment" -version = "0.3.3" +version = "0.4.0" authors = ["Vince Vasta "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/websocket-websys/src/lib.rs b/transports/websocket-websys/src/lib.rs index c96d1d6aa3a..d2589715bbb 100644 --- a/transports/websocket-websys/src/lib.rs +++ b/transports/websocket-websys/src/lib.rs @@ -26,6 +26,7 @@ use bytes::BytesMut; use futures::task::AtomicWaker; use futures::{future::Ready, io, prelude::*}; use js_sys::Array; +use libp2p_core::transport::DialOpts; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, transport::{ListenerId, TransportError, TransportEvent}, @@ -86,7 +87,15 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let url = extract_websocket_url(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?; @@ -101,23 +110,12 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> std::task::Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } // Try to convert Multiaddr to a Websocket url. diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md index 419ff41c6fc..50b1c42d3e1 100644 --- a/transports/websocket/CHANGELOG.md +++ b/transports/websocket/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.44.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) ## 0.43.2 - fix: Avoid websocket panic on polling after errors. See [PR 5482]. diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index f1b0a413115..271631b4021 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-websocket" edition = "2021" rust-version = { workspace = true } description = "WebSocket transport for libp2p" -version = "0.43.2" +version = "0.44.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 69a01fdbd46..fc6a3f0e90e 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -23,9 +23,8 @@ use either::Either; use futures::{future::BoxFuture, prelude::*, ready, stream::BoxStream}; use futures_rustls::{client, rustls, server}; use libp2p_core::{ - connection::Endpoint, multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, Transport, }; use parking_lot::Mutex; @@ -149,19 +148,12 @@ where self.transport.lock().remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.do_dial(addr, Endpoint::Dialer) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { - self.do_dial(addr, Endpoint::Listener) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.lock().address_translation(server, observed) + self.do_dial(addr, dial_opts) } fn poll( @@ -263,7 +255,7 @@ where fn do_dial( &mut self, addr: Multiaddr, - role_override: Endpoint, + dial_opts: DialOpts, ) -> Result<::Dial, TransportError<::Error>> { let mut addr = match parse_ws_dial_addr(addr) { Ok(addr) => addr, @@ -282,8 +274,7 @@ where let future = async move { loop { - match Self::dial_once(transport.clone(), addr, tls_config.clone(), role_override) - .await + match Self::dial_once(transport.clone(), addr, tls_config.clone(), dial_opts).await { Ok(Either::Left(redirect)) => { if remaining_redirects == 0 { @@ -307,18 +298,17 @@ where transport: Arc>, addr: WsAddress, tls_config: tls::Config, - role_override: Endpoint, + dial_opts: DialOpts, ) -> Result>, Error> { tracing::trace!(address=?addr, "Dialing websocket address"); - let dial = match role_override { - Endpoint::Dialer => transport.lock().dial(addr.tcp_addr), - Endpoint::Listener => transport.lock().dial_as_listener(addr.tcp_addr), - } - .map_err(|e| match e { - TransportError::MultiaddrNotSupported(a) => Error::InvalidMultiaddr(a), - TransportError::Other(e) => Error::Transport(e), - })?; + let dial = transport + .lock() + .dial(addr.tcp_addr, dial_opts) + .map_err(|e| match e { + TransportError::MultiaddrNotSupported(a) => Error::InvalidMultiaddr(a), + TransportError::Other(e) => Error::Transport(e), + })?; let stream = dial.map_err(Error::Transport).await?; tracing::trace!(port=%addr.host_port, "TCP connection established"); diff --git a/transports/websocket/src/lib.rs b/transports/websocket/src/lib.rs index e0b3d09ca25..40d6db44471 100644 --- a/transports/websocket/src/lib.rs +++ b/transports/websocket/src/lib.rs @@ -33,7 +33,7 @@ use futures::{future::BoxFuture, prelude::*, ready}; use libp2p_core::{ connection::ConnectedPoint, multiaddr::Multiaddr, - transport::{map::MapFuture, ListenerId, TransportError, TransportEvent}, + transport::{map::MapFuture, DialOpts, ListenerId, TransportError, TransportEvent}, Transport, }; use rw_stream_sink::RwStreamSink; @@ -202,19 +202,12 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.transport.dial(addr) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - self.transport.dial_as_listener(addr) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) + self.transport.dial(addr, opts) } fn poll( @@ -292,7 +285,11 @@ where mod tests { use super::WsConfig; use futures::prelude::*; - use libp2p_core::{multiaddr::Protocol, transport::ListenerId, Multiaddr, Transport}; + use libp2p_core::{ + multiaddr::Protocol, + transport::{DialOpts, ListenerId, PortUse}, + Endpoint, Multiaddr, Transport, + }; use libp2p_identity::PeerId; use libp2p_tcp as tcp; @@ -339,7 +336,13 @@ mod tests { let outbound = new_ws_config() .boxed() - .dial(addr.with(Protocol::P2p(PeerId::random()))) + .dial( + addr.with(Protocol::P2p(PeerId::random())), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap(); let (a, b) = futures::join!(inbound, outbound); diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md index 0409819a63f..2aab226ab12 100644 --- a/transports/webtransport-websys/CHANGELOG.md +++ b/transports/webtransport-websys/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.3.0 * Fix unhandled exceptions thrown when calling `Webtransport::close`. diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index 3defdce5203..68ba7091794 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-webtransport-websys" edition = "2021" rust-version = { workspace = true } description = "WebTransport for libp2p under WASM environment" -version = "0.3.0" +version = "0.4.0" authors = [ "Yiannis Marangos ", "oblique ", diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs index 3f14f3e476b..6a9a9dad954 100644 --- a/transports/webtransport-websys/src/transport.rs +++ b/transports/webtransport-websys/src/transport.rs @@ -1,6 +1,8 @@ use futures::future::FutureExt; use libp2p_core::muxing::StreamMuxerBox; -use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_core::transport::{ + Boxed, DialOpts, ListenerId, Transport as _, TransportError, TransportEvent, +}; use libp2p_identity::{Keypair, PeerId}; use multiaddr::Multiaddr; use std::future::Future; @@ -62,7 +64,15 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let endpoint = Endpoint::from_multiaddr(&addr).map_err(|e| match e { e @ Error::InvalidMultiaddr(_) => { tracing::debug!("{}", e); @@ -83,21 +93,10 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs index 1f420cd6671..938cdf0b3e1 100644 --- a/wasm-tests/webtransport-tests/src/lib.rs +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -1,7 +1,8 @@ use futures::channel::oneshot; use futures::{AsyncReadExt, AsyncWriteExt}; use getrandom::getrandom; -use libp2p_core::{StreamMuxer, Transport as _}; +use libp2p_core::transport::{DialOpts, PortUse}; +use libp2p_core::{Endpoint, StreamMuxer, Transport as _}; use libp2p_identity::{Keypair, PeerId}; use libp2p_noise as noise; use libp2p_webtransport_websys::{Config, Connection, Error, Stream, Transport}; @@ -263,7 +264,17 @@ async fn connect_without_peer_id() { addr.pop(); let mut transport = Transport::new(Config::new(&keypair)); - transport.dial(addr).unwrap().await.unwrap(); + transport + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); } #[wasm_bindgen_test] @@ -278,7 +289,17 @@ async fn error_on_unknown_peer_id() { addr.push(Protocol::P2p(PeerId::random())); let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + let e = transport + .dial( + addr.clone(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap_err(); assert!(matches!(e, Error::UnknownRemotePeerId)); } @@ -297,7 +318,17 @@ async fn error_on_unknown_certhash() { addr.push(peer_id); let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + let e = transport + .dial( + addr.clone(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap_err(); assert!(matches!( e, Error::Noise(noise::Error::UnknownWebTransportCerthashes(..)) @@ -310,7 +341,17 @@ async fn new_connection_to_echo_server() -> Connection { let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, conn) = transport.dial(addr).unwrap().await.unwrap(); + let (_peer_id, conn) = transport + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); conn } From 4de8b839855d2452a09c7a8936c8575e79ebd12a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:12:12 +0000 Subject: [PATCH 11/35] deps: bump EmbarkStudios/cargo-deny-action from 1 to 2 Pull-Request: #5530. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 648945820fa..d0465540d47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -398,6 +398,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check advisories bans licenses sources From 732ade698303a8346c44d8638143ca502458b670 Mon Sep 17 00:00:00 2001 From: Guillaume Michel Date: Mon, 5 Aug 2024 17:47:39 +0200 Subject: [PATCH 12/35] chore: remove RUSTFLAG from docs workflow `"--cfg docsrs"` shouldn't be part of `RUSTFLAGS` but only of `RUSTDOCFLAGS`. This should fix failing docs Actions. Pull-Request: #5517. --- .github/workflows/docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b2a761fd8c1..e2bac78c006 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,6 @@ jobs: run: cargo +nightly doc --no-deps --workspace -F full env: RUSTDOCFLAGS: "--cfg docsrs" - RUSTFLAGS: "--cfg docsrs" - name: Add index file run: | mkdir host-docs From 823acd6f6991b1b6b0f6d09959d89599d6abc730 Mon Sep 17 00:00:00 2001 From: Hannes <55623006+umgefahren@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:14:49 +0200 Subject: [PATCH 13/35] docs: As implied by #5517 make sure everything still builds on docs.rs As @guillaumemichel already found out, passing the `docsrs` cfg to rustc now breaks the documentation build. Although I couldn't exactly reproduce the docs.rs build env (everyone is welcome to try; it's explained [here](https://github.com/rust-lang/docs.rs/blob/master/README.md#build-subcommand)) it's safe to assume that the same problem will occur when we push to docs.rs. Passing the docsrs flag is also no longer required since it's now automatically passed to rustdoc when building on docs.rs. Explanation: https://docs.rs/about/builds#detecting-docsrs Pull-Request: #5535. --- core/Cargo.toml | 2 -- identity/Cargo.toml | 2 -- libp2p/Cargo.toml | 2 -- misc/metrics/Cargo.toml | 2 -- misc/multistream-select/Cargo.toml | 2 -- misc/quick-protobuf-codec/Cargo.toml | 2 -- misc/rw-stream-sink/Cargo.toml | 2 -- muxers/mplex/Cargo.toml | 2 -- muxers/yamux/Cargo.toml | 2 -- protocols/autonat/Cargo.toml | 2 -- protocols/dcutr/Cargo.toml | 2 -- protocols/floodsub/Cargo.toml | 2 -- protocols/gossipsub/Cargo.toml | 2 -- protocols/identify/Cargo.toml | 2 -- protocols/kad/Cargo.toml | 2 -- protocols/mdns/Cargo.toml | 2 -- protocols/perf/Cargo.toml | 2 -- protocols/ping/Cargo.toml | 2 -- protocols/relay/Cargo.toml | 2 -- protocols/rendezvous/Cargo.toml | 2 -- protocols/request-response/Cargo.toml | 2 -- protocols/upnp/Cargo.toml | 2 -- swarm-derive/Cargo.toml | 2 -- swarm/Cargo.toml | 2 -- transports/dns/Cargo.toml | 2 -- transports/noise/Cargo.toml | 2 -- transports/plaintext/Cargo.toml | 2 -- transports/pnet/Cargo.toml | 2 -- transports/quic/Cargo.toml | 2 -- transports/tcp/Cargo.toml | 2 -- transports/tls/Cargo.toml | 2 -- transports/uds/Cargo.toml | 2 -- transports/webrtc/Cargo.toml | 2 -- transports/websocket-websys/Cargo.toml | 2 -- transports/websocket/Cargo.toml | 2 -- transports/webtransport-websys/Cargo.toml | 2 -- 36 files changed, 72 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 3f9bd540f23..8a083276e7f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -48,8 +48,6 @@ serde = ["multihash/serde-codec", "dep:serde", "libp2p-identity/serde"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/identity/Cargo.toml b/identity/Cargo.toml index ea49a1adbb4..cb0b8cb000e 100644 --- a/identity/Cargo.toml +++ b/identity/Cargo.toml @@ -56,8 +56,6 @@ harness = false # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index 59a075b777e..68a76e52b58 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -150,8 +150,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/metrics/Cargo.toml b/misc/metrics/Cargo.toml index 5fb927bee84..b75ea5ed0de 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -40,8 +40,6 @@ libp2p-identity = { workspace = true, features = ["rand"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustc-args = ["--cfg", "docsrs"] -rustdoc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/multistream-select/Cargo.toml b/misc/multistream-select/Cargo.toml index 77d8de54332..1bbe3642477 100644 --- a/misc/multistream-select/Cargo.toml +++ b/misc/multistream-select/Cargo.toml @@ -30,8 +30,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/quick-protobuf-codec/Cargo.toml b/misc/quick-protobuf-codec/Cargo.toml index a98ffa5308f..985479059a2 100644 --- a/misc/quick-protobuf-codec/Cargo.toml +++ b/misc/quick-protobuf-codec/Cargo.toml @@ -30,8 +30,6 @@ harness = false # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/rw-stream-sink/Cargo.toml b/misc/rw-stream-sink/Cargo.toml index 557163c438a..20fa2fa23fa 100644 --- a/misc/rw-stream-sink/Cargo.toml +++ b/misc/rw-stream-sink/Cargo.toml @@ -22,8 +22,6 @@ async-std = "1.0" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index fb55e03b614..4fdb4dabedd 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -42,8 +42,6 @@ harness = false # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index f56cd485b9b..289f84fe1dd 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -27,8 +27,6 @@ libp2p-muxer-test-harness = { path = "../test-harness" } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index f047cb7d5ba..7e31a7f3895 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -34,8 +34,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/dcutr/Cargo.toml b/protocols/dcutr/Cargo.toml index cd46840caf3..6b1d04f82f5 100644 --- a/protocols/dcutr/Cargo.toml +++ b/protocols/dcutr/Cargo.toml @@ -47,8 +47,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index 9f0557c6d01..18d77e99e9c 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -30,8 +30,6 @@ tracing = { workspace = true } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index f989e997bfb..4cb590bed0c 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -55,8 +55,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index 320c8465650..cdc5ce587de 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -37,8 +37,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 494d812c6ec..72b29d00ef7 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -52,8 +52,6 @@ serde = ["dep:serde", "bytes/serde"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 0c8229465d6..19ae5ce9f36 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -54,8 +54,6 @@ required-features = ["tokio"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index abe58088caa..398bdce65ec 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -42,8 +42,6 @@ libp2p-swarm-test = { path = "../../swarm-test" } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index 2c347adb2c4..66775d3ba8d 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -33,8 +33,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 1a0e7836158..084fec07efd 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -44,8 +44,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index 32b233813e3..78a6a1a0a4c 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -44,8 +44,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index af70fd9a83e..c6b2eda348b 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -47,8 +47,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/upnp/Cargo.toml b/protocols/upnp/Cargo.toml index 50ed9db0f6f..e9c7414236d 100644 --- a/protocols/upnp/Cargo.toml +++ b/protocols/upnp/Cargo.toml @@ -30,5 +30,3 @@ workspace = true # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/swarm-derive/Cargo.toml b/swarm-derive/Cargo.toml index d4b596a12cf..b31ea188962 100644 --- a/swarm-derive/Cargo.toml +++ b/swarm-derive/Cargo.toml @@ -23,8 +23,6 @@ proc-macro2 = "1.0" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/swarm/Cargo.toml b/swarm/Cargo.toml index 1e2842998a3..375f51490a8 100644 --- a/swarm/Cargo.toml +++ b/swarm/Cargo.toml @@ -67,8 +67,6 @@ required-features = ["macros"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [[bench]] name = "connection_handler" diff --git a/transports/dns/Cargo.toml b/transports/dns/Cargo.toml index b728509364a..707b67fc935 100644 --- a/transports/dns/Cargo.toml +++ b/transports/dns/Cargo.toml @@ -35,8 +35,6 @@ tokio = ["hickory-resolver/tokio-runtime"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 7333d4cddda..76cf9475cc4 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -43,8 +43,6 @@ libp2p-identity = { workspace = true, features = ["rand"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index db6fe75b505..4f07c5fee6e 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -31,8 +31,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/pnet/Cargo.toml b/transports/pnet/Cargo.toml index 76964ce3152..e7b3bfc30ac 100644 --- a/transports/pnet/Cargo.toml +++ b/transports/pnet/Cargo.toml @@ -33,8 +33,6 @@ tokio = { workspace = true, features = ["full"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index d2d8c6fc2b0..1f540c9542a 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -35,8 +35,6 @@ async-std = ["dep:async-std", "if-watch/smol", "quinn/runtime-async-std"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index b17d7f3b58e..03e7fac491c 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -37,8 +37,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/tls/Cargo.toml b/transports/tls/Cargo.toml index c4b30951e66..41b4c215dd9 100644 --- a/transports/tls/Cargo.toml +++ b/transports/tls/Cargo.toml @@ -40,8 +40,6 @@ tokio = { workspace = true, features = ["full"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index df5159f3c02..f70ac388fa8 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -24,8 +24,6 @@ tempfile = "3.10" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index a205810e7c4..fc2748d93c3 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -55,5 +55,3 @@ workspace = true # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/transports/websocket-websys/Cargo.toml b/transports/websocket-websys/Cargo.toml index 0b3148a8b92..32483f28c57 100644 --- a/transports/websocket-websys/Cargo.toml +++ b/transports/websocket-websys/Cargo.toml @@ -26,8 +26,6 @@ web-sys = { version = "0.3.69", features = ["BinaryType", "CloseEvent", "Message # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [dev-dependencies] libp2p-yamux = { workspace = true } diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index 271631b4021..e08346da5ca 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -36,8 +36,6 @@ rcgen = { workspace = true } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index 68ba7091794..370158190b1 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -44,8 +44,6 @@ multibase = "0.9.1" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true From 98da34a7dc216b5e022ff5400356c73a23dfac96 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 7 Aug 2024 12:19:54 +0300 Subject: [PATCH 14/35] feat(websocket): Allow wss connections on IP addresses Pull-Request: #5525. --- transports/websocket/CHANGELOG.md | 3 + transports/websocket/src/framed.rs | 141 +++++++++++++++++++++++++---- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md index 50b1c42d3e1..df51e2c807d 100644 --- a/transports/websocket/CHANGELOG.md +++ b/transports/websocket/CHANGELOG.md @@ -2,6 +2,9 @@ - Implement refactored `Transport`. See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Allow wss connections on IP addresses. + See [PR 5525](https://github.com/libp2p/rust-libp2p/pull/5525). + ## 0.43.2 - fix: Avoid websocket panic on polling after errors. See [PR 5482]. diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index fc6a3f0e90e..074271e672f 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -21,7 +21,8 @@ use crate::{error::Error, quicksink, tls}; use either::Either; use futures::{future::BoxFuture, prelude::*, ready, stream::BoxStream}; -use futures_rustls::{client, rustls, server}; +use futures_rustls::rustls::pki_types::ServerName; +use futures_rustls::{client, server}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, transport::{DialOpts, ListenerId, TransportError, TransportEvent}, @@ -32,6 +33,7 @@ use soketto::{ connection::{self, CloseReason}, handshake, }; +use std::net::IpAddr; use std::{collections::HashMap, ops::DerefMut, sync::Arc}; use std::{fmt, io, mem, pin::Pin, task::Context, task::Poll}; use url::Url; @@ -315,15 +317,12 @@ where let stream = if addr.use_tls { // begin TLS session - let dns_name = addr - .dns_name - .expect("for use_tls we have checked that dns_name is some"); - tracing::trace!(?dns_name, "Starting TLS handshake"); + tracing::trace!(?addr.server_name, "Starting TLS handshake"); let stream = tls_config .client - .connect(dns_name.clone(), stream) + .connect(addr.server_name.clone(), stream) .map_err(|e| { - tracing::debug!(?dns_name, "TLS handshake failed: {}", e); + tracing::debug!(?addr.server_name, "TLS handshake failed: {}", e); Error::Tls(tls::Error::from(e)) }) .await?; @@ -451,7 +450,7 @@ where struct WsAddress { host_port: String, path: String, - dns_name: Option>, + server_name: ServerName<'static>, use_tls: bool, tcp_addr: Multiaddr, } @@ -468,19 +467,21 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { let mut protocols = addr.iter(); let mut ip = protocols.next(); let mut tcp = protocols.next(); - let (host_port, dns_name) = loop { + let (host_port, server_name) = loop { match (ip, tcp) { (Some(Protocol::Ip4(ip)), Some(Protocol::Tcp(port))) => { - break (format!("{ip}:{port}"), None) + let server_name = ServerName::IpAddress(IpAddr::V4(ip).into()); + break (format!("{ip}:{port}"), server_name); } (Some(Protocol::Ip6(ip)), Some(Protocol::Tcp(port))) => { - break (format!("{ip}:{port}"), None) + let server_name = ServerName::IpAddress(IpAddr::V6(ip).into()); + break (format!("[{ip}]:{port}"), server_name); } (Some(Protocol::Dns(h)), Some(Protocol::Tcp(port))) | (Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port))) | (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) | (Some(Protocol::Dnsaddr(h)), Some(Protocol::Tcp(port))) => { - break (format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?)) + break (format!("{h}:{port}"), tls::dns_name_ref(&h)?) } (Some(_), Some(p)) => { ip = Some(p); @@ -499,13 +500,7 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { match protocols.pop() { p @ Some(Protocol::P2p(_)) => p2p = p, Some(Protocol::Ws(path)) => break (false, path.into_owned()), - Some(Protocol::Wss(path)) => { - if dns_name.is_none() { - tracing::debug!(address=%addr, "Missing DNS name in WSS address"); - return Err(Error::InvalidMultiaddr(addr)); - } - break (true, path.into_owned()); - } + Some(Protocol::Wss(path)) => break (true, path.into_owned()), _ => return Err(Error::InvalidMultiaddr(addr)), } }; @@ -519,7 +514,7 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { Ok(WsAddress { host_port, - dns_name, + server_name, path, use_tls, tcp_addr, @@ -757,3 +752,109 @@ where .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } } + +#[cfg(test)] +mod tests { + use super::*; + use libp2p_identity::PeerId; + use std::io; + + #[test] + fn dial_addr() { + let peer_id = PeerId::random(); + + // Check `/wss` + let addr = "/dns4/example.com/tcp/2222/wss" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/wss` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/wss/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/wss` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/wss".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/wss` with `/ip6` + let addr = "/ip6/::1/tcp/2222/wss".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + + // Check `/ws` + let addr = "/dns4/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + + // Check non-ws address + let addr = "/ip4/127.0.0.1/tcp/2222".parse::().unwrap(); + parse_ws_dial_addr::(addr).unwrap_err(); + } +} From 417968e07cfb79163a541ee2e5871a9bb6fe9534 Mon Sep 17 00:00:00 2001 From: Guillaume Michel Date: Thu, 8 Aug 2024 14:16:43 +0200 Subject: [PATCH 15/35] feat(kad): configurable bucket size Making bucket size configurable. Currently `K_VALUE` is used by default, and the only way to change the bucket size is to edit the const. Resolves https://github.com/libp2p/rust-libp2p/issues/5389 Pull-Request: #5414. --- protocols/kad/CHANGELOG.md | 8 ++- protocols/kad/src/behaviour.rs | 26 +++++++-- protocols/kad/src/kbucket.rs | 91 +++++++++++++++++++++-------- protocols/kad/src/kbucket/bucket.rs | 78 ++++++++++++++++++------- 4 files changed, 151 insertions(+), 52 deletions(-) diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index a5b404ae6bd..cbb5a4decf2 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -3,9 +3,9 @@ - Included multiaddresses of found peers alongside peer IDs in `GetClosestPeers` query results. See [PR 5475](https://github.com/libp2p/rust-libp2p/pull/5475) - Changed `FIND_NODE` response: now includes a list of closest peers when querying the recipient peer ID. Previously, this request yielded an empty response. - See [PR 5270](https://github.com/libp2p/rust-libp2p/pull/5270) -- Update to DHT republish interval and expiration time defaults to 22h and 48h respectively, rationale in [libp2p/specs#451](https://github.com/libp2p/specs/pull/451) - See [PR 3230](https://github.com/libp2p/rust-libp2p/pull/3230) + See [PR 5270](https://github.com/libp2p/rust-libp2p/pull/5270). +- Update to DHT republish interval and expiration time defaults to 22h and 48h respectively, rationale in [libp2p/specs#451](https://github.com/libp2p/specs/pull/451). + See [PR 3230](https://github.com/libp2p/rust-libp2p/pull/3230). - Use default dial conditions more consistently. See [PR 4957](https://github.com/libp2p/rust-libp2p/pull/4957) - QueryClose progress whenever closer in range, instead of having to be the closest. @@ -19,6 +19,8 @@ See [PR 5148](https://github.com/libp2p/rust-libp2p/pull/5148). - Derive `Copy` for `kbucket::key::Key`. See [PR 5317](https://github.com/libp2p/rust-libp2p/pull/5317). +⁻ `KBucket` size can now be modified without changing the `K_VALUE`. + See [PR 5414](https://github.com/libp2p/rust-libp2p/pull/5414). - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 8d6f86d7e0f..fc3d8a1adaa 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -24,7 +24,7 @@ mod test; use crate::addresses::Addresses; use crate::handler::{Handler, HandlerEvent, HandlerIn, RequestId}; -use crate::kbucket::{self, Distance, KBucketsTable, NodeStatus}; +use crate::kbucket::{self, Distance, KBucketConfig, KBucketsTable, NodeStatus}; use crate::protocol::{ConnectionType, KadPeer, ProtocolConfig}; use crate::query::{Query, QueryConfig, QueryId, QueryPool, QueryPoolState}; use crate::record::{ @@ -172,7 +172,7 @@ pub enum StoreInserts { /// The configuration is consumed by [`Behaviour::new`]. #[derive(Debug, Clone)] pub struct Config { - kbucket_pending_timeout: Duration, + kbucket_config: KBucketConfig, query_config: QueryConfig, protocol_config: ProtocolConfig, record_ttl: Option, @@ -215,7 +215,7 @@ impl Config { /// Builds a new `Config` with the given protocol name. pub fn new(protocol_name: StreamProtocol) -> Self { Config { - kbucket_pending_timeout: Duration::from_secs(60), + kbucket_config: KBucketConfig::default(), query_config: QueryConfig::default(), protocol_config: ProtocolConfig::new(protocol_name), record_ttl: Some(Duration::from_secs(48 * 60 * 60)), @@ -424,6 +424,24 @@ impl Config { self } + /// Sets the configuration for the k-buckets. + /// + /// * Default to K_VALUE. + pub fn set_kbucket_size(&mut self, size: NonZeroUsize) -> &mut Self { + self.kbucket_config.set_bucket_size(size); + self + } + + /// Sets the timeout duration after creation of a pending entry after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + /// + /// * Default to `60` s. + pub fn set_kbucket_pending_timeout(&mut self, timeout: Duration) -> &mut Self { + self.kbucket_config.set_pending_timeout(timeout); + self + } + /// Sets the time to wait before calling [`Behaviour::bootstrap`] after a new peer is inserted in the routing table. /// This prevent cascading bootstrap requests when multiple peers are inserted into the routing table "at the same time". /// This also allows to wait a little bit for other potential peers to be inserted into the routing table before @@ -481,7 +499,7 @@ where Behaviour { store, caching: config.caching, - kbuckets: KBucketsTable::new(local_key, config.kbucket_pending_timeout), + kbuckets: KBucketsTable::new(local_key, config.kbucket_config), kbucket_inserts: config.kbucket_inserts, protocol_config: config.protocol_config, record_filtering: config.record_filtering, diff --git a/protocols/kad/src/kbucket.rs b/protocols/kad/src/kbucket.rs index 7ed10f7f853..28d7df03917 100644 --- a/protocols/kad/src/kbucket.rs +++ b/protocols/kad/src/kbucket.rs @@ -75,15 +75,49 @@ mod key; pub use bucket::NodeStatus; pub use entry::*; -use arrayvec::ArrayVec; use bucket::KBucket; use std::collections::VecDeque; +use std::num::NonZeroUsize; use std::time::Duration; use web_time::Instant; /// Maximum number of k-buckets. const NUM_BUCKETS: usize = 256; +/// The configuration for `KBucketsTable`. +#[derive(Debug, Clone, Copy)] +pub(crate) struct KBucketConfig { + /// Maximal number of nodes that a bucket can contain. + bucket_size: usize, + /// Specifies the duration after creation of a [`PendingEntry`] after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + pending_timeout: Duration, +} + +impl Default for KBucketConfig { + fn default() -> Self { + KBucketConfig { + bucket_size: K_VALUE.get(), + pending_timeout: Duration::from_secs(60), + } + } +} + +impl KBucketConfig { + /// Modifies the maximal number of nodes that a bucket can contain. + pub(crate) fn set_bucket_size(&mut self, bucket_size: NonZeroUsize) { + self.bucket_size = bucket_size.get(); + } + + /// Modifies the duration after creation of a [`PendingEntry`] after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + pub(crate) fn set_pending_timeout(&mut self, pending_timeout: Duration) { + self.pending_timeout = pending_timeout; + } +} + /// A `KBucketsTable` represents a Kademlia routing table. #[derive(Debug, Clone)] pub(crate) struct KBucketsTable { @@ -91,6 +125,8 @@ pub(crate) struct KBucketsTable { local_key: TKey, /// The buckets comprising the routing table. buckets: Vec>, + /// The maximal number of nodes that a bucket can contain. + bucket_size: usize, /// The list of evicted entries that have been replaced with pending /// entries since the last call to [`KBucketsTable::take_applied_pending`]. applied_pending: VecDeque>, @@ -151,17 +187,12 @@ where TVal: Clone, { /// Creates a new, empty Kademlia routing table with entries partitioned - /// into buckets as per the Kademlia protocol. - /// - /// The given `pending_timeout` specifies the duration after creation of - /// a [`PendingEntry`] after which it becomes eligible for insertion into - /// a full bucket, replacing the least-recently (dis)connected node. - pub(crate) fn new(local_key: TKey, pending_timeout: Duration) -> Self { + /// into buckets as per the Kademlia protocol using the provided config. + pub(crate) fn new(local_key: TKey, config: KBucketConfig) -> Self { KBucketsTable { local_key, - buckets: (0..NUM_BUCKETS) - .map(|_| KBucket::new(pending_timeout)) - .collect(), + buckets: (0..NUM_BUCKETS).map(|_| KBucket::new(config)).collect(), + bucket_size: config.bucket_size, applied_pending: VecDeque::new(), } } @@ -247,13 +278,16 @@ where T: AsRef, { let distance = self.local_key.as_ref().distance(target); + let bucket_size = self.bucket_size; ClosestIter { target, iter: None, table: self, buckets_iter: ClosestBucketsIter::new(distance), - fmap: |b: &KBucket| -> ArrayVec<_, { K_VALUE.get() }> { - b.iter().map(|(n, _)| n.key.clone()).collect() + fmap: move |b: &KBucket| -> Vec<_> { + let mut vec = Vec::with_capacity(bucket_size); + vec.extend(b.iter().map(|(n, _)| n.key.clone())); + vec }, } } @@ -269,13 +303,15 @@ where TVal: Clone, { let distance = self.local_key.as_ref().distance(target); + let bucket_size = self.bucket_size; ClosestIter { target, iter: None, table: self, buckets_iter: ClosestBucketsIter::new(distance), - fmap: |b: &KBucket<_, TVal>| -> ArrayVec<_, { K_VALUE.get() }> { + fmap: move |b: &KBucket<_, TVal>| -> Vec<_> { b.iter() + .take(bucket_size) .map(|(n, status)| EntryView { node: n.clone(), status, @@ -324,7 +360,7 @@ struct ClosestIter<'a, TTarget, TKey, TVal, TMap, TOut> { /// distance of the local key to the target. buckets_iter: ClosestBucketsIter, /// The iterator over the entries in the currently traversed bucket. - iter: Option>, + iter: Option>, /// The projection function / mapping applied on each bucket as /// it is encountered, producing the next `iter`ator. fmap: TMap, @@ -429,7 +465,7 @@ where TTarget: AsRef, TKey: Clone + AsRef, TVal: Clone, - TMap: Fn(&KBucket) -> ArrayVec, + TMap: Fn(&KBucket) -> Vec, TOut: AsRef, { type Item = TOut; @@ -535,11 +571,14 @@ mod tests { fn arbitrary(g: &mut Gen) -> TestTable { let local_key = Key::from(PeerId::random()); let timeout = Duration::from_secs(g.gen_range(1..360)); - let mut table = TestTable::new(local_key.into(), timeout); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let bucket_size = config.bucket_size; + let mut table = TestTable::new(local_key.into(), config); let mut num_total = g.gen_range(0..100); for (i, b) in &mut table.buckets.iter_mut().enumerate().rev() { let ix = BucketIndex(i); - let num = g.gen_range(0..usize::min(K_VALUE.get(), num_total) + 1); + let num = g.gen_range(0..usize::min(bucket_size, num_total) + 1); num_total -= num; for _ in 0..num { let distance = ix.rand_distance(&mut rand::thread_rng()); @@ -560,7 +599,9 @@ mod tests { fn buckets_are_non_overlapping_and_exhaustive() { let local_key = Key::from(PeerId::random()); let timeout = Duration::from_secs(0); - let mut table = KBucketsTable::::new(local_key.into(), timeout); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let mut table = KBucketsTable::::new(local_key.into(), config); let mut prev_max = U256::from(0); @@ -577,7 +618,9 @@ mod tests { fn bucket_contains_range() { fn prop(ix: u8) { let index = BucketIndex(ix as usize); - let mut bucket = KBucket::, ()>::new(Duration::from_secs(0)); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(Duration::from_secs(0)); + let mut bucket = KBucket::, ()>::new(config); let bucket_ref = KBucketRef { index, bucket: &mut bucket, @@ -623,7 +666,7 @@ mod tests { let local_key = Key::from(PeerId::random()); let other_id = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_secs(5)); + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); if let Some(Entry::Absent(entry)) = table.entry(&other_id) { match entry.insert((), NodeStatus::Connected) { InsertResult::Inserted => (), @@ -641,7 +684,7 @@ mod tests { #[test] fn entry_self() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_secs(5)); + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); assert!(table.entry(&local_key).is_none()) } @@ -649,7 +692,7 @@ mod tests { #[test] fn closest() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_secs(5)); + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); let mut count = 0; loop { if count == 100 { @@ -684,7 +727,9 @@ mod tests { #[test] fn applied_pending() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_millis(1)); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(Duration::from_millis(1)); + let mut table = KBucketsTable::<_, ()>::new(local_key, config); let expected_applied; let full_bucket_index; loop { diff --git a/protocols/kad/src/kbucket/bucket.rs b/protocols/kad/src/kbucket/bucket.rs index 1bd4389eb3d..1426017aa7a 100644 --- a/protocols/kad/src/kbucket/bucket.rs +++ b/protocols/kad/src/kbucket/bucket.rs @@ -88,15 +88,17 @@ pub struct Node { } /// The position of a node in a `KBucket`, i.e. a non-negative integer -/// in the range `[0, K_VALUE)`. +/// in the range `[0, bucket_size)`. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct Position(usize); -/// A `KBucket` is a list of up to `K_VALUE` keys and associated values, +/// A `KBucket` is a list of up to `capacity` keys and associated values, /// ordered from least-recently connected to most-recently connected. #[derive(Debug, Clone)] pub(crate) struct KBucket { /// The nodes contained in the bucket. - nodes: ArrayVec, { K_VALUE.get() }>, + nodes: Vec>, + /// The maximal number of nodes that a bucket can contain. + capacity: usize, /// The position (index) in `nodes` that marks the first connected node. /// @@ -104,7 +106,7 @@ pub(crate) struct KBucket { /// most-recently connected, all entries above this index are also considered /// connected, i.e. the range `[0, first_connected_pos)` marks the sub-list of entries /// that are considered disconnected and the range - /// `[first_connected_pos, K_VALUE)` marks sub-list of entries that are + /// `[first_connected_pos, capacity)` marks sub-list of entries that are /// considered connected. /// /// `None` indicates that there are no connected entries in the bucket, i.e. @@ -156,18 +158,31 @@ pub(crate) struct AppliedPending { pub(crate) evicted: Option>, } +impl Default for KBucket { + fn default() -> Self { + KBucket { + nodes: Vec::with_capacity(K_VALUE.get()), + capacity: K_VALUE.get(), + first_connected_pos: None, + pending: None, + pending_timeout: Duration::from_secs(60), + } + } +} + impl KBucket where TKey: Clone + AsRef, TVal: Clone, { - /// Creates a new `KBucket` with the given timeout for pending entries. - pub(crate) fn new(pending_timeout: Duration) -> Self { + /// Creates a new `KBucket` with the given configuration. + pub(crate) fn new(config: KBucketConfig) -> Self { KBucket { - nodes: ArrayVec::new(), + nodes: Vec::with_capacity(config.bucket_size), + capacity: config.bucket_size, first_connected_pos: None, pending: None, - pending_timeout, + pending_timeout: config.pending_timeout, } } @@ -205,7 +220,7 @@ where pub(crate) fn apply_pending(&mut self) -> Option> { if let Some(pending) = self.pending.take() { if pending.replace <= Instant::now() { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { if self.status(Position(0)) == NodeStatus::Connected { // The bucket is full with connected nodes. Drop the pending node. return None; @@ -316,7 +331,7 @@ where ) -> InsertResult { match status { NodeStatus::Connected => { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { if self.first_connected_pos == Some(0) || self.pending.is_some() { return InsertResult::Full; } else { @@ -336,7 +351,7 @@ where InsertResult::Inserted } NodeStatus::Disconnected => { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { return InsertResult::Full; } if let Some(ref mut p) = self.first_connected_pos { @@ -435,8 +450,10 @@ mod tests { impl Arbitrary for KBucket, ()> { fn arbitrary(g: &mut Gen) -> KBucket, ()> { let timeout = Duration::from_secs(g.gen_range(1..g.size()) as u64); - let mut bucket = KBucket::, ()>::new(timeout); - let num_nodes = g.gen_range(1..K_VALUE.get() + 1); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let mut bucket = KBucket::, ()>::new(config); + let num_nodes = g.gen_range(1..bucket.capacity + 1); for _ in 0..num_nodes { let key = Key::from(PeerId::random()); let node = Node { key, value: () }; @@ -469,7 +486,7 @@ mod tests { // Fill a bucket with random nodes with the given status. fn fill_bucket(bucket: &mut KBucket, ()>, status: NodeStatus) { let num_entries_start = bucket.num_entries(); - for i in 0..K_VALUE.get() - num_entries_start { + for i in 0..bucket.capacity - num_entries_start { let key = Key::from(PeerId::random()); let node = Node { key, value: () }; assert_eq!(InsertResult::Inserted, bucket.insert(node, status)); @@ -480,7 +497,7 @@ mod tests { #[test] fn ordering() { fn prop(status: Vec) -> bool { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); // The expected lists of connected and disconnected nodes. let mut connected = VecDeque::new(); @@ -490,7 +507,7 @@ mod tests { for status in status { let key = Key::from(PeerId::random()); let node = Node { key, value: () }; - let full = bucket.num_entries() == K_VALUE.get(); + let full = bucket.num_entries() == bucket.capacity; if let InsertResult::Inserted = bucket.insert(node, status) { let vec = match status { NodeStatus::Connected => &mut connected, @@ -522,7 +539,7 @@ mod tests { #[test] fn full_bucket() { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); // Fill the bucket with disconnected nodes. fill_bucket(&mut bucket, NodeStatus::Disconnected); @@ -536,7 +553,7 @@ mod tests { } // One-by-one fill the bucket with connected nodes, replacing the disconnected ones. - for i in 0..K_VALUE.get() { + for i in 0..bucket.capacity { let (first, first_status) = bucket.iter().next().unwrap(); let first_disconnected = first.clone(); assert_eq!(first_status, NodeStatus::Disconnected); @@ -573,11 +590,11 @@ mod tests { ); assert_eq!(Some((&node, NodeStatus::Connected)), bucket.iter().last()); assert!(bucket.pending().is_none()); - assert_eq!(Some(K_VALUE.get() - (i + 1)), bucket.first_connected_pos); + assert_eq!(Some(bucket.capacity - (i + 1)), bucket.first_connected_pos); } assert!(bucket.pending().is_none()); - assert_eq!(K_VALUE.get(), bucket.num_entries()); + assert_eq!(bucket.capacity, bucket.num_entries()); // Trying to insert another connected node fails. let key = Key::from(PeerId::random()); @@ -590,7 +607,7 @@ mod tests { #[test] fn full_bucket_discard_pending() { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); fill_bucket(&mut bucket, NodeStatus::Disconnected); let (first, _) = bucket.iter().next().unwrap(); let first_disconnected = first.clone(); @@ -622,7 +639,7 @@ mod tests { bucket.first_connected_pos ); assert_eq!(1, bucket.num_connected()); - assert_eq!(K_VALUE.get() - 1, bucket.num_disconnected()); + assert_eq!(bucket.capacity - 1, bucket.num_disconnected()); } #[test] @@ -654,4 +671,21 @@ mod tests { quickcheck(prop as fn(_, _, _) -> _); } + + #[test] + fn test_custom_bucket_size() { + let bucket_sizes: [NonZeroUsize; 4] = [ + NonZeroUsize::new(2).unwrap(), + NonZeroUsize::new(20).unwrap(), + NonZeroUsize::new(200).unwrap(), + NonZeroUsize::new(2000).unwrap(), + ]; + for &size in &bucket_sizes { + let mut config = KBucketConfig::default(); + config.set_bucket_size(size); + let mut bucket = KBucket::, ()>::new(config); + fill_bucket(&mut bucket, NodeStatus::Disconnected); + assert_eq!(size.get(), bucket.num_entries()); + } + } } From ddda3be64468af360419daf92f824375247f40e3 Mon Sep 17 00:00:00 2001 From: Guillaume Michel Date: Thu, 8 Aug 2024 15:05:11 +0200 Subject: [PATCH 16/35] chore: add Gui as a maintainer Pull-Request: #5538. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f43cf044ef3..eeab866768d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). (In alphabetical order.) +- Guillaume Michel ([@guillaumemichel](https://github.com/guillaumemichel)) - João Oliveira ([@jxs](https://github.com/jxs)) ## Notable users From 848e2e9e103af75f7b1d868839e971d2cc5dc706 Mon Sep 17 00:00:00 2001 From: Hannes <55623006+umgefahren@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:35:29 +0200 Subject: [PATCH 17/35] feat(autonat): Implement AutoNATv2 Closes: #4524 This is the implementation of the evolved AutoNAT protocol, named AutonatV2 as defined in the [spec](https://github.com/libp2p/specs/blob/03718ef0f2dea4a756a85ba716ee33f97e4a6d6c/autonat/autonat-v2.md). The stabilization PR for the spec can be found under libp2p/specs#538. The work on the Rust implementation can be found in the PR to my fork: umgefahren/rust-libp2p#1. The implementation has been smoke-tested with the Go implementation (PR: libp2p/go-libp2p#2469). The new protocol addresses shortcomings of the original AutoNAT protocol: - Since the server now always dials back over a newly allocated port, this made #4568 necessary; the client can be sure of the reachability state for other peers, even if the connection to the server was made through a hole punch. - The server can now test addresses different from the observed address (i.e., the connection to the server was made through a `p2p-circuit`). To mitigate against DDoS attacks, the client has to send more data to the server than the dial-back costs. Pull-Request: #5526. --- Cargo.lock | 227 ++++++- Cargo.toml | 1 + examples/autonatv2/Cargo.toml | 38 ++ examples/autonatv2/Dockerfile | 20 + examples/autonatv2/docker-compose.yml | 16 + .../autonatv2/src/bin/autonatv2_client.rs | 111 ++++ .../autonatv2/src/bin/autonatv2_server.rs | 87 +++ protocols/autonat/CHANGELOG.md | 7 + protocols/autonat/Cargo.toml | 31 +- protocols/autonat/src/lib.rs | 45 +- protocols/autonat/src/v1.rs | 45 ++ protocols/autonat/src/{ => v1}/behaviour.rs | 4 +- .../src/{ => v1}/behaviour/as_client.rs | 0 .../src/{ => v1}/behaviour/as_server.rs | 1 - .../autonat/src/{ => v1}/generated/mod.rs | 0 .../src/{ => v1}/generated/structs.proto | 0 .../autonat/src/{ => v1}/generated/structs.rs | 0 protocols/autonat/src/{ => v1}/protocol.rs | 0 protocols/autonat/src/v2.rs | 37 ++ protocols/autonat/src/v2/client.rs | 5 + protocols/autonat/src/v2/client/behaviour.rs | 439 ++++++++++++++ protocols/autonat/src/v2/client/handler.rs | 2 + .../src/v2/client/handler/dial_back.rs | 141 +++++ .../src/v2/client/handler/dial_request.rs | 343 +++++++++++ protocols/autonat/src/v2/generated/mod.rs | 2 + .../autonat/src/v2/generated/structs.proto | 54 ++ protocols/autonat/src/v2/generated/structs.rs | 403 +++++++++++++ protocols/autonat/src/v2/protocol.rs | 337 +++++++++++ protocols/autonat/src/v2/server.rs | 5 + protocols/autonat/src/v2/server/behaviour.rs | 156 +++++ protocols/autonat/src/v2/server/handler.rs | 8 + .../src/v2/server/handler/dial_back.rs | 140 +++++ .../src/v2/server/handler/dial_request.rs | 332 ++++++++++ protocols/autonat/tests/autonatv2.rs | 568 ++++++++++++++++++ 34 files changed, 3525 insertions(+), 80 deletions(-) create mode 100644 examples/autonatv2/Cargo.toml create mode 100644 examples/autonatv2/Dockerfile create mode 100644 examples/autonatv2/docker-compose.yml create mode 100644 examples/autonatv2/src/bin/autonatv2_client.rs create mode 100644 examples/autonatv2/src/bin/autonatv2_server.rs create mode 100644 protocols/autonat/src/v1.rs rename protocols/autonat/src/{ => v1}/behaviour.rs (99%) rename protocols/autonat/src/{ => v1}/behaviour/as_client.rs (100%) rename protocols/autonat/src/{ => v1}/behaviour/as_server.rs (99%) rename protocols/autonat/src/{ => v1}/generated/mod.rs (100%) rename protocols/autonat/src/{ => v1}/generated/structs.proto (100%) rename protocols/autonat/src/{ => v1}/generated/structs.rs (100%) rename protocols/autonat/src/{ => v1}/protocol.rs (100%) create mode 100644 protocols/autonat/src/v2.rs create mode 100644 protocols/autonat/src/v2/client.rs create mode 100644 protocols/autonat/src/v2/client/behaviour.rs create mode 100644 protocols/autonat/src/v2/client/handler.rs create mode 100644 protocols/autonat/src/v2/client/handler/dial_back.rs create mode 100644 protocols/autonat/src/v2/client/handler/dial_request.rs create mode 100644 protocols/autonat/src/v2/generated/mod.rs create mode 100644 protocols/autonat/src/v2/generated/structs.proto create mode 100644 protocols/autonat/src/v2/generated/structs.rs create mode 100644 protocols/autonat/src/v2/protocol.rs create mode 100644 protocols/autonat/src/v2/server.rs create mode 100644 protocols/autonat/src/v2/server/behaviour.rs create mode 100644 protocols/autonat/src/v2/server/handler.rs create mode 100644 protocols/autonat/src/v2/server/handler/dial_back.rs create mode 100644 protocols/autonat/src/v2/server/handler/dial_request.rs create mode 100644 protocols/autonat/tests/autonatv2.rs diff --git a/Cargo.lock b/Cargo.lock index cf0cde790c3..af3ceb9eb0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -528,6 +528,23 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "autonatv2" +version = "0.1.0" +dependencies = [ + "cfg-if", + "clap", + "libp2p", + "opentelemetry 0.21.0", + "opentelemetry-jaeger", + "opentelemetry_sdk 0.21.2", + "rand 0.8.5", + "tokio", + "tracing", + "tracing-opentelemetry 0.22.0", + "tracing-subscriber", +] + [[package]] name = "axum" version = "0.6.20" @@ -1120,6 +1137,15 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.3" @@ -1146,12 +1172,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -2404,6 +2427,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + [[package]] name = "interceptor" version = "0.10.0" @@ -2454,7 +2483,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-logger", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -2664,9 +2693,13 @@ dependencies = [ "async-std", "async-trait", "asynchronous-codec", + "bytes", + "either", "futures", + "futures-bounded", "futures-timer", "libp2p-core", + "libp2p-identify", "libp2p-identity", "libp2p-request-response", "libp2p-swarm", @@ -2674,9 +2707,13 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "rand 0.8.5", + "rand_core 0.6.4", + "thiserror", + "tokio", "tracing", "tracing-subscriber", - "web-time", + "void", + "web-time 1.1.0", ] [[package]] @@ -2724,7 +2761,7 @@ dependencies = [ "tracing", "unsigned-varint 0.8.0", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -2758,7 +2795,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -2833,7 +2870,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -2921,7 +2958,7 @@ dependencies = [ "tracing-subscriber", "uint", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -2984,7 +3021,7 @@ dependencies = [ "libp2p-swarm", "pin-project", "prometheus-client", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3076,7 +3113,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3096,7 +3133,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3193,7 +3230,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3223,7 +3260,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3252,7 +3289,7 @@ dependencies = [ "tracing", "tracing-subscriber", "void", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3323,7 +3360,7 @@ dependencies = [ "trybuild", "void", "wasm-bindgen-futures", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -3741,13 +3778,13 @@ dependencies = [ "axum 0.7.5", "futures", "libp2p", - "opentelemetry", + "opentelemetry 0.23.0", "opentelemetry-otlp", - "opentelemetry_sdk", + "opentelemetry_sdk 0.23.0", "prometheus-client", "tokio", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.24.0", "tracing-subscriber", ] @@ -4134,6 +4171,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +dependencies = [ + "futures-core", + "futures-sink", + "indexmap 2.2.1", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", +] + [[package]] name = "opentelemetry" version = "0.23.0" @@ -4148,6 +4201,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "opentelemetry-jaeger" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e617c66fd588e40e0dbbd66932fdc87393095b125d4459b1a3a10feb1712f8a1" +dependencies = [ + "async-trait", + "futures-core", + "futures-util", + "opentelemetry 0.21.0", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk 0.21.2", + "thrift", + "tokio", +] + [[package]] name = "opentelemetry-otlp" version = "0.16.0" @@ -4157,9 +4226,9 @@ dependencies = [ "async-trait", "futures-core", "http 0.2.9", - "opentelemetry", + "opentelemetry 0.23.0", "opentelemetry-proto", - "opentelemetry_sdk", + "opentelemetry_sdk 0.23.0", "prost", "thiserror", "tokio", @@ -4172,12 +4241,43 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" dependencies = [ - "opentelemetry", - "opentelemetry_sdk", + "opentelemetry 0.23.0", + "opentelemetry_sdk 0.23.0", "prost", "tonic", ] +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" +dependencies = [ + "opentelemetry 0.21.0", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry 0.21.0", + "ordered-float 4.2.0", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + [[package]] name = "opentelemetry_sdk" version = "0.23.0" @@ -4191,8 +4291,8 @@ dependencies = [ "glob", "lazy_static", "once_cell", - "opentelemetry", - "ordered-float", + "opentelemetry 0.23.0", + "ordered-float 4.2.0", "percent-encoding", "rand 0.8.5", "thiserror", @@ -4200,6 +4300,15 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "4.2.0" @@ -5926,6 +6035,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float 2.10.1", + "threadpool", +] + [[package]] name = "time" version = "0.3.36" @@ -6230,6 +6361,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry 0.21.0", + "opentelemetry_sdk 0.21.2", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time 0.2.4", +] + [[package]] name = "tracing-opentelemetry" version = "0.24.0" @@ -6238,14 +6387,14 @@ checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" dependencies = [ "js-sys", "once_cell", - "opentelemetry", - "opentelemetry_sdk", + "opentelemetry 0.23.0", + "opentelemetry_sdk 0.23.0", "smallvec", "tracing", "tracing-core", "tracing-log", "tracing-subscriber", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -6429,6 +6578,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.1" @@ -6632,6 +6787,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -7227,7 +7392,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "static_assertions", - "web-time", + "web-time 1.1.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7eb1517d24c..3f00734ae3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "core", "examples/autonat", + "examples/autonatv2", "examples/browser-webrtc", "examples/chat", "examples/dcutr", diff --git a/examples/autonatv2/Cargo.toml b/examples/autonatv2/Cargo.toml new file mode 100644 index 00000000000..6c862ee22e4 --- /dev/null +++ b/examples/autonatv2/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "autonatv2" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT or Apache-2.0" + +[package.metadata.release] +release = false + +[[bin]] +name = "autonatv2_client" + +[[bin]] +name = "autonatv2_server" + +[dependencies] +libp2p = { workspace = true, features = ["macros", "tokio", "tcp", "noise", "yamux", "autonat", "identify", "dns", "quic"] } +clap = { version = "4.4.18", features = ["derive"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +rand = "0.8.5" +opentelemetry = { version = "0.21.0", optional = true } +opentelemetry_sdk = { version = "0.21.1", optional = true, features = ["rt-tokio"] } +tracing-opentelemetry = { version = "0.22.0", optional = true } +opentelemetry-jaeger = { version = "0.20.0", optional = true, features = ["rt-tokio"] } +cfg-if = "1.0.0" + +[features] +jaeger = ["opentelemetry", "opentelemetry_sdk", "tracing-opentelemetry", "opentelemetry-jaeger"] +opentelemetry = ["dep:opentelemetry"] +opentelemetry_sdk = ["dep:opentelemetry_sdk"] +tracing-opentelemetry = ["dep:tracing-opentelemetry"] +opentelemetry-jaeger = ["dep:opentelemetry-jaeger"] + +[lints] +workspace = true diff --git a/examples/autonatv2/Dockerfile b/examples/autonatv2/Dockerfile new file mode 100644 index 00000000000..5a523649d80 --- /dev/null +++ b/examples/autonatv2/Dockerfile @@ -0,0 +1,20 @@ +FROM rust:1.75-alpine as builder + +RUN apk add musl-dev + +WORKDIR /workspace +COPY . . +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + cargo build --release --package autonatv2 --bin autonatv2_server -F jaeger + +RUN --mount=type=cache,target=./target \ + mv ./target/release/autonatv2_server /usr/local/bin/autonatv2_server + +FROM alpine:latest + +COPY --from=builder /usr/local/bin/autonatv2_server /app/autonatv2_server + +EXPOSE 4884 + +ENTRYPOINT [ "/app/autonatv2_server", "-l", "4884" ] diff --git a/examples/autonatv2/docker-compose.yml b/examples/autonatv2/docker-compose.yml new file mode 100644 index 00000000000..75f44e7e6f9 --- /dev/null +++ b/examples/autonatv2/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' + +services: + autonatv2: + build: + context: ../.. + dockerfile: examples/autonatv2/Dockerfile + ports: + - 4884:4884 + jaeger: + image: jaegertracing/all-in-one + ports: + - 6831:6831/udp + - 6832:6832/udp + - 16686:16686 + - 14268:14268 diff --git a/examples/autonatv2/src/bin/autonatv2_client.rs b/examples/autonatv2/src/bin/autonatv2_client.rs new file mode 100644 index 00000000000..de902514dd8 --- /dev/null +++ b/examples/autonatv2/src/bin/autonatv2_client.rs @@ -0,0 +1,111 @@ +use std::{error::Error, net::Ipv4Addr, time::Duration}; + +use clap::Parser; +use libp2p::{ + autonat, + futures::StreamExt, + identify, identity, + multiaddr::Protocol, + noise, + swarm::{dial_opts::DialOpts, NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, SwarmBuilder, +}; +use rand::rngs::OsRng; +use tracing_subscriber::EnvFilter; + +#[derive(Debug, Parser)] +#[clap(name = "libp2p autonatv2 client")] +struct Opt { + /// Port where the client will listen for incoming connections. + #[clap(short = 'p', long, default_value_t = 0)] + listen_port: u16, + + /// Address of the server where want to connect to. + #[clap(short = 'a', long)] + server_address: Multiaddr, + + /// Probe interval in seconds. + #[clap(short = 't', long, default_value = "2")] + probe_interval: u64, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + + let opt = Opt::parse(); + + let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_dns()? + .with_behaviour(|key| Behaviour::new(key.public(), opt.probe_interval))? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(10))) + .build(); + + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port)), + )?; + + swarm.dial( + DialOpts::unknown_peer_id() + .address(opt.server_address) + .build(), + )?; + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {address:?}"); + } + SwarmEvent::Behaviour(BehaviourEvent::Autonat(autonat::v2::client::Event { + server, + tested_addr, + bytes_sent, + result: Ok(()), + })) => { + println!("Tested {tested_addr} with {server}. Sent {bytes_sent} bytes for verification. Everything Ok and verified."); + } + SwarmEvent::Behaviour(BehaviourEvent::Autonat(autonat::v2::client::Event { + server, + tested_addr, + bytes_sent, + result: Err(e), + })) => { + println!("Tested {tested_addr} with {server}. Sent {bytes_sent} bytes for verification. Failed with {e:?}."); + } + SwarmEvent::ExternalAddrConfirmed { address } => { + println!("External address confirmed: {address}"); + } + _ => {} + } + } +} + +#[derive(NetworkBehaviour)] +pub struct Behaviour { + autonat: autonat::v2::client::Behaviour, + identify: identify::Behaviour, +} + +impl Behaviour { + pub fn new(key: identity::PublicKey, probe_interval: u64) -> Self { + Self { + autonat: autonat::v2::client::Behaviour::new( + OsRng, + autonat::v2::client::Config::default() + .with_probe_interval(Duration::from_secs(probe_interval)), + ), + identify: identify::Behaviour::new(identify::Config::new("/ipfs/0.1.0".into(), key)), + } + } +} diff --git a/examples/autonatv2/src/bin/autonatv2_server.rs b/examples/autonatv2/src/bin/autonatv2_server.rs new file mode 100644 index 00000000000..849ed3b3b0a --- /dev/null +++ b/examples/autonatv2/src/bin/autonatv2_server.rs @@ -0,0 +1,87 @@ +use std::{error::Error, net::Ipv4Addr, time::Duration}; + +use cfg_if::cfg_if; +use clap::Parser; +use libp2p::{ + autonat, + futures::StreamExt, + identify, identity, + multiaddr::Protocol, + noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, SwarmBuilder, +}; +use rand::rngs::OsRng; + +#[derive(Debug, Parser)] +#[clap(name = "libp2p autonatv2 server")] +struct Opt { + #[clap(short, long, default_value_t = 0)] + listen_port: u16, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + cfg_if! { + if #[cfg(feature = "jaeger")] { + use tracing_subscriber::layer::SubscriberExt; + use opentelemetry_sdk::runtime::Tokio; + let tracer = opentelemetry_jaeger::new_agent_pipeline() + .with_endpoint("jaeger:6831") + .with_service_name("autonatv2") + .install_batch(Tokio)?; + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + let subscriber = tracing_subscriber::Registry::default() + .with(telemetry); + } else { + let subscriber = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(); + } + } + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + + let opt = Opt::parse(); + + let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_dns()? + .with_behaviour(|key| Behaviour::new(key.public()))? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port)), + )?; + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"), + SwarmEvent::Behaviour(event) => println!("{event:?}"), + e => println!("{e:?}"), + } + } +} + +#[derive(NetworkBehaviour)] +pub struct Behaviour { + autonat: autonat::v2::server::Behaviour, + identify: identify::Behaviour, +} + +impl Behaviour { + pub fn new(key: identity::PublicKey) -> Self { + Self { + autonat: autonat::v2::server::Behaviour::new(OsRng), + identify: identify::Behaviour::new(identify::Config::new("/ipfs/0.1.0".into(), key)), + } + } +} diff --git a/protocols/autonat/CHANGELOG.md b/protocols/autonat/CHANGELOG.md index 2a799221f7c..e171412aa58 100644 --- a/protocols/autonat/CHANGELOG.md +++ b/protocols/autonat/CHANGELOG.md @@ -3,6 +3,13 @@ - Due to the refactor of `Transport` it's no longer required to create a seperate transport for AutoNAT where port reuse is disabled. This information is now passed by the behaviour. See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568). +- Introduce the new AutoNATv2 protocol. + It's split into a client and a server part, represented in their respective modules + Features: + - The server now always dials back over a newly allocated port. + This more accurately reflects the reachability state for other peers and avoids accidental hole punching. + - The server can now test addresses different from the observed address (i.e., the connection to the server was made through a `p2p-circuit`). To mitigate against DDoS attacks, the client has to send more data to the server than the dial-back costs. + See [PR 5526](https://github.com/libp2p/rust-libp2p/pull/5526). diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 7e31a7f3895..2c01d18dceb 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -3,32 +3,47 @@ name = "libp2p-autonat" edition = "2021" rust-version = { workspace = true } description = "NAT and firewall detection for libp2p" -authors = ["David Craven ", "Elena Frank "] version = "0.13.0" +authors = ["David Craven ", "Elena Frank ", "Hannes Furmans "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] + [dependencies] -async-trait = "0.1" +async-trait = { version = "0.1", optional = true } +asynchronous-codec = { workspace = true } +bytes = { version = "1", optional = true } +either = { version = "1.9.0", optional = true } futures = { workspace = true } +futures-bounded = { workspace = true, optional = true } futures-timer = "3.0" -web-time = { workspace = true } +web-time = { workspace = true, optional = true } libp2p-core = { workspace = true } -libp2p-swarm = { workspace = true } -libp2p-request-response = { workspace = true } libp2p-identity = { workspace = true } +libp2p-request-response = { workspace = true, optional = true } +libp2p-swarm = { workspace = true } quick-protobuf = "0.8" -rand = "0.8" tracing = { workspace = true } quick-protobuf-codec = { workspace = true } -asynchronous-codec = { workspace = true } +rand = "0.8" +rand_core = { version = "0.6", optional = true } +thiserror = { version = "1.0.52", optional = true } +void = { version = "1", optional = true } [dev-dependencies] +tokio = { version = "1", features = ["macros", "rt", "sync"]} async-std = { version = "1.10", features = ["attributes"] } libp2p-swarm-test = { path = "../../swarm-test" } -tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-identify = { workspace = true } +libp2p-swarm = { workspace = true, features = ["macros"]} + +[features] +default = ["v1", "v2"] +v1 = ["dep:libp2p-request-response", "dep:web-time", "dep:async-trait"] +v2 = ["dep:bytes", "dep:either", "dep:futures-bounded", "dep:thiserror", "dep:void", "dep:rand_core"] # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 10c87b1e984..e49eaadcb83 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -1,41 +1,10 @@ -// Copyright 2021 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. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] -//! Implementation of the [AutoNAT](https://github.com/libp2p/specs/blob/master/autonat/README.md) protocol. +#[cfg(feature = "v1")] +pub mod v1; -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[cfg(feature = "v2")] +pub mod v2; -mod behaviour; -mod protocol; - -pub use self::{ - behaviour::{ - Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, NatStatus, - OutboundProbeError, OutboundProbeEvent, ProbeId, - }, - protocol::{ResponseError, DEFAULT_PROTOCOL_NAME}, -}; -pub use libp2p_request_response::{InboundFailure, OutboundFailure}; - -mod proto { - #![allow(unreachable_pub)] - include!("generated/mod.rs"); - pub(crate) use self::structs::{mod_Message::*, Message}; -} +#[cfg(feature = "v1")] +pub use v1::*; diff --git a/protocols/autonat/src/v1.rs b/protocols/autonat/src/v1.rs new file mode 100644 index 00000000000..c60e4805f40 --- /dev/null +++ b/protocols/autonat/src/v1.rs @@ -0,0 +1,45 @@ +// Copyright 2021 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. + +//! Implementation of the [AutoNAT](https://github.com/libp2p/specs/blob/master/autonat/README.md) protocol. +//! +//! ## Eventual Deprecation +//! This version of the protocol will eventually be deprecated in favor of [v2](crate::v2). +//! We recommend using v2 for new projects. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub(crate) mod behaviour; +pub(crate) mod protocol; + +pub use self::{ + behaviour::{ + Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, NatStatus, + OutboundProbeError, OutboundProbeEvent, ProbeId, + }, + protocol::{ResponseError, DEFAULT_PROTOCOL_NAME}, +}; +pub use libp2p_request_response::{InboundFailure, OutboundFailure}; + +pub(crate) mod proto { + #![allow(unreachable_pub)] + include!("v1/generated/mod.rs"); + pub(crate) use self::structs::{mod_Message::*, Message}; +} diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/v1/behaviour.rs similarity index 99% rename from protocols/autonat/src/behaviour.rs rename to protocols/autonat/src/v1/behaviour.rs index 64bebfb6233..7a717baed8d 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/v1/behaviour.rs @@ -339,7 +339,7 @@ impl Behaviour { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, - .. + port_use: _, } => { if let Some(event) = self.as_server().on_outbound_connection(&peer, address) { self.pending_actions @@ -349,7 +349,7 @@ impl Behaviour { ConnectedPoint::Dialer { address: _, role_override: Endpoint::Listener, - .. + port_use: _, } => { // Outgoing connection was dialed as a listener. In other words outgoing connection // was dialed as part of a hole punch. `libp2p-autonat` never attempts to hole diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/v1/behaviour/as_client.rs similarity index 100% rename from protocols/autonat/src/behaviour/as_client.rs rename to protocols/autonat/src/v1/behaviour/as_client.rs diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/v1/behaviour/as_server.rs similarity index 99% rename from protocols/autonat/src/behaviour/as_server.rs rename to protocols/autonat/src/v1/behaviour/as_server.rs index 8f1d6642de5..3ecdd3ac26e 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/v1/behaviour/as_server.rs @@ -17,7 +17,6 @@ // 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 super::{ Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, ProbeId, ResponseError, diff --git a/protocols/autonat/src/generated/mod.rs b/protocols/autonat/src/v1/generated/mod.rs similarity index 100% rename from protocols/autonat/src/generated/mod.rs rename to protocols/autonat/src/v1/generated/mod.rs diff --git a/protocols/autonat/src/generated/structs.proto b/protocols/autonat/src/v1/generated/structs.proto similarity index 100% rename from protocols/autonat/src/generated/structs.proto rename to protocols/autonat/src/v1/generated/structs.proto diff --git a/protocols/autonat/src/generated/structs.rs b/protocols/autonat/src/v1/generated/structs.rs similarity index 100% rename from protocols/autonat/src/generated/structs.rs rename to protocols/autonat/src/v1/generated/structs.rs diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/v1/protocol.rs similarity index 100% rename from protocols/autonat/src/protocol.rs rename to protocols/autonat/src/v1/protocol.rs diff --git a/protocols/autonat/src/v2.rs b/protocols/autonat/src/v2.rs new file mode 100644 index 00000000000..994497cb1a0 --- /dev/null +++ b/protocols/autonat/src/v2.rs @@ -0,0 +1,37 @@ +//! The second version of the autonat protocol. +//! +//! The implementation follows the [libp2p spec](https://github.com/libp2p/specs/blob/03718ef0f2dea4a756a85ba716ee33f97e4a6d6c/autonat/autonat-v2.md). +//! +//! The new version fixes the issues of the first version: +//! - The server now always dials back over a newly allocated port. This greatly reduces the risk of +//! false positives that often occured in the first version, when the clinet-server connection +//! occured over a hole-punched port. +//! - The server protects against DoS attacks by requiring the client to send more data to the +//! server then the dial back puts on the client, thus making the protocol unatractive for an +//! attacker. +//! +//! The protocol is seperated into two parts: +//! - The client part, which is implemented in the `client` module. (The client is the party that +//! wants to check if it is reachable from the outside.) +//! - The server part, which is implemented in the `server` module. (The server is the party +//! performing reachability checks on behalf of the client.) +//! +//! The two can be used together. + +use libp2p_swarm::StreamProtocol; + +pub mod client; +pub(crate) mod protocol; +pub mod server; + +pub(crate) mod generated { + #![allow(unreachable_pub)] + include!("v2/generated/mod.rs"); +} + +pub(crate) const DIAL_REQUEST_PROTOCOL: StreamProtocol = + StreamProtocol::new("/libp2p/autonat/2/dial-request"); +pub(crate) const DIAL_BACK_PROTOCOL: StreamProtocol = + StreamProtocol::new("/libp2p/autonat/2/dial-back"); + +type Nonce = u64; diff --git a/protocols/autonat/src/v2/client.rs b/protocols/autonat/src/v2/client.rs new file mode 100644 index 00000000000..d3272512f35 --- /dev/null +++ b/protocols/autonat/src/v2/client.rs @@ -0,0 +1,5 @@ +mod behaviour; +mod handler; + +pub use behaviour::Event; +pub use behaviour::{Behaviour, Config}; diff --git a/protocols/autonat/src/v2/client/behaviour.rs b/protocols/autonat/src/v2/client/behaviour.rs new file mode 100644 index 00000000000..97509c05443 --- /dev/null +++ b/protocols/autonat/src/v2/client/behaviour.rs @@ -0,0 +1,439 @@ +use std::{ + collections::{HashMap, VecDeque}, + task::{Context, Poll}, + time::Duration, +}; + +use either::Either; +use futures::FutureExt; +use futures_timer::Delay; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + behaviour::ConnectionEstablished, ConnectionClosed, ConnectionDenied, ConnectionHandler, + ConnectionId, FromSwarm, NetworkBehaviour, NewExternalAddrCandidate, NotifyHandler, ToSwarm, +}; +use rand::prelude::*; +use rand_core::OsRng; +use std::fmt::{Debug, Display, Formatter}; + +use crate::v2::{protocol::DialRequest, Nonce}; + +use super::handler::{ + dial_back::{self, IncomingNonce}, + dial_request, +}; + +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// How many candidates we will test at most. + pub(crate) max_candidates: usize, + + /// The interval at which we will attempt to confirm candidates as external addresses. + pub(crate) probe_interval: Duration, +} + +impl Config { + pub fn with_max_candidates(self, max_candidates: usize) -> Self { + Self { + max_candidates, + ..self + } + } + + pub fn with_probe_interval(self, probe_interval: Duration) -> Self { + Self { + probe_interval, + ..self + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + max_candidates: 10, + probe_interval: Duration::from_secs(5), + } + } +} + +pub struct Behaviour +where + R: RngCore + 'static, +{ + rng: R, + config: Config, + pending_events: VecDeque< + ToSwarm< + ::ToSwarm, + <::ConnectionHandler as ConnectionHandler>::FromBehaviour, + >, + >, + address_candidates: HashMap, + next_tick: Delay, + peer_info: HashMap, +} + +impl NetworkBehaviour for Behaviour +where + R: RngCore + 'static, +{ + type ConnectionHandler = Either; + + type ToSwarm = Event; + + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Right(dial_back::Handler::new())) + } + + fn handle_established_outbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: Endpoint, + _: PortUse, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Left(dial_request::Handler::new())) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::NewExternalAddrCandidate(NewExternalAddrCandidate { addr }) => { + self.address_candidates + .entry(addr.clone()) + .or_default() + .score += 1; + } + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + connection_id, + endpoint: _, + .. + }) => { + self.peer_info.insert( + connection_id, + ConnectionInfo { + peer_id, + supports_autonat: false, + }, + ); + } + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + .. + }) => { + let info = self + .peer_info + .remove(&connection_id) + .expect("inconsistent state"); + + if info.supports_autonat { + tracing::debug!(%peer_id, "Disconnected from AutoNAT server"); + } + } + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + event: ::ToBehaviour, + ) { + let (nonce, outcome) = match event { + Either::Right(IncomingNonce { nonce, sender }) => { + let Some((_, info)) = self + .address_candidates + .iter_mut() + .find(|(_, info)| info.is_pending_with_nonce(nonce)) + else { + let _ = sender.send(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Received unexpected nonce: {nonce} from {peer_id}"), + ))); + return; + }; + + info.status = TestStatus::Received(nonce); + tracing::debug!(%peer_id, %nonce, "Successful dial-back"); + + let _ = sender.send(Ok(())); + + return; + } + Either::Left(dial_request::ToBehaviour::PeerHasServerSupport) => { + self.peer_info + .get_mut(&connection_id) + .expect("inconsistent state") + .supports_autonat = true; + return; + } + Either::Left(dial_request::ToBehaviour::TestOutcome { nonce, outcome }) => { + (nonce, outcome) + } + }; + + let ((tested_addr, bytes_sent), result) = match outcome { + Ok(address) => { + let received_dial_back = self + .address_candidates + .iter_mut() + .any(|(_, info)| info.is_received_with_nonce(nonce)); + + if !received_dial_back { + tracing::warn!( + %peer_id, + %nonce, + "Server reported reachbility but we never received a dial-back" + ); + return; + } + + self.pending_events + .push_back(ToSwarm::ExternalAddrConfirmed(address.0.clone())); + + (address, Ok(())) + } + Err(dial_request::Error::UnsupportedProtocol) => { + self.peer_info + .get_mut(&connection_id) + .expect("inconsistent state") + .supports_autonat = false; + + self.reset_status_to(nonce, TestStatus::Untested); // Reset so it will be tried again. + + return; + } + Err(dial_request::Error::Io(e)) => { + tracing::debug!( + %peer_id, + %nonce, + "Failed to complete AutoNAT probe: {e}" + ); + + self.reset_status_to(nonce, TestStatus::Untested); // Reset so it will be tried again. + + return; + } + Err(dial_request::Error::AddressNotReachable { + address, + bytes_sent, + error, + }) => { + self.reset_status_to(nonce, TestStatus::Failed); + + ((address, bytes_sent), Err(error)) + } + }; + + self.pending_events.push_back(ToSwarm::GenerateEvent(Event { + tested_addr, + bytes_sent, + server: peer_id, + result: result.map_err(|e| Error { inner: e }), + })); + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll::FromBehaviour>> + { + loop { + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + + if self.next_tick.poll_unpin(cx).is_ready() { + self.next_tick.reset(self.config.probe_interval); + + self.issue_dial_requests_for_untested_candidates(); + continue; + } + + return Poll::Pending; + } + } +} + +impl Behaviour +where + R: RngCore + 'static, +{ + pub fn new(rng: R, config: Config) -> Self { + Self { + rng, + next_tick: Delay::new(config.probe_interval), + config, + pending_events: VecDeque::new(), + address_candidates: HashMap::new(), + peer_info: HashMap::new(), + } + } + + /// Issues dial requests to random AutoNAT servers for the most frequently reported, untested candidates. + /// + /// In the current implementation, we only send a single address to each AutoNAT server. + /// This spreads our candidates out across all servers we are connected to which should give us pretty fast feedback on all of them. + fn issue_dial_requests_for_untested_candidates(&mut self) { + for addr in self.untested_candidates() { + let Some((conn_id, peer_id)) = self.random_autonat_server() else { + tracing::debug!("Not connected to any AutoNAT servers"); + return; + }; + + let nonce = self.rng.gen(); + self.address_candidates + .get_mut(&addr) + .expect("only emit candidates") + .status = TestStatus::Pending(nonce); + + self.pending_events.push_back(ToSwarm::NotifyHandler { + peer_id, + handler: NotifyHandler::One(conn_id), + event: Either::Left(DialRequest { + nonce, + addrs: vec![addr], + }), + }); + } + } + + /// Returns all untested candidates, sorted by the frequency they were reported at. + /// + /// More frequently reported candidates are considered to more likely be external addresses and thus tested first. + fn untested_candidates(&self) -> impl Iterator { + let mut entries = self + .address_candidates + .iter() + .filter(|(_, info)| info.status == TestStatus::Untested) + .map(|(addr, count)| (addr.clone(), *count)) + .collect::>(); + + entries.sort_unstable_by_key(|(_, info)| info.score); + + if entries.is_empty() { + tracing::debug!("No untested address candidates"); + } + + entries + .into_iter() + .rev() // `sort_unstable` is ascending + .take(self.config.max_candidates) + .map(|(addr, _)| addr) + } + + /// Chooses an active connection to one of our peers that reported support for the [`DIAL_REQUEST_PROTOCOL`](crate::v2::DIAL_REQUEST_PROTOCOL) protocol. + fn random_autonat_server(&mut self) -> Option<(ConnectionId, PeerId)> { + let (conn_id, info) = self + .peer_info + .iter() + .filter(|(_, info)| info.supports_autonat) + .choose(&mut self.rng)?; + + Some((*conn_id, info.peer_id)) + } + + fn reset_status_to(&mut self, nonce: Nonce, new_status: TestStatus) { + let Some((_, info)) = self + .address_candidates + .iter_mut() + .find(|(_, i)| i.is_pending_with_nonce(nonce) || i.is_received_with_nonce(nonce)) + else { + return; + }; + + info.status = new_status; + } + + // FIXME: We don't want test-only APIs in our public API. + #[doc(hidden)] + pub fn validate_addr(&mut self, addr: &Multiaddr) { + if let Some(info) = self.address_candidates.get_mut(addr) { + info.status = TestStatus::Received(self.rng.next_u64()); + } + } +} + +impl Default for Behaviour { + fn default() -> Self { + Self::new(OsRng, Config::default()) + } +} + +pub struct Error { + pub(crate) inner: dial_request::DialBackError, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.inner, f) + } +} + +#[derive(Debug)] +pub struct Event { + /// The address that was selected for testing. + pub tested_addr: Multiaddr, + /// The amount of data that was sent to the server. + /// Is 0 if it wasn't necessary to send any data. + /// Otherwise it's a number between 30.000 and 100.000. + pub bytes_sent: usize, + /// The peer id of the server that was selected for testing. + pub server: PeerId, + /// The result of the test. If the test was successful, this is `Ok(())`. + /// Otherwise it's an error. + pub result: Result<(), Error>, +} + +struct ConnectionInfo { + peer_id: PeerId, + supports_autonat: bool, +} + +#[derive(Copy, Clone, Default)] +struct AddressInfo { + score: usize, + status: TestStatus, +} + +impl AddressInfo { + fn is_pending_with_nonce(&self, nonce: Nonce) -> bool { + match self.status { + TestStatus::Pending(c) => c == nonce, + _ => false, + } + } + + fn is_received_with_nonce(&self, nonce: Nonce) -> bool { + match self.status { + TestStatus::Received(c) => c == nonce, + _ => false, + } + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +enum TestStatus { + #[default] + Untested, + Pending(Nonce), + Failed, + Received(Nonce), +} diff --git a/protocols/autonat/src/v2/client/handler.rs b/protocols/autonat/src/v2/client/handler.rs new file mode 100644 index 00000000000..e526c2fb44c --- /dev/null +++ b/protocols/autonat/src/v2/client/handler.rs @@ -0,0 +1,2 @@ +pub(crate) mod dial_back; +pub(crate) mod dial_request; diff --git a/protocols/autonat/src/v2/client/handler/dial_back.rs b/protocols/autonat/src/v2/client/handler/dial_back.rs new file mode 100644 index 00000000000..b94580e69ba --- /dev/null +++ b/protocols/autonat/src/v2/client/handler/dial_back.rs @@ -0,0 +1,141 @@ +use std::{ + io, + task::{Context, Poll}, + time::Duration, +}; + +use futures::channel::oneshot; +use futures_bounded::StreamSet; +use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; +use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound, ListenUpgradeError}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, +}; +use void::Void; + +use crate::v2::{protocol, Nonce, DIAL_BACK_PROTOCOL}; + +pub struct Handler { + inbound: StreamSet>, +} + +impl Handler { + pub(crate) fn new() -> Self { + Self { + inbound: StreamSet::new(Duration::from_secs(5), 2), + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = Void; + type ToBehaviour = IncomingNonce; + type InboundProtocol = ReadyUpgrade; + type OutboundProtocol = DeniedUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(ReadyUpgrade::new(DIAL_BACK_PROTOCOL), ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + loop { + match self.inbound.poll_next_unpin(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => continue, + Poll::Ready(Some(Err(err))) => { + tracing::debug!("Stream timed out: {err}"); + continue; + } + Poll::Ready(Some(Ok(Err(err)))) => { + tracing::debug!("Dial back handler failed with: {err:?}"); + continue; + } + Poll::Ready(Some(Ok(Ok(incoming_nonce)))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(incoming_nonce)); + } + } + } + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol, .. + }) => { + if self.inbound.try_push(perform_dial_back(protocol)).is_err() { + tracing::warn!("Dial back request dropped, too many requests in flight"); + } + } + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { error, .. }) => { + void::unreachable(error); + } + _ => {} + } + } +} + +struct State { + stream: libp2p_swarm::Stream, + oneshot: Option>>, +} + +#[derive(Debug)] +pub struct IncomingNonce { + pub nonce: Nonce, + pub sender: oneshot::Sender>, +} + +fn perform_dial_back( + stream: libp2p_swarm::Stream, +) -> impl futures::Stream> { + let state = State { + stream, + oneshot: None, + }; + futures::stream::unfold(state, |mut state| async move { + if let Some(ref mut receiver) = state.oneshot { + match receiver.await { + Ok(Ok(())) => {} + Ok(Err(e)) => return Some((Err(e), state)), + Err(_) => { + return Some(( + Err(io::Error::new(io::ErrorKind::Other, "Sender got cancelled")), + state, + )); + } + } + if let Err(e) = protocol::dial_back_response(&mut state.stream).await { + return Some((Err(e), state)); + } + return None; + } + + let nonce = match protocol::recv_dial_back(&mut state.stream).await { + Ok(nonce) => nonce, + Err(err) => { + return Some((Err(err), state)); + } + }; + + let (sender, receiver) = oneshot::channel(); + state.oneshot = Some(receiver); + Some((Ok(IncomingNonce { nonce, sender }), state)) + }) +} diff --git a/protocols/autonat/src/v2/client/handler/dial_request.rs b/protocols/autonat/src/v2/client/handler/dial_request.rs new file mode 100644 index 00000000000..9d2df8ee6b4 --- /dev/null +++ b/protocols/autonat/src/v2/client/handler/dial_request.rs @@ -0,0 +1,343 @@ +use futures::{channel::oneshot, AsyncWrite}; +use futures_bounded::FuturesMap; +use libp2p_core::{ + upgrade::{DeniedUpgrade, ReadyUpgrade}, + Multiaddr, +}; + +use libp2p_swarm::{ + handler::{ + ConnectionEvent, DialUpgradeError, FullyNegotiatedOutbound, OutboundUpgradeSend, + ProtocolsChange, + }, + ConnectionHandler, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, + SubstreamProtocol, +}; +use std::{ + collections::VecDeque, + io, + iter::{once, repeat}, + task::{Context, Poll}, + time::Duration, +}; + +use crate::v2::{ + generated::structs::{mod_DialResponse::ResponseStatus, DialStatus}, + protocol::{ + Coder, DialDataRequest, DialDataResponse, DialRequest, Response, + DATA_FIELD_LEN_UPPER_BOUND, DATA_LEN_LOWER_BOUND, DATA_LEN_UPPER_BOUND, + }, + Nonce, DIAL_REQUEST_PROTOCOL, +}; + +#[derive(Debug)] +pub enum ToBehaviour { + TestOutcome { + nonce: Nonce, + outcome: Result<(Multiaddr, usize), Error>, + }, + PeerHasServerSupport, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Address is not reachable: {error}")] + AddressNotReachable { + address: Multiaddr, + bytes_sent: usize, + error: DialBackError, + }, + #[error("Peer does not support AutoNAT dial-request protocol")] + UnsupportedProtocol, + #[error("IO error: {0}")] + Io(io::Error), +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum DialBackError { + #[error("server failed to establish a connection")] + NoConnection, + #[error("dial back stream failed")] + StreamFailed, +} + +pub struct Handler { + queued_events: VecDeque< + ConnectionHandlerEvent< + ::OutboundProtocol, + ::OutboundOpenInfo, + ::ToBehaviour, + >, + >, + outbound: FuturesMap>, + queued_streams: VecDeque< + oneshot::Sender< + Result< + Stream, + StreamUpgradeError< as OutboundUpgradeSend>::Error>, + >, + >, + >, +} + +impl Handler { + pub(crate) fn new() -> Self { + Self { + queued_events: VecDeque::new(), + outbound: FuturesMap::new(Duration::from_secs(10), 10), + queued_streams: VecDeque::default(), + } + } + + fn perform_request(&mut self, req: DialRequest) { + let (tx, rx) = oneshot::channel(); + self.queued_streams.push_back(tx); + self.queued_events + .push_back(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(ReadyUpgrade::new(DIAL_REQUEST_PROTOCOL), ()), + }); + if self + .outbound + .try_push(req.nonce, start_stream_handle(req, rx)) + .is_err() + { + tracing::debug!("Dial request dropped, too many requests in flight"); + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = DialRequest; + type ToBehaviour = ToBehaviour; + type InboundProtocol = DeniedUpgrade; + type OutboundProtocol = ReadyUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(DeniedUpgrade, ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + if let Some(event) = self.queued_events.pop_front() { + return Poll::Ready(event); + } + + match self.outbound.poll_unpin(cx) { + Poll::Ready((nonce, Ok(outcome))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::TestOutcome { nonce, outcome }, + )) + } + Poll::Ready((nonce, Err(_))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::TestOutcome { + nonce, + outcome: Err(Error::Io(io::ErrorKind::TimedOut.into())), + }, + )); + } + Poll::Pending => {} + } + + Poll::Pending + } + + fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { + self.perform_request(event); + } + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::DialUpgradeError(DialUpgradeError { error, .. }) => { + tracing::debug!("Dial request failed: {}", error); + match self.queued_streams.pop_front() { + Some(stream_tx) => { + let _ = stream_tx.send(Err(error)); + } + None => { + tracing::warn!( + "Opened unexpected substream without a pending dial request" + ); + } + } + } + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol, .. + }) => match self.queued_streams.pop_front() { + Some(stream_tx) => { + if stream_tx.send(Ok(protocol)).is_err() { + tracing::debug!("Failed to send stream to dead handler"); + } + } + None => { + tracing::warn!("Opened unexpected substream without a pending dial request"); + } + }, + ConnectionEvent::RemoteProtocolsChange(ProtocolsChange::Added(mut added)) => { + if added.any(|p| p.as_ref() == DIAL_REQUEST_PROTOCOL) { + self.queued_events + .push_back(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::PeerHasServerSupport, + )); + } + } + _ => {} + } + } +} + +async fn start_stream_handle( + req: DialRequest, + stream_recv: oneshot::Receiver>>, +) -> Result<(Multiaddr, usize), Error> { + let stream = stream_recv + .await + .map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))? + .map_err(|e| match e { + StreamUpgradeError::NegotiationFailed => Error::UnsupportedProtocol, + StreamUpgradeError::Timeout => Error::Io(io::ErrorKind::TimedOut.into()), + StreamUpgradeError::Apply(v) => void::unreachable(v), + StreamUpgradeError::Io(e) => Error::Io(e), + })?; + + let mut coder = Coder::new(stream); + coder.send(req.clone()).await?; + + let (res, bytes_sent) = match coder.next().await? { + Response::Data(DialDataRequest { + addr_idx, + num_bytes, + }) => { + if addr_idx >= req.addrs.len() { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "address index out of bounds", + ))); + } + if !(DATA_LEN_LOWER_BOUND..=DATA_LEN_UPPER_BOUND).contains(&num_bytes) { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "requested bytes out of bounds", + ))); + } + + send_aap_data(&mut coder, num_bytes).await?; + + let Response::Dial(dial_response) = coder.next().await? else { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "expected message", + ))); + }; + + (dial_response, num_bytes) + } + Response::Dial(dial_response) => (dial_response, 0), + }; + match coder.close().await { + Ok(_) => {} + Err(err) => { + if err.kind() == io::ErrorKind::ConnectionReset { + // The AutoNAT server may have already closed the stream (this is normal because the probe is finished), in this case we have this error: + // Err(Custom { kind: ConnectionReset, error: Stopped(0) }) + // so we silently ignore this error + } else { + return Err(err.into()); + } + } + } + + match res.status { + ResponseStatus::E_REQUEST_REJECTED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server rejected request", + ))) + } + ResponseStatus::E_DIAL_REFUSED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server refused dial", + ))) + } + ResponseStatus::E_INTERNAL_ERROR => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server encountered internal error", + ))) + } + ResponseStatus::OK => {} + } + + let tested_address = req + .addrs + .get(res.addr_idx) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "address index out of bounds"))? + .clone(); + + match res.dial_status { + DialStatus::UNUSED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "unexpected message", + ))) + } + DialStatus::E_DIAL_ERROR => { + return Err(Error::AddressNotReachable { + address: tested_address, + bytes_sent, + error: DialBackError::NoConnection, + }) + } + DialStatus::E_DIAL_BACK_ERROR => { + return Err(Error::AddressNotReachable { + address: tested_address, + bytes_sent, + error: DialBackError::StreamFailed, + }) + } + DialStatus::OK => {} + } + + Ok((tested_address, bytes_sent)) +} + +async fn send_aap_data(stream: &mut Coder, num_bytes: usize) -> io::Result<()> +where + I: AsyncWrite + Unpin, +{ + let count_full = num_bytes / DATA_FIELD_LEN_UPPER_BOUND; + let partial_len = num_bytes % DATA_FIELD_LEN_UPPER_BOUND; + for req in repeat(DATA_FIELD_LEN_UPPER_BOUND) + .take(count_full) + .chain(once(partial_len)) + .filter(|e| *e > 0) + .map(|data_count| { + DialDataResponse::new(data_count).expect("data count is unexpectedly too big") + }) + { + stream.send(req).await?; + } + + Ok(()) +} diff --git a/protocols/autonat/src/v2/generated/mod.rs b/protocols/autonat/src/v2/generated/mod.rs new file mode 100644 index 00000000000..e52c5a80bc0 --- /dev/null +++ b/protocols/autonat/src/v2/generated/mod.rs @@ -0,0 +1,2 @@ +// Automatically generated mod.rs +pub mod structs; diff --git a/protocols/autonat/src/v2/generated/structs.proto b/protocols/autonat/src/v2/generated/structs.proto new file mode 100644 index 00000000000..d298f43d047 --- /dev/null +++ b/protocols/autonat/src/v2/generated/structs.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package structs; + +message Message { + oneof msg { + DialRequest dialRequest = 1; + DialResponse dialResponse = 2; + DialDataRequest dialDataRequest = 3; + DialDataResponse dialDataResponse = 4; + } +} + +message DialRequest { + repeated bytes addrs = 1; + fixed64 nonce = 2; +} + +message DialDataRequest { + uint32 addrIdx = 1; + uint64 numBytes = 2; +} + +enum DialStatus { + UNUSED = 0; + E_DIAL_ERROR = 100; + E_DIAL_BACK_ERROR = 101; + OK = 200; +} + +message DialResponse { + enum ResponseStatus { + E_INTERNAL_ERROR = 0; + E_REQUEST_REJECTED = 100; + E_DIAL_REFUSED = 101; + OK = 200; + } + + ResponseStatus status = 1; + uint32 addrIdx = 2; + DialStatus dialStatus = 3; +} + +message DialDataResponse { bytes data = 1; } + +message DialBack { fixed64 nonce = 1; } + +message DialBackResponse { + enum DialBackStatus { + OK = 0; + } + + DialBackStatus status = 1; +} diff --git a/protocols/autonat/src/v2/generated/structs.rs b/protocols/autonat/src/v2/generated/structs.rs new file mode 100644 index 00000000000..e188adb8a42 --- /dev/null +++ b/protocols/autonat/src/v2/generated/structs.rs @@ -0,0 +1,403 @@ +// Automatically generated rust module for 'structs.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DialStatus { + UNUSED = 0, + E_DIAL_ERROR = 100, + E_DIAL_BACK_ERROR = 101, + OK = 200, +} + +impl Default for DialStatus { + fn default() -> Self { + DialStatus::UNUSED + } +} + +impl From for DialStatus { + fn from(i: i32) -> Self { + match i { + 0 => DialStatus::UNUSED, + 100 => DialStatus::E_DIAL_ERROR, + 101 => DialStatus::E_DIAL_BACK_ERROR, + 200 => DialStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for DialStatus { + fn from(s: &'a str) -> Self { + match s { + "UNUSED" => DialStatus::UNUSED, + "E_DIAL_ERROR" => DialStatus::E_DIAL_ERROR, + "E_DIAL_BACK_ERROR" => DialStatus::E_DIAL_BACK_ERROR, + "OK" => DialStatus::OK, + _ => Self::default(), + } + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Message { + pub msg: structs::mod_Message::OneOfmsg, +} + +impl<'a> MessageRead<'a> for Message { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.msg = structs::mod_Message::OneOfmsg::dialRequest(r.read_message::(bytes)?), + Ok(18) => msg.msg = structs::mod_Message::OneOfmsg::dialResponse(r.read_message::(bytes)?), + Ok(26) => msg.msg = structs::mod_Message::OneOfmsg::dialDataRequest(r.read_message::(bytes)?), + Ok(34) => msg.msg = structs::mod_Message::OneOfmsg::dialDataResponse(r.read_message::(bytes)?), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Message { + fn get_size(&self) -> usize { + 0 + + match self.msg { + structs::mod_Message::OneOfmsg::dialRequest(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialResponse(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialDataRequest(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialDataResponse(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::None => 0, + } } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + match self.msg { structs::mod_Message::OneOfmsg::dialRequest(ref m) => { w.write_with_tag(10, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialResponse(ref m) => { w.write_with_tag(18, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialDataRequest(ref m) => { w.write_with_tag(26, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialDataResponse(ref m) => { w.write_with_tag(34, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::None => {}, + } Ok(()) + } +} + +pub mod mod_Message { + +use super::*; + +#[derive(Debug, PartialEq, Clone)] +pub enum OneOfmsg { + dialRequest(structs::DialRequest), + dialResponse(structs::DialResponse), + dialDataRequest(structs::DialDataRequest), + dialDataResponse(structs::DialDataResponse), + None, +} + +impl Default for OneOfmsg { + fn default() -> Self { + OneOfmsg::None + } +} + +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialRequest { + pub addrs: Vec>, + pub nonce: u64, +} + +impl<'a> MessageRead<'a> for DialRequest { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.addrs.push(r.read_bytes(bytes)?.to_owned()), + Ok(17) => msg.nonce = r.read_fixed64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialRequest { + fn get_size(&self) -> usize { + 0 + + self.addrs.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + + if self.nonce == 0u64 { 0 } else { 1 + 8 } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + for s in &self.addrs { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } + if self.nonce != 0u64 { w.write_with_tag(17, |w| w.write_fixed64(*&self.nonce))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialDataRequest { + pub addrIdx: u32, + pub numBytes: u64, +} + +impl<'a> MessageRead<'a> for DialDataRequest { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.addrIdx = r.read_uint32(bytes)?, + Ok(16) => msg.numBytes = r.read_uint64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialDataRequest { + fn get_size(&self) -> usize { + 0 + + if self.addrIdx == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.addrIdx) as u64) } + + if self.numBytes == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.numBytes) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.addrIdx != 0u32 { w.write_with_tag(8, |w| w.write_uint32(*&self.addrIdx))?; } + if self.numBytes != 0u64 { w.write_with_tag(16, |w| w.write_uint64(*&self.numBytes))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialResponse { + pub status: structs::mod_DialResponse::ResponseStatus, + pub addrIdx: u32, + pub dialStatus: structs::DialStatus, +} + +impl<'a> MessageRead<'a> for DialResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.status = r.read_enum(bytes)?, + Ok(16) => msg.addrIdx = r.read_uint32(bytes)?, + Ok(24) => msg.dialStatus = r.read_enum(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialResponse { + fn get_size(&self) -> usize { + 0 + + if self.status == structs::mod_DialResponse::ResponseStatus::E_INTERNAL_ERROR { 0 } else { 1 + sizeof_varint(*(&self.status) as u64) } + + if self.addrIdx == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.addrIdx) as u64) } + + if self.dialStatus == structs::DialStatus::UNUSED { 0 } else { 1 + sizeof_varint(*(&self.dialStatus) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.status != structs::mod_DialResponse::ResponseStatus::E_INTERNAL_ERROR { w.write_with_tag(8, |w| w.write_enum(*&self.status as i32))?; } + if self.addrIdx != 0u32 { w.write_with_tag(16, |w| w.write_uint32(*&self.addrIdx))?; } + if self.dialStatus != structs::DialStatus::UNUSED { w.write_with_tag(24, |w| w.write_enum(*&self.dialStatus as i32))?; } + Ok(()) + } +} + +pub mod mod_DialResponse { + + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ResponseStatus { + E_INTERNAL_ERROR = 0, + E_REQUEST_REJECTED = 100, + E_DIAL_REFUSED = 101, + OK = 200, +} + +impl Default for ResponseStatus { + fn default() -> Self { + ResponseStatus::E_INTERNAL_ERROR + } +} + +impl From for ResponseStatus { + fn from(i: i32) -> Self { + match i { + 0 => ResponseStatus::E_INTERNAL_ERROR, + 100 => ResponseStatus::E_REQUEST_REJECTED, + 101 => ResponseStatus::E_DIAL_REFUSED, + 200 => ResponseStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for ResponseStatus { + fn from(s: &'a str) -> Self { + match s { + "E_INTERNAL_ERROR" => ResponseStatus::E_INTERNAL_ERROR, + "E_REQUEST_REJECTED" => ResponseStatus::E_REQUEST_REJECTED, + "E_DIAL_REFUSED" => ResponseStatus::E_DIAL_REFUSED, + "OK" => ResponseStatus::OK, + _ => Self::default(), + } + } +} + +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialDataResponse { + pub data: Vec, +} + +impl<'a> MessageRead<'a> for DialDataResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.data = r.read_bytes(bytes)?.to_owned(), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialDataResponse { + fn get_size(&self) -> usize { + 0 + + if self.data.is_empty() { 0 } else { 1 + sizeof_len((&self.data).len()) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if !self.data.is_empty() { w.write_with_tag(10, |w| w.write_bytes(&**&self.data))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialBack { + pub nonce: u64, +} + +impl<'a> MessageRead<'a> for DialBack { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(9) => msg.nonce = r.read_fixed64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialBack { + fn get_size(&self) -> usize { + 0 + + if self.nonce == 0u64 { 0 } else { 1 + 8 } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.nonce != 0u64 { w.write_with_tag(9, |w| w.write_fixed64(*&self.nonce))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialBackResponse { + pub status: structs::mod_DialBackResponse::DialBackStatus, +} + +impl<'a> MessageRead<'a> for DialBackResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.status = r.read_enum(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialBackResponse { + fn get_size(&self) -> usize { + 0 + + if self.status == structs::mod_DialBackResponse::DialBackStatus::OK { 0 } else { 1 + sizeof_varint(*(&self.status) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.status != structs::mod_DialBackResponse::DialBackStatus::OK { w.write_with_tag(8, |w| w.write_enum(*&self.status as i32))?; } + Ok(()) + } +} + +pub mod mod_DialBackResponse { + + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DialBackStatus { + OK = 0, +} + +impl Default for DialBackStatus { + fn default() -> Self { + DialBackStatus::OK + } +} + +impl From for DialBackStatus { + fn from(i: i32) -> Self { + match i { + 0 => DialBackStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for DialBackStatus { + fn from(s: &'a str) -> Self { + match s { + "OK" => DialBackStatus::OK, + _ => Self::default(), + } + } +} + +} + diff --git a/protocols/autonat/src/v2/protocol.rs b/protocols/autonat/src/v2/protocol.rs new file mode 100644 index 00000000000..4077fd65f5d --- /dev/null +++ b/protocols/autonat/src/v2/protocol.rs @@ -0,0 +1,337 @@ +// change to quick-protobuf-codec + +use std::io; +use std::io::ErrorKind; + +use asynchronous_codec::{Framed, FramedRead, FramedWrite}; + +use futures::{AsyncRead, AsyncWrite, SinkExt, StreamExt}; +use libp2p_core::Multiaddr; + +use quick_protobuf_codec::Codec; +use rand::Rng; + +use crate::v2::{generated::structs as proto, Nonce}; + +const REQUEST_MAX_SIZE: usize = 4104; +pub(super) const DATA_LEN_LOWER_BOUND: usize = 30_000u32 as usize; +pub(super) const DATA_LEN_UPPER_BOUND: usize = 100_000u32 as usize; +pub(super) const DATA_FIELD_LEN_UPPER_BOUND: usize = 4096; + +fn new_io_invalid_data_err(msg: impl Into) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, msg.into()) +} + +pub(crate) struct Coder { + inner: Framed>, +} + +impl Coder +where + I: AsyncWrite + AsyncRead + Unpin, +{ + pub(crate) fn new(io: I) -> Self { + Self { + inner: Framed::new(io, Codec::new(REQUEST_MAX_SIZE)), + } + } + pub(crate) async fn close(mut self) -> io::Result<()> { + self.inner.close().await?; + Ok(()) + } +} + +impl Coder +where + I: AsyncRead + Unpin, +{ + pub(crate) async fn next(&mut self) -> io::Result + where + proto::Message: TryInto, + io::Error: From, + { + Ok(self.next_msg().await?.try_into()?) + } + + async fn next_msg(&mut self) -> io::Result { + self.inner + .next() + .await + .ok_or(io::Error::new( + ErrorKind::UnexpectedEof, + "no request to read", + ))? + .map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) + } +} + +impl Coder +where + I: AsyncWrite + Unpin, +{ + pub(crate) async fn send(&mut self, msg: M) -> io::Result<()> + where + M: Into, + { + self.inner.send(msg.into()).await?; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Request { + Dial(DialRequest), + Data(DialDataResponse), +} + +impl From for proto::Message { + fn from(val: DialRequest) -> Self { + let addrs = val.addrs.iter().map(|e| e.to_vec()).collect(); + let nonce = val.nonce; + + proto::Message { + msg: proto::mod_Message::OneOfmsg::dialRequest(proto::DialRequest { addrs, nonce }), + } + } +} + +impl From for proto::Message { + fn from(val: DialDataResponse) -> Self { + debug_assert!( + val.data_count <= DATA_FIELD_LEN_UPPER_BOUND, + "data_count too large" + ); + proto::Message { + msg: proto::mod_Message::OneOfmsg::dialDataResponse(proto::DialDataResponse { + data: vec![0; val.data_count], // One could use Cow::Borrowed here, but it will require a modification of the generated code and that will fail the CI + }), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DialRequest { + pub(crate) addrs: Vec, + pub(crate) nonce: u64, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct DialDataResponse { + data_count: usize, +} + +impl DialDataResponse { + pub(crate) fn new(data_count: usize) -> Option { + if data_count <= DATA_FIELD_LEN_UPPER_BOUND { + Some(Self { data_count }) + } else { + None + } + } + + pub(crate) fn get_data_count(&self) -> usize { + self.data_count + } +} + +impl TryFrom for Request { + type Error = io::Error; + + fn try_from(msg: proto::Message) -> Result { + match msg.msg { + proto::mod_Message::OneOfmsg::dialRequest(proto::DialRequest { addrs, nonce }) => { + let addrs = addrs + .into_iter() + .map(|e| e.to_vec()) + .map(|e| { + Multiaddr::try_from(e).map_err(|err| { + new_io_invalid_data_err(format!("invalid multiaddr: {}", err)) + }) + }) + .collect::, io::Error>>()?; + Ok(Self::Dial(DialRequest { addrs, nonce })) + } + proto::mod_Message::OneOfmsg::dialDataResponse(proto::DialDataResponse { data }) => { + let data_count = data.len(); + Ok(Self::Data(DialDataResponse { data_count })) + } + _ => Err(new_io_invalid_data_err( + "expected dialResponse or dialDataRequest", + )), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Response { + Dial(DialResponse), + Data(DialDataRequest), +} + +#[derive(Debug, Clone)] +pub(crate) struct DialDataRequest { + pub(crate) addr_idx: usize, + pub(crate) num_bytes: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct DialResponse { + pub(crate) status: proto::mod_DialResponse::ResponseStatus, + pub(crate) addr_idx: usize, + pub(crate) dial_status: proto::DialStatus, +} + +impl TryFrom for Response { + type Error = io::Error; + + fn try_from(msg: proto::Message) -> Result { + match msg.msg { + proto::mod_Message::OneOfmsg::dialResponse(proto::DialResponse { + status, + addrIdx, + dialStatus, + }) => Ok(Response::Dial(DialResponse { + status, + addr_idx: addrIdx as usize, + dial_status: dialStatus, + })), + proto::mod_Message::OneOfmsg::dialDataRequest(proto::DialDataRequest { + addrIdx, + numBytes, + }) => Ok(Self::Data(DialDataRequest { + addr_idx: addrIdx as usize, + num_bytes: numBytes as usize, + })), + _ => Err(new_io_invalid_data_err( + "invalid message type, expected dialResponse or dialDataRequest", + )), + } + } +} + +impl From for proto::Message { + fn from(val: Response) -> Self { + match val { + Response::Dial(DialResponse { + status, + addr_idx, + dial_status, + }) => proto::Message { + msg: proto::mod_Message::OneOfmsg::dialResponse(proto::DialResponse { + status, + addrIdx: addr_idx as u32, + dialStatus: dial_status, + }), + }, + Response::Data(DialDataRequest { + addr_idx, + num_bytes, + }) => proto::Message { + msg: proto::mod_Message::OneOfmsg::dialDataRequest(proto::DialDataRequest { + addrIdx: addr_idx as u32, + numBytes: num_bytes as u64, + }), + }, + } + } +} + +impl DialDataRequest { + pub(crate) fn from_rng(addr_idx: usize, mut rng: R) -> Self { + let num_bytes = rng.gen_range(DATA_LEN_LOWER_BOUND..=DATA_LEN_UPPER_BOUND); + Self { + addr_idx, + num_bytes, + } + } +} + +const DIAL_BACK_MAX_SIZE: usize = 10; + +pub(crate) async fn dial_back(stream: impl AsyncWrite + Unpin, nonce: Nonce) -> io::Result<()> { + let msg = proto::DialBack { nonce }; + let mut framed = FramedWrite::new(stream, Codec::::new(DIAL_BACK_MAX_SIZE)); + + framed + .send(msg) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + Ok(()) +} + +pub(crate) async fn recv_dial_back(stream: impl AsyncRead + Unpin) -> io::Result { + let framed = &mut FramedRead::new(stream, Codec::::new(DIAL_BACK_MAX_SIZE)); + let proto::DialBack { nonce } = framed + .next() + .await + .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))??; + Ok(nonce) +} + +pub(crate) async fn dial_back_response(stream: impl AsyncWrite + Unpin) -> io::Result<()> { + let msg = proto::DialBackResponse { + status: proto::mod_DialBackResponse::DialBackStatus::OK, + }; + let mut framed = FramedWrite::new( + stream, + Codec::::new(DIAL_BACK_MAX_SIZE), + ); + framed + .send(msg) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + Ok(()) +} + +pub(crate) async fn recv_dial_back_response( + stream: impl AsyncRead + AsyncWrite + Unpin, +) -> io::Result<()> { + let framed = &mut FramedRead::new( + stream, + Codec::::new(DIAL_BACK_MAX_SIZE), + ); + let proto::DialBackResponse { status } = framed + .next() + .await + .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))??; + + if proto::mod_DialBackResponse::DialBackStatus::OK == status { + Ok(()) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid dial back response", + )) + } +} + +#[cfg(test)] +mod tests { + use crate::v2::generated::structs::{ + mod_Message::OneOfmsg, DialDataResponse as GenDialDataResponse, Message, + }; + + #[test] + fn message_correct_max_size() { + let message_bytes = quick_protobuf::serialize_into_vec(&Message { + msg: OneOfmsg::dialDataResponse(GenDialDataResponse { + data: vec![0; 4096], + }), + }) + .unwrap(); + assert_eq!(message_bytes.len(), super::REQUEST_MAX_SIZE); + } + + #[test] + fn dial_back_correct_size() { + let dial_back = super::proto::DialBack { nonce: 0 }; + let buf = quick_protobuf::serialize_into_vec(&dial_back).unwrap(); + assert!(buf.len() <= super::DIAL_BACK_MAX_SIZE); + + let dial_back_max_nonce = super::proto::DialBack { nonce: u64::MAX }; + let buf = quick_protobuf::serialize_into_vec(&dial_back_max_nonce).unwrap(); + assert!(buf.len() <= super::DIAL_BACK_MAX_SIZE); + } +} diff --git a/protocols/autonat/src/v2/server.rs b/protocols/autonat/src/v2/server.rs new file mode 100644 index 00000000000..25819307784 --- /dev/null +++ b/protocols/autonat/src/v2/server.rs @@ -0,0 +1,5 @@ +mod behaviour; +mod handler; + +pub use behaviour::Behaviour; +pub use behaviour::Event; diff --git a/protocols/autonat/src/v2/server/behaviour.rs b/protocols/autonat/src/v2/server/behaviour.rs new file mode 100644 index 00000000000..5f7b21d165b --- /dev/null +++ b/protocols/autonat/src/v2/server/behaviour.rs @@ -0,0 +1,156 @@ +use std::{ + collections::{HashMap, VecDeque}, + io, + task::{Context, Poll}, +}; + +use crate::v2::server::handler::dial_request::DialBackStatus; +use either::Either; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::dial_opts::PeerCondition; +use libp2p_swarm::{ + dial_opts::DialOpts, dummy, ConnectionDenied, ConnectionHandler, ConnectionId, DialFailure, + FromSwarm, NetworkBehaviour, ToSwarm, +}; +use rand_core::{OsRng, RngCore}; + +use crate::v2::server::handler::{ + dial_back, + dial_request::{self, DialBackCommand}, + Handler, +}; + +pub struct Behaviour +where + R: Clone + Send + RngCore + 'static, +{ + dialing_dial_back: HashMap, + pending_events: VecDeque< + ToSwarm< + ::ToSwarm, + <::ConnectionHandler as ConnectionHandler>::FromBehaviour, + >, + >, + rng: R, +} + +impl Default for Behaviour { + fn default() -> Self { + Self::new(OsRng) + } +} + +impl Behaviour +where + R: RngCore + Send + Clone + 'static, +{ + pub fn new(rng: R) -> Self { + Self { + dialing_dial_back: HashMap::new(), + pending_events: VecDeque::new(), + rng, + } + } +} + +impl NetworkBehaviour for Behaviour +where + R: RngCore + Send + Clone + 'static, +{ + type ConnectionHandler = Handler; + + type ToSwarm = Event; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + _local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Right(dial_request::Handler::new( + peer, + remote_addr.clone(), + self.rng.clone(), + ))) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + _peer: PeerId, + _addr: &Multiaddr, + _role_override: Endpoint, + _port_use: PortUse, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(match self.dialing_dial_back.remove(&connection_id) { + Some(cmd) => Either::Left(Either::Left(dial_back::Handler::new(cmd))), + None => Either::Left(Either::Right(dummy::ConnectionHandler)), + }) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + if let FromSwarm::DialFailure(DialFailure { connection_id, .. }) = event { + if let Some(DialBackCommand { back_channel, .. }) = + self.dialing_dial_back.remove(&connection_id) + { + let dial_back_status = DialBackStatus::DialErr; + let _ = back_channel.send(Err(dial_back_status)); + } + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + _connection_id: ConnectionId, + event: as ConnectionHandler>::ToBehaviour, + ) { + match event { + Either::Left(Either::Left(Ok(_))) => {} + Either::Left(Either::Left(Err(e))) => { + tracing::debug!("dial back error: {e:?}"); + } + Either::Left(Either::Right(v)) => void::unreachable(v), + Either::Right(Either::Left(cmd)) => { + let addr = cmd.addr.clone(); + let opts = DialOpts::peer_id(peer_id) + .addresses(Vec::from([addr])) + .condition(PeerCondition::Always) + .allocate_new_port() + .build(); + let conn_id = opts.connection_id(); + self.dialing_dial_back.insert(conn_id, cmd); + self.pending_events.push_back(ToSwarm::Dial { opts }); + } + Either::Right(Either::Right(status_update)) => self + .pending_events + .push_back(ToSwarm::GenerateEvent(status_update)), + } + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll as ConnectionHandler>::FromBehaviour>> { + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + Poll::Pending + } +} + +#[derive(Debug)] +pub struct Event { + /// All address that were submitted for testing. + pub all_addrs: Vec, + /// The address that was eventually tested. + pub tested_addr: Multiaddr, + /// The peer id of the client that submitted addresses for testing. + pub client: PeerId, + /// The amount of data that was requested by the server and was transmitted. + pub data_amount: usize, + /// The result of the test. + pub result: Result<(), io::Error>, +} diff --git a/protocols/autonat/src/v2/server/handler.rs b/protocols/autonat/src/v2/server/handler.rs new file mode 100644 index 00000000000..ffdad69c86f --- /dev/null +++ b/protocols/autonat/src/v2/server/handler.rs @@ -0,0 +1,8 @@ +use either::Either; +use libp2p_swarm::dummy; + +pub(crate) mod dial_back; +pub(crate) mod dial_request; + +pub(crate) type Handler = + Either, dial_request::Handler>; diff --git a/protocols/autonat/src/v2/server/handler/dial_back.rs b/protocols/autonat/src/v2/server/handler/dial_back.rs new file mode 100644 index 00000000000..3cacd4ff32b --- /dev/null +++ b/protocols/autonat/src/v2/server/handler/dial_back.rs @@ -0,0 +1,140 @@ +use std::{ + convert::identity, + io, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{AsyncRead, AsyncWrite}; +use futures_bounded::FuturesSet; +use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; +use libp2p_swarm::{ + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedOutbound}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, StreamUpgradeError, + SubstreamProtocol, +}; + +use crate::v2::{ + protocol::{dial_back, recv_dial_back_response}, + DIAL_BACK_PROTOCOL, +}; + +use super::dial_request::{DialBackCommand, DialBackStatus as DialBackRes}; + +pub(crate) type ToBehaviour = io::Result<()>; + +pub struct Handler { + pending_nonce: Option, + requested_substream_nonce: Option, + outbound: FuturesSet, +} + +impl Handler { + pub(crate) fn new(cmd: DialBackCommand) -> Self { + Self { + pending_nonce: Some(cmd), + requested_substream_nonce: None, + outbound: FuturesSet::new(Duration::from_secs(10), 5), + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = (); + type ToBehaviour = ToBehaviour; + type InboundProtocol = DeniedUpgrade; + type OutboundProtocol = ReadyUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(DeniedUpgrade, ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + if let Poll::Ready(result) = self.outbound.poll_unpin(cx) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + result + .map_err(|timeout| io::Error::new(io::ErrorKind::TimedOut, timeout)) + .and_then(identity), + )); + } + if let Some(cmd) = self.pending_nonce.take() { + self.requested_substream_nonce = Some(cmd); + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(ReadyUpgrade::new(DIAL_BACK_PROTOCOL), ()), + }); + } + Poll::Pending + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol, .. + }) => { + if let Some(cmd) = self.requested_substream_nonce.take() { + if self + .outbound + .try_push(perform_dial_back(protocol, cmd)) + .is_err() + { + tracing::warn!("Dial back dropped, too many requests in flight"); + } + } else { + tracing::warn!("received dial back substream without nonce"); + } + } + ConnectionEvent::DialUpgradeError(DialUpgradeError { + error: StreamUpgradeError::NegotiationFailed | StreamUpgradeError::Timeout, + .. + }) => { + if let Some(cmd) = self.requested_substream_nonce.take() { + let _ = cmd.back_channel.send(Err(DialBackRes::DialBackErr)); + } + } + _ => {} + } + } +} + +async fn perform_dial_back( + mut stream: impl AsyncRead + AsyncWrite + Unpin, + DialBackCommand { + nonce, + back_channel, + .. + }: DialBackCommand, +) -> io::Result<()> { + let res = dial_back(&mut stream, nonce) + .await + .map_err(|_| DialBackRes::DialBackErr) + .map(|_| ()); + + let res = match res { + Ok(()) => recv_dial_back_response(stream) + .await + .map_err(|_| DialBackRes::DialBackErr) + .map(|_| ()), + Err(e) => Err(e), + }; + back_channel + .send(res) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "send error"))?; + Ok(()) +} diff --git a/protocols/autonat/src/v2/server/handler/dial_request.rs b/protocols/autonat/src/v2/server/handler/dial_request.rs new file mode 100644 index 00000000000..9a3729d4ccf --- /dev/null +++ b/protocols/autonat/src/v2/server/handler/dial_request.rs @@ -0,0 +1,332 @@ +use std::{ + io, + task::{Context, Poll}, + time::Duration, +}; + +use either::Either; +use futures::{ + channel::{mpsc, oneshot}, + AsyncRead, AsyncWrite, SinkExt, StreamExt, +}; +use futures_bounded::FuturesSet; +use libp2p_core::{ + upgrade::{DeniedUpgrade, ReadyUpgrade}, + Multiaddr, +}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound, ListenUpgradeError}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, +}; +use rand_core::RngCore; + +use crate::v2::{ + generated::structs::{mod_DialResponse::ResponseStatus, DialStatus}, + protocol::{Coder, DialDataRequest, DialRequest, DialResponse, Request, Response}, + server::behaviour::Event, + Nonce, DIAL_REQUEST_PROTOCOL, +}; + +#[derive(Debug, PartialEq)] +pub(crate) enum DialBackStatus { + /// Failure during dial + DialErr, + /// Failure during dial back + DialBackErr, +} + +#[derive(Debug)] +pub struct DialBackCommand { + pub(crate) addr: Multiaddr, + pub(crate) nonce: Nonce, + pub(crate) back_channel: oneshot::Sender>, +} + +pub struct Handler { + client_id: PeerId, + observed_multiaddr: Multiaddr, + dial_back_cmd_sender: mpsc::Sender, + dial_back_cmd_receiver: mpsc::Receiver, + inbound: FuturesSet, + rng: R, +} + +impl Handler +where + R: RngCore, +{ + pub(crate) fn new(client_id: PeerId, observed_multiaddr: Multiaddr, rng: R) -> Self { + let (dial_back_cmd_sender, dial_back_cmd_receiver) = mpsc::channel(10); + Self { + client_id, + observed_multiaddr, + dial_back_cmd_sender, + dial_back_cmd_receiver, + inbound: FuturesSet::new(Duration::from_secs(10), 10), + rng, + } + } +} + +impl ConnectionHandler for Handler +where + R: RngCore + Send + Clone + 'static, +{ + type FromBehaviour = void::Void; + type ToBehaviour = Either; + type InboundProtocol = ReadyUpgrade; + type OutboundProtocol = DeniedUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(ReadyUpgrade::new(DIAL_REQUEST_PROTOCOL), ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + loop { + match self.inbound.poll_unpin(cx) { + Poll::Ready(Ok(event)) => { + if let Err(e) = &event.result { + tracing::warn!("inbound request handle failed: {:?}", e); + } + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Either::Right( + event, + ))); + } + Poll::Ready(Err(e)) => { + tracing::warn!("inbound request handle timed out {e:?}"); + } + Poll::Pending => break, + } + } + if let Poll::Ready(Some(cmd)) = self.dial_back_cmd_receiver.poll_next_unpin(cx) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Either::Left(cmd))); + } + Poll::Pending + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol, .. + }) => { + if self + .inbound + .try_push(handle_request( + protocol, + self.observed_multiaddr.clone(), + self.client_id, + self.dial_back_cmd_sender.clone(), + self.rng.clone(), + )) + .is_err() + { + tracing::warn!( + "failed to push inbound request handler, too many requests in flight" + ); + } + } + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { error, .. }) => { + tracing::debug!("inbound request failed: {:?}", error); + } + _ => {} + } + } +} + +enum HandleFail { + InternalError(usize), + RequestRejected, + DialRefused, + DialBack { + idx: usize, + result: Result<(), DialBackStatus>, + }, +} + +impl From for DialResponse { + fn from(value: HandleFail) -> Self { + match value { + HandleFail::InternalError(addr_idx) => Self { + status: ResponseStatus::E_INTERNAL_ERROR, + addr_idx, + dial_status: DialStatus::UNUSED, + }, + HandleFail::RequestRejected => Self { + status: ResponseStatus::E_REQUEST_REJECTED, + addr_idx: 0, + dial_status: DialStatus::UNUSED, + }, + HandleFail::DialRefused => Self { + status: ResponseStatus::E_DIAL_REFUSED, + addr_idx: 0, + dial_status: DialStatus::UNUSED, + }, + HandleFail::DialBack { idx, result } => Self { + status: ResponseStatus::OK, + addr_idx: idx, + dial_status: match result { + Err(DialBackStatus::DialErr) => DialStatus::E_DIAL_ERROR, + Err(DialBackStatus::DialBackErr) => DialStatus::E_DIAL_BACK_ERROR, + Ok(()) => DialStatus::OK, + }, + }, + } + } +} + +async fn handle_request( + stream: impl AsyncRead + AsyncWrite + Unpin, + observed_multiaddr: Multiaddr, + client: PeerId, + dial_back_cmd_sender: mpsc::Sender, + rng: impl RngCore, +) -> Event { + let mut coder = Coder::new(stream); + let mut all_addrs = Vec::new(); + let mut tested_addr_opt = None; + let mut data_amount = 0; + let response = handle_request_internal( + &mut coder, + observed_multiaddr.clone(), + dial_back_cmd_sender, + rng, + &mut all_addrs, + &mut tested_addr_opt, + &mut data_amount, + ) + .await + .unwrap_or_else(|e| e.into()); + let Some(tested_addr) = tested_addr_opt else { + return Event { + all_addrs, + tested_addr: observed_multiaddr, + client, + data_amount, + result: Err(io::Error::new( + io::ErrorKind::Other, + "client is not conformint to protocol. the tested address is not the observed address", + )), + }; + }; + if let Err(e) = coder.send(Response::Dial(response)).await { + return Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Err(e), + }; + } + if let Err(e) = coder.close().await { + return Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Err(e), + }; + } + Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + } +} + +async fn handle_request_internal( + coder: &mut Coder, + observed_multiaddr: Multiaddr, + dial_back_cmd_sender: mpsc::Sender, + mut rng: impl RngCore, + all_addrs: &mut Vec, + tested_addrs: &mut Option, + data_amount: &mut usize, +) -> Result +where + I: AsyncRead + AsyncWrite + Unpin, +{ + let DialRequest { mut addrs, nonce } = match coder + .next() + .await + .map_err(|_| HandleFail::InternalError(0))? + { + Request::Dial(dial_request) => dial_request, + Request::Data(_) => { + return Err(HandleFail::RequestRejected); + } + }; + all_addrs.clone_from(&addrs); + let idx = 0; + let addr = addrs.pop().ok_or(HandleFail::DialRefused)?; + *tested_addrs = Some(addr.clone()); + *data_amount = 0; + if addr != observed_multiaddr { + let dial_data_request = DialDataRequest::from_rng(idx, &mut rng); + let mut rem_data = dial_data_request.num_bytes; + coder + .send(Response::Data(dial_data_request)) + .await + .map_err(|_| HandleFail::InternalError(idx))?; + while rem_data > 0 { + let data_count = match coder + .next() + .await + .map_err(|_e| HandleFail::InternalError(idx))? + { + Request::Dial(_) => { + return Err(HandleFail::RequestRejected); + } + Request::Data(dial_data_response) => dial_data_response.get_data_count(), + }; + rem_data = rem_data.saturating_sub(data_count); + *data_amount += data_count; + } + } + let (back_channel, rx) = oneshot::channel(); + let dial_back_cmd = DialBackCommand { + addr, + nonce, + back_channel, + }; + dial_back_cmd_sender + .clone() + .send(dial_back_cmd) + .await + .map_err(|_| HandleFail::DialBack { + idx, + result: Err(DialBackStatus::DialErr), + })?; + + let dial_back = rx.await.map_err(|_e| HandleFail::InternalError(idx))?; + if let Err(err) = dial_back { + return Err(HandleFail::DialBack { + idx, + result: Err(err), + }); + } + Ok(DialResponse { + status: ResponseStatus::OK, + addr_idx: idx, + dial_status: DialStatus::OK, + }) +} diff --git a/protocols/autonat/tests/autonatv2.rs b/protocols/autonat/tests/autonatv2.rs new file mode 100644 index 00000000000..abd0c4bd8eb --- /dev/null +++ b/protocols/autonat/tests/autonatv2.rs @@ -0,0 +1,568 @@ +use libp2p_autonat::v2::client::{self, Config}; +use libp2p_autonat::v2::server; +use libp2p_core::transport::TransportError; +use libp2p_core::Multiaddr; +use libp2p_swarm::{ + DialError, FromSwarm, NetworkBehaviour, NewExternalAddrCandidate, Swarm, SwarmEvent, +}; +use libp2p_swarm_test::SwarmExt; +use rand_core::OsRng; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::oneshot; +use tracing_subscriber::EnvFilter; + +#[tokio::test] +async fn confirm_successful() { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + let (mut alice, mut bob) = start_and_connect().await; + + let cor_server_peer = *alice.local_peer_id(); + let cor_client_peer = *bob.local_peer_id(); + let bob_external_addrs = Arc::new(bob.external_addresses().cloned().collect::>()); + let alice_bob_external_addrs = bob_external_addrs.clone(); + + let alice_task = async { + let _ = alice + .wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { .. } => Some(()), + _ => None, + }) + .await; + + let (dialed_peer_id, dialed_connection_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + .. + } => peer_id.map(|peer_id| (peer_id, connection_id)), + _ => None, + }) + .await; + + assert_eq!(dialed_peer_id, cor_client_peer); + + let _ = alice + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } if peer_id == dialed_peer_id + && peer_id == cor_client_peer + && connection_id == dialed_connection_id => + { + Some(()) + } + _ => None, + }) + .await; + + let server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result, + } = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(status_update)) => { + Some(status_update) + } + _ => None, + }) + .await; + + assert_eq!(tested_addr, bob_external_addrs.first().cloned().unwrap()); + assert_eq!(data_amount, 0); + assert_eq!(client, cor_client_peer); + assert_eq!(&all_addrs[..], &bob_external_addrs[..]); + assert!(result.is_ok(), "Result: {result:?}"); + }; + + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { address } => Some(address), + _ => None, + }) + .await; + let incoming_conn_id = bob + .wait(|event| match event { + SwarmEvent::IncomingConnection { connection_id, .. } => Some(connection_id), + _ => None, + }) + .await; + + let _ = bob + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + connection_id, + peer_id, + .. + } if incoming_conn_id == connection_id && peer_id == cor_server_peer => Some(()), + _ => None, + }) + .await; + + let client::Event { + tested_addr, + bytes_sent, + server, + result, + } = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(status_update)) => { + Some(status_update) + } + _ => None, + }) + .await; + assert_eq!( + tested_addr, + alice_bob_external_addrs.first().cloned().unwrap() + ); + assert_eq!(bytes_sent, 0); + assert_eq!(server, cor_server_peer); + assert!(result.is_ok(), "Result is {result:?}"); + }; + + tokio::join!(alice_task, bob_task); +} + +#[tokio::test] +async fn dial_back_to_unsupported_protocol() { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + let (mut alice, mut bob) = bootstrap().await; + + let alice_peer_id = *alice.local_peer_id(); + + let test_addr: Multiaddr = "/ip4/127.0.0.1/udp/1234/quic/webtransport".parse().unwrap(); + let bob_test_addr = test_addr.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr: &test_addr }, + )); + + let (bob_done_tx, bob_done_rx) = oneshot::channel(); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|e| (e, connection_id)), + _ => None, + }) + .await; + let mut outgoing_conn_error = alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::Transport(transport_errs), + } if connection_id == alice_conn_id && alice_dialing_peer == peer_id => { + Some(transport_errs) + } + _ => None, + }) + .await; + if let Some((multiaddr, TransportError::MultiaddrNotSupported(not_supported_addr))) = + outgoing_conn_error.pop() + { + assert_eq!( + multiaddr, + test_addr.clone().with_p2p(alice_dialing_peer).unwrap() + ); + assert_eq!(not_supported_addr, multiaddr,); + } else { + panic!("Peers are empty"); + } + assert_eq!(outgoing_conn_error.len(), 0); + let data_amount = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![test_addr.clone()] + && tested_addr == test_addr.clone() + && client == alice_dialing_peer => + { + Some(data_amount) + } + _ => None, + }) + .await; + + let handler = tokio::spawn(async move { + alice.loop_on_next().await; + }); + let _ = bob_done_rx.await; + handler.abort(); + data_amount + }; + + let bob_task = async { + let data_amount = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if server == alice_peer_id && tested_addr == bob_test_addr => Some(bytes_sent), + _ => None, + }) + .await; + bob_done_tx.send(()).unwrap(); + data_amount + }; + let (alice_amount, bob_amount) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_amount, bob_amount); +} + +#[tokio::test] +async fn dial_back_to_non_libp2p() { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + let (mut alice, mut bob) = bootstrap().await; + let alice_peer_id = *alice.local_peer_id(); + + for addr_str in ["/ip4/169.150.247.38/tcp/32", "/ip6/::1/tcp/1000"] { + let addr: Multiaddr = addr_str.parse().unwrap(); + let bob_addr = addr.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr: &addr }, + )); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|p| (p, connection_id)), + _ => None, + }) + .await; + let mut outgoing_conn_error = alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::Transport(peers), + } if connection_id == alice_conn_id && peer_id == alice_dialing_peer => { + Some(peers) + } + _ => None, + }) + .await; + + if let Some((multiaddr, TransportError::Other(o))) = outgoing_conn_error.pop() { + assert_eq!( + multiaddr, + addr.clone().with_p2p(alice_dialing_peer).unwrap() + ); + let error_string = o.to_string(); + assert!( + error_string.contains("Connection refused"), + "Correct error string: {error_string} for {addr_str}" + ); + } else { + panic!("No outgoing connection errors"); + } + + alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![addr.clone()] + && tested_addr == addr + && alice_dialing_peer == client => + { + Some(data_amount) + } + _ => None, + }) + .await + }; + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if tested_addr == bob_addr && server == alice_peer_id => Some(bytes_sent), + _ => None, + }) + .await + }; + + let (alice_bytes_sent, bob_bytes_sent) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_bytes_sent, bob_bytes_sent); + bob.behaviour_mut().autonat.validate_addr(&addr); + } +} + +#[tokio::test] +async fn dial_back_to_not_supporting() { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + + let (mut alice, mut bob) = bootstrap().await; + let alice_peer_id = *alice.local_peer_id(); + + let (bob_done_tx, bob_done_rx) = oneshot::channel(); + + let hannes = new_dummy().await; + let hannes_peer_id = *hannes.local_peer_id(); + let unreachable_address = hannes.external_addresses().next().unwrap().clone(); + let bob_unreachable_address = unreachable_address.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { + addr: &unreachable_address, + }, + )); + + let handler = tokio::spawn(async { hannes.loop_on_next().await }); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|p| (p, connection_id)), + _ => None, + }) + .await; + alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::WrongPeerId { obtained, .. }, + } if connection_id == alice_conn_id + && peer_id == alice_dialing_peer + && obtained == hannes_peer_id => + { + Some(()) + } + _ => None, + }) + .await; + + let data_amount = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![unreachable_address.clone()] + && tested_addr == unreachable_address + && alice_dialing_peer == client => + { + Some(data_amount) + } + _ => None, + }) + .await; + tokio::select! { + _ = bob_done_rx => { + data_amount + } + _ = alice.loop_on_next() => { + unreachable!(); + } + } + }; + + let bob_task = async { + let bytes_sent = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if tested_addr == bob_unreachable_address && server == alice_peer_id => { + Some(bytes_sent) + } + _ => None, + }) + .await; + bob_done_tx.send(()).unwrap(); + bytes_sent + }; + + let (alice_bytes_sent, bob_bytes_sent) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_bytes_sent, bob_bytes_sent); + handler.abort(); +} + +async fn new_server() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| CombinedServer { + autonat: libp2p_autonat::v2::server::Behaviour::default(), + identify: libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )), + }); + node.listen().with_tcp_addr_external().await; + + node +} + +async fn new_client() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| CombinedClient { + autonat: libp2p_autonat::v2::client::Behaviour::new( + OsRng, + Config::default().with_probe_interval(Duration::from_millis(100)), + ), + identify: libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )), + }); + node.listen().with_tcp_addr_external().await; + node +} + +#[derive(libp2p_swarm::NetworkBehaviour)] +#[behaviour(prelude = "libp2p_swarm::derive_prelude")] +struct CombinedServer { + autonat: libp2p_autonat::v2::server::Behaviour, + identify: libp2p_identify::Behaviour, +} + +#[derive(libp2p_swarm::NetworkBehaviour)] +#[behaviour(prelude = "libp2p_swarm::derive_prelude")] +struct CombinedClient { + autonat: libp2p_autonat::v2::client::Behaviour, + identify: libp2p_identify::Behaviour, +} + +async fn new_dummy() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| { + libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )) + }); + node.listen().with_tcp_addr_external().await; + node +} + +async fn start_and_connect() -> (Swarm, Swarm) { + let mut alice = new_server().await; + let mut bob = new_client().await; + + bob.connect(&mut alice).await; + (alice, bob) +} + +async fn bootstrap() -> (Swarm, Swarm) { + let (mut alice, mut bob) = start_and_connect().await; + + let cor_server_peer = *alice.local_peer_id(); + let cor_client_peer = *bob.local_peer_id(); + + let alice_task = async { + let _ = alice + .wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { .. } => Some(()), + _ => None, + }) + .await; + + let (dialed_peer_id, dialed_connection_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + .. + } => peer_id.map(|peer_id| (peer_id, connection_id)), + _ => None, + }) + .await; + + let _ = alice + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } if peer_id == dialed_peer_id + && peer_id == cor_client_peer + && connection_id == dialed_connection_id => + { + Some(()) + } + _ => None, + }) + .await; + + alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(_)) => Some(()), + _ => None, + }) + .await; + }; + + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { address } => Some(address), + _ => None, + }) + .await; + let incoming_conn_id = bob + .wait(|event| match event { + SwarmEvent::IncomingConnection { connection_id, .. } => Some(connection_id), + _ => None, + }) + .await; + + let _ = bob + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + connection_id, + peer_id, + .. + } if incoming_conn_id == connection_id && peer_id == cor_server_peer => Some(()), + _ => None, + }) + .await; + + bob.wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(_)) => Some(()), + _ => None, + }) + .await; + }; + + tokio::join!(alice_task, bob_task); + (alice, bob) +} From 7b2a77e066a39fc052178bd4362792e6b63822ef Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 8 Aug 2024 17:36:25 +0300 Subject: [PATCH 18/35] chore(metrics): Add `/tls/ws` in protocol stack tests Pull-Request: #5540. --- misc/metrics/src/protocol_stack.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/misc/metrics/src/protocol_stack.rs b/misc/metrics/src/protocol_stack.rs index 59e8c0bfa6a..57760df79a1 100644 --- a/misc/metrics/src/protocol_stack.rs +++ b/misc/metrics/src/protocol_stack.rs @@ -23,5 +23,11 @@ mod tests { let protocol_stack = as_string(&ma); assert_eq!(protocol_stack, "/ip6/tcp/wss/p2p"); + + let ma = Multiaddr::try_from("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/tls/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC").expect("testbad"); + + let protocol_stack = as_string(&ma); + + assert_eq!(protocol_stack, "/ip6/tcp/tls/ws/p2p"); } } From 4725f46f0d0e14f3ef4e88edc085729c1312dff6 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 8 Aug 2024 18:11:33 +0300 Subject: [PATCH 19/35] feat(websocket,websocket-websys): Add support for `/tls/ws` To keep backward compatibility the following rules were implemented: * On dialing, `/tls/ws` and `/wss` are equivalent. * On listening: * If user listens with `/tls/ws` then `/tls/ws` will be advertised. * If user listens with `/wss` then `/wss` will be advertised. Closes #2449 Closes #5509 Pull-Request: #5523. --- interop-tests/src/arch.rs | 4 +- transports/websocket-websys/CHANGELOG.md | 3 + transports/websocket-websys/src/lib.rs | 107 +++++++++++- transports/websocket/CHANGELOG.md | 2 + transports/websocket/src/framed.rs | 214 +++++++++++++++++++---- transports/websocket/src/lib.rs | 2 +- 6 files changed, 289 insertions(+), 43 deletions(-) diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index a7755b95977..df36f8e5baf 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -245,7 +245,7 @@ pub(crate) mod wasm { .with_behaviour(behaviour_constructor)? .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), - format!("/ip4/{ip}/tcp/0/wss"), + format!("/ip4/{ip}/tcp/0/tls/ws"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => ( libp2p::SwarmBuilder::with_new_identity() @@ -262,7 +262,7 @@ pub(crate) mod wasm { .with_behaviour(behaviour_constructor)? .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), - format!("/ip4/{ip}/tcp/0/wss"), + format!("/ip4/{ip}/tcp/0/tls/ws"), ), (Transport::WebRtcDirect, None, None) => ( libp2p::SwarmBuilder::with_new_identity() diff --git a/transports/websocket-websys/CHANGELOG.md b/transports/websocket-websys/CHANGELOG.md index 70d866e6141..d0aeb509823 100644 --- a/transports/websocket-websys/CHANGELOG.md +++ b/transports/websocket-websys/CHANGELOG.md @@ -2,6 +2,9 @@ - Implement refactored `Transport`. See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Add support for `/tls/ws` and keep `/wss` backward compatible. + See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523). + ## 0.3.3 - Fix use-after-free handler invocation from JS side. diff --git a/transports/websocket-websys/src/lib.rs b/transports/websocket-websys/src/lib.rs index d2589715bbb..f353d92b204 100644 --- a/transports/websocket-websys/src/lib.rs +++ b/transports/websocket-websys/src/lib.rs @@ -137,9 +137,10 @@ fn extract_websocket_url(addr: &Multiaddr) -> Option { _ => return None, }; - let (scheme, wspath) = match protocols.next() { - Some(Protocol::Ws(path)) => ("ws", path.into_owned()), - Some(Protocol::Wss(path)) => ("wss", path.into_owned()), + let (scheme, wspath) = match (protocols.next(), protocols.next()) { + (Some(Protocol::Tls), Some(Protocol::Ws(path))) => ("wss", path.into_owned()), + (Some(Protocol::Ws(path)), _) => ("ws", path.into_owned()), + (Some(Protocol::Wss(path)), _) => ("wss", path.into_owned()), _ => return None, }; @@ -453,3 +454,103 @@ impl Drop for Connection { .clear_interval_with_handle(self.inner.buffered_amount_low_interval); } } + +#[cfg(test)] +mod tests { + use super::*; + use libp2p_identity::PeerId; + + #[test] + fn extract_url() { + let peer_id = PeerId::random(); + + // Check `/tls/ws` + let addr = "/dns4/example.com/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/tls/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/tls/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://127.0.0.1:2222/"); + + // Check `/tls/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/tls/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://[::1]:2222/"); + + // Check `/wss` + let addr = "/dns4/example.com/tcp/2222/wss" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/wss` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/wss/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/wss` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/wss".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://127.0.0.1:2222/"); + + // Check `/wss` with `/ip6` + let addr = "/ip6/::1/tcp/2222/wss".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://[::1]:2222/"); + + // Check `/ws` + let addr = "/dns4/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://example.com:2222/"); + + // Check `/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://example.com:2222/"); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://127.0.0.1:2222/"); + + // Check `/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://[::1]:2222/"); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://127.0.0.1:2222/"); + + // Check that `/tls/wss` is invalid + let addr = "/ip4/127.0.0.1/tcp/2222/tls/wss" + .parse::() + .unwrap(); + assert!(extract_websocket_url(&addr).is_none()); + + // Check non-ws address + let addr = "/ip4/127.0.0.1/tcp/2222".parse::().unwrap(); + assert!(extract_websocket_url(&addr).is_none()); + } +} diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md index df51e2c807d..cd079cfdd5a 100644 --- a/transports/websocket/CHANGELOG.md +++ b/transports/websocket/CHANGELOG.md @@ -4,6 +4,8 @@ See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) - Allow wss connections on IP addresses. See [PR 5525](https://github.com/libp2p/rust-libp2p/pull/5525). +- Add support for `/tls/ws` and keep `/wss` backward compatible. + See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523). ## 0.43.2 diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 074271e672f..a547aea21ef 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -33,6 +33,7 @@ use soketto::{ connection::{self, CloseReason}, handshake, }; +use std::borrow::Cow; use std::net::IpAddr; use std::{collections::HashMap, ops::DerefMut, sync::Arc}; use std::{fmt, io, mem, pin::Pin, task::Context, task::Poll}; @@ -51,10 +52,7 @@ pub struct WsConfig { tls_config: tls::Config, max_redirects: u8, /// Websocket protocol of the inner listener. - /// - /// This is the suffix of the address provided in `listen_on`. - /// Can only be [`Protocol::Ws`] or [`Protocol::Wss`]. - listener_protos: HashMap>, + listener_protos: HashMap>, } impl WsConfig @@ -121,22 +119,19 @@ where id: ListenerId, addr: Multiaddr, ) -> Result<(), TransportError> { - let mut inner_addr = addr.clone(); - let proto = match inner_addr.pop() { - Some(p @ Protocol::Wss(_)) => { - if self.tls_config.server.is_some() { - p - } else { - tracing::debug!("/wss address but TLS server support is not configured"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - } - Some(p @ Protocol::Ws(_)) => p, - _ => { - tracing::debug!(address=%addr, "Address is not a websocket multiaddr"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - }; + let (inner_addr, proto) = parse_ws_listen_addr(&addr).ok_or_else(|| { + tracing::debug!(address=%addr, "Address is not a websocket multiaddr"); + TransportError::MultiaddrNotSupported(addr.clone()) + })?; + + if proto.use_tls() && self.tls_config.server.is_none() { + tracing::debug!( + "{} address but TLS server support is not configured", + proto.prefix() + ); + return Err(TransportError::MultiaddrNotSupported(addr)); + } + match self.transport.lock().listen_on(id, inner_addr) { Ok(()) => { self.listener_protos.insert(id, proto); @@ -175,11 +170,10 @@ where mut listen_addr, } => { // Append the ws / wss protocol back to the inner address. - let proto = self - .listener_protos + self.listener_protos .get(&listener_id) - .expect("Protocol was inserted in Transport::listen_on."); - listen_addr.push(proto.clone()); + .expect("Protocol was inserted in Transport::listen_on.") + .append_on_addr(&mut listen_addr); tracing::debug!(address=%listen_addr, "Listening on address"); TransportEvent::NewAddress { listener_id, @@ -190,11 +184,10 @@ where listener_id, mut listen_addr, } => { - let proto = self - .listener_protos + self.listener_protos .get(&listener_id) - .expect("Protocol was inserted in Transport::listen_on."); - listen_addr.push(proto.clone()); + .expect("Protocol was inserted in Transport::listen_on.") + .append_on_addr(&mut listen_addr); TransportEvent::AddressExpired { listener_id, listen_addr, @@ -226,13 +219,9 @@ where .listener_protos .get(&listener_id) .expect("Protocol was inserted in Transport::listen_on."); - let use_tls = match proto { - Protocol::Wss(_) => true, - Protocol::Ws(_) => false, - _ => unreachable!("Map contains only ws and wss protocols."), - }; - local_addr.push(proto.clone()); - send_back_addr.push(proto.clone()); + let use_tls = proto.use_tls(); + proto.append_on_addr(&mut local_addr); + proto.append_on_addr(&mut send_back_addr); let upgrade = self.map_upgrade(upgrade, send_back_addr.clone(), use_tls); TransportEvent::Incoming { listener_id, @@ -446,6 +435,48 @@ where } } +#[derive(Debug, PartialEq)] +pub(crate) enum WsListenProto<'a> { + Ws(Cow<'a, str>), + Wss(Cow<'a, str>), + TlsWs(Cow<'a, str>), +} + +impl<'a> WsListenProto<'a> { + pub(crate) fn append_on_addr(&self, addr: &mut Multiaddr) { + match self { + WsListenProto::Ws(path) => { + addr.push(Protocol::Ws(path.clone())); + } + // `/tls/ws` and `/wss` are equivalend, however we regenerate + // the one that user passed at `listen_on` for backward compatibility. + WsListenProto::Wss(path) => { + addr.push(Protocol::Wss(path.clone())); + } + WsListenProto::TlsWs(path) => { + addr.push(Protocol::Tls); + addr.push(Protocol::Ws(path.clone())); + } + } + } + + pub(crate) fn use_tls(&self) -> bool { + match self { + WsListenProto::Ws(_) => false, + WsListenProto::Wss(_) => true, + WsListenProto::TlsWs(_) => true, + } + } + + pub(crate) fn prefix(&self) -> &'static str { + match self { + WsListenProto::Ws(_) => "/ws", + WsListenProto::Wss(_) => "/wss", + WsListenProto::TlsWs(_) => "/tls/ws", + } + } +} + #[derive(Debug)] struct WsAddress { host_port: String, @@ -499,7 +530,14 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { let (use_tls, path) = loop { match protocols.pop() { p @ Some(Protocol::P2p(_)) => p2p = p, - Some(Protocol::Ws(path)) => break (false, path.into_owned()), + Some(Protocol::Ws(path)) => match protocols.pop() { + Some(Protocol::Tls) => break (true, path.into_owned()), + Some(p) => { + protocols.push(p); + break (false, path.into_owned()); + } + None => return Err(Error::InvalidMultiaddr(addr)), + }, Some(Protocol::Wss(path)) => break (true, path.into_owned()), _ => return Err(Error::InvalidMultiaddr(addr)), } @@ -521,6 +559,22 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { }) } +fn parse_ws_listen_addr(addr: &Multiaddr) -> Option<(Multiaddr, WsListenProto<'static>)> { + let mut inner_addr = addr.clone(); + + match inner_addr.pop()? { + Protocol::Wss(path) => Some((inner_addr, WsListenProto::Wss(path))), + Protocol::Ws(path) => match inner_addr.pop()? { + Protocol::Tls => Some((inner_addr, WsListenProto::TlsWs(path))), + p => { + inner_addr.push(p); + Some((inner_addr, WsListenProto::Ws(path))) + } + }, + _ => None, + } +} + // Given a location URL, build a new websocket [`Multiaddr`]. fn location_to_multiaddr(location: &str) -> Result> { match Url::parse(location) { @@ -537,7 +591,8 @@ fn location_to_multiaddr(location: &str) -> Result> { } let s = url.scheme(); if s.eq_ignore_ascii_case("https") | s.eq_ignore_ascii_case("wss") { - a.push(Protocol::Wss(url.path().into())) + a.push(Protocol::Tls); + a.push(Protocol::Ws(url.path().into())); } else if s.eq_ignore_ascii_case("http") | s.eq_ignore_ascii_case("ws") { a.push(Protocol::Ws(url.path().into())) } else { @@ -759,10 +814,95 @@ mod tests { use libp2p_identity::PeerId; use std::io; + #[test] + fn listen_addr() { + let tcp_addr = "/ip4/0.0.0.0/tcp/2222".parse::().unwrap(); + + // Check `/tls/ws` + let addr = tcp_addr + .clone() + .with(Protocol::Tls) + .with(Protocol::Ws("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::TlsWs("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + + // Check `/wss` + let addr = tcp_addr.clone().with(Protocol::Wss("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::Wss("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + + // Check `/ws` + let addr = tcp_addr.clone().with(Protocol::Ws("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::Ws("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + } + #[test] fn dial_addr() { let peer_id = PeerId::random(); + // Check `/tls/ws` + let addr = "/dns4/example.com/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/tls/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/tls/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/tls/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/tls/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + // Check `/wss` let addr = "/dns4/example.com/tcp/2222/wss" .parse::() diff --git a/transports/websocket/src/lib.rs b/transports/websocket/src/lib.rs index 40d6db44471..cbc923613dd 100644 --- a/transports/websocket/src/lib.rs +++ b/transports/websocket/src/lib.rs @@ -84,7 +84,7 @@ use std::{ /// let cert = websocket::tls::Certificate::new(rcgen_cert.serialize_der().unwrap()); /// transport.set_tls_config(websocket::tls::Config::new(priv_key, vec![cert]).unwrap()); /// -/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/wss".parse().unwrap()).unwrap(); +/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/tls/ws".parse().unwrap()).unwrap(); /// /// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)).await.into_new_address().unwrap(); /// println!("Listening on {addr}"); From 0c143ffde97cad2c17c94033d0e2accdff37418f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Fri, 9 Aug 2024 17:31:22 +0100 Subject: [PATCH 20/35] chore: prepare libp2p 0.54 release Pull-Request: #5542. --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 22 +++++++++++----------- misc/allow-block-list/CHANGELOG.md | 4 ++++ misc/allow-block-list/Cargo.toml | 2 +- misc/connection-limits/CHANGELOG.md | 4 ++++ misc/connection-limits/Cargo.toml | 2 +- misc/memory-connection-limits/CHANGELOG.md | 4 ++++ misc/memory-connection-limits/Cargo.toml | 2 +- misc/webrtc-utils/CHANGELOG.md | 4 ++++ misc/webrtc-utils/Cargo.toml | 2 +- muxers/mplex/CHANGELOG.md | 4 ++++ muxers/mplex/Cargo.toml | 4 ++-- muxers/yamux/CHANGELOG.md | 4 ++++ muxers/yamux/Cargo.toml | 2 +- swarm-test/CHANGELOG.md | 4 ++++ swarm-test/Cargo.toml | 2 +- transports/noise/CHANGELOG.md | 4 ++++ transports/noise/Cargo.toml | 2 +- transports/plaintext/CHANGELOG.md | 4 ++++ transports/plaintext/Cargo.toml | 4 ++-- transports/pnet/CHANGELOG.md | 4 ++++ transports/pnet/Cargo.toml | 4 ++-- transports/tls/CHANGELOG.md | 4 ++++ transports/tls/Cargo.toml | 2 +- 24 files changed, 80 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af3ceb9eb0f..e0f448810fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,7 +2675,7 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" -version = "0.3.0" +version = "0.4.0" dependencies = [ "async-std", "libp2p-core", @@ -2718,7 +2718,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.3.1" +version = "0.4.0" dependencies = [ "async-std", "libp2p-core", @@ -2989,7 +2989,7 @@ dependencies = [ [[package]] name = "libp2p-memory-connection-limits" -version = "0.2.0" +version = "0.3.0" dependencies = [ "async-std", "libp2p-core", @@ -3026,7 +3026,7 @@ dependencies = [ [[package]] name = "libp2p-mplex" -version = "0.41.0" +version = "0.42.0" dependencies = [ "async-std", "asynchronous-codec", @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.44.0" +version = "0.45.0" dependencies = [ "asynchronous-codec", "bytes", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "libp2p-plaintext" -version = "0.41.0" +version = "0.42.0" dependencies = [ "asynchronous-codec", "bytes", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "libp2p-pnet" -version = "0.24.0" +version = "0.25.0" dependencies = [ "futures", "libp2p-core", @@ -3375,7 +3375,7 @@ dependencies = [ [[package]] name = "libp2p-swarm-test" -version = "0.3.0" +version = "0.4.0" dependencies = [ "async-trait", "futures", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "libp2p-tls" -version = "0.4.1" +version = "0.5.0" dependencies = [ "futures", "futures-rustls", @@ -3487,7 +3487,7 @@ dependencies = [ [[package]] name = "libp2p-webrtc-utils" -version = "0.2.1" +version = "0.3.0" dependencies = [ "asynchronous-codec", "bytes", @@ -3591,7 +3591,7 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.45.2" +version = "0.46.0" dependencies = [ "async-std", "either", diff --git a/Cargo.toml b/Cargo.toml index 3f00734ae3c..7d12e1f6318 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,9 +76,9 @@ asynchronous-codec = { version = "0.7.0" } futures-bounded = { version = "0.2.4" } futures-rustls = { version = "0.26.0", default-features = false } libp2p = { version = "0.54.0", path = "libp2p" } -libp2p-allow-block-list = { version = "0.3.0", path = "misc/allow-block-list" } +libp2p-allow-block-list = { version = "0.4.0", path = "misc/allow-block-list" } libp2p-autonat = { version = "0.13.0", path = "protocols/autonat" } -libp2p-connection-limits = { version = "0.3.1", path = "misc/connection-limits" } +libp2p-connection-limits = { version = "0.4.0", path = "misc/connection-limits" } libp2p-core = { version = "0.42.0", path = "core" } libp2p-dcutr = { version = "0.12.0", path = "protocols/dcutr" } libp2p-dns = { version = "0.42.0", path = "transports/dns" } @@ -88,14 +88,14 @@ libp2p-identify = { version = "0.45.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.9" } libp2p-kad = { version = "0.46.0", path = "protocols/kad" } libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } -libp2p-memory-connection-limits = { version = "0.2.0", path = "misc/memory-connection-limits" } +libp2p-memory-connection-limits = { version = "0.3.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.14.2", path = "misc/metrics" } -libp2p-mplex = { version = "0.41.0", path = "muxers/mplex" } -libp2p-noise = { version = "0.44.0", path = "transports/noise" } +libp2p-mplex = { version = "0.42.0", path = "muxers/mplex" } +libp2p-noise = { version = "0.45.0", path = "transports/noise" } libp2p-perf = { version = "0.4.0", path = "protocols/perf" } libp2p-ping = { version = "0.45.0", path = "protocols/ping" } -libp2p-plaintext = { version = "0.41.0", path = "transports/plaintext" } -libp2p-pnet = { version = "0.24.0", path = "transports/pnet" } +libp2p-plaintext = { version = "0.42.0", path = "transports/plaintext" } +libp2p-pnet = { version = "0.25.0", path = "transports/pnet" } libp2p-quic = { version = "0.11.0", path = "transports/quic" } libp2p-relay = { version = "0.18.0", path = "protocols/relay" } libp2p-rendezvous = { version = "0.15.0", path = "protocols/rendezvous" } @@ -104,18 +104,18 @@ libp2p-server = { version = "0.12.7", path = "misc/server" } libp2p-stream = { version = "0.2.0-alpha", path = "protocols/stream" } libp2p-swarm = { version = "0.45.0", path = "swarm" } libp2p-swarm-derive = { version = "=0.34.2", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. -libp2p-swarm-test = { version = "0.3.0", path = "swarm-test" } +libp2p-swarm-test = { version = "0.4.0", path = "swarm-test" } libp2p-tcp = { version = "0.42.0", path = "transports/tcp" } -libp2p-tls = { version = "0.4.1", path = "transports/tls" } +libp2p-tls = { version = "0.5.0", path = "transports/tls" } libp2p-uds = { version = "0.41.0", path = "transports/uds" } libp2p-upnp = { version = "0.3.0", path = "protocols/upnp" } libp2p-webrtc = { version = "0.8.0-alpha", path = "transports/webrtc" } -libp2p-webrtc-utils = { version = "0.2.1", path = "misc/webrtc-utils" } +libp2p-webrtc-utils = { version = "0.3.0", path = "misc/webrtc-utils" } libp2p-webrtc-websys = { version = "0.4.0-alpha", path = "transports/webrtc-websys" } libp2p-websocket = { version = "0.44.0", path = "transports/websocket" } libp2p-websocket-websys = { version = "0.4.0", path = "transports/websocket-websys" } libp2p-webtransport-websys = { version = "0.4.0", path = "transports/webtransport-websys" } -libp2p-yamux = { version = "0.45.2", path = "muxers/yamux" } +libp2p-yamux = { version = "0.46.0", path = "muxers/yamux" } multiaddr = "0.18.1" multihash = "0.19.1" multistream-select = { version = "0.13.0", path = "misc/multistream-select" } diff --git a/misc/allow-block-list/CHANGELOG.md b/misc/allow-block-list/CHANGELOG.md index 7778e924886..0017cbc8648 100644 --- a/misc/allow-block-list/CHANGELOG.md +++ b/misc/allow-block-list/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0 + + + ## 0.3.0 diff --git a/misc/allow-block-list/Cargo.toml b/misc/allow-block-list/Cargo.toml index c620e7f4a2b..4209d72ab4f 100644 --- a/misc/allow-block-list/Cargo.toml +++ b/misc/allow-block-list/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-allow-block-list" edition = "2021" rust-version = { workspace = true } description = "Allow/block list connection management for libp2p." -version = "0.3.0" +version = "0.4.0" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] diff --git a/misc/connection-limits/CHANGELOG.md b/misc/connection-limits/CHANGELOG.md index 4654281a83e..db88e99ffa7 100644 --- a/misc/connection-limits/CHANGELOG.md +++ b/misc/connection-limits/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0 + + + ## 0.3.1 - Add function to mutate `ConnectionLimits`. diff --git a/misc/connection-limits/Cargo.toml b/misc/connection-limits/Cargo.toml index 8ecb0005cb1..56fe97f984b 100644 --- a/misc/connection-limits/Cargo.toml +++ b/misc/connection-limits/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Connection limits for libp2p." -version = "0.3.1" +version = "0.4.0" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] diff --git a/misc/memory-connection-limits/CHANGELOG.md b/misc/memory-connection-limits/CHANGELOG.md index fc598872d50..9e580c5a1d2 100644 --- a/misc/memory-connection-limits/CHANGELOG.md +++ b/misc/memory-connection-limits/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0 + + + ## 0.2.0 diff --git a/misc/memory-connection-limits/Cargo.toml b/misc/memory-connection-limits/Cargo.toml index 9f7406599e7..f56ed33d5ad 100644 --- a/misc/memory-connection-limits/Cargo.toml +++ b/misc/memory-connection-limits/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-memory-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Memory usage based connection limits for libp2p." -version = "0.2.0" +version = "0.3.0" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] diff --git a/misc/webrtc-utils/CHANGELOG.md b/misc/webrtc-utils/CHANGELOG.md index b69bf74bfc8..3bb31610fa1 100644 --- a/misc/webrtc-utils/CHANGELOG.md +++ b/misc/webrtc-utils/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0 + + + ## 0.2.1 - Fix end of stream handling when buffer is empty or not present. diff --git a/misc/webrtc-utils/Cargo.toml b/misc/webrtc-utils/Cargo.toml index d870e43781e..88f576f12d9 100644 --- a/misc/webrtc-utils/Cargo.toml +++ b/misc/webrtc-utils/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT" name = "libp2p-webrtc-utils" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -version = "0.2.1" +version = "0.3.0" publish = true [dependencies] diff --git a/muxers/mplex/CHANGELOG.md b/muxers/mplex/CHANGELOG.md index 48ab616e131..f0c2c0353da 100644 --- a/muxers/mplex/CHANGELOG.md +++ b/muxers/mplex/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.42.0 + + + ## 0.41.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 4fdb4dabedd..7f887c8b3b8 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-mplex" edition = "2021" rust-version = { workspace = true } description = "Mplex multiplexing protocol for libp2p" -version = "0.41.0" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -38,7 +38,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } name = "split_send_size" harness = false -# 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/muxers/yamux/CHANGELOG.md b/muxers/yamux/CHANGELOG.md index 295ee251f65..855b3a33773 100644 --- a/muxers/yamux/CHANGELOG.md +++ b/muxers/yamux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.46.0 + + + ## 0.45.2 - Update `yamux` to version `v0.13.3`.` diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index 289f84fe1dd..0c52eca3fd4 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-yamux" edition = "2021" rust-version = { workspace = true } description = "Yamux multiplexing protocol for libp2p" -version = "0.45.2" +version = "0.46.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/swarm-test/CHANGELOG.md b/swarm-test/CHANGELOG.md index 95223e60272..98027fcbea2 100644 --- a/swarm-test/CHANGELOG.md +++ b/swarm-test/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0 + + + ## 0.3.0 diff --git a/swarm-test/Cargo.toml b/swarm-test/Cargo.toml index 1bd40bdace3..b285da34f87 100644 --- a/swarm-test/Cargo.toml +++ b/swarm-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-swarm-test" -version = "0.3.0" +version = "0.4.0" edition = "2021" rust-version = { workspace = true } license = "MIT" diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index 78effb673d2..f599ae3533f 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.45.0 + + + ## 0.44.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 76cf9475cc4..7f8e9004cd0 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-noise" edition = "2021" rust-version = { workspace = true } description = "Cryptographic handshake protocol using the noise framework." -version = "0.44.0" +version = "0.45.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/transports/plaintext/CHANGELOG.md b/transports/plaintext/CHANGELOG.md index 42b53d12a88..91860c76590 100644 --- a/transports/plaintext/CHANGELOG.md +++ b/transports/plaintext/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.42.0 + + + ## 0.41.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index 4f07c5fee6e..47a3191baa9 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-plaintext" edition = "2021" rust-version = { workspace = true } description = "Plaintext encryption dummy protocol for libp2p" -version = "0.41.0" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -27,7 +27,7 @@ rand = "0.8" futures_ringbuf = "0.4.0" tracing-subscriber = { workspace = true, features = ["env-filter"] } -# 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/pnet/CHANGELOG.md b/transports/pnet/CHANGELOG.md index 1fbc2d08807..86d519e8640 100644 --- a/transports/pnet/CHANGELOG.md +++ b/transports/pnet/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.25.0 + + + ## 0.24.0 diff --git a/transports/pnet/Cargo.toml b/transports/pnet/Cargo.toml index e7b3bfc30ac..db5c72fb7cc 100644 --- a/transports/pnet/Cargo.toml +++ b/transports/pnet/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-pnet" edition = "2021" rust-version = { workspace = true } description = "Private swarm support for libp2p" -version = "0.24.0" +version = "0.25.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -29,7 +29,7 @@ libp2p-yamux = { workspace = true } quickcheck = { workspace = true } tokio = { workspace = true, 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/tls/CHANGELOG.md b/transports/tls/CHANGELOG.md index 0343c690834..e27b8b4cf1f 100644 --- a/transports/tls/CHANGELOG.md +++ b/transports/tls/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0 + + + ## 0.4.1 - Fix a panic caused by `rustls` parsing the libp2p TLS extension. diff --git a/transports/tls/Cargo.toml b/transports/tls/Cargo.toml index 41b4c215dd9..c27e14bb537 100644 --- a/transports/tls/Cargo.toml +++ b/transports/tls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-tls" -version = "0.4.1" +version = "0.5.0" edition = "2021" rust-version = { workspace = true } description = "TLS configuration based on libp2p TLS specs." From 2357b04a250a4593dc8b9508e5e8e4cfdc46a8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Fri, 9 Aug 2024 23:51:15 +0100 Subject: [PATCH 21/35] fix(swarm): release libp2p-swarm-derive 0.35.0 Release libp2p-swarm-derive 0.35.0 and update `libp2p-swarm` to it to fix `NetworkBehaviour` derive macro generation. Pull-Request: #5545. --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- swarm-derive/CHANGELOG.md | 5 +++++ swarm-derive/Cargo.toml | 2 +- swarm/CHANGELOG.md | 5 +++++ swarm/Cargo.toml | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0f448810fa..70744c1bf40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3330,7 +3330,7 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.45.0" +version = "0.45.1" dependencies = [ "async-std", "criterion", @@ -3365,7 +3365,7 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" -version = "0.34.2" +version = "0.35.0" dependencies = [ "heck 0.5.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 7d12e1f6318..257c07c7c98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,8 +102,8 @@ libp2p-rendezvous = { version = "0.15.0", path = "protocols/rendezvous" } libp2p-request-response = { version = "0.27.0", path = "protocols/request-response" } libp2p-server = { version = "0.12.7", path = "misc/server" } libp2p-stream = { version = "0.2.0-alpha", path = "protocols/stream" } -libp2p-swarm = { version = "0.45.0", path = "swarm" } -libp2p-swarm-derive = { version = "=0.34.2", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. +libp2p-swarm = { version = "0.45.1", path = "swarm" } +libp2p-swarm-derive = { version = "=0.35.0", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. libp2p-swarm-test = { version = "0.4.0", path = "swarm-test" } libp2p-tcp = { version = "0.42.0", path = "transports/tcp" } libp2p-tls = { version = "0.5.0", path = "transports/tls" } diff --git a/swarm-derive/CHANGELOG.md b/swarm-derive/CHANGELOG.md index 271025aee9f..25932459ba8 100644 --- a/swarm-derive/CHANGELOG.md +++ b/swarm-derive/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.35.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.34.2 - Generate code for `libp2p-swarm`'s `FromSwarm::NewExternalAddrOfPeer` enum variant. diff --git a/swarm-derive/Cargo.toml b/swarm-derive/Cargo.toml index b31ea188962..91c643a459d 100644 --- a/swarm-derive/Cargo.toml +++ b/swarm-derive/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-swarm-derive" edition = "2021" rust-version = { workspace = true } description = "Procedural macros of libp2p-swarm" -version = "0.34.2" +version = "0.35.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index 9aeaa5a1ccc..b5fb72d68ac 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.45.1 + +- Update `libp2p-swarm-derive` to version `0.35.0`, see [PR XXXX] + +[PR XXXX]: https://github.com/libp2p/rust-libp2p/pull/XXXX ## 0.45.0 diff --git a/swarm/Cargo.toml b/swarm/Cargo.toml index 375f51490a8..3d0b1a84eee 100644 --- a/swarm/Cargo.toml +++ b/swarm/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-swarm" edition = "2021" rust-version = { workspace = true } description = "The libp2p swarm" -version = "0.45.0" +version = "0.45.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" From c34668e2ca6ab6fad4e097fceeeccc1f4881b3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Sat, 10 Aug 2024 14:25:52 +0100 Subject: [PATCH 22/35] chore(swarm): add PR number to 0.45.1 release Pull-Request: #5548. --- swarm/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index b5fb72d68ac..e7931a60de2 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,8 +1,8 @@ ## 0.45.1 -- Update `libp2p-swarm-derive` to version `0.35.0`, see [PR XXXX] +- Update `libp2p-swarm-derive` to version `0.35.0`, see [PR 5545] -[PR XXXX]: https://github.com/libp2p/rust-libp2p/pull/XXXX +[PR 5545]: https://github.com/libp2p/rust-libp2p/pull/5545 ## 0.45.0 From 0ca13880bfece59ee01531d89297e62c3487980b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Sat, 10 Aug 2024 15:32:44 +0100 Subject: [PATCH 23/35] fix(quic): release libp2p-quic 0.11.1 closes #5546 Pull-Request: #5547. --- Cargo.lock | 2 +- Cargo.toml | 2 +- transports/quic/CHANGELOG.md | 6 ++++++ transports/quic/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70744c1bf40..65a3f27bbdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3177,7 +3177,7 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.11.0" +version = "0.11.1" dependencies = [ "async-std", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 257c07c7c98..92562489ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ libp2p-perf = { version = "0.4.0", path = "protocols/perf" } libp2p-ping = { version = "0.45.0", path = "protocols/ping" } libp2p-plaintext = { version = "0.42.0", path = "transports/plaintext" } libp2p-pnet = { version = "0.25.0", path = "transports/pnet" } -libp2p-quic = { version = "0.11.0", path = "transports/quic" } +libp2p-quic = { version = "0.11.1", path = "transports/quic" } libp2p-relay = { version = "0.18.0", path = "protocols/relay" } libp2p-rendezvous = { version = "0.15.0", path = "protocols/rendezvous" } libp2p-request-response = { version = "0.27.0", path = "protocols/request-response" } diff --git a/transports/quic/CHANGELOG.md b/transports/quic/CHANGELOG.md index 2593af605df..6fc64c5df36 100644 --- a/transports/quic/CHANGELOG.md +++ b/transports/quic/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.11.1 + +- Update `libp2p-tls` to version `0.5.0`, see [PR 5547] + +[PR 5547]: https://github.com/libp2p/rust-libp2p/pull/5547 + ## 0.11.0 - Implement refactored `Transport`. diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 1f540c9542a..42cc8e54edb 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-quic" -version = "0.11.0" +version = "0.11.1" authors = ["Parity Technologies "] edition = "2021" rust-version = { workspace = true } From 8f5f2680f7af095d5810922df155aa49c7e74e12 Mon Sep 17 00:00:00 2001 From: timesince Date: Tue, 13 Aug 2024 08:20:52 +0800 Subject: [PATCH 24/35] chore: fix some comments fix some comments Pull-Request: #5550. --- README.md | 2 +- protocols/autonat/src/v2.rs | 4 ++-- transports/tcp/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eeab866768d..d818c6ba7b4 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,4 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). - [Substrate](https://github.com/paritytech/substrate) - Framework for blockchain innovation, used by [Polkadot](https://www.parity.io/technologies/polkadot/). - [Taple](https://github.com/opencanarias/taple-core) - Sustainable DLT for asset and process traceability by [OpenCanarias](https://www.opencanarias.com/en/). -- [Ceylon](https://github.com/ceylonai/ceylon) - A Multi-Agent System (MAS) Development Framwork. +- [Ceylon](https://github.com/ceylonai/ceylon) - A Multi-Agent System (MAS) Development Framework. diff --git a/protocols/autonat/src/v2.rs b/protocols/autonat/src/v2.rs index 994497cb1a0..cdc807ea303 100644 --- a/protocols/autonat/src/v2.rs +++ b/protocols/autonat/src/v2.rs @@ -4,8 +4,8 @@ //! //! The new version fixes the issues of the first version: //! - The server now always dials back over a newly allocated port. This greatly reduces the risk of -//! false positives that often occured in the first version, when the clinet-server connection -//! occured over a hole-punched port. +//! false positives that often occurred in the first version, when the clinet-server connection +//! occurred over a hole-punched port. //! - The server protects against DoS attacks by requiring the client to send more data to the //! server then the dial back puts on the client, thus making the protocol unatractive for an //! attacker. diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index 386caa78b0f..4c4fa7c6b84 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -173,7 +173,7 @@ impl Config { /// /// The new implementation works on a per-connaction basis, defined by the behaviour. This /// removes the necessaity to configure the transport for port reuse, instead the behaviour - /// requiring this behaviour can decide wether to use port reuse or not. + /// requiring this behaviour can decide whether to use port reuse or not. /// /// The API to configure port reuse is part of [`Transport`] and the option can be found in /// [`libp2p_core::transport::DialOpts`]. From 539bdd96834170a50b364ff7b1f7751157d52160 Mon Sep 17 00:00:00 2001 From: Dave Huseby Date: Mon, 12 Aug 2024 18:36:35 -0600 Subject: [PATCH 25/35] Create funding.json (#5553) ## Description ## Notes & open questions ## Change checklist - [ ] I have performed a self-review of my own code - [ ] I have made corresponding changes to the documentation - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] A changelog entry has been made in the appropriate crates --- funding.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 funding.json diff --git a/funding.json b/funding.json new file mode 100644 index 00000000000..bcf7fc2783d --- /dev/null +++ b/funding.json @@ -0,0 +1,5 @@ +{ + "opRetro": { + "projectId": "0x966804cb492e1a4bde5d781a676a44a23d69aa5dd2562fa7a4f95bb606021c8b" + } +} From 0861e39e31f4bc5b8696111878569d18e33e4d3c Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 13 Aug 2024 18:08:07 +0300 Subject: [PATCH 26/35] chore: Fix identity changelog link Noticed this when checking changelog of the last release Pull-Request: #5556. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3466708c9..4fccb9a489e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Main APIs - [`libp2p-core` CHANGELOG](core/CHANGELOG.md) +- [`libp2p-identity` CHANGELOG](identity/CHANGELOG.md) - [`libp2p-swarm` CHANGELOG](swarm/CHANGELOG.md) - [`libp2p-swarm-derive` CHANGELOG](swarm-derive/CHANGELOG.md) @@ -13,7 +14,6 @@ - [`libp2p-floodsub` CHANGELOG](protocols/floodsub/CHANGELOG.md) - [`libp2p-gossipsub` CHANGELOG](protocols/gossipsub/CHANGELOG.md) - [`libp2p-identify` CHANGELOG](protocols/identify/CHANGELOG.md) -- [`libp2p-identity` CHANGELOG](protocols/identity/CHANGELOG.md) - [`libp2p-kad` CHANGELOG](protocols/kad/CHANGELOG.md) - [`libp2p-mdns` CHANGELOG](protocols/mdns/CHANGELOG.md) - [`libp2p-ping` CHANGELOG](protocols/ping/CHANGELOG.md) From d9ee266bc03e7159e415e406e35ffafbb1660d42 Mon Sep 17 00:00:00 2001 From: Mivik <54128043+Mivik@users.noreply.github.com> Date: Wed, 14 Aug 2024 00:27:40 +0800 Subject: [PATCH 27/35] feat(kad): New provider record update strategy In `MemoryStore`, the number of provider records per key is limited by `max_providers_per_key`. Former implementations keep provider records sorted by their distance to the key, and only keep those with the smallest distance. This strategy is vulnerable to Sybil attack, in which an attacker can flood the network with false identities in order to eclipse a key. This commit change the strategy to simply keep old providers and ignore new ones. This new strategy however, can cause load imbalance, but can be mitigated by increasing `max_providers_per_key`. In addition, old implementations failed to keep `provided` and `providers` in sync, and this commit fixes this issue. Pull-Request: #5536. --- Cargo.lock | 4 +- Cargo.toml | 2 +- libp2p/CHANGELOG.md | 5 + libp2p/Cargo.toml | 2 +- protocols/kad/CHANGELOG.md | 5 + protocols/kad/Cargo.toml | 2 +- protocols/kad/src/record/store/memory.rs | 122 ++++++++++++----------- 7 files changed, 79 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65a3f27bbdf..c228c7e31ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2623,7 +2623,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libp2p" -version = "0.54.0" +version = "0.54.1" dependencies = [ "async-std", "async-trait", @@ -2928,7 +2928,7 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.46.0" +version = "0.46.1" dependencies = [ "arrayvec", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 92562489ed5..802779774cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" } libp2p-gossipsub = { version = "0.47.0", path = "protocols/gossipsub" } libp2p-identify = { version = "0.45.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.9" } -libp2p-kad = { version = "0.46.0", path = "protocols/kad" } +libp2p-kad = { version = "0.46.1", path = "protocols/kad" } libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } libp2p-memory-connection-limits = { version = "0.3.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.14.2", path = "misc/metrics" } diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index 7e1b95c6e75..f2041399042 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.54.1 + +- Update individual crates. + - Update to [`libp2p-kad` `v0.46.1`](protocols/kad/CHANGELOG.md#0461). + ## 0.54.0 - Update individual crates. diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index 68a76e52b58..b1017f5958c 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p" edition = "2021" rust-version = { workspace = true } description = "Peer-to-peer networking library" -version = "0.54.0" +version = "0.54.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index cbb5a4decf2..a41d6b9a131 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.46.1 + +- Use new provider record update strategy to prevent Sybil attack. + See [PR 5536](https://github.com/libp2p/rust-libp2p/pull/5536). + ## 0.46.0 - Included multiaddresses of found peers alongside peer IDs in `GetClosestPeers` query results. diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 72b29d00ef7..a00959fced6 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-kad" edition = "2021" rust-version = { workspace = true } description = "Kademlia protocol for libp2p" -version = "0.46.0" +version = "0.46.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/kad/src/record/store/memory.rs b/protocols/kad/src/record/store/memory.rs index 3bd3c56e30c..3fb6d2be3e8 100644 --- a/protocols/kad/src/record/store/memory.rs +++ b/protocols/kad/src/record/store/memory.rs @@ -152,38 +152,31 @@ impl RecordStore for MemoryStore { } .or_insert_with(Default::default); - if let Some(i) = providers.iter().position(|p| p.provider == record.provider) { - // In-place update of an existing provider record. - providers.as_mut()[i] = record; - } else { - // It is a new provider record for that key. - let local_key = self.local_key; - let key = kbucket::Key::new(record.key.clone()); - let provider = kbucket::Key::from(record.provider); - if let Some(i) = providers.iter().position(|p| { - let pk = kbucket::Key::from(p.provider); - provider.distance(&key) < pk.distance(&key) - }) { - // Insert the new provider. - if local_key.preimage() == &record.provider { + for p in providers.iter_mut() { + if p.provider == record.provider { + // In-place update of an existing provider record. + if self.local_key.preimage() == &record.provider { + self.provided.remove(p); self.provided.insert(record.clone()); } - providers.insert(i, record); - // Remove the excess provider, if any. - if providers.len() > self.config.max_providers_per_key { - if let Some(p) = providers.pop() { - self.provided.remove(&p); - } - } - } else if providers.len() < self.config.max_providers_per_key { - // The distance of the new provider to the key is larger than - // the distance of any existing provider, but there is still room. - if local_key.preimage() == &record.provider { - self.provided.insert(record.clone()); - } - providers.push(record); + *p = record; + return Ok(()); } } + + // If the providers list is full, we ignore the new provider. + // This strategy can mitigate Sybil attacks, in which an attacker + // floods the network with fake provider records. + if providers.len() == self.config.max_providers_per_key { + return Ok(()); + } + + // Otherwise, insert the new provider record. + if self.local_key.preimage() == &record.provider { + self.provided.insert(record.clone()); + } + providers.push(record); + Ok(()) } @@ -202,7 +195,9 @@ impl RecordStore for MemoryStore { let providers = e.get_mut(); if let Some(i) = providers.iter().position(|p| &p.provider == provider) { let p = providers.remove(i); - self.provided.remove(&p); + if &p.provider == self.local_key.preimage() { + self.provided.remove(&p); + } } if providers.is_empty() { e.remove(); @@ -221,11 +216,6 @@ mod tests { fn random_multihash() -> Multihash<64> { Multihash::wrap(SHA_256_MH, &rand::thread_rng().gen::<[u8; 32]>()).unwrap() } - - fn distance(r: &ProviderRecord) -> kbucket::Distance { - kbucket::Key::new(r.key.clone()).distance(&kbucket::Key::from(r.provider)) - } - #[test] fn put_get_remove_record() { fn prop(r: Record) { @@ -250,30 +240,6 @@ mod tests { quickcheck(prop as fn(_)) } - #[test] - fn providers_ordered_by_distance_to_key() { - fn prop(providers: Vec>) -> bool { - let mut store = MemoryStore::new(PeerId::random()); - let key = Key::from(random_multihash()); - - let mut records = providers - .into_iter() - .map(|p| ProviderRecord::new(key.clone(), p.into_preimage(), Vec::new())) - .collect::>(); - - for r in &records { - assert!(store.add_provider(r.clone()).is_ok()); - } - - records.sort_by_key(distance); - records.truncate(store.config.max_providers_per_key); - - records == store.providers(&key).to_vec() - } - - quickcheck(prop as fn(_) -> _) - } - #[test] fn provided() { let id = PeerId::random(); @@ -302,6 +268,46 @@ mod tests { assert_eq!(vec![rec.clone()], store.providers(&rec.key).to_vec()); } + #[test] + fn update_provided() { + let prv = PeerId::random(); + let mut store = MemoryStore::new(prv); + let key = random_multihash(); + let mut rec = ProviderRecord::new(key, prv, Vec::new()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert_eq!( + vec![Cow::Borrowed(&rec)], + store.provided().collect::>() + ); + rec.expires = Some(Instant::now()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert_eq!( + vec![Cow::Borrowed(&rec)], + store.provided().collect::>() + ); + } + + #[test] + fn max_providers_per_key() { + let config = MemoryStoreConfig::default(); + let key = kbucket::Key::new(Key::from(random_multihash())); + + let mut store = MemoryStore::with_config(PeerId::random(), config.clone()); + let peers = (0..config.max_providers_per_key) + .map(|_| PeerId::random()) + .collect::>(); + for peer in peers { + let rec = ProviderRecord::new(key.preimage().clone(), peer, Vec::new()); + assert!(store.add_provider(rec).is_ok()); + } + + // The new provider cannot be added because the key is already saturated. + let peer = PeerId::random(); + let rec = ProviderRecord::new(key.preimage().clone(), peer, Vec::new()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert!(!store.providers(&rec.key).contains(&rec)); + } + #[test] fn max_provided_keys() { let mut store = MemoryStore::new(PeerId::random()); From 41f67c975cbdd686738a801dfb9e3533a6c96874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Mon, 19 Aug 2024 15:31:52 +0100 Subject: [PATCH 28/35] chore(metrics): release 0.15.0 Pull-Request: #5562. --- Cargo.lock | 2 +- Cargo.toml | 2 +- misc/metrics/CHANGELOG.md | 2 +- misc/metrics/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c228c7e31ac..8932f40a04c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3007,7 +3007,7 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.14.2" +version = "0.15.0" dependencies = [ "futures", "libp2p-core", diff --git a/Cargo.toml b/Cargo.toml index 802779774cb..ebd8e39c29d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ libp2p-identity = { version = "0.2.9" } libp2p-kad = { version = "0.46.1", path = "protocols/kad" } libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } libp2p-memory-connection-limits = { version = "0.3.0", path = "misc/memory-connection-limits" } -libp2p-metrics = { version = "0.14.2", path = "misc/metrics" } +libp2p-metrics = { version = "0.15.0", path = "misc/metrics" } libp2p-mplex = { version = "0.42.0", path = "muxers/mplex" } libp2p-noise = { version = "0.45.0", path = "transports/noise" } libp2p-perf = { version = "0.4.0", path = "protocols/perf" } diff --git a/misc/metrics/CHANGELOG.md b/misc/metrics/CHANGELOG.md index c36d7f95ebc..bd109c42811 100644 --- a/misc/metrics/CHANGELOG.md +++ b/misc/metrics/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.14.2 +## 0.15.0 - Use `web-time` instead of `instant`. See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). diff --git a/misc/metrics/Cargo.toml b/misc/metrics/Cargo.toml index b75ea5ed0de..0b7a3c93b2f 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-metrics" edition = "2021" rust-version = { workspace = true } description = "Metrics for libp2p" -version = "0.14.2" +version = "0.15.0" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" From d7beb55f672dce54017fa4b30f67ecb8d66b9810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Mon, 19 Aug 2024 17:17:02 +0100 Subject: [PATCH 29/35] chore(libp2p): release 0.54.1 Pull-Request: #5563. --- Cargo.toml | 2 +- libp2p/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ebd8e39c29d..31c3a8e4b9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ rust-version = "1.75.0" asynchronous-codec = { version = "0.7.0" } futures-bounded = { version = "0.2.4" } futures-rustls = { version = "0.26.0", default-features = false } -libp2p = { version = "0.54.0", path = "libp2p" } +libp2p = { version = "0.54.1", path = "libp2p" } libp2p-allow-block-list = { version = "0.4.0", path = "misc/allow-block-list" } libp2p-autonat = { version = "0.13.0", path = "protocols/autonat" } libp2p-connection-limits = { version = "0.4.0", path = "misc/connection-limits" } diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index f2041399042..72a624786d4 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.54.1 - Update individual crates. - - Update to [`libp2p-kad` `v0.46.1`](protocols/kad/CHANGELOG.md#0461). + - Update to [`libp2p-metrics` `0.15.0`](misc/metrics/CHANGELOG.md#0150). ## 0.54.0 From 4dfc45bfec008405988edca2432c27ed5b93535f Mon Sep 17 00:00:00 2001 From: P1R0 Date: Wed, 21 Aug 2024 02:18:46 -0600 Subject: [PATCH 30/35] refactor: ping tutorial using tokio Solves issue #5554 also referenced first in #4449 Pull-Request: #5559. --- libp2p/src/tutorials/ping.rs | 131 +++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/libp2p/src/tutorials/ping.rs b/libp2p/src/tutorials/ping.rs index 309d5b36baa..31bf5ba3a14 100644 --- a/libp2p/src/tutorials/ping.rs +++ b/libp2p/src/tutorials/ping.rs @@ -44,7 +44,7 @@ //! 3. Adding `libp2p` as well as `futures` as dependencies in the //! `Cargo.toml` file. Current crate versions may be found at //! [crates.io](https://crates.io/). -//! We will also include `async-std` with the +//! We will also include `tokio` with the //! "attributes" feature to allow for an `async main`. //! At the time of writing we have: //! @@ -55,9 +55,9 @@ //! edition = "2021" //! //! [dependencies] -//! libp2p = { version = "0.52", features = ["tcp", "tls", "dns", "async-std", "noise", "yamux", "websocket", "ping", "macros"] } -//! futures = "0.3.21" -//! async-std = { version = "1.12.0", features = ["attributes"] } +//! libp2p = { version = "0.54", features = ["noise", "ping", "tcp", "tokio", "yamux"] } +//! futures = "0.3.30" +//! tokio = { version = "1.37.0", features = ["full"] } //! tracing-subscriber = { version = "0.3", features = ["env-filter"] } //! ``` //! @@ -74,9 +74,11 @@ //! use std::error::Error; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity(); //! @@ -89,7 +91,7 @@ //! ## Transport //! //! Next up we need to construct a transport. Each transport in libp2p provides encrypted streams. -//! E.g. combining TCP to establish connections, TLS to encrypt these connections and Yamux to run +//! E.g. combining TCP to establish connections, NOISE to encrypt these connections and Yamux to run //! one or more streams on a connection. Another libp2p transport is QUIC, providing encrypted //! streams out-of-the-box. We will stick to TCP for now. Each of these implement the [`Transport`] //! trait. @@ -97,17 +99,20 @@ //! ```rust //! use std::error::Error; //! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, tcp, yamux}; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )?; //! //! Ok(()) @@ -137,20 +142,22 @@ //! With the above in mind, let's extend our example, creating a [`ping::Behaviour`](crate::ping::Behaviour) at the end: //! //! ```rust -//! use libp2p::ping; -//! use tracing_subscriber::EnvFilter; //! use std::error::Error; +//! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, ping, tcp, yamux}; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())?; //! @@ -166,20 +173,22 @@ //! to the [`Transport`] as well as events from the [`Transport`] to the [`NetworkBehaviour`]. //! //! ```rust -//! use libp2p::ping; //! use std::error::Error; //! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, ping, tcp, yamux}; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? //! .build(); @@ -199,24 +208,25 @@ //! Thus, without any other behaviour in place, we would not be able to observe the pings. //! //! ```rust -//! use libp2p::ping; -//! use std::error::Error; -//! use std::time::Duration; +//! use std::{error::Error, time::Duration}; //! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, ping, tcp, yamux}; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely. +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) //! .build(); //! //! Ok(()) @@ -250,24 +260,25 @@ //! remote peer. //! //! ```rust -//! use libp2p::{ping, Multiaddr}; -//! use std::error::Error; -//! use std::time::Duration; +//! use std::{error::Error, time::Duration}; //! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, ping, tcp, yamux, Multiaddr}; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely.. +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned @@ -293,26 +304,26 @@ //! outgoing connection in case we specify an address on the CLI. //! //! ```no_run -//! use futures::prelude::*; -//! use libp2p::swarm::SwarmEvent; -//! use libp2p::{ping, Multiaddr}; -//! use std::error::Error; -//! use std::time::Duration; +//! use std::{error::Error, time::Duration}; //! use tracing_subscriber::EnvFilter; +//! use libp2p::{noise, ping, tcp, yamux, Multiaddr, swarm::SwarmEvent}; +//! use futures::prelude::*; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) // Allows us to observe pings indefinitely. +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned From f0cbd4fb0cef8d1ae2298901eab95acb5f104ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Thu, 22 Aug 2024 18:49:08 +0100 Subject: [PATCH 31/35] chore(ci): update Rust stable version Pull-Request: #5568. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0465540d47..2c1dfc8aaef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,7 +225,7 @@ jobs: fail-fast: false matrix: rust-version: [ - 1.78.0, # current stable + 1.80.0, # current stable beta, ] steps: From de8cba961b16d2f418153c6ca550f39256d8c12b Mon Sep 17 00:00:00 2001 From: Panagiotis Ganelis <50522617+PanGan21@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:25:13 +0300 Subject: [PATCH 32/35] feat(kad): emit `ToSwarm::NewExternalAddrOfPeer` (#5549) ## Description Updates `libp2p-kad` to emit new event `ToSwarm::NewExternalAddrOfPeer` whenever it discovers a new address through the DHT. Related: #5103 ## Notes & open questions ## Change checklist - [X] I have performed a self-review of my own code - [ ] I have made corresponding changes to the documentation - [X] I have added tests that prove my fix is effective or that my feature works - [ ] A changelog entry has been made in the appropriate crates --------- Co-authored-by: Guillaume Michel --- Cargo.lock | 2 +- Cargo.toml | 2 +- protocols/kad/CHANGELOG.md | 5 +++++ protocols/kad/Cargo.toml | 2 +- protocols/kad/src/behaviour.rs | 8 +++++++- protocols/kad/tests/client_mode.rs | 18 ++++++++++++++++-- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8932f40a04c..783480bb8b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,7 +2928,7 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.46.1" +version = "0.46.2" dependencies = [ "arrayvec", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 31c3a8e4b9e..8216c7a1787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" } libp2p-gossipsub = { version = "0.47.0", path = "protocols/gossipsub" } libp2p-identify = { version = "0.45.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.9" } -libp2p-kad = { version = "0.46.1", path = "protocols/kad" } +libp2p-kad = { version = "0.46.2", path = "protocols/kad" } libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } libp2p-memory-connection-limits = { version = "0.3.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.15.0", path = "misc/metrics" } diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index a41d6b9a131..f4e25e0de05 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.46.2 + +- Emit `ToSwarm::NewExternalAddrOfPeer`. + See [PR 5549](https://github.com/libp2p/rust-libp2p/pull/5549) + ## 0.46.1 - Use new provider record update strategy to prevent Sybil attack. diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index a00959fced6..11a670933db 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-kad" edition = "2021" rust-version = { workspace = true } description = "Kademlia protocol for libp2p" -version = "0.46.1" +version = "0.46.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index fc3d8a1adaa..a541648707a 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -2562,13 +2562,19 @@ where // Drain applied pending entries from the routing table. if let Some(entry) = self.kbuckets.take_applied_pending() { let kbucket::Node { key, value } = entry.inserted; + let peer_id = key.into_preimage(); + self.queued_events + .push_back(ToSwarm::NewExternalAddrOfPeer { + peer_id, + address: value.first().clone(), + }); let event = Event::RoutingUpdated { bucket_range: self .kbuckets .bucket(&key) .map(|b| b.range()) .expect("Self to never be applied from pending."), - peer: key.into_preimage(), + peer: peer_id, is_new_peer: true, addresses: value, old_peer: entry.evicted.map(|n| n.key.into_preimage()), diff --git a/protocols/kad/tests/client_mode.rs b/protocols/kad/tests/client_mode.rs index 6aceeb27263..2c8d11beac7 100644 --- a/protocols/kad/tests/client_mode.rs +++ b/protocols/kad/tests/client_mode.rs @@ -23,14 +23,21 @@ async fn server_gets_added_to_routing_table_by_client() { let server_peer_id = *server.local_peer_id(); async_std::task::spawn(server.loop_on_next()); - let peer = client + let external_event_peer = client + .wait(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, .. } => Some(peer_id), + _ => None, + }) + .await; + let routing_updated_peer = client .wait(|e| match e { SwarmEvent::Behaviour(Kad(RoutingUpdated { peer, .. })) => Some(peer), _ => None, }) .await; - assert_eq!(peer, server_peer_id); + assert_eq!(external_event_peer, server_peer_id); + assert_eq!(routing_updated_peer, server_peer_id); } #[async_std::test] @@ -126,6 +133,12 @@ async fn set_client_to_server_mode() { let server_peer_id = *server.local_peer_id(); + let peer_id = client + .wait(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, .. } => Some(peer_id), + _ => None, + }) + .await; let client_event = client.wait(|e| match e { SwarmEvent::Behaviour(Kad(RoutingUpdated { peer, .. })) => Some(peer), _ => None, @@ -138,6 +151,7 @@ async fn set_client_to_server_mode() { let (peer, info) = futures::future::join(client_event, server_event).await; assert_eq!(peer, server_peer_id); + assert_eq!(peer_id, server_peer_id); assert!(info .protocols .iter() From aa9317fbdd88cc4c9a39ea608fa1d57e022297fc Mon Sep 17 00:00:00 2001 From: Probot <94048855+Prabhat1308@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:32:30 +0530 Subject: [PATCH 33/35] fix: Change `__Nonexhaustive` to `__Invalid` and update web-sys (#5569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Bumps up web-sys version to `0.3.70` fixes #5557 ## Change checklist - [X] I have performed a self-review of my own code - [ ] I have made corresponding changes to the documentation - [ ] I have added tests that prove my fix is effective or that my feature works - [X] A changelog entry has been made in the appropriate crates --------- Co-authored-by: Darius Clark Co-authored-by: João Oliveira Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- Cargo.lock | 54 +++++++++++-------- Cargo.toml | 2 +- transports/webrtc-websys/CHANGELOG.md | 5 ++ transports/webrtc-websys/Cargo.toml | 4 +- transports/webrtc-websys/src/connection.rs | 9 ++-- transports/webrtc-websys/src/sdp.rs | 8 +-- .../src/stream/poll_data_channel.rs | 3 +- transports/webtransport-websys/CHANGELOG.md | 2 + transports/webtransport-websys/Cargo.toml | 8 +-- wasm-tests/webtransport-tests/Cargo.toml | 8 +-- 10 files changed, 62 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 783480bb8b1..d4350c0496b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2571,9 +2571,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -3509,7 +3509,7 @@ dependencies = [ [[package]] name = "libp2p-webrtc-websys" -version = "0.4.0-alpha" +version = "0.4.0-alpha.2" dependencies = [ "bytes", "futures", @@ -3804,6 +3804,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -6677,19 +6687,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -6702,9 +6713,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -6714,9 +6725,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6724,9 +6735,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -6737,18 +6748,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-bindgen-test" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" +checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -6757,9 +6769,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" +checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", @@ -6779,9 +6791,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 8216c7a1787..bd6018b024f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ libp2p-uds = { version = "0.41.0", path = "transports/uds" } libp2p-upnp = { version = "0.3.0", path = "protocols/upnp" } libp2p-webrtc = { version = "0.8.0-alpha", path = "transports/webrtc" } libp2p-webrtc-utils = { version = "0.3.0", path = "misc/webrtc-utils" } -libp2p-webrtc-websys = { version = "0.4.0-alpha", path = "transports/webrtc-websys" } +libp2p-webrtc-websys = { version = "0.4.0-alpha.2", path = "transports/webrtc-websys" } libp2p-websocket = { version = "0.44.0", path = "transports/websocket" } libp2p-websocket-websys = { version = "0.4.0", path = "transports/websocket-websys" } libp2p-webtransport-websys = { version = "0.4.0", path = "transports/webtransport-websys" } diff --git a/transports/webrtc-websys/CHANGELOG.md b/transports/webrtc-websys/CHANGELOG.md index 475b13727e6..5b8f2efb3b0 100644 --- a/transports/webrtc-websys/CHANGELOG.md +++ b/transports/webrtc-websys/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0-alpha.2 + +- Bump version of web-sys and update `__Nonexhaustive` to `__Invalid`. + See [PR 5569](https://github.com/libp2p/rust-libp2p/pull/5569) + ## 0.4.0-alpha - Implement refactored `Transport`. diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index c874b33bfc7..453abe57f74 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "libp2p-webrtc-websys" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -version = "0.4.0-alpha" +version = "0.4.0-alpha.2" publish = true [dependencies] @@ -25,7 +25,7 @@ thiserror = "1" tracing = { workspace = true } wasm-bindgen = { version = "0.2.90" } wasm-bindgen-futures = { version = "0.4.42" } -web-sys = { version = "0.3.69", features = ["Document", "Location", "MessageEvent", "Navigator", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", "Window"] } +web-sys = { version = "0.3.70", features = ["Document", "Location", "MessageEvent", "Navigator", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", "Window"] } [lints] workspace = true diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index b858237da63..d0c6ccd2238 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -186,11 +186,11 @@ impl RtcPeerConnection { let certificate = JsFuture::from(certificate_promise).await?; - let mut config = RtcConfiguration::default(); + let config = RtcConfiguration::default(); // wrap certificate in a js Array first before adding it to the config object let certificate_arr = js_sys::Array::new(); certificate_arr.push(&certificate); - config.certificates(&certificate_arr); + config.set_certificates(&certificate_arr); let inner = web_sys::RtcPeerConnection::new_with_configuration(&config)?; @@ -214,8 +214,9 @@ impl RtcPeerConnection { let dc = match negotiated { true => { - let mut options = RtcDataChannelInit::new(); - options.negotiated(true).id(0); // id is only ever set to zero when negotiated is true + let options = RtcDataChannelInit::new(); + options.set_negotiated(true); + options.set_id(0); // id is only ever set to zero when negotiated is true self.inner .create_data_channel_with_data_channel_dict(LABEL, &options) diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 439182ea4db..9e63fd92462 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -8,8 +8,8 @@ pub(crate) fn answer( server_fingerprint: Fingerprint, client_ufrag: &str, ) -> RtcSessionDescriptionInit { - let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); - answer_obj.sdp(&libp2p_webrtc_utils::sdp::answer( + let answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); + answer_obj.set_sdp(&libp2p_webrtc_utils::sdp::answer( addr, server_fingerprint, client_ufrag, @@ -48,8 +48,8 @@ pub(crate) fn offer(offer: String, client_ufrag: &str) -> RtcSessionDescriptionI tracing::trace!(offer=%munged_sdp_offer, "Created SDP offer"); - let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); - offer_obj.sdp(&munged_sdp_offer); + let offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); + offer_obj.set_sdp(&munged_sdp_offer); offer_obj } diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index dfd861de9df..3ec744342eb 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -143,7 +143,8 @@ impl PollDataChannel { RtcDataChannelState::Closing | RtcDataChannelState::Closed => { return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) } - RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => {} + RtcDataChannelState::Open | RtcDataChannelState::__Invalid => {} + _ => {} } if self.overloaded.load(Ordering::SeqCst) { diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md index 2aab226ab12..411117918bd 100644 --- a/transports/webtransport-websys/CHANGELOG.md +++ b/transports/webtransport-websys/CHANGELOG.md @@ -2,6 +2,8 @@ - Implement refactored `Transport`. See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Bump version of web-sys and wasm-bindgen. + See [PR 5569](https://github.com/libp2p/rust-libp2p/pull/5569) ## 0.3.0 diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index 370158190b1..9541c49b737 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] futures = { workspace = true } -js-sys = "0.3.69" +js-sys = "0.3.70" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-noise = { workspace = true } @@ -24,9 +24,9 @@ multihash = { workspace = true } send_wrapper = { version = "0.6.0", features = ["futures"] } thiserror = "1.0.61" tracing = { workspace = true } -wasm-bindgen = "0.2.90" -wasm-bindgen-futures = "0.4.42" -web-sys = { version = "0.3.69", features = [ +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" +web-sys = { version = "0.3.70", features = [ "ReadableStreamDefaultReader", "WebTransport", "WebTransportBidirectionalStream", diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml index cf51a510a3f..d7db378ab1a 100644 --- a/wasm-tests/webtransport-tests/Cargo.toml +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -17,10 +17,10 @@ libp2p-noise = { workspace = true } libp2p-webtransport-websys = { workspace = true } multiaddr = { workspace = true } multihash = { workspace = true } -wasm-bindgen = "0.2.90" -wasm-bindgen-futures = "0.4.42" -wasm-bindgen-test = "0.3.42" -web-sys = { version = "0.3.69", features = ["Response", "Window"] } +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" +wasm-bindgen-test = "0.3.43" +web-sys = { version = "0.3.70", features = ["Response", "Window"] } [lints] workspace = true From 56b6c62f6c71e8fa217e9e772d98824ba5d79c5c Mon Sep 17 00:00:00 2001 From: maqi Date: Wed, 28 Aug 2024 00:03:31 +0800 Subject: [PATCH 34/35] feat(kad): expose a kad query facility allowing dynamic num_results (#5555) ## Description This PR is to expose a kad query facility that allowing specify num_results dynamically. It is related to the [Sybil Defence issue](https://github.com/libp2p/rust-libp2p/issues/4769), that during the attempt of implementation on higher level code, it is find will be useful if libp2p-kad can expose such facility. The PR try not to cause any interference to the existing work flow, only introduce an `extra exposal`. ## Change checklist - [x] I have performed a self-review of my own code - [x] I have made corresponding changes to the documentation - [ ] I have added tests that prove my fix is effective or that my feature works - [x] A changelog entry has been made in the appropriate crates --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- Cargo.lock | 2 +- Cargo.toml | 2 +- protocols/kad/CHANGELOG.md | 5 +++ protocols/kad/Cargo.toml | 2 +- protocols/kad/src/behaviour.rs | 32 ++++++++++++++- protocols/kad/src/behaviour/test.rs | 64 ++++++++++++++++++++++++++++- protocols/kad/src/query.rs | 10 ++++- 7 files changed, 110 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4350c0496b..41ed8883c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,7 +2928,7 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.46.2" +version = "0.47.0" dependencies = [ "arrayvec", "async-std", diff --git a/Cargo.toml b/Cargo.toml index bd6018b024f..c23bb8650f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" } libp2p-gossipsub = { version = "0.47.0", path = "protocols/gossipsub" } libp2p-identify = { version = "0.45.0", path = "protocols/identify" } libp2p-identity = { version = "0.2.9" } -libp2p-kad = { version = "0.46.2", path = "protocols/kad" } +libp2p-kad = { version = "0.47.0", path = "protocols/kad" } libp2p-mdns = { version = "0.46.0", path = "protocols/mdns" } libp2p-memory-connection-limits = { version = "0.3.0", path = "misc/memory-connection-limits" } libp2p-metrics = { version = "0.15.0", path = "misc/metrics" } diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index f4e25e0de05..12ccca2d7f1 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.47.0 + +- Expose a kad query facility allowing specify num_results dynamicly. + See [PR 5555](https://github.com/libp2p/rust-libp2p/pull/5555). + ## 0.46.2 - Emit `ToSwarm::NewExternalAddrOfPeer`. diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 11a670933db..5b95b8ac17d 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-kad" edition = "2021" rust-version = { workspace = true } description = "Kademlia protocol for libp2p" -version = "0.46.2" +version = "0.47.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index a541648707a..50715c53c74 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -732,6 +732,31 @@ where /// The result of the query is delivered in a /// [`Event::OutboundQueryProgressed{QueryResult::GetClosestPeers}`]. pub fn get_closest_peers(&mut self, key: K) -> QueryId + where + K: Into> + Into> + Clone, + { + self.get_closest_peers_inner(key, None) + } + + /// Initiates an iterative query for the closest peers to the given key. + /// The expected responding peers is specified by `num_results` + /// Note that the result is capped after exceeds K_VALUE + /// + /// The result of the query is delivered in a + /// [`Event::OutboundQueryProgressed{QueryResult::GetClosestPeers}`]. + pub fn get_n_closest_peers(&mut self, key: K, num_results: NonZeroUsize) -> QueryId + where + K: Into> + Into> + Clone, + { + // The inner code never expect higher than K_VALUE results to be returned. + // And removing such cap will be tricky, + // since it would involve forging a new key and additional requests. + // Hence bound to K_VALUE here to set clear expectation and prevent unexpected behaviour. + let capped_num_results = std::cmp::min(num_results, K_VALUE); + self.get_closest_peers_inner(key, Some(capped_num_results)) + } + + fn get_closest_peers_inner(&mut self, key: K, num_results: Option) -> QueryId where K: Into> + Into> + Clone, { @@ -740,6 +765,7 @@ where let info = QueryInfo::GetClosestPeers { key, step: ProgressStep::first(), + num_results, }; let peer_keys: Vec> = self.kbuckets.closest_keys(&target).collect(); self.queries.add_iter_closest(target, peer_keys, info) @@ -1485,7 +1511,7 @@ where }) } - QueryInfo::GetClosestPeers { key, mut step } => { + QueryInfo::GetClosestPeers { key, mut step, .. } => { step.last = true; Some(Event::OutboundQueryProgressed { @@ -1702,7 +1728,7 @@ where }, }), - QueryInfo::GetClosestPeers { key, mut step } => { + QueryInfo::GetClosestPeers { key, mut step, .. } => { step.last = true; Some(Event::OutboundQueryProgressed { id: query_id, @@ -3181,6 +3207,8 @@ pub enum QueryInfo { key: Vec, /// Current index of events. step: ProgressStep, + /// If required, `num_results` specifies expected responding peers + num_results: Option, }, /// A (repeated) query initiated by [`Behaviour::get_providers`]. diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index c4859f2f138..7409168ac2a 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -263,7 +263,7 @@ fn query_iter() { match swarms[0].behaviour_mut().query(&qid) { Some(q) => match q.info() { - QueryInfo::GetClosestPeers { key, step } => { + QueryInfo::GetClosestPeers { key, step, .. } => { assert_eq!(&key[..], search_target.to_bytes().as_slice()); assert_eq!(usize::from(step.count), 1); } @@ -425,6 +425,68 @@ fn unresponsive_not_returned_indirect() { })) } +// Test the result of get_closest_peers with different num_results +// Note that the result is capped after exceeds K_VALUE +#[test] +fn get_closest_with_different_num_results() { + let k_value = K_VALUE.get(); + for replication_factor in [5, k_value / 2, k_value] { + for num_results in k_value / 2..k_value * 2 { + get_closest_with_different_num_results_inner(num_results, replication_factor) + } + } +} + +fn get_closest_with_different_num_results_inner(num_results: usize, replication_factor: usize) { + let k_value = K_VALUE.get(); + let num_of_nodes = 3 * k_value; + let mut cfg = Config::new(PROTOCOL_NAME); + cfg.set_replication_factor(NonZeroUsize::new(replication_factor).unwrap()); + let swarms = build_connected_nodes_with_config(num_of_nodes, replication_factor - 1, cfg); + + let mut swarms = swarms + .into_iter() + .map(|(_addr, swarm)| swarm) + .collect::>(); + + // Ask first to search a random value. + let search_target = PeerId::random(); + let Some(num_results_nonzero) = std::num::NonZeroUsize::new(num_results) else { + panic!("Unexpected NonZeroUsize val of {num_results}"); + }; + swarms[0] + .behaviour_mut() + .get_n_closest_peers(search_target, num_results_nonzero); + + block_on(poll_fn(move |ctx| { + for swarm in &mut swarms { + loop { + match swarm.poll_next_unpin(ctx) { + Poll::Ready(Some(SwarmEvent::Behaviour(Event::OutboundQueryProgressed { + result: QueryResult::GetClosestPeers(Ok(ok)), + .. + }))) => { + assert_eq!(&ok.key[..], search_target.to_bytes().as_slice()); + if num_results > k_value { + assert_eq!(ok.peers.len(), k_value, "Failed with replication_factor: {replication_factor}, num_results: {num_results}"); + } else { + assert_eq!(ok.peers.len(), num_results, "Failed with replication_factor: {replication_factor}, num_results: {num_results}"); + } + + return Poll::Ready(()); + } + // Ignore any other event. + Poll::Ready(Some(_)) => (), + e @ Poll::Ready(_) => panic!("Unexpected return value: {e:?}"), + Poll::Pending => break, + } + } + } + + Poll::Pending + })) +} + #[test] fn get_record_not_found() { let mut swarms = build_nodes(3); diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index c598bac012e..1a895d9627c 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -138,8 +138,16 @@ impl QueryPool { T: Into + Clone, I: IntoIterator>, { + let num_results = match info { + QueryInfo::GetClosestPeers { + num_results: Some(val), + .. + } => val, + _ => self.config.replication_factor, + }; + let cfg = ClosestPeersIterConfig { - num_results: self.config.replication_factor, + num_results, parallelism: self.config.parallelism, ..ClosestPeersIterConfig::default() }; From 64c6eb299ce905b2f81fd1317ec616f56dd37986 Mon Sep 17 00:00:00 2001 From: Elias Rad <146735585+nnsW3@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:54:23 +0300 Subject: [PATCH 35/35] chore: fix spelling issues (#5522) Hello I found several spelling issues in your docs. Br, Elias. --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- docs/maintainer-handbook.md | 2 +- examples/ipfs-kad/README.md | 2 +- wasm-tests/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/maintainer-handbook.md b/docs/maintainer-handbook.md index 6d36f6fe77c..0b090901216 100644 --- a/docs/maintainer-handbook.md +++ b/docs/maintainer-handbook.md @@ -31,7 +31,7 @@ This will have mergify approve your PR, thus fulfilling all requirements to auto Our CI checks that each crate which is modified gets a changelog entry. Whilst this is a good default safety-wise, it creates a lot of false-positives for changes that are internal and don't need a changelog entry. -For PRs that in the categories `chore`, `deps`, `refactor` and `docs`, this check is disabled automatically. +For PRs in the categories `chore`, `deps`, `refactor` and `docs`, this check is disabled automatically. Any other PR needs to explicitly disable this check if desired by applying the `internal-change` label. ## Dependencies diff --git a/examples/ipfs-kad/README.md b/examples/ipfs-kad/README.md index a46246a3920..05556c89382 100644 --- a/examples/ipfs-kad/README.md +++ b/examples/ipfs-kad/README.md @@ -87,5 +87,5 @@ Failed to insert the PK record ## Conclusion In conclusion, this example provides a practical demonstration of using the Rust P2P Library to interact with the Kademlia protocol on the IPFS network. -By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs various basic actions like getting the closes peers or inserting records into the DHT. +By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs various basic actions like getting the closest peers or inserting records into the DHT. This knowledge can be valuable when developing peer-to-peer applications or understanding decentralized networks. diff --git a/wasm-tests/README.md b/wasm-tests/README.md index 1d0902b106c..2538e48a145 100644 --- a/wasm-tests/README.md +++ b/wasm-tests/README.md @@ -8,4 +8,4 @@ Before you run the tests you need to install the following: # Run tests -Just call `run-all.sh` or `run.sh` in the test directory you are interested. +Just call `run-all.sh` or `run.sh` in the test directory if you are interested.