1use std::{collections::HashSet, sync::Arc};
5
6use dashmap::DashMap;
7use iroh_base::EndpointId;
8use n0_future::IterExt;
9use tokio::sync::mpsc::error::TrySendError;
10use tracing::{debug, trace};
11
12use super::{
13 ConnectionId, OnDisconnectGuard,
14 client::{Client, Config, ForwardPacketError},
15};
16use crate::{
17 protos::{
18 relay::{Datagrams, Status},
19 streams::BytesStreamSink,
20 },
21 server::{client::SendError, metrics::Metrics},
22};
23
24#[derive(Debug, Clone, Default)]
29pub struct Clients(Arc<Inner>);
30
31#[derive(Debug, Default)]
32struct Inner {
33 clients: DashMap<EndpointId, ClientState>,
35 sent_to: DashMap<EndpointId, HashSet<EndpointId>>,
37}
38
39#[derive(Debug)]
40struct ClientState {
41 active: Client,
42 inactive: Vec<Client>,
43}
44
45impl ClientState {
46 async fn shutdown_all(mut self) {
47 [self.active]
48 .into_iter()
49 .chain(self.inactive.drain(..))
50 .map(Client::shutdown)
51 .join_all()
52 .await;
53 }
54}
55
56impl Clients {
57 pub async fn shutdown(&self) {
63 let keys: Vec<_> = self.0.clients.iter().map(|x| *x.key()).collect();
64 trace!("shutting down {} clients", keys.len());
65 let clients = keys.into_iter().filter_map(|k| self.0.clients.remove(&k));
66 n0_future::join_all(clients.map(|(_, state)| state.shutdown_all())).await;
67 }
68
69 pub fn register<S>(&self, client_config: Config<S>, metrics: Arc<Metrics>)
74 where
75 S: BytesStreamSink + Send + 'static,
76 {
77 let endpoint_id = client_config.guard.endpoint_id;
78 trace!(remote_endpoint = %endpoint_id.fmt_short(), "registering client");
79
80 let client = Client::new(client_config, self, metrics.clone());
81 match self.0.clients.entry(endpoint_id) {
82 dashmap::Entry::Occupied(mut entry) => {
83 let state = entry.get_mut();
84 let old_client = std::mem::replace(&mut state.active, client);
85 debug!(
86 remote_endpoint = %endpoint_id.fmt_short(),
87 "multiple connections found, deactivating old connection",
88 );
89 old_client
90 .try_send_health(Status::SameEndpointIdConnected)
91 .ok();
92 state.inactive.push(old_client);
93 metrics.clients_inactive_added.inc();
94 }
95 dashmap::Entry::Vacant(entry) => {
96 entry.insert(ClientState {
97 active: client,
98 inactive: Vec::new(),
99 });
100 }
101 }
102 }
103
104 pub(super) fn unregister(&self, guard: OnDisconnectGuard, metrics: &Metrics) {
110 let endpoint_id = guard.endpoint_id;
111 let connection_id = guard.connection_id;
112 trace!(
113 endpoint_id = %endpoint_id.fmt_short(),
114 %connection_id, "unregistering client"
115 );
116
117 let mut notify_peers = None;
118
119 self.0.clients.remove_if_mut(&endpoint_id, |_id, state| {
120 if state.active.connection_id() == connection_id {
121 if let Some(last_inactive_client) = state.inactive.pop() {
123 metrics.clients_inactive_removed.inc();
124 state.active = last_inactive_client;
126 state.active.try_send_health(Status::Healthy).ok();
128 false
130 } else {
131 notify_peers = self.0.sent_to.remove(&endpoint_id).map(|(_, peers)| peers);
133 true
135 }
136 } else {
137 state
139 .inactive
140 .retain(|client| client.connection_id() != connection_id);
141 metrics.clients_inactive_removed.inc();
142 false
144 }
145 });
146
147 if let Some(peers) = notify_peers {
150 for peer_id in peers {
151 if let Some(peer) = self.0.clients.get(&peer_id) {
152 match peer.active.try_send_peer_gone(endpoint_id) {
153 Ok(_) => {}
154 Err(TrySendError::Full(_)) => {
155 debug!(
156 dst = %peer_id.fmt_short(),
157 "client too busy to receive peer gone notification, dropping"
158 );
159 }
160 Err(TrySendError::Closed(_)) => {
161 debug!(
162 dst = %peer_id.fmt_short(),
163 "can no longer write to client, dropping peer gone notification"
164 );
165 }
166 }
167 }
168 }
169 }
170 }
171
172 pub fn disconnect(&self, endpoint_id: EndpointId, connection_id: Option<ConnectionId>) -> bool {
182 let Some(state) = self.0.clients.get(&endpoint_id) else {
183 return false;
184 };
185 let mut clients = state.inactive.iter().chain([&state.active]);
186 if let Some(id) = connection_id {
187 let Some(client) = clients.find(|c| c.connection_id() == id) else {
188 return false;
189 };
190 client.start_shutdown();
191 } else {
192 for client in clients {
193 client.start_shutdown();
194 }
195 }
196 true
197 }
198
199 pub(super) fn send_packet(
201 &self,
202 dst: EndpointId,
203 data: Datagrams,
204 src: EndpointId,
205 metrics: &Metrics,
206 ) -> Result<(), ForwardPacketError> {
207 let Some(client) = self.0.clients.get(&dst) else {
208 debug!(dst = %dst.fmt_short(), "no connected client, dropped packet");
209 metrics.send_packets_dropped.inc();
210 return Ok(());
211 };
212 match client.active.try_send_packet(src, data) {
213 Ok(_) => {
214 self.0.sent_to.entry(src).or_default().insert(dst);
216 Ok(())
217 }
218 Err(TrySendError::Full(_)) => {
219 debug!(
220 dst = %dst.fmt_short(),
221 "client too busy to receive packet, dropping packet"
222 );
223 Err(ForwardPacketError::new(SendError::Full))
224 }
225 Err(TrySendError::Closed(_)) => {
226 debug!(
227 dst = %dst.fmt_short(),
228 "can no longer write to client, dropping message and pruning connection"
229 );
230 client.active.start_shutdown();
231 Err(ForwardPacketError::new(SendError::Closed))
232 }
233 }
234 }
235
236 #[cfg(test)]
237 fn active_connection_id(&self, endpoint_id: EndpointId) -> Option<ConnectionId> {
238 self.0
239 .clients
240 .get(&endpoint_id)
241 .map(|s| s.active.connection_id())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use std::time::Duration;
248
249 use iroh_base::SecretKey;
250 use n0_error::{Result, StdResultExt};
251 use n0_future::{Stream, StreamExt};
252 use n0_tracing_test::traced_test;
253 use rand::{RngExt, SeedableRng};
254
255 use super::*;
256 use crate::{
257 client::conn::Conn,
258 http::ProtocolVersion,
259 protos::{common::FrameType, relay::RelayToClientMsg, streams::WsBytesFramed},
260 server::streams::{MaybeTlsStream, RateLimited, ServerRelayedStream},
261 };
262
263 async fn recv_frame<
264 E: std::error::Error + Sync + Send + 'static,
265 S: Stream<Item = Result<RelayToClientMsg, E>> + Unpin,
266 >(
267 frame_type: FrameType,
268 mut stream: S,
269 ) -> Result<RelayToClientMsg> {
270 match stream.next().await {
271 Some(Ok(frame)) => {
272 if frame_type != frame.typ() {
273 n0_error::bail_any!(
274 "Unexpected frame, got {:?}, but expected {:?}",
275 frame.typ(),
276 frame_type
277 );
278 }
279 Ok(frame)
280 }
281 Some(Err(err)) => Err(err).anyerr(),
282 None => n0_error::bail_any!("Unexpected EOF, expected frame {frame_type:?}"),
283 }
284 }
285
286 fn test_client_builder(
287 key: EndpointId,
288 ) -> (Config<WsBytesFramed<RateLimited<MaybeTlsStream>>>, Conn) {
289 let (server, client) = tokio::io::duplex(1024);
290 let guard = OnDisconnectGuard::empty(key);
291 let protocol_version = ProtocolVersion::default();
292 let mut config = Config::new(guard, ServerRelayedStream::test(server), protocol_version);
293 config.write_timeout = Duration::from_secs(1);
294 config.channel_capacity = 10;
295 (config, Conn::test(client, protocol_version))
296 }
297
298 #[tokio::test]
299 #[traced_test]
300 async fn test_clients() -> Result {
301 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
302 let a_key = SecretKey::from_bytes(&rng.random()).public();
303 let b_key = SecretKey::from_bytes(&rng.random()).public();
304
305 let (builder_a, mut a_rw) = test_client_builder(a_key);
306
307 let clients = Clients::default();
308 let metrics = Arc::new(Metrics::default());
309 clients.register(builder_a, metrics.clone());
310
311 let data = b"hello world!";
313 clients.send_packet(a_key, Datagrams::from(&data[..]), b_key, &metrics)?;
314 let frame = recv_frame(FrameType::RelayToClientDatagram, &mut a_rw).await?;
315 assert_eq!(
316 frame,
317 RelayToClientMsg::Datagrams {
318 remote_endpoint_id: b_key,
319 datagrams: data.to_vec().into(),
320 }
321 );
322
323 {
324 let client = clients.0.clients.get(&a_key).unwrap();
325 client.active.start_shutdown();
327 }
328
329 let c = clients.clone();
331 tokio::time::timeout(Duration::from_secs(1), async move {
332 loop {
333 if !c.0.clients.contains_key(&a_key) {
334 break;
335 }
336 tokio::time::sleep(Duration::from_millis(100)).await;
337 }
338 })
339 .await
340 .std_context("timeout")?;
341 clients.shutdown().await;
342
343 Ok(())
344 }
345
346 #[tokio::test]
347 #[traced_test]
348 async fn test_clients_same_endpoint_id() -> Result {
349 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
350 let a_key = SecretKey::from_bytes(&rng.random()).public();
351 let b_key = SecretKey::from_bytes(&rng.random()).public();
352
353 let (a1_builder, mut a1_rw) = test_client_builder(a_key);
354
355 let clients = Clients::default();
356 let metrics = Arc::new(Metrics::default());
357
358 clients.register(a1_builder, metrics.clone());
360 let a1_conn_id = clients.active_connection_id(a_key).unwrap();
361
362 let data = b"hello world!";
364 clients.send_packet(a_key, Datagrams::from(&data[..]), b_key, &metrics)?;
365 let frame = recv_frame(FrameType::RelayToClientDatagram, &mut a1_rw).await?;
366 assert_eq!(
367 frame,
368 RelayToClientMsg::Datagrams {
369 remote_endpoint_id: b_key,
370 datagrams: data.to_vec().into(),
371 }
372 );
373
374 let (a2_builder, mut a2_rw) = test_client_builder(a_key);
376 clients.register(a2_builder, metrics.clone());
377 let a2_conn_id = clients.active_connection_id(a_key).unwrap();
378 assert!(a2_conn_id != a1_conn_id);
379
380 let frame = recv_frame(FrameType::Status, &mut a1_rw).await?;
382 assert_eq!(
383 frame,
384 RelayToClientMsg::Status(Status::SameEndpointIdConnected)
385 );
386
387 clients.send_packet(a_key, Datagrams::from(&data[..]), b_key, &metrics)?;
389 let frame = recv_frame(FrameType::RelayToClientDatagram, &mut a2_rw).await?;
390 assert_eq!(
391 frame,
392 RelayToClientMsg::Datagrams {
393 remote_endpoint_id: b_key,
394 datagrams: data.to_vec().into(),
395 }
396 );
397
398 clients
400 .0
401 .clients
402 .get(&a_key)
403 .unwrap()
404 .active
405 .start_shutdown();
406
407 tokio::time::timeout(Duration::from_secs(1), {
409 let clients = clients.clone();
410 async move {
411 while clients.active_connection_id(a_key) == Some(a2_conn_id) {
413 tokio::time::sleep(Duration::from_millis(100)).await;
414 }
415 }
416 })
417 .await
418 .std_context("timeout")?;
419
420 assert_eq!(clients.active_connection_id(a_key), Some(a1_conn_id));
422
423 let frame = recv_frame(FrameType::Status, &mut a1_rw).await?;
425 assert_eq!(frame, RelayToClientMsg::Status(Status::Healthy));
426
427 clients.send_packet(a_key, Datagrams::from(&data[..]), b_key, &metrics)?;
429 let frame = recv_frame(FrameType::RelayToClientDatagram, &mut a1_rw).await?;
430 assert_eq!(
431 frame,
432 RelayToClientMsg::Datagrams {
433 remote_endpoint_id: b_key,
434 datagrams: data.to_vec().into(),
435 }
436 );
437
438 clients
440 .0
441 .clients
442 .get(&a_key)
443 .unwrap()
444 .active
445 .start_shutdown();
446
447 tokio::time::timeout(Duration::from_secs(1), {
449 let clients = clients.clone();
450 async move {
451 while clients.0.clients.contains_key(&a_key) {
453 tokio::time::sleep(Duration::from_millis(100)).await;
454 }
455 }
456 })
457 .await
458 .std_context("timeout")?;
459
460 clients.shutdown().await;
461
462 Ok(())
463 }
464
465 #[tokio::test]
466 #[traced_test]
467 async fn test_peer_gone_notification() -> Result {
468 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
469 let a_key = SecretKey::from_bytes(&rng.random()).public();
470 let b_key = SecretKey::from_bytes(&rng.random()).public();
471
472 let clients = Clients::default();
473 let metrics = Arc::new(Metrics::default());
474
475 let (builder_a, _a_rw) = test_client_builder(a_key);
477 let (builder_b, mut b_rw) = test_client_builder(b_key);
478 clients.register(builder_a, metrics.clone());
479 clients.register(builder_b, metrics.clone());
480
481 let data = b"hello b!";
483 clients.send_packet(b_key, Datagrams::from(&data[..]), a_key, &metrics)?;
484
485 let frame = recv_frame(FrameType::RelayToClientDatagram, &mut b_rw).await?;
487 assert_eq!(
488 frame,
489 RelayToClientMsg::Datagrams {
490 remote_endpoint_id: a_key,
491 datagrams: data.to_vec().into(),
492 }
493 );
494
495 {
497 let client = clients.0.clients.get(&a_key).unwrap();
498 client.active.start_shutdown();
499 }
500
501 tokio::time::timeout(Duration::from_secs(1), {
503 let clients = clients.clone();
504 async move {
505 while clients.0.clients.contains_key(&a_key) {
506 tokio::time::sleep(Duration::from_millis(100)).await;
507 }
508 }
509 })
510 .await
511 .std_context("timeout waiting for A to unregister")?;
512
513 let frame = recv_frame(FrameType::EndpointGone, &mut b_rw).await?;
515 assert_eq!(frame, RelayToClientMsg::EndpointGone(a_key));
516
517 clients.shutdown().await;
518 Ok(())
519 }
520}