Skip to main content

ouisync/network/
mod.rs

1mod addr_filter;
2mod choke;
3mod client;
4mod connection;
5mod connection_monitor;
6mod constants;
7mod crypto;
8mod debug_payload;
9mod dht;
10mod event;
11mod gateway;
12mod ip;
13mod local_discovery;
14mod message;
15mod message_broker;
16mod message_dispatcher;
17mod peer_addr;
18mod peer_exchange;
19mod peer_info;
20mod peer_source;
21mod peer_state;
22mod protocol;
23mod request_tracker;
24mod runtime_id;
25mod seen_peers;
26mod server;
27mod stats;
28mod stun;
29mod stun_server_list;
30#[cfg(test)]
31mod tests;
32mod upnp;
33
34pub use self::{
35    addr_filter::AddrFilter,
36    connection::PeerInfoCollector,
37    dht::{DEFAULT_DHT_ROUTERS, DhtContactsStoreTrait, DhtLookupStream, DhtPin},
38    event::{NetworkEvent, NetworkEventReceiver, NetworkEventStream},
39    peer_addr::PeerAddr,
40    peer_info::PeerInfo,
41    peer_source::PeerSource,
42    peer_state::PeerState,
43    runtime_id::{PublicRuntimeId, SecretRuntimeId},
44    stats::Stats,
45};
46use dht::DhtEvent;
47pub use net::{
48    bus::{BusRecvStream as RecvStream, BusSendStream as SendStream, TopicId},
49    stun::NatBehavior,
50};
51
52use self::{
53    choke::Choker,
54    connection::{ConnectionPermit, ConnectionSet, ReserveResult},
55    connection_monitor::ConnectionMonitor,
56    constants::REQUEST_TIMEOUT,
57    dht::DhtDiscovery,
58    event::ProtocolVersions,
59    gateway::{Connectivity, Gateway, StackAddresses},
60    local_discovery::LocalDiscovery,
61    message_broker::MessageBroker,
62    peer_addr::PeerPort,
63    peer_exchange::{PexDiscovery, PexRepository},
64    peer_source::ConnectionDirection,
65    protocol::{MAGIC, VERSION, Version},
66    request_tracker::RequestTracker,
67    seen_peers::{SeenPeer, SeenPeers},
68    stats::{ByteCounters, StatsTracker},
69    stun::StunClients,
70};
71use crate::{
72    protocol::RepositoryId,
73    repository::{RepositoryHandle, Vault},
74};
75use backoff::{ExponentialBackoffBuilder, backoff::Backoff};
76use btdht::{self, INFO_HASH_LEN, InfoHash};
77use deadlock::BlockingMutex;
78use futures_util::future;
79use net::{
80    quic,
81    unified::{Connection, ConnectionError},
82};
83use scoped_task::ScopedAbortHandle;
84use slab::Slab;
85use state_monitor::StateMonitor;
86use std::{
87    collections::HashSet,
88    io, mem,
89    net::{SocketAddr, SocketAddrV4, SocketAddrV6},
90    sync::{
91        Arc, Weak,
92        atomic::{AtomicBool, Ordering},
93    },
94};
95use thiserror::Error;
96use tokio::{
97    io::{AsyncReadExt, AsyncWriteExt},
98    sync::{mpsc, watch},
99    task::{AbortHandle, JoinSet},
100    time::Duration,
101};
102use tracing::{Instrument, Span};
103
104#[derive(Default)]
105pub struct NetworkBuilder {
106    dht_contacts: Option<Arc<dyn DhtContactsStoreTrait>>,
107    monitor: Option<StateMonitor>,
108    runtime_id: Option<SecretRuntimeId>,
109    addr_filter: AddrFilter,
110}
111
112impl NetworkBuilder {
113    pub fn dht_contacts(self, contacts: Arc<dyn DhtContactsStoreTrait>) -> Self {
114        Self {
115            dht_contacts: Some(contacts),
116            ..self
117        }
118    }
119
120    pub fn monitor(self, monitor: StateMonitor) -> Self {
121        Self {
122            monitor: Some(monitor),
123            ..self
124        }
125    }
126
127    pub fn runtime_id(self, runtime_id: SecretRuntimeId) -> Self {
128        Self {
129            runtime_id: Some(runtime_id),
130            ..self
131        }
132    }
133
134    pub fn addr_filter(self, addr_filter: AddrFilter) -> Self {
135        Self {
136            addr_filter,
137            ..self
138        }
139    }
140
141    pub fn build(self) -> Network {
142        let (incoming_tx, incoming_rx) = mpsc::channel(1);
143        let gateway = Gateway::new(incoming_tx);
144        let monitor = self.monitor.unwrap_or_else(StateMonitor::make_root);
145
146        // Note that we're now only using quic for the transport discovered over the dht.
147        // This is because the dht doesn't let us specify whether the remote peer SocketAddr is
148        // TCP, UDP or anything else.
149        // TODO: There are ways to address this: e.g. we could try both, or we could include
150        // the protocol information in the info-hash generation. There are pros and cons to
151        // these approaches.
152        let dht_discovery =
153            DhtDiscovery::new(None, None, self.dht_contacts, monitor.make_child("DHT"));
154        // TODO: do we need unbounded channel here?
155        let (dht_discovery_tx, dht_discovery_rx) = mpsc::unbounded_channel();
156
157        let port_forwarder = upnp::PortForwarder::new(monitor.make_child("UPnP"));
158
159        let (pex_discovery_tx, pex_discovery_rx) = mpsc::channel(1);
160        let pex_discovery = PexDiscovery::new(pex_discovery_tx);
161
162        let user_provided_peers = SeenPeers::new();
163
164        let this_runtime_id = self.runtime_id.unwrap_or_else(SecretRuntimeId::random);
165        let this_runtime_id_public = this_runtime_id.public();
166
167        let connections_monitor = monitor.make_child("Connections");
168        let peers_monitor = monitor.make_child("Peers");
169
170        let tasks = Arc::new(BlockingMutex::new(JoinSet::new()));
171
172        let inner = Arc::new(Inner {
173            main_monitor: monitor,
174            connections_monitor,
175            peers_monitor,
176            span: Span::current(),
177            gateway,
178            this_runtime_id,
179            registry: BlockingMutex::new(Registry {
180                peers: Some(Slab::new()),
181                repos: Slab::new(),
182            }),
183            port_forwarder,
184            port_forwarder_state: BlockingMutex::new(ComponentState::disabled(
185                DisableReason::Explicit,
186            )),
187            local_discovery_state: BlockingMutex::new(ComponentState::disabled(
188                DisableReason::Explicit,
189            )),
190            dht_discovery,
191            dht_discovery_tx,
192            local_dht_enabled: AtomicBool::new(false),
193            pex_discovery,
194            stun_clients: StunClients::new(),
195            connections: ConnectionSet::new(),
196            user_provided_peers,
197            tasks: Arc::downgrade(&tasks),
198            protocol_versions: watch::Sender::new(ProtocolVersions::new()),
199            our_addresses: BlockingMutex::new(HashSet::default()),
200            stats_tracker: StatsTracker::default(),
201            addr_filter: self.addr_filter,
202        });
203
204        inner.spawn(inner.clone().handle_incoming_connections(incoming_rx));
205        inner.spawn(inner.clone().run_dht(dht_discovery_rx));
206        inner.spawn(inner.clone().run_peer_exchange(pex_discovery_rx));
207
208        tracing::debug!(this_runtime_id = ?this_runtime_id_public.as_public_key(), "Network created");
209
210        Network {
211            inner,
212            _tasks: tasks,
213        }
214    }
215}
216
217pub struct Network {
218    inner: Arc<Inner>,
219    // We keep tasks here instead of in Inner because we want them to be
220    // destroyed when Network is Dropped.
221    _tasks: Arc<BlockingMutex<JoinSet<()>>>,
222}
223
224impl Network {
225    /// Returns builder to create `Network` with custom options.
226    pub fn builder() -> NetworkBuilder {
227        NetworkBuilder::default()
228    }
229
230    /// Create network with default options. Equal to `Self::builder().build()`.
231    pub fn new() -> Self {
232        Self::builder().build()
233    }
234
235    /// Binds the network to the specified addresses.
236    /// Rebinds if already bound. Unbinds and disables the network if `addrs` is empty.
237    ///
238    /// NOTE: currently at most one address per protocol (QUIC/TCP) and family (IPv4/IPv6) is used
239    /// and the rest are ignored, but this might change in the future.
240    pub async fn bind(&self, addrs: &[PeerAddr]) {
241        self.inner.bind(addrs).await
242    }
243
244    pub fn listener_local_addrs(&self) -> Vec<PeerAddr> {
245        self.inner.gateway.listener_local_addrs()
246    }
247
248    pub fn set_port_forwarding_enabled(&self, enabled: bool) {
249        let mut state = self.inner.port_forwarder_state.lock().unwrap();
250
251        if enabled {
252            if state.is_enabled() {
253                return;
254            }
255
256            state.enable(PortMappings::new(
257                &self.inner.port_forwarder,
258                &self.inner.gateway,
259            ));
260        } else {
261            state.disable(DisableReason::Explicit);
262        }
263    }
264
265    pub fn is_port_forwarding_enabled(&self) -> bool {
266        self.inner.port_forwarder_state.lock().unwrap().is_enabled()
267    }
268
269    pub fn set_local_discovery_enabled(&self, enabled: bool) {
270        let mut state = self.inner.local_discovery_state.lock().unwrap();
271
272        if enabled {
273            if state.is_enabled() {
274                return;
275            }
276
277            if let Some(handle) = self.inner.spawn_local_discovery() {
278                state.enable(handle.into());
279            } else {
280                state.disable(DisableReason::Implicit);
281            }
282        } else {
283            state.disable(DisableReason::Explicit);
284        }
285    }
286
287    pub fn is_local_discovery_enabled(&self) -> bool {
288        self.inner
289            .local_discovery_state
290            .lock()
291            .unwrap()
292            .is_enabled()
293    }
294
295    /// Sets whether sending contacts to other peer over peer exchange is enabled.
296    ///
297    /// Note: PEX sending for a given repo is enabled only if it's enabled globally using this
298    /// function and also for the repo using [Registration::set_pex_enabled].
299    pub fn set_pex_send_enabled(&self, enabled: bool) {
300        self.inner.pex_discovery.set_send_enabled(enabled)
301    }
302
303    pub fn is_pex_send_enabled(&self) -> bool {
304        self.inner.pex_discovery.is_send_enabled()
305    }
306
307    /// Sets whether receiving contacts over peer exchange is enabled.
308    ///
309    /// Note: PEX receiving for a given repo is enabled only if it's enabled globally using this
310    /// function and also for the repo using [Registration::set_pex_enabled].
311    pub fn set_pex_recv_enabled(&self, enabled: bool) {
312        self.inner.pex_discovery.set_recv_enabled(enabled)
313    }
314
315    pub fn is_pex_recv_enabled(&self) -> bool {
316        self.inner.pex_discovery.is_recv_enabled()
317    }
318    /// Find out external address using the STUN protocol.
319    /// Currently QUIC only.
320    pub async fn external_addr_v4(&self) -> Option<SocketAddrV4> {
321        self.inner.stun_clients.external_addr_v4().await
322    }
323
324    /// Find out external address using the STUN protocol.
325    /// Currently QUIC only.
326    pub async fn external_addr_v6(&self) -> Option<SocketAddrV6> {
327        self.inner.stun_clients.external_addr_v6().await
328    }
329
330    /// Determine the behaviour of the NAT we are behind. Returns `None` on unknown.
331    /// Currently IPv4 only.
332    pub async fn nat_behavior(&self) -> Option<NatBehavior> {
333        self.inner.stun_clients.nat_behavior().await
334    }
335
336    /// Get the network traffic stats.
337    pub fn stats(&self) -> Stats {
338        self.inner.stats_tracker.read()
339    }
340
341    pub fn add_user_provided_peer(&self, peer: &PeerAddr) {
342        self.inner.clone().establish_user_provided_connection(peer);
343    }
344
345    pub fn remove_user_provided_peer(&self, peer: &PeerAddr) {
346        self.inner.user_provided_peers.remove(peer)
347    }
348
349    pub fn this_runtime_id(&self) -> PublicRuntimeId {
350        self.inner.this_runtime_id.public()
351    }
352
353    pub fn peer_info_collector(&self) -> PeerInfoCollector {
354        self.inner.connections.peer_info_collector()
355    }
356
357    pub fn peer_info(&self, addr: PeerAddr) -> Option<PeerInfo> {
358        self.inner.connections.get_peer_info(addr)
359    }
360
361    pub fn current_protocol_version(&self) -> u64 {
362        self.inner.protocol_versions.borrow().our.into()
363    }
364
365    pub fn highest_seen_protocol_version(&self) -> u64 {
366        self.inner.protocol_versions.borrow().highest_seen.into()
367    }
368
369    /// Subscribe to network events.
370    pub fn subscribe(&self) -> NetworkEventReceiver {
371        NetworkEventReceiver::new(
372            self.inner.protocol_versions.subscribe(),
373            self.inner.connections.subscribe(),
374        )
375    }
376
377    /// Register a local repository into the network. This links the repository with all matching
378    /// repositories of currently connected remote replicas as well as any replicas connected in
379    /// the future. The repository is automatically deregistered when the returned handle is
380    /// dropped.
381    ///
382    /// Note: A repository should have at most one registration - creating more than one has
383    /// undesired effects. This is currently not enforced and so it's a responsibility of the
384    /// caller.
385    pub fn register(&self, handle: RepositoryHandle) -> Registration {
386        *handle.vault.monitor.info_hash.get() =
387            Some(repository_info_hash(handle.vault.repository_id()));
388
389        let pex = self.inner.pex_discovery.new_repository();
390
391        let request_tracker = RequestTracker::new(handle.vault.monitor.traffic.clone());
392        request_tracker.set_timeout(REQUEST_TIMEOUT);
393
394        // TODO: Should this be global instead of per repo?
395        let choker = Choker::new();
396
397        let stats_tracker = StatsTracker::default();
398
399        let mut registry = self.inner.registry.lock().unwrap();
400
401        registry.create_link(
402            handle.vault.clone(),
403            &pex,
404            &request_tracker,
405            &choker,
406            stats_tracker.bytes.clone(),
407        );
408
409        let key = registry.repos.insert(RegistrationHolder {
410            vault: handle.vault,
411            dht: None,
412            pex,
413            request_tracker,
414            choker,
415            stats_tracker,
416        });
417
418        Registration {
419            inner: self.inner.clone(),
420            key,
421        }
422    }
423
424    /// Gracefully disconnect from peers. Failing to call this function on app termination will
425    /// cause the peers to not learn that we disconnected just now. They will still find out later
426    /// once the keep-alive mechanism kicks in, but in the mean time we will not be able to
427    /// reconnect (by starting the app again) because the remote peer will keep dropping new
428    /// connections from us.
429    pub async fn shutdown(&self) {
430        // TODO: Would be a nice-to-have to also wait for all the spawned tasks here (e.g. dicovery
431        // mechanisms).
432        let Some(peers) = self.inner.registry.lock().unwrap().peers.take() else {
433            tracing::warn!("Network already shut down");
434            return;
435        };
436
437        shutdown_peers(peers).await;
438    }
439
440    /// Change the sync protocol request timeout. Useful mostly for testing and benchmarking as the
441    /// default value should be sufficient for most use cases.
442    pub fn set_request_timeout(&self, timeout: Duration) {
443        for (_, holder) in &self.inner.registry.lock().unwrap().repos {
444            holder.request_tracker.set_timeout(timeout);
445        }
446    }
447
448    /// Opens a side channel for the underlying IPv4 UDP socket, or `None` if IPv4 QUIC stack isn't
449    /// configured.
450    ///
451    /// The side channel is used to send/receive raw UDP datagrams on the same socket that the sync
452    /// protocol uses.
453    pub fn open_udp_side_channel_v4(&self) -> Option<quic::SideChannel> {
454        self.inner
455            .gateway
456            .udp_side_channel_maker_v4()
457            .as_ref()
458            .map(|m| m.make())
459    }
460
461    /// Opens a side channel for the underlying IPv6 UDP socket, or `None` if IPv6 QUIC stack isn't
462    /// configured.
463    ///
464    /// The side channel is used to send/receive raw UDP datagrams on the same socket that the sync
465    /// protocol uses.
466    pub fn open_udp_side_channel_v6(&self) -> Option<quic::SideChannel> {
467        self.inner
468            .gateway
469            .udp_side_channel_maker_v4()
470            .as_ref()
471            .map(|m| m.make())
472    }
473
474    /// Opens raw byte stream to the given peer, bound to the given topic. This can be used to
475    /// send/recv arbitrary data to the peer, outside of the ouisync protocol.
476    ///
477    /// Returns `None` if no active connection to the peer exists.
478    pub fn open_stream(
479        &self,
480        addr: PeerAddr,
481        topic_id: TopicId,
482    ) -> Option<(SendStream, RecvStream)> {
483        let key = self.inner.connections.get_peer_key(addr)?;
484        Some(
485            self.inner
486                .registry
487                .lock()
488                .unwrap()
489                .peers
490                .as_ref()?
491                .get(key)?
492                .open_stream(topic_id),
493        )
494    }
495
496    /// Changes the DHT routers (boostrap nodes), rebootstraps the DHTs and restarts any ongoing
497    /// lookups.
498    pub fn set_dht_routers(&self, routers: HashSet<String>) {
499        self.inner.dht_discovery.set_routers(routers);
500    }
501
502    /// Returns the current DHT routers (bootstrap nodes).
503    pub fn dht_routers(&self) -> HashSet<String> {
504        self.inner.dht_discovery.routers()
505    }
506
507    /// Performs explicit DHT lookup or announce for the given infohash and returns a stream of the
508    /// discovered peer addresses. It will not automatically connect to them.
509    pub fn dht_lookup(&self, info_hash: InfoHash, announce: bool) -> DhtLookupStream {
510        DhtLookupStream::start(
511            &self.inner.dht_discovery,
512            info_hash,
513            announce,
514            self.is_local_dht_enabled(),
515        )
516    }
517
518    /// Set whether DHT on the local network (or localhost) is enabled. By default this is `false`
519    /// because DHT is a global discovery mechanism and finding a local peer on it is unexpected
520    /// (and could indicate malice). However, is some situations it's still useful to enable it
521    /// (typically for testing).
522    ///
523    /// Note: this option is currently experimental and unstable (semver extempt). It's possible it
524    /// will be removed in the future.
525    pub fn set_local_dht_enabled(&self, enabled: bool) {
526        let prev = self
527            .inner
528            .local_dht_enabled
529            .swap(enabled, Ordering::Release);
530
531        if prev != enabled {
532            self.inner.rebind_dht(self.inner.gateway.connectivity());
533        }
534    }
535
536    pub fn is_local_dht_enabled(&self) -> bool {
537        self.inner.local_dht_enabled.load(Ordering::Acquire)
538    }
539
540    /// Creates a "pin" which starts the DHT instances and keeps them running. This prevents the
541    /// DHTs to shut down even when there are no more ongoing lookups. This is useful if one wants
542    /// to avoid having to rebootstrap the DHT when doing another lookup in the future.
543    ///
544    /// Note that DHT is automatically started and kept running when there is at least one
545    /// repository with DHT enabled. Thus, pinning the DHT while having DHT-enabled repos is
546    /// unnecessary (but harmless).
547    pub async fn pin_dht(&self) -> DhtPin {
548        self.inner.dht_discovery.pin().await
549    }
550}
551
552impl Default for Network {
553    fn default() -> Self {
554        Self::new()
555    }
556}
557
558pub struct Registration {
559    inner: Arc<Inner>,
560    key: usize,
561}
562
563impl Registration {
564    pub fn set_dht_enabled(&self, enabled: bool) {
565        let mut registry = self.inner.registry.lock().unwrap();
566        let holder = &mut registry.repos[self.key];
567
568        if enabled {
569            holder.dht = Some(
570                self.inner
571                    .start_dht_lookup(repository_info_hash(holder.vault.repository_id())),
572            );
573        } else {
574            holder.dht = None;
575        }
576    }
577
578    /// This function provides the information to the user whether DHT is enabled for this
579    /// repository, not necessarily whether the DHT tasks are currently running. The subtle
580    /// difference is in that this function should return true even in case e.g. the whole network
581    /// is disabled.
582    pub fn is_dht_enabled(&self) -> bool {
583        self.inner.registry.lock().unwrap().repos[self.key]
584            .dht
585            .is_some()
586    }
587
588    /// Enables/disables peer exchange for this repo.
589    ///
590    /// Note: sending/receiving over PEX for this repo is enabled only if it's enabled using this
591    /// function and also globally using [Network::set_pex_send_enabled] and/or
592    /// [Network::set_pex_recv_enabled].
593    pub fn set_pex_enabled(&self, enabled: bool) {
594        let registry = self.inner.registry.lock().unwrap();
595        registry.repos[self.key].pex.set_enabled(enabled);
596    }
597
598    pub fn is_pex_enabled(&self) -> bool {
599        self.inner.registry.lock().unwrap().repos[self.key]
600            .pex
601            .is_enabled()
602    }
603
604    /// Fetch per-repository network statistics.
605    pub fn stats(&self) -> Stats {
606        self.inner.registry.lock().unwrap().repos[self.key]
607            .stats_tracker
608            .read()
609    }
610}
611
612impl Drop for Registration {
613    fn drop(&mut self) {
614        let mut registry = self
615            .inner
616            .registry
617            .lock()
618            .unwrap_or_else(|error| error.into_inner());
619
620        if let Some(holder) = registry.repos.try_remove(self.key) {
621            for (_, peer) in registry.peers.as_mut().into_iter().flatten() {
622                peer.destroy_link(holder.vault.repository_id());
623            }
624        }
625    }
626}
627
628struct RegistrationHolder {
629    vault: Vault,
630    dht: Option<dht::LookupRequest>,
631    pex: PexRepository,
632    request_tracker: RequestTracker,
633    choker: Choker,
634    stats_tracker: StatsTracker,
635}
636
637struct Inner {
638    main_monitor: StateMonitor,
639    connections_monitor: StateMonitor,
640    peers_monitor: StateMonitor,
641    span: Span,
642    gateway: Gateway,
643    this_runtime_id: SecretRuntimeId,
644    registry: BlockingMutex<Registry>,
645    port_forwarder: upnp::PortForwarder,
646    port_forwarder_state: BlockingMutex<ComponentState<PortMappings>>,
647    local_discovery_state: BlockingMutex<ComponentState<ScopedAbortHandle>>,
648    dht_discovery: DhtDiscovery,
649    dht_discovery_tx: mpsc::UnboundedSender<DhtEvent>,
650    local_dht_enabled: AtomicBool,
651    pex_discovery: PexDiscovery,
652    stun_clients: StunClients,
653    connections: ConnectionSet,
654    protocol_versions: watch::Sender<ProtocolVersions>,
655    user_provided_peers: SeenPeers,
656    // Note that unwrapping the upgraded weak pointer should be fine because if the underlying Arc
657    // was Dropped, we would not be asking for the upgrade in the first place.
658    tasks: Weak<BlockingMutex<JoinSet<()>>>,
659    // Used to prevent repeatedly connecting to self.
660    our_addresses: BlockingMutex<HashSet<PeerAddr>>,
661    stats_tracker: StatsTracker,
662    addr_filter: AddrFilter,
663}
664
665struct Registry {
666    // This is None once the network calls shutdown.
667    peers: Option<Slab<MessageBroker>>,
668    repos: Slab<RegistrationHolder>,
669}
670
671impl Registry {
672    fn create_link(
673        &mut self,
674        repo: Vault,
675        pex: &PexRepository,
676        request_tracker: &RequestTracker,
677        choker: &Choker,
678        byte_counters: Arc<ByteCounters>,
679    ) {
680        if let Some(peers) = &mut self.peers {
681            for (_, peer) in peers {
682                peer.create_link(
683                    repo.clone(),
684                    pex,
685                    request_tracker.clone(),
686                    choker.clone(),
687                    byte_counters.clone(),
688                )
689            }
690        }
691    }
692}
693
694impl Inner {
695    fn is_shutdown(&self) -> bool {
696        self.registry.lock().unwrap().peers.is_none()
697    }
698
699    async fn bind(self: &Arc<Self>, bind: &[PeerAddr]) {
700        let bind = StackAddresses::from(bind);
701
702        // TODO: Would be preferable to only rebind those stacks that actually need rebinding.
703        if !self.gateway.addresses().any_stack_needs_rebind(&bind) {
704            return;
705        }
706
707        // Gateway
708        self.span.in_scope(|| self.gateway.bind(&bind));
709
710        let conn = self.gateway.connectivity();
711
712        // STUN
713        match conn {
714            Connectivity::Full => self.stun_clients.rebind(
715                self.gateway.udp_side_channel_maker_v4().map(|m| m.make()),
716                self.gateway.udp_side_channel_maker_v6().map(|m| m.make()),
717            ),
718            Connectivity::LocalOnly | Connectivity::Disabled => (),
719        }
720
721        // DHT
722        self.rebind_dht(conn);
723
724        // Port forwarding
725        match conn {
726            Connectivity::Full => {
727                let mut state = self.port_forwarder_state.lock().unwrap();
728                if !state.is_disabled(DisableReason::Explicit) {
729                    state.enable(PortMappings::new(&self.port_forwarder, &self.gateway));
730                }
731            }
732            Connectivity::LocalOnly | Connectivity::Disabled => {
733                self.port_forwarder_state
734                    .lock()
735                    .unwrap()
736                    .disable_if_enabled(DisableReason::Implicit);
737            }
738        }
739
740        // Local discovery
741        //
742        // Note: no need to check the Connectivity because local discovery depends only on whether
743        // Gateway is bound.
744        {
745            let mut state = self.local_discovery_state.lock().unwrap();
746            if !state.is_disabled(DisableReason::Explicit) {
747                if let Some(handle) = self.spawn_local_discovery() {
748                    state.enable(handle.into());
749                } else {
750                    state.disable(DisableReason::Implicit);
751                }
752            }
753        }
754
755        // - If we are disabling connectivity, disconnect from all existing peers.
756        // - If we are going from `Full` -> `LocalOnly`, also disconnect from all with the
757        //   assumption that the local ones will be subsequently re-established. Ideally we would
758        //   disconnect only the non-local ones to avoid the reconnect overhead, but the
759        //   implementation is simpler this way and the trade-off doesn't seem to be too bad.
760        // - If we are going to `Full`, keep all existing connections.
761        if matches!(conn, Connectivity::LocalOnly | Connectivity::Disabled) {
762            self.disconnect_all().await;
763        }
764    }
765
766    // Disconnect from all currently connected peers, regardless of their source.
767    async fn disconnect_all(&self) {
768        let Some(peers) = self.registry.lock().unwrap().peers.replace(Slab::default()) else {
769            return;
770        };
771
772        shutdown_peers(peers).await;
773    }
774
775    fn spawn_local_discovery(self: &Arc<Self>) -> Option<AbortHandle> {
776        let ports: Vec<_> = self
777            .gateway
778            .listener_local_addrs()
779            .into_iter()
780            .filter_map(|addr| match addr {
781                PeerAddr::Tcp(SocketAddr::V4(addr)) => Some(PeerPort::Tcp(addr.port())),
782                PeerAddr::Quic(SocketAddr::V4(addr)) => Some(PeerPort::Quic(addr.port())),
783                _ => None,
784            })
785            .collect();
786
787        if !ports.is_empty() {
788            Some(
789                self.spawn(
790                    self.clone()
791                        .run_local_discovery(ports)
792                        .instrument(self.span.clone()),
793                ),
794            )
795        } else {
796            tracing::error!("Not enabling local discovery because there is no IPv4 listener");
797            None
798        }
799    }
800
801    async fn run_local_discovery(self: Arc<Self>, listener_ports: Vec<PeerPort>) {
802        let mut discovery = LocalDiscovery::new(
803            listener_ports,
804            self.main_monitor.make_child("LocalDiscovery"),
805        );
806
807        loop {
808            let peer = discovery.recv().await;
809
810            if self.is_shutdown() {
811                break;
812            }
813
814            self.spawn(
815                self.clone()
816                    .handle_peer_found(peer, PeerSource::LocalDiscovery),
817            );
818        }
819    }
820
821    fn start_dht_lookup(&self, info_hash: InfoHash) -> dht::LookupRequest {
822        self.dht_discovery
823            .start_lookup(info_hash, true, self.dht_discovery_tx.clone())
824    }
825
826    fn rebind_dht(&self, conn: Connectivity) {
827        match (conn, self.local_dht_enabled.load(Ordering::Acquire)) {
828            (Connectivity::Full, _) | (Connectivity::LocalOnly, true) => self.dht_discovery.rebind(
829                self.gateway.udp_side_channel_maker_v4(),
830                self.gateway.udp_side_channel_maker_v6(),
831            ),
832            (Connectivity::LocalOnly, false) | (Connectivity::Disabled, _) => {
833                self.dht_discovery.rebind(None, None)
834            }
835        }
836    }
837
838    async fn run_dht(self: Arc<Self>, mut discovery_rx: mpsc::UnboundedReceiver<DhtEvent>) {
839        while let Some(event) = discovery_rx.recv().await {
840            if self.is_shutdown() {
841                break;
842            }
843
844            let peer = match event {
845                DhtEvent::PeerFound(peer) => peer,
846                DhtEvent::RoundEnded => continue,
847            };
848
849            if !self.local_dht_enabled.load(Ordering::Acquire) && peer.initial_addr().is_local() {
850                continue;
851            }
852
853            self.spawn(self.clone().handle_peer_found(peer, PeerSource::Dht));
854        }
855    }
856
857    async fn run_peer_exchange(self: Arc<Self>, mut discovery_rx: mpsc::Receiver<SeenPeer>) {
858        while let Some(peer) = discovery_rx.recv().await {
859            if self.is_shutdown() {
860                break;
861            }
862
863            self.spawn(
864                self.clone()
865                    .handle_peer_found(peer, PeerSource::PeerExchange),
866            );
867        }
868    }
869
870    fn establish_user_provided_connection(self: Arc<Self>, peer: &PeerAddr) {
871        let peer = match self.user_provided_peers.insert(*peer) {
872            Some(peer) => peer,
873            // Already in `user_provided_peers`.
874            None => return,
875        };
876
877        self.spawn(
878            self.clone()
879                .handle_peer_found(peer, PeerSource::UserProvided),
880        );
881    }
882
883    async fn handle_incoming_connections(
884        self: Arc<Self>,
885        mut rx: mpsc::Receiver<(Connection, PeerAddr)>,
886    ) {
887        while let Some((connection, addr)) = rx.recv().await {
888            match self.connections.reserve(addr, PeerSource::Listener) {
889                ReserveResult::Permit(permit) => {
890                    if self.is_shutdown() {
891                        break;
892                    }
893
894                    let this = self.clone();
895
896                    let monitor = self.span.in_scope(|| {
897                        ConnectionMonitor::new(
898                            &self.connections_monitor,
899                            &permit.addr(),
900                            permit.source(),
901                        )
902                    });
903                    monitor.mark_as_connecting(permit.id());
904
905                    self.spawn(async move {
906                        this.handle_connection(connection, permit, &monitor).await;
907                    });
908                }
909                ReserveResult::Occupied(_, _their_source, permit_id) => {
910                    tracing::debug!(?addr, ?permit_id, "dropping accepted duplicate connection");
911                }
912            }
913        }
914    }
915
916    async fn handle_peer_found(self: Arc<Self>, peer: SeenPeer, source: PeerSource) {
917        let create_backoff = || {
918            ExponentialBackoffBuilder::new()
919                .with_initial_interval(Duration::from_millis(100))
920                .with_max_interval(Duration::from_secs(8))
921                .with_max_elapsed_time(None)
922                .build()
923        };
924
925        let mut backoff = create_backoff();
926
927        let mut next_sleep = None;
928
929        loop {
930            let monitor = self.span.in_scope(|| {
931                ConnectionMonitor::new(&self.connections_monitor, peer.initial_addr(), source)
932            });
933
934            // TODO: We should also check whether the user still wants to accept connections from
935            // the given `source` (the preference may have changed in the mean time).
936
937            if self.is_shutdown() {
938                return;
939            }
940
941            let addr = match peer.addr_if_seen() {
942                Some(addr) => *addr,
943                None => return,
944            };
945
946            if self.our_addresses.lock().unwrap().contains(&addr) {
947                // Don't connect to self.
948                return;
949            }
950
951            let permit = match self.connections.reserve(addr, source) {
952                ReserveResult::Permit(permit) => permit,
953                ReserveResult::Occupied(on_release, their_source, connection_id) => {
954                    if source == their_source {
955                        // This is a duplicate from the same source, ignore it.
956                        return;
957                    }
958
959                    // This is a duplicate from a different source, if the other source releases
960                    // it, then we may want to try to keep hold of it.
961                    monitor.mark_as_awaiting_permit();
962                    tracing::debug!(
963                        parent: monitor.span(),
964                        %connection_id,
965                        "Duplicate from different source - awaiting permit"
966                    );
967
968                    on_release.await;
969
970                    next_sleep = None;
971                    backoff = create_backoff();
972
973                    continue;
974                }
975            };
976
977            if let Some(sleep) = next_sleep {
978                tracing::debug!(parent: monitor.span(), "Next connection attempt in {:?}", sleep);
979                tokio::time::sleep(sleep).await;
980            }
981
982            next_sleep = backoff.next_backoff();
983
984            permit.mark_as_connecting();
985            monitor.mark_as_connecting(permit.id());
986            tracing::trace!(parent: monitor.span(), "Connecting");
987
988            let Some(addr) = peer.addr_if_seen() else {
989                break;
990            };
991
992            if !self.addr_filter.apply(addr.socket_addr()) {
993                tracing::debug!("Invalid peer address - discarding");
994                break;
995            }
996
997            let socket = match self
998                .gateway
999                .connect_with_retries(&peer)
1000                .instrument(monitor.span().clone())
1001                .await
1002            {
1003                Some(socket) => socket,
1004                None => break,
1005            };
1006
1007            if !self.handle_connection(socket, permit, &monitor).await {
1008                break;
1009            }
1010        }
1011    }
1012
1013    /// Return true iff the peer is suitable for reconnection.
1014    async fn handle_connection(
1015        &self,
1016        connection: Connection,
1017        permit: ConnectionPermit,
1018        monitor: &ConnectionMonitor,
1019    ) -> bool {
1020        tracing::trace!(parent: monitor.span(), "Handshaking");
1021
1022        permit.mark_as_handshaking();
1023        monitor.mark_as_handshaking();
1024
1025        let handshake_result = perform_handshake(
1026            &connection,
1027            VERSION,
1028            &self.this_runtime_id,
1029            permit.source().direction(),
1030        )
1031        .await;
1032
1033        if let Err(error) = &handshake_result {
1034            tracing::debug!(parent: monitor.span(), ?error, "Handshake failed");
1035        }
1036
1037        let that_runtime_id = match handshake_result {
1038            Ok(writer_id) => writer_id,
1039            Err(HandshakeError::ProtocolVersionMismatch(their_version)) => {
1040                self.on_protocol_mismatch(their_version);
1041                return false;
1042            }
1043            Err(
1044                HandshakeError::Timeout
1045                | HandshakeError::BadMagic
1046                | HandshakeError::Io(_)
1047                | HandshakeError::Connection(_),
1048            ) => return false,
1049        };
1050
1051        // prevent self-connections.
1052        if that_runtime_id == self.this_runtime_id.public() {
1053            tracing::debug!(parent: monitor.span(), "Connection from self, discarding");
1054            self.our_addresses.lock().unwrap().insert(permit.addr());
1055            return false;
1056        }
1057
1058        let closed = connection.closed();
1059
1060        let key = {
1061            let mut registry = self.registry.lock().unwrap();
1062            let registry = &mut *registry;
1063
1064            let Some(peers) = &mut registry.peers else {
1065                // Network has been shut down.
1066                return false;
1067            };
1068
1069            let pex_peer = self.pex_discovery.new_peer();
1070            pex_peer.handle_connection(permit.addr(), permit.source(), permit.released());
1071
1072            let mut peer = monitor.span().in_scope(|| {
1073                MessageBroker::new(
1074                    self.this_runtime_id.public(),
1075                    that_runtime_id,
1076                    connection,
1077                    pex_peer,
1078                    self.peers_monitor.make_child(format!(
1079                        "{} {}",
1080                        permit.source().direction().glyph(),
1081                        permit.addr()
1082                    )),
1083                    self.stats_tracker.bytes.clone(),
1084                    permit.byte_counters(),
1085                )
1086            });
1087
1088            // TODO: for DHT connection we should only link the repository for which we did the
1089            // lookup but make sure we correctly handle edge cases, for example, when we have
1090            // more than one repository shared with the peer.
1091            for (_, holder) in &registry.repos {
1092                peer.create_link(
1093                    holder.vault.clone(),
1094                    &holder.pex,
1095                    holder.request_tracker.clone(),
1096                    holder.choker.clone(),
1097                    holder.stats_tracker.bytes.clone(),
1098                );
1099            }
1100
1101            peers.insert(peer)
1102        };
1103
1104        permit.mark_as_active(that_runtime_id, key);
1105        monitor.mark_as_active(that_runtime_id);
1106
1107        // Wait until the connection gets closed, then remove the `MessageBroker` instance. Using a
1108        // RAII to also remove it in case this function gets cancelled.
1109        let _guard = PeerGuard {
1110            registry: &self.registry,
1111            key,
1112        };
1113
1114        closed.await;
1115
1116        true
1117    }
1118
1119    fn on_protocol_mismatch(&self, their_version: Version) {
1120        self.protocol_versions.send_if_modified(|versions| {
1121            if versions.highest_seen < their_version {
1122                versions.highest_seen = their_version;
1123                true
1124            } else {
1125                false
1126            }
1127        });
1128    }
1129
1130    fn spawn<Fut>(&self, f: Fut) -> AbortHandle
1131    where
1132        Fut: Future<Output = ()> + Send + 'static,
1133    {
1134        // TODO: this `unwrap` is sketchy. Maybe we should simply not spawn if `tasks` can't be
1135        // upgraded?
1136        let tasks = self.tasks.upgrade().unwrap();
1137        let mut tasks = tasks.lock().unwrap();
1138
1139        // IMPORTANT: Drain completed tasks. This is necessary because `JoinSet` doesn't
1140        // automatically remove completed tasks (presumably to not lose their results), and so not
1141        // doing it would cause memory leak.
1142        while tasks.try_join_next().is_some() {}
1143
1144        tasks.spawn(f.instrument(Span::current()))
1145    }
1146}
1147
1148//------------------------------------------------------------------------------
1149
1150// Exchange runtime ids with the peer. Returns their (verified) runtime id.
1151async fn perform_handshake(
1152    connection: &Connection,
1153    this_version: Version,
1154    this_runtime_id: &SecretRuntimeId,
1155    dir: ConnectionDirection,
1156) -> Result<PublicRuntimeId, HandshakeError> {
1157    let result = tokio::time::timeout(std::time::Duration::from_secs(5), async move {
1158        let (mut writer, mut reader) = match dir {
1159            ConnectionDirection::Incoming => connection.incoming().await?,
1160            ConnectionDirection::Outgoing => connection.outgoing().await?,
1161        };
1162
1163        writer.write_all(MAGIC).await?;
1164
1165        // Backward fix for Ouisync *App* versions v0.8.3 (and possibly prior)
1166        {
1167            // Those versions of Ouisync App had a race condition where if some peer with a higher
1168            // protocol version managed to connect and handshake before the app subscribed to
1169            // "higher protocol version" notifications then they would never get the notification.
1170            // On Pixel 9a, and when connecting to a PC, the handshake happened ~100ms prior to
1171            // subscribe. Using 700ms to account for slower devices.
1172            tokio::time::sleep(std::time::Duration::from_millis(700)).await;
1173        }
1174
1175        this_version.write_into(&mut writer).await?;
1176
1177        let mut that_magic = [0; MAGIC.len()];
1178        reader.read_exact(&mut that_magic).await?;
1179
1180        if MAGIC != &that_magic {
1181            return Err(HandshakeError::BadMagic);
1182        }
1183
1184        let that_version = Version::read_from(&mut reader).await?;
1185        if that_version > this_version {
1186            return Err(HandshakeError::ProtocolVersionMismatch(that_version));
1187        }
1188
1189        let that_runtime_id =
1190            runtime_id::exchange(this_runtime_id, &mut writer, &mut reader).await?;
1191
1192        writer.shutdown().await?;
1193
1194        Ok(that_runtime_id)
1195    })
1196    .await;
1197
1198    match result {
1199        Ok(subresult) => subresult,
1200        Err(_) => Err(HandshakeError::Timeout),
1201    }
1202}
1203
1204#[derive(Debug, Error)]
1205enum HandshakeError {
1206    #[error("protocol version mismatch")]
1207    ProtocolVersionMismatch(Version),
1208    #[error("bad magic")]
1209    BadMagic,
1210    #[error("timeout")]
1211    Timeout,
1212    #[error("IO error")]
1213    Io(#[from] io::Error),
1214    #[error("connection error")]
1215    Connection(#[from] ConnectionError),
1216}
1217
1218// RAII guard which when dropped removes the peer from the registry.
1219struct PeerGuard<'a> {
1220    registry: &'a BlockingMutex<Registry>,
1221    key: usize,
1222}
1223
1224impl Drop for PeerGuard<'_> {
1225    fn drop(&mut self) {
1226        if let Some(peers) = &mut self
1227            .registry
1228            .lock()
1229            .unwrap_or_else(|error| error.into_inner())
1230            .peers
1231        {
1232            peers.try_remove(self.key);
1233        }
1234    }
1235}
1236
1237struct PortMappings {
1238    _mappings: Vec<upnp::Mapping>,
1239}
1240
1241impl PortMappings {
1242    fn new(forwarder: &upnp::PortForwarder, gateway: &Gateway) -> Self {
1243        let mappings = gateway
1244            .listener_local_addrs()
1245            .into_iter()
1246            .filter_map(|addr| {
1247                match addr {
1248                    PeerAddr::Quic(SocketAddr::V4(addr)) => {
1249                        Some(forwarder.add_mapping(
1250                            addr.port(), // internal
1251                            addr.port(), // external
1252                            ip::Protocol::Udp,
1253                        ))
1254                    }
1255                    PeerAddr::Tcp(SocketAddr::V4(addr)) => {
1256                        Some(forwarder.add_mapping(
1257                            addr.port(), // internal
1258                            addr.port(), // external
1259                            ip::Protocol::Tcp,
1260                        ))
1261                    }
1262                    PeerAddr::Quic(SocketAddr::V6(_)) | PeerAddr::Tcp(SocketAddr::V6(_)) => {
1263                        // TODO: the ipv6 port typically doesn't need to be port-mapped but it might
1264                        // need to be opened in the firewall ("pinholed"). Consider using UPnP for that
1265                        // as well.
1266                        None
1267                    }
1268                }
1269            })
1270            .collect();
1271
1272        Self {
1273            _mappings: mappings,
1274        }
1275    }
1276}
1277
1278enum ComponentState<T> {
1279    Enabled(T),
1280    Disabled(DisableReason),
1281}
1282
1283impl<T> ComponentState<T> {
1284    fn disabled(reason: DisableReason) -> Self {
1285        Self::Disabled(reason)
1286    }
1287
1288    fn is_enabled(&self) -> bool {
1289        matches!(self, Self::Enabled(_))
1290    }
1291
1292    fn is_disabled(&self, reason: DisableReason) -> bool {
1293        match self {
1294            Self::Disabled(current_reason) if *current_reason == reason => true,
1295            Self::Disabled(_) | Self::Enabled(_) => false,
1296        }
1297    }
1298
1299    fn disable(&mut self, reason: DisableReason) -> Option<T> {
1300        match mem::replace(self, Self::Disabled(reason)) {
1301            Self::Enabled(payload) => Some(payload),
1302            Self::Disabled(_) => None,
1303        }
1304    }
1305
1306    fn disable_if_enabled(&mut self, reason: DisableReason) -> Option<T> {
1307        match self {
1308            Self::Enabled(_) => match mem::replace(self, Self::Disabled(reason)) {
1309                Self::Enabled(payload) => Some(payload),
1310                Self::Disabled(_) => unreachable!(),
1311            },
1312            Self::Disabled(_) => None,
1313        }
1314    }
1315
1316    fn enable(&mut self, payload: T) -> Option<T> {
1317        match mem::replace(self, Self::Enabled(payload)) {
1318            Self::Enabled(payload) => Some(payload),
1319            Self::Disabled(_) => None,
1320        }
1321    }
1322}
1323
1324#[derive(Eq, PartialEq)]
1325enum DisableReason {
1326    // Disabled implicitly because `Network` was disabled
1327    Implicit,
1328    // Disabled explicitly
1329    Explicit,
1330}
1331
1332pub fn repository_info_hash(id: &RepositoryId) -> InfoHash {
1333    // Calculate the info hash by hashing the id with BLAKE3 and taking the first 20 bytes.
1334    // (bittorrent uses SHA-1 but that is less secure).
1335    // `unwrap` is OK because the byte slice has the correct length.
1336    InfoHash::try_from(&id.salted_hash(b"ouisync repository info-hash").as_ref()[..INFO_HASH_LEN])
1337        .unwrap()
1338}
1339
1340async fn shutdown_peers(peers: Slab<MessageBroker>) {
1341    future::join_all(peers.into_iter().map(|(_, peer)| peer.shutdown())).await;
1342}