aboutsummaryrefslogtreecommitdiffstats
path: root/src/transport/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/transport/mod.rs')
-rw-r--r--src/transport/mod.rs230
1 files changed, 207 insertions, 23 deletions
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index fb13ad9..96f7651 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -1,7 +1,5 @@
use anyhow::Result;
use async_trait::async_trait;
-use automerge::ChangeHash;
-use automerge::sync::Message;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::boxed::Box;
@@ -11,6 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
pub(crate) use crate::document::replica::Epoch;
+use crate::document::ChangeHash;
+
// Used by the transports that don't authenticate their peers themselves (`tor`).
#[cfg_attr(not(feature = "tor"), allow(dead_code))]
pub(crate) mod challenge;
@@ -18,6 +18,7 @@ pub(crate) mod firewall;
pub(crate) mod identity;
#[cfg(feature = "libp2p")]
pub(crate) mod libp2p;
+#[cfg(feature = "mycelium")]
pub(crate) mod mycelium;
pub(crate) mod paths;
#[cfg(test)]
@@ -28,6 +29,67 @@ pub(crate) mod tor;
pub(crate) mod traffic;
pub(crate) mod wire;
+#[cfg(not(any(feature = "mycelium", feature = "libp2p", feature = "tor")))]
+compile_error!(
+ "continuum needs a transport: build with at least one of the mycelium, libp2p and tor features, e.g. `cargo build --features mycelium`"
+);
+
+/// The options of the transports this build has, each checked by its transport when the
+/// configuration is loaded (`config::Settings::check`, after the transports enabled):
+/// Mycelium's always (its conflicts are errors wherever they come from, as before);
+/// libp2p's and Tor's if the node uses them. Options of transports not built aren't
+/// checked: they're ignored, and said so (`config::Loaded::unused_transport_options`).
+pub(crate) fn check(settings: &crate::config::Settings) -> Result<()> {
+ let uses = |name: &str| settings.transports.iter().any(|used| used == name);
+ let _ = uses;
+ #[cfg(feature = "mycelium")]
+ mycelium::check(settings)?;
+ #[cfg(feature = "libp2p")]
+ if uses("libp2p") {
+ libp2p::check(settings)?;
+ }
+ #[cfg(feature = "tor")]
+ if uses("tor") {
+ tor::TorTransport::check(settings)?;
+ }
+ Ok(())
+}
+
+/// Starts the transport `name` (one of `config::TRANSPORTS`) as `settings` configure
+/// it: its scheme and the transport, or None if `stop` came first (some take a while to
+/// start: a supervised Mycelium daemon, arti). `key`: the node key, which some prove
+/// themselves.
+pub(crate) async fn start(
+ name: &str,
+ settings: &crate::config::Settings,
+ key: &std::sync::Arc<crate::identity::NodeKey>,
+ stop: tokio::sync::watch::Receiver<bool>,
+) -> Result<Option<(&'static str, std::sync::Arc<dyn Transport>)>> {
+ use std::sync::Arc;
+ let _ = (key, &stop);
+ let started: Option<(&'static str, Arc<dyn Transport>)> = match name {
+ #[cfg(feature = "mycelium")]
+ "mycelium" => mycelium::start(settings, stop)
+ .await?
+ .map(|transport| (mycelium::SCHEME, Arc::new(transport) as Arc<dyn Transport>)),
+ #[cfg(feature = "libp2p")]
+ "libp2p" => {
+ let _ = stop;
+ let config = libp2p::Libp2pConfig::from_settings(settings, false)?;
+ Some((
+ libp2p::SCHEME,
+ Arc::new(libp2p::Libp2pTransport::start(config)?) as Arc<dyn Transport>,
+ ))
+ }
+ #[cfg(feature = "tor")]
+ "tor" => tor::TorTransport::from_settings(settings, key, stop)
+ .await?
+ .map(|transport| (tor::SCHEME, Arc::new(transport) as Arc<dyn Transport>)),
+ other => anyhow::bail!("transports: no {other} in this build"),
+ };
+ Ok(started)
+}
+
/// Random identifier of one run of a node (from start to exit).
pub(crate) type RunId = [u8; 16];
@@ -137,6 +199,9 @@ pub(crate) struct Status {
/// The sender would sync with the recipient: counts it as an active member on its
/// own epoch.
pub(crate) syncs_with_you: bool,
+ /// The sender is shutting down: its farewell (`keepalive::farewell`). The
+ /// recipient holds it dormant at once, rather than after `dormant_after` of silence.
+ pub(crate) leaving: bool,
}
/// Cheap to clone: large parts are shared (`bytes::Bytes`).
@@ -154,8 +219,17 @@ pub(crate) enum ParsedMessage {
Me(IdentityProof),
/// Asks for a PONG with this nonce, over the same path (`transport::paths`).
Ping(u64),
- /// Answers a PING.
- Pong(u64),
+ /// Answers a PING: its nonce, and when it was answered by the answerer's clock
+ /// (`at`), for comparing clocks (`transport::paths`).
+ Pong {
+ nonce: u64,
+ at: crate::clock::UnixMillis,
+ },
+ /// Asks a member how it is linked to its peers, for a map of the swarm
+ /// (`swarm::links`).
+ Links(LinksRequest),
+ /// Answers a LINKS.
+ LinksReply(LinksReply),
}
/// A node's proof that it holds the key of its node ID (`identity`), sending from its
@@ -281,6 +355,8 @@ pub(crate) enum MessageKind {
Me,
Ping,
Pong,
+ Links,
+ LinksReply,
}
impl ParsedMessage {
@@ -295,7 +371,9 @@ impl ParsedMessage {
ParsedMessage::Who(_) => MessageKind::Who,
ParsedMessage::Me(_) => MessageKind::Me,
ParsedMessage::Ping(_) => MessageKind::Ping,
- ParsedMessage::Pong(_) => MessageKind::Pong,
+ ParsedMessage::Pong { .. } => MessageKind::Pong,
+ ParsedMessage::Links(_) => MessageKind::Links,
+ ParsedMessage::LinksReply(_) => MessageKind::LinksReply,
}
}
@@ -310,14 +388,79 @@ impl ParsedMessage {
Some((status.epoch, status.epoch_seq))
}
ParsedMessage::Blob(blob) => Some((blob.epoch, blob.epoch_seq)),
+ ParsedMessage::Links(request) => Some((request.epoch, request.epoch_seq)),
+ ParsedMessage::LinksReply(reply) => Some((reply.epoch, reply.epoch_seq)),
ParsedMessage::Who(_)
| ParsedMessage::Me(_)
| ParsedMessage::Ping(_)
- | ParsedMessage::Pong(_) => None,
+ | ParsedMessage::Pong { .. } => None,
}
}
}
+/// LINKS: what a member is asked for a map of the swarm (`swarm::links`).
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub(crate) struct LinksRequest {
+ #[serde(with = "crate::transport::wire::codec::epoch")]
+ pub(crate) epoch: Epoch,
+ /// See `SyncEnvelope::epoch_seq`.
+ pub(crate) epoch_seq: u32,
+ /// Repeated in the reply: which request it answers.
+ pub(crate) nonce: u64,
+ /// For this long (seconds), a LINKS_REPLY again whenever the links change (a
+ /// subscription; the next LINKS renews it). 0: this reply only, and a subscription
+ /// ends.
+ pub(crate) subscribe_secs: u32,
+}
+
+/// LINKS_REPLY: how the sender is linked to its neighbours, as it measures them.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub(crate) struct LinksReply {
+ #[serde(with = "crate::transport::wire::codec::epoch")]
+ pub(crate) epoch: Epoch,
+ /// See `SyncEnvelope::epoch_seq`.
+ pub(crate) epoch_seq: u32,
+ /// The request's.
+ pub(crate) nonce: u64,
+ /// At most `swarm::links::MAX_LINKS`.
+ pub(crate) links: Vec<Link>,
+}
+
+/// One of a member's links, in a LINKS_REPLY.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub(crate) struct Link {
+ /// The peer's node ID.
+ pub(crate) peer: String,
+ /// The transport its messages go over now (the scheme of the path in use), if any.
+ pub(crate) transport: Option<String>,
+ /// Only through a relay (libp2p).
+ pub(crate) relayed: bool,
+ /// The path in use is down: none to the peer answers (`paths`), whatever its
+ /// connection was.
+ pub(crate) down: bool,
+ /// The round trip on that path, in microseconds, if measured.
+ pub(crate) rtt_us: Option<u32>,
+ /// Silent for long (`peers.md`, dormancy).
+ pub(crate) dormant: bool,
+ /// The peer's other transports (`swarm::links::MAX_OTHERS` at most): where else
+ /// it proved itself, those paths up.
+ pub(crate) others: Vec<OtherPath>,
+ /// How far the peer's clock is ahead of the member's (`paths::ClockOffset`), if
+ /// measured: with every member's, a map of the swarm shows whose clock is off.
+ pub(crate) clock: Option<paths::ClockOffset>,
+}
+
+/// A transport of a link besides the one in use, in a LINKS_REPLY.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub(crate) struct OtherPath {
+ /// Its scheme (`mycelium`, `libp2p`, `tor`).
+ pub(crate) transport: String,
+ /// The round trip of its fastest path, in microseconds, if any is measured.
+ pub(crate) rtt_us: Option<u32>,
+ /// Only through a relay (libp2p): no direct connection at any of its paths.
+ pub(crate) relayed: bool,
+}
+
#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct HelloRequest {
/// The sender's swarm, if it knows it (a new node doesn't yet). A receiver in
@@ -361,17 +504,34 @@ pub(crate) struct Destination {
pub(crate) proven: bool,
/// The transport's connection to it, if the transport has connections.
pub(crate) connection: Option<Connection>,
+ /// While it is connected only through a relay: how hole punching went.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) punching: Option<Punching>,
/// Every address it proved itself at, best first, as the paths layer measures them
/// (`transport::paths`).
pub(crate) paths: Vec<stats::PathInfo>,
}
+/// Hole punching to a peer connected only through a relay (libp2p), once a punch failed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct Punching {
+ /// Punches that failed in a row.
+ pub(crate) failed: usize,
+ /// When it is tried again, in seconds from now (0: due).
+ pub(crate) next_in_secs: u64,
+}
+
/// How a transport with connections (libp2p) is connected to a peer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
+// Only libp2p and Tor have connections; without both (the cargo features) nothing makes
+// one.
+#[cfg_attr(not(any(feature = "libp2p", feature = "tor")), allow(dead_code))]
pub(crate) enum Connection {
+ /// libp2p's to the peer itself; Tor's always (an onion circuit).
Direct,
- /// Only through a relay.
+ /// Only through a relay (libp2p).
Relayed,
}
@@ -489,7 +649,7 @@ pub(crate) enum SyncBody {
/// The changes in the history of `to` but not of `from`: what a peer at `from` lacks of
/// a document at `to`. Every node holding `to` produces the same bytes for it
-/// (`swarm::blob::delta_bytes`).
+/// (`document::changes::delta_bytes`).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct DeltaSpec {
/// Sorted.
@@ -523,23 +683,13 @@ impl DeltaSpec {
}
}
-/// An Automerge sync message in Automerge's own encoding. Decoded only where it is
-/// applied (`decode`), after the sender proved to be a member and off the consumer: a
-/// large message from anyone else costs nothing but its bytes.
+/// A sync message as the document's engine encodes it (`document::sync`): bytes to the
+/// transports. Decoded only where it is applied (`document::sync::decode`), after the sender
+/// proved to be a member and off the consumer: a large message from anyone else costs
+/// nothing but its bytes.
#[derive(Clone)]
pub(crate) struct SyncPayload(pub(crate) bytes::Bytes);
-impl SyncPayload {
- pub(crate) fn encode(message: Message) -> Self {
- Self(message.encode().into())
- }
-
- pub(crate) fn decode(&self) -> Result<Message> {
- Message::decode(&self.0)
- .map_err(|error| anyhow::anyhow!("Automerge decode failed: {error:?}"))
- }
-}
-
/// Sizes instead of contents: logged messages stay short.
impl std::fmt::Debug for SyncPayload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -585,7 +735,7 @@ impl<T: std::fmt::Display> std::fmt::Display for Addressed<T> {
/// consumer hands them on as soon as they are read (`hand_on_probe`), not after the
/// messages read before them are handled (which can take a while: sync).
pub(crate) fn is_probe(message: &ParsedMessage) -> bool {
- matches!(message, ParsedMessage::Ping(_) | ParsedMessage::Pong(_))
+ matches!(message, ParsedMessage::Ping(_) | ParsedMessage::Pong { .. })
}
/// Hands a PING or PONG (`is_probe`) to `handler` in a task of its own; `scheme` is the
@@ -618,6 +768,13 @@ pub(crate) trait Transport: Send + Sync {
/// a message sent is delivered or lost for good.
fn delivery_limit(&self) -> std::time::Duration;
+ /// The first wait for an answer to a sync message (`Timing::sync_reply_timeout`): by
+ /// default `SYNC_REPLY_TIMEOUT`, longer on a transport whose round trips take
+ /// seconds.
+ fn sync_reply_timeout(&self) -> std::time::Duration {
+ crate::state::SYNC_REPLY_TIMEOUT
+ }
+
/// Where this node can be reached, as addresses other nodes can use: published in
/// the member list (`swarm::members`). By default its identity, for a transport
/// whose identities are addresses (Mycelium: a public key).
@@ -668,6 +825,19 @@ pub(crate) trait Transport: Send + Sync {
Vec::new()
}
+ /// Has the transport tell `found` whenever it finds a seed by itself
+ /// (`discovered_seeds`). By default it never does.
+ fn on_seeds_found(&self, found: std::sync::Arc<tokio::sync::Notify>) {
+ let _ = found;
+ }
+
+ /// Has the transport tell `reachable` whenever others may reach this node where they
+ /// couldn't before (libp2p: a relay granted it a slot, or AutoNAT found it public).
+ /// By default it never does.
+ fn on_reachable(&self, reachable: std::sync::Arc<tokio::sync::Notify>) {
+ let _ = reachable;
+ }
+
/// Whether this node has a way to reach `peer` at all: an address of it on one of
/// its transports. Members it can't reach aren't its neighbours (`swarm::topology`).
/// By default yes, as for `local_addresses`.
@@ -712,6 +882,13 @@ pub(crate) trait Transport: Send + Sync {
None
}
+ /// How hole punching to `target` went, if a punch failed and it is still connected
+ /// only through a relay (libp2p). By default never.
+ fn punching(&self, target: &str) -> Option<Punching> {
+ let _ = target;
+ None
+ }
+
/// The smoothed round-trip time of the path messages to `peer` go over, if
/// measured (`transport::paths`). By default none.
fn rtt(&self, peer: &str) -> Option<std::time::Duration> {
@@ -719,6 +896,13 @@ pub(crate) trait Transport: Send + Sync {
None
}
+ /// How far `peer`'s clock is ahead of ours, as its PONGs say (`paths::ClockOffset`):
+ /// the narrowest bound of its paths. By default unknown.
+ fn clock_offset(&self, peer: &str) -> Option<paths::ClockOffset> {
+ let _ = peer;
+ None
+ }
+
/// Whether the transport answers PINGs with PONGs itself (the paths layer,
/// `transport::paths`); if not (a bare transport, in tests), the node does. By
/// default not.