1use crate::{peer_gossip::guard, recorded::RecordedError};
7use libp2p::{Multiaddr, PeerId, multiaddr::Protocol};
8use std::{collections::BTreeSet, fmt::Display, sync::Mutex};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum PeerLink {
14 Direct,
15 Relayed,
16}
17
18impl PeerLink {
19 pub fn of(remote: &Multiaddr) -> Self {
21 match remote.iter().any(|part| part == Protocol::P2pCircuit) {
22 true => Self::Relayed,
23 false => Self::Direct,
24 }
25 }
26
27 pub fn as_str(self) -> &'static str {
28 match self {
29 Self::Direct => "direct",
30 Self::Relayed => "relayed",
31 }
32 }
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub enum Reachability {
38 #[default]
39 Unknown,
40 Public,
41 Private,
42}
43
44impl Reachability {
45 pub fn as_str(self) -> &'static str {
46 match self {
47 Self::Unknown => "unknown",
48 Self::Public => "public",
49 Self::Private => "private",
50 }
51 }
52}
53
54#[derive(Clone, Debug, Default)]
56pub(crate) struct HolePunch {
57 pub(crate) attempts: u64,
58 pub(crate) successes: u64,
59 pub(crate) last_error: Option<RecordedError>,
60}
61
62#[derive(Default)]
63pub(crate) struct Nat {
64 reachability: Mutex<Reachability>,
65 external: Mutex<Vec<Multiaddr>>,
66 relays: Mutex<BTreeSet<PeerId>>,
67 reserved_by_others: Mutex<BTreeSet<PeerId>>,
68 circuits: Mutex<Vec<Multiaddr>>,
69 punch: Mutex<HolePunch>,
70}
71
72fn mark(set: &Mutex<BTreeSet<PeerId>>, peer: PeerId, active: bool) {
73 let mut set = guard(set);
74 match active {
75 true => set.insert(peer),
76 false => set.remove(&peer),
77 };
78}
79
80fn place(list: &Mutex<Vec<Multiaddr>>, address: Multiaddr, present: bool) {
81 let mut list = guard(list);
82 list.retain(|known| *known != address);
83 if present {
84 list.push(address);
85 }
86}
87
88impl Nat {
89 pub(crate) fn reachability(&self) -> Reachability {
90 *guard(&self.reachability)
91 }
92
93 pub(crate) fn set_reachability(&self, reachability: Reachability) {
94 *guard(&self.reachability) = reachability;
95 }
96
97 pub(crate) fn external(&self) -> Vec<Multiaddr> {
100 guard(&self.external).clone()
101 }
102
103 pub(crate) fn set_external(&self, address: Multiaddr, confirmed: bool) {
104 place(&self.external, address, confirmed);
105 }
106
107 pub(crate) fn relays(&self) -> Vec<PeerId> {
109 guard(&self.relays).iter().copied().collect()
110 }
111
112 pub(crate) fn set_relay(&self, relay: PeerId, active: bool) {
113 mark(&self.relays, relay, active);
114 }
115
116 pub(crate) fn reserved_by_others(&self) -> Vec<PeerId> {
118 guard(&self.reserved_by_others).iter().copied().collect()
119 }
120
121 pub(crate) fn set_reserved_by(&self, holder: PeerId, active: bool) {
122 mark(&self.reserved_by_others, holder, active);
123 }
124
125 pub(crate) fn circuits(&self) -> Vec<Multiaddr> {
127 guard(&self.circuits).clone()
128 }
129
130 pub(crate) fn set_circuit(&self, address: Multiaddr, bound: bool) {
131 place(&self.circuits, address, bound);
132 }
133
134 pub(crate) fn hole_punch(&self) -> HolePunch {
135 guard(&self.punch).clone()
136 }
137
138 pub(crate) fn punch_succeeded(&self) {
139 let mut punch = guard(&self.punch);
140 punch.attempts += 1;
141 punch.successes += 1;
142 }
143
144 pub(crate) fn punch_failed(&self, error: impl Display) {
145 let mut punch = guard(&self.punch);
146 punch.attempts += 1;
147 punch.last_error = Some(RecordedError::now(error));
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::{peer_gossip::Shared, peer_protocol::Reply, peerbook::PeerBook};
155 use bitcoin::{BlockHash, hashes::Hash};
156 use libp2p::identity::Keypair;
157
158 fn peer() -> PeerId {
159 Keypair::generate_ed25519().public().to_peer_id()
160 }
161
162 #[test]
163 fn a_relay_serves_its_reservation_holders_as_circuits_a_book_stores() {
164 let relay = peer();
165 let shared = Shared::new(relay);
166 let (holder, requester) = (peer(), peer());
167 shared.nat.set_reserved_by(holder, true);
168 shared.nat.set_reserved_by(requester, true);
169 assert!(
170 matches!(shared.serve(requester, false, 8), Reply::Peers(peers) if peers.is_empty())
171 );
172 let external: Multiaddr = "/ip4/11.0.0.1/tcp/28332".parse().unwrap();
173 shared.nat.set_external(external.clone(), true);
174 let Reply::Peers(peers) = shared.serve(requester, false, 8) else {
175 panic!("peers request refused");
176 };
177 let hop = external
178 .with(Protocol::P2p(relay))
179 .with(Protocol::P2pCircuit);
180 assert_eq!(peers.len(), 1, "the requester is never listed");
181 assert_eq!(
182 (peers[0].peer_id, peers[0].addrs.clone()),
183 (holder, vec![hop.clone()])
184 );
185 let mut book = PeerBook::empty(BlockHash::from_byte_array([42; 32]));
186 assert_eq!(book.merge(&peers, requester, &[]), 1);
187 let entry = &book.entries()[0];
188 assert_eq!(
189 (entry.peer_id, entry.addrs.clone()),
190 (Some(holder), vec![hop])
191 );
192 assert!(entry.circuit_only());
193 }
194
195 #[test]
196 fn reservations_circuits_and_punch_counters_track_their_latest_state() {
197 let nat = Nat::default();
198 let (relay, holder) = (peer(), peer());
199 nat.set_relay(relay, true);
200 nat.set_reserved_by(holder, true);
201 assert_eq!(
202 (nat.relays(), nat.reserved_by_others()),
203 (vec![relay], vec![holder])
204 );
205 nat.set_reserved_by(holder, false);
206 assert!(nat.reserved_by_others().is_empty());
207 let circuit: Multiaddr = format!("/ip4/1.2.3.4/tcp/1/p2p/{relay}/p2p-circuit")
208 .parse()
209 .unwrap();
210 nat.set_circuit(circuit.clone(), true);
211 nat.set_circuit(circuit.clone(), true);
212 assert_eq!(nat.circuits(), vec![circuit]);
213 nat.punch_succeeded();
214 nat.punch_failed("timeout");
215 let punch = nat.hole_punch();
216 assert_eq!((punch.attempts, punch.successes), (2, 1));
217 assert_eq!(
218 punch.last_error.map(|error| error.message).as_deref(),
219 Some("timeout")
220 );
221 assert_eq!(nat.reachability(), Reachability::Unknown);
222 }
223}