Skip to main content

urma_node/
peer_transport.rs

1//! The URMA overlay swarm: TCP, Noise, bounded yamux and, when the system
2//! resolver configuration can be read, DNS. Without it the overlay still runs
3//! for IP-literal peers; named addresses (`/dns*`, built-in `/dnsaddr` seeds)
4//! are then skipped and the reason is reported, never a public resolver.
5use crate::{
6    Error, peer::REQUEST_TIMEOUT, peer_codec::Codec, peer_limits::Limits, peer_protocol::PROTOCOL,
7};
8use libp2p::{
9    Multiaddr, StreamProtocol, Swarm, SwarmBuilder, identify, identity::Keypair,
10    multiaddr::Protocol, noise, request_response, swarm::NetworkBehaviour, tcp, yamux,
11};
12use std::{
13    num::{NonZeroU8, NonZeroUsize},
14    time::Duration,
15};
16
17#[derive(NetworkBehaviour)]
18pub(crate) struct Behaviour {
19    // Admission must precede request-response connection side effects.
20    pub(crate) limits: Limits,
21    pub(crate) records: request_response::Behaviour<Codec>,
22    pub(crate) identify: identify::Behaviour,
23}
24
25impl Behaviour {
26    const IDENTIFY_PROTOCOL: &str = "/urma/node/4";
27}
28
29/// How the overlay resolves names. `Refuse` stands in for an unreadable
30/// system configuration (tests inject it); it is never a fallback resolver.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub enum DnsMode {
33    System,
34    Refuse(String),
35}
36
37/// Whether this swarm can resolve names, and why not.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum Dns {
40    Available,
41    Unavailable(String),
42}
43
44impl Dns {
45    /// Names need the resolver; IP literals never do.
46    pub fn admits(&self, address: &Multiaddr) -> Result<(), Error> {
47        let named = address.iter().any(|part| {
48            matches!(
49                part,
50                Protocol::Dns(_) | Protocol::Dns4(_) | Protocol::Dns6(_) | Protocol::Dnsaddr(_)
51            )
52        });
53        match (self, named) {
54            (Self::Unavailable(cause), true) => Err(Error::Transport(format!(
55                "system DNS resolver unavailable: {cause}"
56            ))),
57            (Self::Unavailable(_) | Self::Available, false) | (Self::Available, true) => Ok(()),
58        }
59    }
60}
61
62fn behaviour(identity: &Keypair) -> Behaviour {
63    let records = request_response::Behaviour::with_codec(
64        Codec,
65        [(
66            StreamProtocol::new(PROTOCOL),
67            request_response::ProtocolSupport::Full,
68        )],
69        request_response::Config::default()
70            .with_request_timeout(REQUEST_TIMEOUT)
71            .with_max_concurrent_streams(2),
72    );
73    let identify = identify::Behaviour::new(
74        identify::Config::new(Behaviour::IDENTIFY_PROTOCOL.into(), identity.public())
75            .with_agent_version(format!("urma-node/{}", env!("CARGO_PKG_VERSION"))),
76    );
77    Behaviour {
78        limits: Limits::default(),
79        records,
80        identify,
81    }
82}
83
84fn multiplexer() -> yamux::Config {
85    let mut config = yamux::Config::default();
86    // These setters select libp2p-yamux's bounded 0.12 backend.
87    config.set_max_num_streams(4);
88    config.set_max_buffer_size(256 * 1024);
89    config.set_receive_window_size(256 * 1024);
90    config
91}
92
93fn configure(config: libp2p::swarm::Config) -> libp2p::swarm::Config {
94    config
95        .with_notify_handler_buffer_size(NonZeroUsize::new(2).unwrap())
96        .with_per_connection_event_buffer_size(2)
97        .with_max_negotiating_inbound_streams(2)
98        .with_dial_concurrency_factor(NonZeroU8::MIN)
99        .with_idle_connection_timeout(Duration::from_secs(20))
100}
101
102fn transport_error(error: impl std::fmt::Display) -> Error {
103    Error::Invalid(format!("peer transport: {error}"))
104}
105
106fn behaviour_error(error: impl std::fmt::Display) -> Error {
107    Error::Invalid(format!("peer behaviour: {error}"))
108}
109
110/// Tries the system resolver first; if its configuration cannot be read the
111/// same swarm is built without DNS and the cause is returned.
112pub(crate) fn swarm(identity: Keypair, mode: &DnsMode) -> Result<(Swarm<Behaviour>, Dns), Error> {
113    let cause = match mode {
114        DnsMode::System => {
115            let tcp = SwarmBuilder::with_existing_identity(identity.clone())
116                .with_tokio()
117                .with_tcp(
118                    tcp::Config::default().nodelay(true),
119                    noise::Config::new,
120                    multiplexer,
121                )
122                .map_err(transport_error)?;
123            match tcp.with_dns() {
124                Ok(resolving) => {
125                    let swarm = resolving
126                        .with_behaviour(behaviour)
127                        .map_err(behaviour_error)?
128                        .with_swarm_config(configure)
129                        .with_connection_timeout(Duration::from_secs(5))
130                        .build();
131                    return Ok((swarm, Dns::Available));
132                }
133                Err(error) => error.to_string(),
134            }
135        }
136        DnsMode::Refuse(cause) => cause.clone(),
137    };
138    tracing::warn!(%cause, "system DNS resolver unavailable; overlay runs for IP-literal peers only");
139    let swarm = SwarmBuilder::with_existing_identity(identity)
140        .with_tokio()
141        .with_tcp(
142            tcp::Config::default().nodelay(true),
143            noise::Config::new,
144            multiplexer,
145        )
146        .map_err(transport_error)?
147        .with_behaviour(behaviour)
148        .map_err(behaviour_error)?
149        .with_swarm_config(configure)
150        .with_connection_timeout(Duration::from_secs(5))
151        .build();
152    Ok((swarm, Dns::Unavailable(cause)))
153}